This commit is contained in:
mertalev
2026-06-09 14:49:22 -04:00
parent 89b387c67f
commit 450c9c6e16
5 changed files with 29 additions and 13 deletions
+10
View File
@@ -4734,6 +4734,16 @@
"maximum": 9007199254740991,
"type": "integer"
}
},
{
"name": "x-immich-hls-msn",
"required": false,
"in": "header",
"schema": {
"minimum": 0,
"maximum": 9007199254740991,
"type": "integer"
}
}
],
"responses": {
-1
View File
@@ -227,7 +227,6 @@ export const HLS_LEASE_DURATION_MS = 30 * 60 * 1000;
export const HLS_PLAYLIST_CONTENT_TYPE = 'application/vnd.apple.mpegurl';
export const HLS_SEGMENT_DURATION = 2;
export const HLS_SEGMENT_FILENAME_REGEX = /^seg_(\d+)\.m4s$/;
export const HLS_TARGET_SEGMENT_HEADER = 'x-immich-hls-msn';
export const HLS_VARIANTS = [
{ resolution: 480, codec: VideoCodec.Av1, bitrate: 1_000_000, codecString: 'av01.0.04M.08' },
{ resolution: 480, codec: VideoCodec.Hevc, bitrate: 1_200_000, codecString: 'hvc1.1.6.L90.B0' },
@@ -1,11 +1,16 @@
import { Controller, Delete, Get, Header, Headers, HttpCode, HttpStatus, Next, Param, Res } from '@nestjs/common';
import { ApiProduces, ApiTags } from '@nestjs/swagger';
import { NextFunction, Response } from 'express';
import { HLS_PLAYLIST_CONTENT_TYPE, HLS_TARGET_SEGMENT_HEADER } from 'src/constants';
import { HLS_PLAYLIST_CONTENT_TYPE } from 'src/constants';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { HlsSegmentParamDto, HlsSessionParamDto, HlsVariantParamDto } from 'src/dtos/streaming.dto';
import { ApiTag, Permission, RouteKey } from 'src/enum';
import {
HlsSegmentHeaderDto,
HlsSegmentParamDto,
HlsSessionParamDto,
HlsVariantParamDto,
} from 'src/dtos/streaming.dto';
import { ApiTag, ImmichHeader, Permission, RouteKey } from 'src/enum';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { HlsService } from 'src/services/hls.service';
@@ -59,21 +64,14 @@ export class VideoStreamController {
async getSegment(
@Auth() auth: AuthDto,
@Param() { id, sessionId, variantIndex, filename }: HlsSegmentParamDto,
// Allows the client to hint at which segment will be loaded after init.mp4
@Headers(HLS_TARGET_SEGMENT_HEADER) targetSegment: string | undefined,
@Headers() { [ImmichHeader.HlsTargetSegment]: targetSegment }: HlsSegmentHeaderDto,
@Res() res: Response,
@Next() next: NextFunction,
) {
const target = targetSegment === undefined ? undefined : Number.parseInt(targetSegment);
if (target !== undefined && (!Number.isFinite(target) || target < 0)) {
res.status(HttpStatus.BAD_REQUEST).send('Invalid target segment');
return;
}
await sendFile(
res,
next,
() => this.service.getSegment(auth, id, sessionId, variantIndex, filename, target),
() => this.service.getSegment(auth, id, sessionId, variantIndex, filename, targetSegment),
this.logger,
);
}
+8
View File
@@ -1,4 +1,5 @@
import { createZodDto } from 'nestjs-zod';
import { ImmichHeader } from 'src/enum';
import z from 'zod';
const HlsSessionParamSchema = z.object({
@@ -24,3 +25,10 @@ const HlsSegmentParamSchema = z.object({
});
export class HlsSegmentParamDto extends createZodDto(HlsSegmentParamSchema) {}
const HlsSegmentHeaderSchema = z.object({
// Lets the client hint at which segment will be loaded after init.mp4.
[ImmichHeader.HlsTargetSegment]: z.coerce.number().int().min(0).optional(),
});
export class HlsSegmentHeaderDto extends createZodDto(HlsSegmentHeaderSchema) {}
+1
View File
@@ -24,6 +24,7 @@ export enum ImmichHeader {
SharedLinkSlug = 'x-immich-share-slug',
Checksum = 'x-immich-checksum',
CorrelationId = 'X-Correlation-ID',
HlsTargetSegment = 'x-immich-hls-msn',
}
export enum ImmichQuery {