{"record":{"id":"a8ec1ca241996ce4","repo":"immich-app/immich","slug":"asset-not-found-or-metadata-not-yet-ready-for-stre","errorCode":null,"errorMessage":"Asset not found or metadata not yet ready for streaming","messagePattern":"Asset not found or metadata not yet ready for streaming","errorType":"http","errorClass":"NotFoundException","httpStatus":404,"severity":"warning","filePath":"server/src/services/hls.service.ts","lineNumber":74,"sourceCode":"      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');\n    }\n\n    const segmentation = this.getSegmentation(asset);\n    const hintedSegment = position === undefined ? undefined : this.positionToSegmentIndex(segmentation, position);\n    this.prewarmVariant(assetId, sessionId, variantIndex, hintedSegment);\n\n    return this.generateMediaPlaylist(asset, segmentation);\n  }\n\n  async getSegment(\n    auth: AuthDto,\n    assetId: string,\n    sessionId: string,\n    variantIndex: number,\n    filename: string,\n    initSegment?: number,\n  ) {\n    await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] });","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/hls.service.ts#L56-L92","documentation":"When a client requests a media (variant) playlist via getMediaPlaylist, the service calls videoStreamRepository.getForMediaPlaylist(assetId, sessionId). A null result means either the asset was deleted, the asset has not yet had its streaming metadata prepared, or the provided sessionId does not match a known session for the asset. Because all three collapse into null, Immich throws 404 with a combined message.","triggerScenarios":"GET /assets/{id}/video/playlists/media.m3u8?sessionId=...&variantIndex=... where the asset is gone, its media-playlist data isn't ready, or the sessionId is stale/wrong. Common right after the main playlist was generated but before the worker produced variant metadata, or when a stale player retries after session expiry.","commonSituations":"Player holding a sessionId after the HLS session was closed; race during initial segment generation; asset deleted while someone was watching; microservices lag.","solutions":["Re-fetch the main playlist to obtain a fresh sessionId, then request the media playlist again.","If the asset was just uploaded, wait for metadata/segment jobs to finish then retry.","Confirm the asset still exists (GET /assets/{id}); if 404, stop playback.","Ensure microservices are running so session/variant metadata gets produced promptly."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// always derive sessionId from a fresh main playlist call\nconst main = await api.hlsApi.getMainPlaylist(auth, assetId);\nconst sessionId = parseSessionId(main); // valid before requesting media playlist\nawait api.hlsApi.getMediaPlaylist(auth, assetId, sessionId, variantIndex);","typeGuard":"const isValidSession = (s: string | undefined): s is string =>\n  typeof s === 'string' && s.length > 0;","tryCatchPattern":"try {\n  await api.hlsApi.getMediaPlaylist(auth, assetId, sessionId, variantIndex);\n} catch (e) {\n  if (e.status === 404) {\n    // refresh main playlist and retry once with a new session\n    const main = await api.hlsApi.getMainPlaylist(auth, assetId);\n    return api.hlsApi.getMediaPlaylist(auth, assetId, parseSessionId(main), variantIndex);\n  }\n  throw e;\n}","preventionTips":["Never cache sessionId longer than the playback session.","Handle 404 by re-fetching the main playlist.","Confirm microservices are running for timely variant metadata."],"tags":["video","hls","session","metadata","not-found","immich","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}