-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(web): fall back to raw upload when MP4 playback fails #1702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,47 @@ const ApiLive = HttpApiBuilder.api(Api).pipe( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resolveRawPreviewKey = (video: Video.Video) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Effect.gen(function* () { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const db = yield* Database; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [s3] = yield* S3Buckets.getBucketAccess(video.bucketId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [uploadRecord] = yield* db.use((db) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor perf:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select({ rawFileKey: Db.videoUploads.rawFileKey }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(Db.videoUploads) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(eq(Db.videoUploads.videoId, video.id)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (uploadRecord?.rawFileKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return uploadRecord.rawFileKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (video.source.type !== "webMP4") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const candidateKeys = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `${video.ownerId}/${video.id}/raw-upload.mp4`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `${video.ownerId}/${video.id}/raw-upload.webm`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headResults = yield* Effect.all( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidateKeys.map((key) => s3.headObject(key).pipe(Effect.option)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { concurrency: "unbounded" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const [index, candidateKey] of candidateKeys.entries()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawHead = headResults[index]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rawHead && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Option.isSome(rawHead) && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (rawHead.value.ContentLength ?? 0) > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return candidateKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The S3 client inside Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/web/app/api/playlist/route.ts
Line: 86-121
Comment:
**Redundant `getBucketAccess` call on the happy path**
`resolveRawPreviewKey` unconditionally acquires an S3 client via `S3Buckets.getBucketAccess(video.bucketId)` at the very start of its generator, but when `uploadRecord.rawFileKey` is already set (the common case) that client is never used — the function returns the DB key immediately. Meanwhile `getPlaylistResponse` (the only caller) already holds its own `s3` from its own `getBucketAccess` call at line 128.
The S3 client inside `resolveRawPreviewKey` is only needed for the `headObject` probe in the candidate-key fallback branch (the `webMP4` path). Moving `yield* S3Buckets.getBucketAccess(video.bucketId)` to just before the `candidateKeys` block — after the early returns for the DB hit and the non-`webMP4` guard — would skip the bucket lookup entirely for the common path.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const getPlaylistResponse = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video: Video.Video, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urlParams: (typeof GetPlaylistParams)["Type"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -93,20 +134,9 @@ const getPlaylistResponse = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video.source.type === "desktopMP4" || video.source.type === "webMP4"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (urlParams.videoType === "raw-preview") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const db = yield* Database; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [uploadRecord] = yield* db.use((db) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select({ rawFileKey: Db.videoUploads.rawFileKey }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(Db.videoUploads) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(eq(Db.videoUploads.videoId, urlParams.videoId)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!uploadRecord?.rawFileKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawFileKey = yield* resolveRawPreviewKey(video); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* s3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .getSignedObjectUrl(uploadRecord.rawFileKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .getSignedObjectUrl(rawFileKey) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .pipe(Effect.map(HttpServerResponse.redirect)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -201,12 +231,17 @@ const getPlaylistResponse = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s3.listObjects({ prefix: audioPrefix, maxKeys: 1 }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const videoMetadata = yield* s3.headObject( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| videoSegment.Contents?.[0]?.Key ?? "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const videoSegmentKey = videoSegment.Contents?.[0]?.Key; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!videoSegmentKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const videoMetadata = yield* s3.headObject(videoSegmentKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const audioMetadata = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| audioSegment?.KeyCount && audioSegment.KeyCount > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? yield* s3.headObject(audioSegment.Contents?.[0]?.Key ?? "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? audioSegment.Contents?.[0]?.Key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? yield* s3.headObject(audioSegment.Contents[0].Key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const generatedPlaylist = generateMasterPlaylist( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ import { | |||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| type ResolvedPlaybackSource, | ||||||||||||||||||||||||||||||||
| resolvePlaybackSource, | ||||||||||||||||||||||||||||||||
| shouldFallbackToRawPlaybackSource, | ||||||||||||||||||||||||||||||||
| } from "./playback-source"; | ||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| MediaPlayer, | ||||||||||||||||||||||||||||||||
|
|
@@ -139,6 +140,8 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| const [hasError, setHasError] = useState(false); | ||||||||||||||||||||||||||||||||
| const [isRetryingProcessing, setIsRetryingProcessing] = useState(false); | ||||||||||||||||||||||||||||||||
| const [playerDuration, setPlayerDuration] = useState(fallbackDuration ?? 0); | ||||||||||||||||||||||||||||||||
| const [preferredSource, setPreferredSource] = useState<"mp4" | "raw">("mp4"); | ||||||||||||||||||||||||||||||||
| const [hasTriedRawFallback, setHasTriedRawFallback] = useState(false); | ||||||||||||||||||||||||||||||||
| const queryClient = useQueryClient(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||
|
|
@@ -166,14 +169,21 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| const shouldDeferResolvedSource = shouldDeferPlaybackSource(uploadProgress); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const resolvedSrc = useQuery<ResolvedPlaybackSource | null>({ | ||||||||||||||||||||||||||||||||
| queryKey: ["resolvedSrc", videoSrc, rawFallbackSrc, enableCrossOrigin], | ||||||||||||||||||||||||||||||||
| queryKey: [ | ||||||||||||||||||||||||||||||||
| "resolvedSrc", | ||||||||||||||||||||||||||||||||
| videoSrc, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| enableCrossOrigin, | ||||||||||||||||||||||||||||||||
| preferredSource, | ||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||
| queryFn: shouldDeferResolvedSource | ||||||||||||||||||||||||||||||||
| ? skipToken | ||||||||||||||||||||||||||||||||
| : () => | ||||||||||||||||||||||||||||||||
| resolvePlaybackSource({ | ||||||||||||||||||||||||||||||||
| videoSrc, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| enableCrossOrigin, | ||||||||||||||||||||||||||||||||
| preferredSource, | ||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||
| refetchOnWindowFocus: false, | ||||||||||||||||||||||||||||||||
| staleTime: Number.POSITIVE_INFINITY, | ||||||||||||||||||||||||||||||||
|
|
@@ -186,6 +196,8 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| setVideoLoaded(false); | ||||||||||||||||||||||||||||||||
| setHasError(false); | ||||||||||||||||||||||||||||||||
| setShowPlayButton(false); | ||||||||||||||||||||||||||||||||
| setPreferredSource("mp4"); | ||||||||||||||||||||||||||||||||
| setHasTriedRawFallback(false); | ||||||||||||||||||||||||||||||||
| }, [videoSrc, rawFallbackSrc]); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Track video duration for comment markers | ||||||||||||||||||||||||||||||||
|
|
@@ -280,6 +292,21 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const handleError = () => { | ||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||
| shouldFallbackToRawPlaybackSource( | ||||||||||||||||||||||||||||||||
| resolvedSrc.data?.type, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| hasTriedRawFallback, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||
| setHasTriedRawFallback(true); | ||||||||||||||||||||||||||||||||
| setPreferredSource("raw"); | ||||||||||||||||||||||||||||||||
| setVideoLoaded(false); | ||||||||||||||||||||||||||||||||
| setHasError(false); | ||||||||||||||||||||||||||||||||
| setShowPlayButton(false); | ||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| setHasError(true); | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -365,7 +392,14 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| captionTrack.removeEventListener("cuechange", handleCueChange); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| }, [hasPlayedOnce, resolvedSrc.isPending, videoRef.current]); | ||||||||||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||||||||||
| hasPlayedOnce, | ||||||||||||||||||||||||||||||||
| hasTriedRawFallback, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| resolvedSrc.data?.type, | ||||||||||||||||||||||||||||||||
| resolvedSrc.isPending, | ||||||||||||||||||||||||||||||||
| videoRef.current, | ||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const generateVideoFrameThumbnail = useCallback( | ||||||||||||||||||||||||||||||||
| (time: number): string => { | ||||||||||||||||||||||||||||||||
|
|
@@ -443,12 +477,19 @@ export function CapVideoPlayer({ | |||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||
| setHasError(false); | ||||||||||||||||||||||||||||||||
| void queryClient.invalidateQueries({ | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When processing finishes (or upload completes), this can stay stuck preferring
Suggested change
|
||||||||||||||||||||||||||||||||
| queryKey: ["resolvedSrc", videoSrc, rawFallbackSrc, enableCrossOrigin], | ||||||||||||||||||||||||||||||||
| queryKey: [ | ||||||||||||||||||||||||||||||||
| "resolvedSrc", | ||||||||||||||||||||||||||||||||
| videoSrc, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| enableCrossOrigin, | ||||||||||||||||||||||||||||||||
| preferredSource, | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When upload processing completes, consider resetting |
||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
Comment on lines
479
to
487
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once
Suggested change
|
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| prevUploadProgress.current = uploadProgress; | ||||||||||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||||||||||
| enableCrossOrigin, | ||||||||||||||||||||||||||||||||
| preferredSource, | ||||||||||||||||||||||||||||||||
| queryClient, | ||||||||||||||||||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||||||||||||||||||
| uploadProgress, | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ type ResolvePlaybackSourceInput = { | |||||||||||||||
| fetchImpl?: typeof fetch; | ||||||||||||||||
| now?: () => number; | ||||||||||||||||
| createVideoElement?: () => Pick<HTMLVideoElement, "canPlayType">; | ||||||||||||||||
| preferredSource?: "mp4" | "raw"; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| function appendCacheBust(url: string, timestamp: number): string { | ||||||||||||||||
|
|
@@ -92,14 +93,56 @@ export function canPlayRawContentType( | |||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export function shouldFallbackToRawPlaybackSource( | ||||||||||||||||
| resolvedSourceType: ResolvedPlaybackSource["type"] | null | undefined, | ||||||||||||||||
| rawFallbackSrc: string | undefined, | ||||||||||||||||
| hasTriedRawFallback: boolean, | ||||||||||||||||
| ): boolean { | ||||||||||||||||
| return Boolean( | ||||||||||||||||
| rawFallbackSrc && resolvedSourceType === "mp4" && !hasTriedRawFallback, | ||||||||||||||||
| ); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export async function resolvePlaybackSource({ | ||||||||||||||||
| videoSrc, | ||||||||||||||||
| rawFallbackSrc, | ||||||||||||||||
| enableCrossOrigin = false, | ||||||||||||||||
| fetchImpl = fetch, | ||||||||||||||||
| now = () => Date.now(), | ||||||||||||||||
| createVideoElement, | ||||||||||||||||
| preferredSource = "mp4", | ||||||||||||||||
| }: ResolvePlaybackSourceInput): Promise<ResolvedPlaybackSource | null> { | ||||||||||||||||
| const resolveRaw = async (): Promise<ResolvedPlaybackSource | null> => { | ||||||||||||||||
| if (!rawFallbackSrc) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const rawResult = await probePlaybackSource(rawFallbackSrc, fetchImpl, now); | ||||||||||||||||
|
|
||||||||||||||||
| if (!rawResult) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const contentType = rawResult.response.headers.get("content-type") ?? ""; | ||||||||||||||||
|
|
||||||||||||||||
| if ( | ||||||||||||||||
| !canPlayRawContentType(contentType, rawResult.url, createVideoElement) | ||||||||||||||||
| ) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return { | ||||||||||||||||
| url: rawResult.url, | ||||||||||||||||
| type: "raw", | ||||||||||||||||
| supportsCrossOrigin: | ||||||||||||||||
| enableCrossOrigin && detectCrossOriginSupport(rawResult.url), | ||||||||||||||||
| }; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| if (preferredSource === "raw") { | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
|
||||||||||||||||
| return await resolveRaw(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const mp4Result = await probePlaybackSource(videoSrc, fetchImpl, now); | ||||||||||||||||
|
|
||||||||||||||||
| if (mp4Result) { | ||||||||||||||||
|
|
@@ -111,26 +154,5 @@ export async function resolvePlaybackSource({ | |||||||||||||||
| }; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if (!rawFallbackSrc) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const rawResult = await probePlaybackSource(rawFallbackSrc, fetchImpl, now); | ||||||||||||||||
|
|
||||||||||||||||
| if (!rawResult) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const contentType = rawResult.response.headers.get("content-type") ?? ""; | ||||||||||||||||
|
|
||||||||||||||||
| if (!canPlayRawContentType(contentType, rawResult.url, createVideoElement)) { | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return { | ||||||||||||||||
| url: rawResult.url, | ||||||||||||||||
| type: "raw", | ||||||||||||||||
| supportsCrossOrigin: | ||||||||||||||||
| enableCrossOrigin && detectCrossOriginSupport(rawResult.url), | ||||||||||||||||
| }; | ||||||||||||||||
| return await resolveRaw(); | ||||||||||||||||
| } | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getBucketAccesshappens before we know whether we even need S3 (whenrawFileKeyis already in DB). Small perf/readability win to defer it until after the DB check.Also worth double-checking:
headObject(...).pipe(Effect.option)will treat any S3 failure (timeouts/credentials/etc) as “missing” and return a 404 here. If the intent is only to ignore “not found”, it may be better to only swallow that specific error and let other S3 errors bubble to the existing 500 mapping.