feat: enhance streaming functionality with format support and fallback mechanism

- Added support for an optional 'format' query parameter in the streaming endpoint to allow raw file retrieval.
- Implemented a fallback mechanism in the VideoPlayer to automatically switch to raw file playback when HLS streaming fails.
- Improved error handling and logging for HLS playback issues, ensuring better user feedback and recovery attempts.
- Updated the Indeehub API service to accommodate the new format parameter in the streaming URL request.
This commit is contained in:
Dorian
2026-02-14 10:56:37 +00:00
parent 0ae2638af9
commit 674c9f80c5
4 changed files with 129 additions and 55 deletions

View File

@@ -80,6 +80,7 @@ export class ContentsController {
@UseGuards(OptionalHybridAuthGuard)
async stream(
@Param('id') id: string,
@Query('format') format: string,
@Req() req: any,
) {
// Try content ID first; if not found, try looking up project to find its content
@@ -94,7 +95,7 @@ export class ContentsController {
}
this.logger.log(
`[stream] content=${content.id}, file=${content.file}, status=${content.status}, project=${content.project?.id}`,
`[stream] content=${content.id}, file=${content.file}, status=${content.status}, project=${content.project?.id}, format=${format ?? 'auto'}`,
);
if (!content.file) {
@@ -116,6 +117,10 @@ export class ContentsController {
const dto = new StreamContentDTO(content);
// When ?format=raw is requested, skip HLS and go straight to presigned URL.
// This is used as a fallback when the HLS stream fails (e.g. key decryption issues).
const forceRaw = format === 'raw';
// Check if the HLS manifest actually exists in the public bucket.
// In dev (no transcoding pipeline) the raw upload lives in the
// private bucket only — fall back to a presigned URL for it.
@@ -125,17 +130,21 @@ export class ContentsController {
? `${getFileRoute(content.file)}${content.id}/transcoded/file.m3u8`
: `${getFileRoute(content.file)}transcoded/file.m3u8`;
this.logger.log(`[stream] Checking HLS at bucket=${publicBucket}, key=${outputKey}`);
if (!forceRaw) {
this.logger.log(`[stream] Checking HLS at bucket=${publicBucket}, key=${outputKey}`);
const hlsExists = await this.uploadService.objectExists(
outputKey,
publicBucket,
);
const hlsExists = await this.uploadService.objectExists(
outputKey,
publicBucket,
);
if (hlsExists) {
const publicUrl = getPublicS3Url(outputKey);
this.logger.log(`[stream] HLS found, returning publicUrl=${publicUrl}`);
return { ...dto, file: publicUrl };
if (hlsExists) {
const publicUrl = getPublicS3Url(outputKey);
this.logger.log(`[stream] HLS found, returning publicUrl=${publicUrl}`);
return { ...dto, file: publicUrl };
}
} else {
this.logger.log(`[stream] Raw format requested, skipping HLS check`);
}
// HLS not available — check that the raw file actually exists before