immich-app/immich · error · BadRequestException
Real-time transcoding is not enabled
Error message
Real-time transcoding is not enabled
What it means
getMainPlaylist serves HLS playlists for real-time transcoded playback. If the server config has ffmpeg.realtime.enabled=false, streaming playlists are not produced, so the endpoint rejects with 400 BadRequestException before touching the asset.
Solutions
- Enable real-time transcoding: set transcoding policy/realtime in the admin Transcoding settings (or ffmpeg.realtime.enabled=true in config) and restart.
- Use normal progressive playback endpoints instead of the HLS playlist routes.
- Upgrade the clients to a version that only requests HLS when realtime is enabled.
Example fix
// before
ffmpeg: { realtime: { enabled: false }, ... }
// after
ffmpeg: { realtime: { enabled: true, crf: 23 }, ... } Defensive patterns
Strategy: try-catch
Validate before calling
const config = await api.getConfig();
if (!config.ffmpeg.realtime.enabled) console.warn('HLS realtime endpoints unavailable; use progressive playback'); Try / catch
try {
const playlist = await api.getMainPlaylist(assetId);
} catch (e) {
if (/Real-time transcoding is not enabled/.test(e.message)) {
fallbackToProgressivePlayback(assetId);
} else throw e;
} Prevention
- Check ffmpeg.realtime.enabled in server config before using HLS routes.
- Keep client and server versions aligned on transcoding features.
- Document that streaming playback requires enabling realtime in admin settings.
When it happens
Trigger: Requesting GET /assets/:id/video/master.m3u8 (or equivalent playlist route) while the transcoding settings use the default non-realtime (throttled/after-upload) transcode mode.
Common situations: Mobile/web client attempting HLS streaming playback when admin left transcoding policy at default; config reset after restore; custom settings disabling realtime.
Related errors
- OAuth is not enabled
- Asset metadata is not yet ready for streaming
- Cannot update configuration while IMMICH_CONFIG_FILE is in…
- Codec ' ' does not support HLS codec strings
- Codec ' ' is unsupported
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/78c1c8bca63844ea.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/hls.service.ts:51
}
}
@OnEvent({ name: 'HlsSessionEnd', server: true, workers: [ImmichWorker.Api] })
onSessionEnd({ sessionId }: ArgOf<'HlsSessionEnd'>) {
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) {View on GitHub (pinned to e55ac299a4)