segmented MP4 upload pipeline with live HLS playback and server-side muxing#1716
Merged
richiemcilroy merged 27 commits intomainfrom Apr 7, 2026
Merged
segmented MP4 upload pipeline with live HLS playback and server-side muxing#1716richiemcilroy merged 27 commits intomainfrom
richiemcilroy merged 27 commits intomainfrom
Conversation
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
…system Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Member
Author
|
@greptileai please review the PR |
Paragon SummaryThis pull request review identified 2 issues across 1 category in 52 files. The review analyzed code changes, potential bugs, security vulnerabilities, performance issues, and code quality concerns using automated analysis tools. This PR makes changes across apps, crates, packages directories. Key changes:
Confidence score: 3/5
52 files reviewed, 2 comments Severity breakdown: High: 2 |
| if (ownerId) { | ||
| const segmentsPrefix = `${ownerId}/${videoId}/segments/`; | ||
| Effect.gen(function* () { | ||
| const bucketId = Option.fromNullable( |
There was a problem hiding this comment.
listed.Contents entries can include an undefined Key, which would end up sending { Key: undefined } to S3. Also, the bucketId cast looks unnecessary since videos.bucket is already typed as S3BucketId | null.
Suggested change
| const bucketId = Option.fromNullable( | |
| const bucketId = Option.fromNullable(currentVideo.bucket); | |
| const [bucket] = yield* S3Buckets.getBucketAccess(bucketId); | |
| let totalDeleted = 0; | |
| let continuationToken: string | undefined; | |
| do { | |
| const listed = yield* bucket.listObjects({ | |
| prefix: segmentsPrefix, | |
| continuationToken, | |
| }); | |
| if (listed.Contents && listed.Contents.length > 0) { | |
| const objects = listed.Contents.flatMap((c: { Key?: string }) => | |
| c.Key ? [{ Key: c.Key }] : [], | |
| ); | |
| if (objects.length > 0) { | |
| yield* bucket.deleteObjects(objects); | |
| totalDeleted += objects.length; | |
| } | |
| } | |
| continuationToken = listed.IsTruncated | |
| ? listed.NextContinuationToken | |
| : undefined; | |
| } while (continuationToken); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
hls.jsfor instant web playback during recordingresult.mp4withfaststartDashAudioSegmentEncoderproducing CMAF-compatible init + m4s segments for HLS audioGreptile Summary
This PR replaces the growing-file multipart upload with a per-segment pipeline: fMP4 segments are uploaded to S3 in real-time, served as a live HLS stream via
hls.js, and muxed into a finalresult.mp4by the media server when recording ends. The architecture is well-structured with semaphore-bounded concurrency, retry logic, a graceful full-file fallback, and correctno-storecache headers for the live HLS playlist.validateMediaServerSecretnow hard-rejects all protected media-server routes (including the previously open/processendpoint) whenMEDIA_SERVER_WEBHOOK_SECRETis unset, and the webhook receiver applies the same hard-reject. Self-hosted deployments without this env var will have all background video processing silently broken after upgrading.muxSegmentsAsynccontinues muxing when fewer than 50% of video segments fail to download, potentially producing a truncated or corruptedresult.mp4without surfacing the gap to the caller.Confidence Score: 4/5
Safe to merge after addressing the breaking auth requirement for self-hosted deployments
The segmented upload pipeline is well-implemented with semaphore-bounded concurrency, retry logic, and a graceful full-file fallback. One P1 finding: the hard requirement for MEDIA_SERVER_WEBHOOK_SECRET silently breaks all background video processing in existing deployments that haven't set it. All other findings are P2 quality or reliability concerns.
apps/media-server/src/routes/video.ts and apps/web/app/api/webhooks/media-server/progress/route.ts for the auth breaking change
Important Files Changed
Sequence Diagram
sequenceDiagram participant Desktop as Desktop (Tauri) participant S3 participant WebApp as Web App participant MediaServer as Media Server participant Browser Desktop->>WebApp: POST /api/desktop/video/create?recordingMode=desktopSegments WebApp-->>Desktop: videoId + upload credentials Desktop->>Desktop: Start recording (fMP4 pipeline) loop Per segment Desktop->>S3: PUT segments/video/segment_NNN.m4s Desktop->>S3: PUT segments/manifest.json (is_complete=false) end Browser->>WebApp: GET /api/playlist?videoType=segments-master WebApp->>S3: GET manifest.json WebApp-->>Browser: HLS master playlist Browser->>WebApp: GET /api/playlist?videoType=segments-video WebApp-->>Browser: HLS media playlist (signed segment URLs) Browser->>S3: Fetch segment files (live HLS) Desktop->>S3: PUT manifest.json (is_complete=true) Desktop->>WebApp: POST /api/upload/recording-complete WebApp->>MediaServer: POST /video/mux-segments MediaServer->>S3: Download all segments MediaServer->>MediaServer: FFmpeg concat + mux to result.mp4 MediaServer->>S3: PUT result.mp4 MediaServer->>WebApp: POST /api/webhooks/media-server/progress (phase=complete) WebApp->>WebApp: Update source to desktopMP4 WebApp->>S3: Delete segments/ prefix (async)Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "Add explicit types for cues, subtitles, ..." | Re-trigger Greptile