{"record":{"id":"75d53b95babe924f","repo":"immich-app/immich","slug":"asset-metadata-is-not-yet-ready-for-streaming","errorCode":null,"errorMessage":"Asset metadata is not yet ready for streaming","messagePattern":"Asset metadata is not yet ready for streaming","errorType":"http","errorClass":"NotFoundException","httpStatus":404,"severity":"warning","filePath":"server/src/services/hls.service.ts","lineNumber":56,"sourceCode":"    this.sessions.delete(sessionId);\n    this.pendingSegments.rejectByPrefix(`${sessionId}:`, 'Session ended');\n  }\n\n  @OnEvent({ name: 'HlsSegmentResult', server: true, workers: [ImmichWorker.Api] })\n  onSegmentResult(event: ArgOf<'HlsSegmentResult'>) {\n    this.pendingSegments.complete(this.getSegmentKey(event), event);\n  }\n\n  async getMainPlaylist(auth: AuthDto, assetId: string) {\n    await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] });\n    const { ffmpeg } = await this.getConfig({ withCache: true });\n    if (!ffmpeg.realtime.enabled) {\n      throw new BadRequestException('Real-time transcoding is not enabled');\n    }\n\n    const asset = await this.videoStreamRepository.getForMainPlaylist(assetId);\n    if (!asset) {\n      throw new NotFoundException('Asset metadata is not yet ready for streaming');\n    }\n\n    // Sharing the sessionId allows only one microservices worker to successfully insert to the session table.\n    // The microservices worker that creates a session owns the transcoding lifecycle for it.\n    const sessionId = this.cryptoRepository.randomUUID();\n    this.websocketRepository.serverSend('HlsSessionRequest', { sessionId, assetId, ownerId: auth.user.id });\n    await this.pendingSessions.wait(sessionId);\n    this.trackSession(sessionId);\n\n    return this.generateMainPlaylist(sessionId, ffmpeg, asset);\n  }\n\n  async getMediaPlaylist(auth: AuthDto, assetId: string, sessionId: string, variantIndex: number, position?: number) {\n    await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] });\n\n    const asset = await this.videoStreamRepository.getForMediaPlaylist(assetId, sessionId);\n    if (!asset) {\n      throw new NotFoundException('Asset not found or metadata not yet ready for streaming');","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/hls.service.ts#L38-L74","documentation":"After realtime transcoding is confirmed enabled, the service looks up precomputed video-stream metadata via videoStreamRepository.getForMainPlaylist. If the asset has no ready HLS/video-stream metadata yet (transcoding analysis not finished), a 404 NotFoundException is returned so the client can retry later.","triggerScenarios":"Requesting the main playlist immediately after upload or after enabling realtime transcoding, before the microservices worker has generated and stored stream metadata for the asset.","commonSituations":"Scrolling a timeline and hitting play on freshly uploaded videos; first playback after enabling realtime on a large library; worker backlog delaying metadata generation.","solutions":["Wait and retry: the video stream worker will populate metadata; the client should retry the playlist request after a delay.","Ensure the microservices (workers) container is running and processing jobs (check job queue for video-stream/transcode jobs).","Verify the asset completed processing (check its status in the web UI) before attempting HLS playback."],"exampleFix":"// before\nconst playlist = await api.getMainPlaylist(assetId);\n// after\nconst playlist = await withRetry(() => api.getMainPlaylist(assetId), { retries: 5, delayMs: 2000 });","handlingStrategy":"retry","validationCode":"const streams = await api.getVideoInfo(assetId);\nif (!streams?.videoStreams?.length) console.warn('Video metadata not ready yet; defer HLS request');","typeGuard":null,"tryCatchPattern":"try {\n  return await api.getMainPlaylist(assetId);\n} catch (e) {\n  if (e.status === 404 && /not yet ready for streaming/.test(e.message)) {\n    await sleep(3000);\n    return api.getMainPlaylist(assetId);\n  }\n  throw e;\n}","preventionTips":["Only request HLS playlists for assets whose processing status is 'ready'.","Ensure the microservices worker is healthy and draining the job queue.","Implement bounded exponential backoff on 404s from playlist endpoints."],"tags":["not-found","hls","transcoding","async"],"backgroundTag":"resource-not-found","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}