Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Photo scavenger hunt web app to play with your friends, family, and coworkers.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Scavenger Hunt Party! is a simple web app to manage the experience of a photo scavenger hunt. A photo scavenger hunt is a scavenger hunt where the participants are required to take a photo or video of the item for it to count as being found. When the scavenger hunt is over all participants can see the results as well as the photos and videos taken by others.
bin/setupbin/devbin/test[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES before running the server.Photos and videos go straight from the browser to the storage service with Active Storage direct uploads, then the form sends the signed blob id to the app. In production the service is S3 (config/storage.yml), so the bucket needs a CORS rule that lets the site PUT objects and read the ETag header back. In the bucket's Permissions → Cross-origin resource sharing add:
[
{
"AllowedHeaders": ["Content-Type", "Content-MD5", "Content-Disposition"],
"AllowedMethods": ["PUT"],
"AllowedOrigins": ["https://scavengerhunt.party"],
"ExposeHeaders": ["ETag", "Origin"],
"MaxAgeSeconds": 3600
}
]
Add every origin the app is served from (staging, custom domains) to AllowedOrigins. Without the rule the browser blocks the upload and teams see the "couldn't be uploaded" banner on every attempt. Development and test use the Disk service, which needs no CORS setup.
Videos are limited to 150 MB and 60 seconds (Submission::MAX_VIDEO_BYTES, Submission::MAX_VIDEO_SECONDS); the browser checks both before uploading, the server checks the size when the upload lands, and the worker probes the duration with ffprobe before converting and refuses clips that are too long with a banner on the team's page. Photos are compressed in the browser before upload. A photo captured before the hunt ended is still accepted for 15 minutes after the end (Hunt::LATE_UPLOAD_GRACE) so slow connections do not lose last-minute finds.
Phones upload whatever they record (iPhones send HEVC .mov files that many Android, Windows and Firefox browsers cannot play), so every video is converted on the worker by TranscodeVideoJob into an H.264 MP4 (high profile, level 4.2, at most 1920×1080 and 60 fps) and attached as Submission#playback, with a poster frame generated at the same time. The original stays attached as photo for the archive; Submission#media_url serves the playable version once it exists and the original until then, and the checklist shows "Processing video" in the meantime. The job probes every upload first (ignoring any metadata the client sent) and refuses clips over the limit or ones that did not upload completely by purging the photo and sending a banner over the team's submissions stream. ffmpeg and ffprobe only ever run through VideoTranscoder.run, which restricts them to plain files and the mov/mp4, matroska and webm demuxers and kills them after ten minutes. bin/rails videos:transcode_missing queues the conversion for videos uploaded before this existed. The whole thing sits behind the Flipper feature video_transcoding, enabled by a migration; bin/rails runner 'Flipper.disable(:video_transcoding)' from a Render shell switches it off without a deploy: nothing is queued, queued jobs do nothing, and every page serves the originals again with no posters or processing state. Converted files stay in storage and are served again after Flipper.enable(:video_transcoding).
The conversion needs ffmpeg and ffprobe. Render has no package manager, so bin/install-ffmpeg downloads a pinned static Linux build into vendor/ffmpeg/ during both the web and worker builds (checked against its SHA-256) and production points config.active_storage.paths at it. Development uses whatever is on PATH (brew install ffmpeg); without it videos still upload but are never converted. The test suite never runs the real programs: spec/support/bin/ holds stand-ins that copy a fixture and print canned probe output.
Phones that installed the app and allowed notifications get a push when a hunt is five minutes from starting, when it is over and the results are up, when the photo show starts and when a new team takes the lead. Pushes are signed with VAPID keys from the Rails credentials. Generate a pair once:
bin/rails runner 'key = WebPush.generate_key; puts "public_key: #{key.public_key}", "private_key: #{key.private_key}"'
Then add them with bin/rails credentials:edit:
vapid:
subject: mailto:[email protected]
public_key: <public key>
private_key: <private key>
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY and VAPID_SUBJECT environment variables work too. Without keys the opt-in sheet and the notification settings stay hidden and nothing is sent. The test environment generates a throwaway pair at boot. Rotating the keys invalidates every existing subscription, so phones have to allow notifications again.
Subscriptions live in push_subscriptions, one per phone and hunt, keyed by the viewer's user_id cookie. Only endpoints on the browsers' own push services (Google, Mozilla, Apple, Microsoft; see PushSubscription::PUSH_SERVICES) are accepted, so the worker never posts to an address a viewer made up; add a browser's service there if its subscriptions are refused. Push services answer 404 or 410 for a phone that revoked its subscription; those rows are deleted the next time a notification goes out.
One of the features of Scavenger Hunt is ready made hunt templates. These are loaded in during app boot from app/templates. To create a new scavenger hunt template just make a new YAML file in app/templates that is structured like a Hunt model with associated Categorys and Items.