immich-app/immich · warning · NotFoundException
Asset metadata is not yet ready for streaming
Error message
Asset metadata is not yet ready for streaming
What it means
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.
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.
Example fix
// before
const playlist = await api.getMainPlaylist(assetId);
// after
const playlist = await withRetry(() => api.getMainPlaylist(assetId), { retries: 5, delayMs: 2000 }); Defensive patterns
Strategy: retry
Validate before calling
const streams = await api.getVideoInfo(assetId);
if (!streams?.videoStreams?.length) console.warn('Video metadata not ready yet; defer HLS request'); Try / catch
try {
return await api.getMainPlaylist(assetId);
} catch (e) {
if (e.status === 404 && /not yet ready for streaming/.test(e.message)) {
await sleep(3000);
return api.getMainPlaylist(assetId);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Scrolling a timeline and hitting play on freshly uploaded videos; first playback after enabling realtime on a large library; worker backlog delaying metadata generation.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Asset media not found
- Asset not found
- Asset not found
- Asset not found or asset is not a video
- Both assets must exist
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/75d53b95babe924f.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/hls.service.ts:56
this.sessions.delete(sessionId);
this.pendingSegments.rejectByPrefix(`${sessionId}:`, 'Session ended');
}
@OnEvent({ name: 'HlsSegmentResult', server: true, workers: [ImmichWorker.Api] })
onSegmentResult(event: ArgOf<'HlsSegmentResult'>) {
this.pendingSegments.complete(this.getSegmentKey(event), event);
}
async getMainPlaylist(auth: AuthDto, assetId: string) {
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] });
const { ffmpeg } = await this.getConfig({ withCache: true });
if (!ffmpeg.realtime.enabled) {
throw new BadRequestException('Real-time transcoding is not enabled');
}
const asset = await this.videoStreamRepository.getForMainPlaylist(assetId);
if (!asset) {
throw new NotFoundException('Asset metadata is not yet ready for streaming');
}
// Sharing the sessionId allows only one microservices worker to successfully insert to the session table.
// The microservices worker that creates a session owns the transcoding lifecycle for it.
const sessionId = this.cryptoRepository.randomUUID();
this.websocketRepository.serverSend('HlsSessionRequest', { sessionId, assetId, ownerId: auth.user.id });
await this.pendingSessions.wait(sessionId);
this.trackSession(sessionId);
return this.generateMainPlaylist(sessionId, ffmpeg, asset);
}
async getMediaPlaylist(auth: AuthDto, assetId: string, sessionId: string, variantIndex: number, position?: number) {
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] });
const asset = await this.videoStreamRepository.getForMediaPlaylist(assetId, sessionId);
if (!asset) {
throw new NotFoundException('Asset not found or metadata not yet ready for streaming');View on GitHub (pinned to e55ac299a4)