Files
SignSync/project-specifications.md
2026-02-08 11:18:13 -05:00

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

  1. Let interpreters practice against any YouTube video.
  2. Capture webcam + microphone recordings directly in-browser.
  3. Persist recordings with lightweight user metadata.
  4. Generate a shareable link for collaboration.
  5. Compare two interpreters in synchronized playback.

3. User Roles

  1. First interpreter: Creates an initial recording and share link.
  2. Second interpreter: Opens shared link and adds a second recording.
  3. Viewer/reviewer: Uses playback controls to compare source + interpretations.

4. Primary User Flows

Flow A: Create first recording

  1. User opens app.
  2. User pastes YouTube URL (or raw video ID) and clicks Load Video.
  3. User enters name/email.
  4. User records interpretation from webcam.
  5. User stops recording and previews it.
  6. User clicks Save Recording.
  7. Backend stores metadata + video file and returns unique link ID.
  8. Frontend shows full share URL (?share=<id>) and copy button.
  9. Saved recording appears in "First Recording" panel.
  1. User opens shared link with ?share=<unique_link>.
  2. App loads existing record and cues stored YouTube video.
  3. 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.
  4. User records and submits second video.
  5. Backend updates same row with name_2, email_2, recorded_video_path_2.
  6. Frontend refreshes shared data and shows both videos immediately (no reload needed).
  7. First video blur is removed and playback controls are enabled.
  8. Once both videos exist, form + recorder are hidden.

Flow C: Synchronized comparison

  1. User presses global play/pause button.
  2. App controls YouTube + local videos together.
  3. Timeline slider seeks all videos simultaneously.
  4. First interpretation is always offset +2 seconds (headstart).
  5. User can swap left/right interpreter panels.

5. Functional Requirements

5.1 YouTube Loading

  1. Must support:
  • Full URLs (youtube.com/watch?v=...)
  • Short URLs (youtu.be/...)
  • Embed URLs
  • Raw 11-char video IDs
  1. Invalid input must show alert: Please enter a valid YouTube URL.
  2. Default loaded video ID: wLM5bzt1xks.
  3. Use YouTube IFrame API (https://www.youtube.com/iframe_api).
  4. Show title + duration once available.

5.2 Webcam Recording

  1. Request getUserMedia with:
  • Video: 640x480
  • Audio: true
  1. Use MediaRecorder with preferred mime:
  • video/webm;codecs=vp9 if supported
  • fallback video/webm
  1. Record in 1-second chunks.
  2. Show recording timer (mm:ss) and active indicator.
  3. After stop, create preview player using URL.createObjectURL.
  4. Provide Discard and Re-record actions.
  5. On camera failure, show: Unable to access webcam. Please ensure camera permissions are granted.

5.3 Form Validation

  1. Name required (non-empty).
  2. Email required and regex-validated in UI.
  3. Submission blocked if no recorded blob.
  4. Show inline validation errors in form.

5.4 Recording Submission Logic

  1. New recording (no share context):
  • POST /api/recordings
  • Include name, email, video, youtubeVideoUrl
  1. Second recording (share context, second not yet present):
  • POST /api/share/:uniqueLink/second-video
  • Include name, email, video
  1. On success for first recording:
  • Show success message
  • Generate and display share URL
  • Load saved recording into first video panel immediately (no reload needed)
  1. On success for second recording:
  • Show success message
  • Refresh shared recording data and show second video immediately (no reload needed)
  1. On server/network failure:
  • Show friendly error message.

5.5 Shared Recording Behavior

  1. If URL has share query param, app must load recording via GET /api/share/:uniqueLink.
  2. If record includes YouTube URL, cue corresponding source video.
  3. If first/second local video exists, load into respective HTML5 video elements.
  4. If second recording exists, disable additional recording UI.
  5. 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.

5.6 Playback + Sync

  1. Global play button label toggles:
  • ▶ Play
  • ⏸ Pause
  1. Pressing play should reset all media to start state:
  • YouTube at 0
  • First local video at 2 seconds
  • Second local video at 0
  1. Timeline reflects YouTube current time (poll every 250ms while playing).
  2. Seek operation:
  • YouTube seek to t
  • First local video seek to min(t + 2, duration1)
  • Second local video seek to min(t, duration2)
  1. If user presses play/pause on local videos, YouTube should sync play/pause.

5.7 Video Panels and Swap

  1. Show two local video panels in one row on desktop.
  2. Left panel is "First Recording" and marked with This video has 2 seconds headstart.
  3. Right panel is "Second Recording".
  4. If content missing, show placeholders:
  • No recording yet
  • Waiting for second recording...
  • Record your interpretation above (in shared state with missing second video)
  1. Swap button () exchanges displayed first/second recording content.
  2. Swap disabled until second recording exists.

6. UI/UX Requirements

6.1 Page Layout (top to bottom)

  1. YouTube URL input + Load button
  2. YouTube title + duration
  3. Embedded YouTube player
  4. User form + webcam recorder (when allowed)
  5. Timeline slider with current/duration text
  6. Two local recording panels + swap button
  7. Global play/pause control

6.2 Responsive Behavior

  1. Mobile breakpoint around 600px.
  2. URL input stack vertically on mobile.
  3. Local video panels stack vertically on mobile.
  4. Swap button rotates 90 degrees on mobile.

6.3 Visual Style Baseline

  1. Dark UI theme.
  2. Blue primary actions (#2563eb).
  3. Red recording actions (#e63946).
  4. Rounded cards, slider, and buttons.

7. Technical Architecture

7.1 Frontend

  1. Stack: React + Vite.
  2. Main components:
  • YouTubePlayer (orchestrator + sync + state)
  • WebcamRecorder (capture and preview)
  • UserForm (metadata input + validation)
  1. Frontend API base URL: http://localhost:3001.
  2. Share state is URL-driven (window.location.search).

7.2 Backend

  1. Stack: Node.js + Express + SQLite.
  2. Middleware:
  • cors()
  • express.json()
  • multer for multipart uploads
  1. Upload constraints:
  • max file size: 100MB
  • accept only video/* mimetypes
  1. File naming pattern:
  • recording_<timestamp>.webm
  1. Storage path:
  • public/media/ (local filesystem)

7.3 Data Persistence Model

Single table: recordings

  • id INTEGER PK AUTOINCREMENT
  • unique_link TEXT UNIQUE NOT NULL
  • name TEXT NOT NULL
  • email TEXT NOT NULL
  • recorded_video_path TEXT NOT NULL
  • youtube_video_url TEXT NOT NULL
  • name_2 TEXT NULL
  • email_2 TEXT NULL
  • recorded_video_path_2 TEXT NULL
  • created_at DATETIME 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)
  • video file (required)

Success 200:

  • success
  • id
  • uniqueLink
  • recordedVideoPath
  • message

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.

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)
  • video file (required)

Errors:

  • 404 recording not found
  • 400 second video already recorded
  • 400 missing required fields

Success 200:

  • success
  • recordedVideoPath2
  • message

9. Error Handling and Edge Cases

  1. Invalid YouTube URL input blocks load.
  2. Camera permission denied shows inline error.
  3. Missing recording on submit blocked client-side.
  4. Backend returns generic 500 on server exceptions.
  5. Shared link with invalid token should show not-found behavior (current app logs and remains mostly empty).
  6. If local video shorter than seek target, clamp to video duration.
  7. Newly uploaded video files may not be immediately available from Vite's dev server. A loadWithRetry mechanism retries .load() up to 5 times at 500ms intervals using native addEventListener('error') on the video element.
  8. WebM files from MediaRecorder lack seek indices. Setting currentTime before metadata loads crashes the demuxer. Only set currentTime in onLoadedMetadata handlers.
  9. React's synthetic onError prop on <video> elements is unreliable for media errors. Use native event listeners instead.

10. Browser/Platform Requirements

  1. Modern Chromium/Safari/Firefox with support for:
  • navigator.mediaDevices.getUserMedia
  • MediaRecorder
  • URL.createObjectURL
  • Clipboard API (navigator.clipboard.writeText)
  1. Desktop-first, mobile supported via responsive CSS.

11. Security and Privacy Baseline

  1. No authentication.
  2. No authorization; share link is access mechanism.
  3. No encryption-at-rest beyond host defaults.
  4. User email stored in plaintext SQLite.
  5. CORS enabled broadly (default permissive).
  6. Uploaded files are stored locally and directly web-accessible via /media/....

12. Local Development and Run Commands

  1. Install dependencies: npm install
  2. Run frontend + backend: npm run dev:all
  3. Frontend only: npm run dev (Vite default port 5173)
  4. Backend only: npm run server (port 3001)

13. Implementation Notes for Another AI Builder

  1. Keep functionality equivalent to this baseline rather than redesigning feature behavior.
  2. Preserve the 2-second headstart offset for first recording in all sync/seek/play reset paths.
  3. Keep shared-link mode logic URL-driven.
  4. Ensure form+recorder are hidden once both recordings exist.
  5. Use multipart uploads and SQLite schema exactly unless explicitly changing architecture.
  6. If deploying production, ensure /media static files are served from the same origin expected by frontend paths.
  7. Video loading after upload uses a belt-and-suspenders approach: callback refs trigger loadWithRetry when video elements mount, and backup useEffect hooks watching sharedRecording.recorded_video_path / recorded_video_path_2 catch edge cases where the callback ref alone is insufficient.
  8. Always clean up previous native error listeners before adding new ones on the same video element to prevent duplicate retry loops.
  9. Never set currentTime on a video element before its metadata has loaded — use onLoadedMetadata handlers instead.

14. Acceptance Criteria

  1. User can load a YouTube video and see title/duration.
  2. User can record webcam video with audio and preview it.
  3. User can submit first recording and receive usable share link.
  4. Shared link opens same source video and first recording.
  5. Second user can add second recording exactly once.
  6. App can play/pause/seek source + local videos in sync.
  7. First recording consistently plays with +2s offset.
  8. User can swap left/right recording panels.
  9. UI works on desktop and mobile widths.
  10. After saving first recording, video appears immediately in the first video panel without page reload.
  11. After saving second recording, video appears immediately in the second video panel without page reload.
  12. Second user sees first video blurred with overlay text before recording, with playback controls disabled.