12 KiB
12 KiB
SignSync Web App - Project Specifications
1. Product Summary
SignSync is a browser-based practice and comparison tool for sign language interpreters. Users watch a YouTube source video, record their own interpretation via webcam, save that recording, and share a link so a second interpreter can record against the same source. The app then plays the source plus both interpretation videos in sync for side-by-side review.
2. Core Objectives
- Let interpreters practice against any YouTube video.
- Capture webcam + microphone recordings directly in-browser.
- Persist recordings with lightweight user metadata.
- Generate a shareable link for collaboration.
- Compare two interpreters in synchronized playback.
3. User Roles
- First interpreter: Creates an initial recording and share link.
- Second interpreter: Opens shared link and adds a second recording.
- Viewer/reviewer: Uses playback controls to compare source + interpretations.
4. Primary User Flows
Flow A: Create first recording
- User opens app.
- User pastes YouTube URL (or raw video ID) and clicks
Load Video. - User enters name/email.
- User records interpretation from webcam.
- User stops recording and previews it.
- User clicks
Save Recording. - Backend stores metadata + video file and returns unique link ID.
- Frontend shows full share URL (
?share=<id>) and copy button. - Saved recording appears in "First Recording" panel.
Flow B: Add second recording from shared link
- User opens shared link with
?share=<unique_link>. - App loads existing record and cues stored YouTube video.
- If second recording does not exist:
- First recording video is shown blurred with overlay text: "Recording done, waiting for other person..."
- Playback controls (play/pause, timeline seek) are disabled.
- Form + recorder are shown for the second user.
- User records and submits second video.
- Backend updates same row with
name_2,email_2,recorded_video_path_2. - Frontend refreshes shared data and shows both videos immediately (no reload needed).
- First video blur is removed and playback controls are enabled.
- Once both videos exist, form + recorder are hidden.
Flow C: Synchronized comparison
- User presses global play/pause button.
- App controls YouTube + local videos together.
- Timeline slider seeks all videos simultaneously.
- First interpretation is always offset +2 seconds (headstart).
- User can swap left/right interpreter panels.
5. Functional Requirements
5.1 YouTube Loading
- Must support:
- Full URLs (
youtube.com/watch?v=...) - Short URLs (
youtu.be/...) - Embed URLs
- Raw 11-char video IDs
- Invalid input must show alert:
Please enter a valid YouTube URL. - Default loaded video ID:
wLM5bzt1xks. - Use YouTube IFrame API (
https://www.youtube.com/iframe_api). - Show title + duration once available.
5.2 Webcam Recording
- Request
getUserMediawith:
- Video: 640x480
- Audio: true
- Use
MediaRecorderwith preferred mime:
video/webm;codecs=vp9if supported- fallback
video/webm
- Record in 1-second chunks.
- Show recording timer (
mm:ss) and active indicator. - After stop, create preview player using
URL.createObjectURL. - Provide
DiscardandRe-recordactions. - On camera failure, show:
Unable to access webcam. Please ensure camera permissions are granted.
5.3 Form Validation
- Name required (non-empty).
- Email required and regex-validated in UI.
- Submission blocked if no recorded blob.
- Show inline validation errors in form.
5.4 Recording Submission Logic
- New recording (no share context):
POST /api/recordings- Include
name,email,video,youtubeVideoUrl
- Second recording (share context, second not yet present):
POST /api/share/:uniqueLink/second-video- Include
name,email,video
- On success for first recording:
- Show success message
- Generate and display share URL
- Load saved recording into first video panel immediately (no reload needed)
- On success for second recording:
- Show success message
- Refresh shared recording data and show second video immediately (no reload needed)
- On server/network failure:
- Show friendly error message.
5.5 Shared Recording Behavior
- If URL has
sharequery param, app must load recording viaGET /api/share/:uniqueLink. - If record includes YouTube URL, cue corresponding source video.
- If first/second local video exists, load into respective HTML5 video elements.
- If second recording exists, disable additional recording UI.
- If second recording does not yet exist (
isSecondUserPending):- First video is blurred (
filter: blur(15px)) with pointer events disabled. - Overlay text "Recording done, waiting for other person..." is shown centered on the blurred video.
- Play/pause button and timeline slider are disabled.
- First video is blurred (
5.6 Playback + Sync
- Global play button label toggles:
▶ Play⏸ Pause
- Pressing play should reset all media to start state:
- YouTube at
0 - First local video at
2seconds - Second local video at
0
- Timeline reflects YouTube current time (poll every 250ms while playing).
- Seek operation:
- YouTube seek to
t - First local video seek to
min(t + 2, duration1) - Second local video seek to
min(t, duration2)
- If user presses play/pause on local videos, YouTube should sync play/pause.
5.7 Video Panels and Swap
- Show two local video panels in one row on desktop.
- Left panel is "First Recording" and marked with
This video has 2 seconds headstart. - Right panel is "Second Recording".
- If content missing, show placeholders:
No recording yetWaiting for second recording...Record your interpretation above(in shared state with missing second video)
- Swap button (
⇄) exchanges displayed first/second recording content. - Swap disabled until second recording exists.
6. UI/UX Requirements
6.1 Page Layout (top to bottom)
- YouTube URL input + Load button
- YouTube title + duration
- Embedded YouTube player
- User form + webcam recorder (when allowed)
- Timeline slider with current/duration text
- Two local recording panels + swap button
- Global play/pause control
6.2 Responsive Behavior
- Mobile breakpoint around
600px. - URL input stack vertically on mobile.
- Local video panels stack vertically on mobile.
- Swap button rotates 90 degrees on mobile.
6.3 Visual Style Baseline
- Dark UI theme.
- Blue primary actions (
#2563eb). - Red recording actions (
#e63946). - Rounded cards, slider, and buttons.
7. Technical Architecture
7.1 Frontend
- Stack: React + Vite.
- Main components:
YouTubePlayer(orchestrator + sync + state)WebcamRecorder(capture and preview)UserForm(metadata input + validation)
- Frontend API base URL:
http://localhost:3001. - Share state is URL-driven (
window.location.search).
7.2 Backend
- Stack: Node.js + Express + SQLite.
- Middleware:
cors()express.json()multerfor multipart uploads
- Upload constraints:
- max file size: 100MB
- accept only
video/*mimetypes
- File naming pattern:
recording_<timestamp>.webm
- Storage path:
public/media/(local filesystem)
7.3 Data Persistence Model
Single table: recordings
idINTEGER PK AUTOINCREMENTunique_linkTEXT UNIQUE NOT NULLnameTEXT NOT NULLemailTEXT NOT NULLrecorded_video_pathTEXT NOT NULLyoutube_video_urlTEXT NOT NULLname_2TEXT NULLemail_2TEXT NULLrecorded_video_path_2TEXT NULLcreated_atDATETIME DEFAULT CURRENT_TIMESTAMP
Unique link generation: 8 random bytes hex string.
8. Backend API Contract
POST /api/recordings
Creates first recording.
Multipart fields:
name(required)email(required)youtubeVideoUrl(optional, stored as empty string if missing)videofile (required)
Success 200:
successiduniqueLinkrecordedVideoPathmessage
Validation failure 400:
error: Name, email, and video file are required
GET /api/recordings
Returns all recordings ordered by newest first.
GET /api/recordings/:id
Returns one recording by numeric ID.
404 if not found.
GET /api/share/:uniqueLink
Returns one recording by share token.
404 if not found.
POST /api/share/:uniqueLink/second-video
Adds second interpreter video to existing row.
Multipart fields:
name(required)email(required)videofile (required)
Errors:
404recording not found400second video already recorded400missing required fields
Success 200:
successrecordedVideoPath2message
9. Error Handling and Edge Cases
- Invalid YouTube URL input blocks load.
- Camera permission denied shows inline error.
- Missing recording on submit blocked client-side.
- Backend returns generic
500on server exceptions. - Shared link with invalid token should show not-found behavior (current app logs and remains mostly empty).
- If local video shorter than seek target, clamp to video duration.
- Newly uploaded video files may not be immediately available from Vite's dev server. A
loadWithRetrymechanism retries.load()up to 5 times at 500ms intervals using nativeaddEventListener('error')on the video element. - WebM files from
MediaRecorderlack seek indices. SettingcurrentTimebefore metadata loads crashes the demuxer. Only setcurrentTimeinonLoadedMetadatahandlers. - React's synthetic
onErrorprop on<video>elements is unreliable for media errors. Use native event listeners instead.
10. Browser/Platform Requirements
- Modern Chromium/Safari/Firefox with support for:
navigator.mediaDevices.getUserMediaMediaRecorderURL.createObjectURL- Clipboard API (
navigator.clipboard.writeText)
- Desktop-first, mobile supported via responsive CSS.
11. Security and Privacy Baseline
- No authentication.
- No authorization; share link is access mechanism.
- No encryption-at-rest beyond host defaults.
- User email stored in plaintext SQLite.
- CORS enabled broadly (default permissive).
- Uploaded files are stored locally and directly web-accessible via
/media/....
12. Local Development and Run Commands
- Install dependencies:
npm install - Run frontend + backend:
npm run dev:all - Frontend only:
npm run dev(Vite default port5173) - Backend only:
npm run server(port3001)
13. Implementation Notes for Another AI Builder
- Keep functionality equivalent to this baseline rather than redesigning feature behavior.
- Preserve the 2-second headstart offset for first recording in all sync/seek/play reset paths.
- Keep shared-link mode logic URL-driven.
- Ensure form+recorder are hidden once both recordings exist.
- Use multipart uploads and SQLite schema exactly unless explicitly changing architecture.
- If deploying production, ensure
/mediastatic files are served from the same origin expected by frontend paths. - Video loading after upload uses a belt-and-suspenders approach: callback refs trigger
loadWithRetrywhen video elements mount, and backupuseEffecthooks watchingsharedRecording.recorded_video_path/recorded_video_path_2catch edge cases where the callback ref alone is insufficient. - Always clean up previous native error listeners before adding new ones on the same video element to prevent duplicate retry loops.
- Never set
currentTimeon a video element before its metadata has loaded — useonLoadedMetadatahandlers instead.
14. Acceptance Criteria
- User can load a YouTube video and see title/duration.
- User can record webcam video with audio and preview it.
- User can submit first recording and receive usable share link.
- Shared link opens same source video and first recording.
- Second user can add second recording exactly once.
- App can play/pause/seek source + local videos in sync.
- First recording consistently plays with +2s offset.
- User can swap left/right recording panels.
- UI works on desktop and mobile widths.
- After saving first recording, video appears immediately in the first video panel without page reload.
- After saving second recording, video appears immediately in the second video panel without page reload.
- Second user sees first video blurred with overlay text before recording, with playback controls disabled.