Describe the feature
alwaysAvailableFile and alwaysAvailableTracks are mutually exclusive, making custom offline video impossible for G711 audio
Description
When using the always-available feature, there is no way to combine a custom offline video clip with a G711 audio track. The two configuration mechanisms are mutually exclusive, and G711 is only reachable through the one that forbids custom video.
Steps to reproduce
paths:
mypath:
alwaysAvailable: true
alwaysAvailableFile: "./my-offline.mp4"
alwaysAvailableTracks:
- codec: H264
- codec: G711
sampleRate: 8000
channelCount: 1
muLaw: true
Expected: custom video from the file, G711 silence generated for the audio track.
Actual: the server refuses to start:
'alwaysAvailableFile' and 'alwaysAvailableTracks' cannot be used together
Why there is no workaround
Removing alwaysAvailableTracks and muxing G711 into the MP4 instead does not work either, because G711 is not accepted from a file:
checkAlwaysAvailableFile (internal/conf/path.go:154-159) whitelists only AV1, VP9, H265, H264, Opus, MPEG4Audio, LPCM and rejects anything else with unsupported codec %T.
mediasFromAlwaysAvailableFile (internal/stream/stream.go:24-114) has no *codecs.G711 case, so such a track would produce no media even if validation passed.
Conversely, removing alwaysAvailableFile gives up the custom video. G711 support exists only as synthetic silence generation in offlineSubStreamTrack.run (internal/stream/offline_sub_stream_track.go:163-190, emitting 0xFF for µ-law / 0xD5 for A-law), and that code is unreachable whenever a file is configured — the file branch short-circuits first, for every track:
// internal/stream/offline_sub_stream_track.go:81
if t.file != "" {
// open + runFile + return
}
// synthetic audio generation below never runs
offline_sub_stream.go:45 passes the same AlwaysAvailableFile to every track, so the choice is all-or-nothing across the entire stream rather than per-track.
The same split appears in buildOfflineDesc (internal/stream/stream.go:226-243), which is a plain if/else — when a file is set, alwaysAvailableTracks is never consulted.
Impact
This affects any deployment whose live streams carry G711 audio — common with IP cameras and ONVIF/RTSP sources, and in VoIP/intercom setups. Since the offline segment's codecs must match the publisher's for the gapless concatenation the feature is built around, such users cannot substitute AAC or Opus in the offline segment. They are forced to use the built-in "STREAM IS OFFLINE" clip, with no way to show their own branding or message.
Possible solutions
- Allow the two options to coexist, treating
alwaysAvailableFile as the source for the tracks it provides and alwaysAvailableTracks as the declaration for the rest. This is the most flexible and covers the case above directly.
- Per-track file selection, e.g. an optional
file field on individual entries of alwaysAvailableTracks, letting video come from an MP4 while audio stays synthetic.
Documentation note
docs/2-features/08-always-available.md presents alwaysAvailableFile and alwaysAvailableTracks in separate examples but never states that they cannot be combined, nor that the supported codec set differs between them — G711 is listed as an available value for alwaysAvailableTracks, which is where users will reasonably expect it to keep working. Worth documenting regardless of which fix is chosen.
Describe the feature
alwaysAvailableFileandalwaysAvailableTracksare mutually exclusive, making custom offline video impossible for G711 audioDescription
When using the always-available feature, there is no way to combine a custom offline video clip with a G711 audio track. The two configuration mechanisms are mutually exclusive, and G711 is only reachable through the one that forbids custom video.
Steps to reproduce
Expected: custom video from the file, G711 silence generated for the audio track.
Actual: the server refuses to start:
Why there is no workaround
Removing
alwaysAvailableTracksand muxing G711 into the MP4 instead does not work either, because G711 is not accepted from a file:checkAlwaysAvailableFile(internal/conf/path.go:154-159) whitelists onlyAV1, VP9, H265, H264, Opus, MPEG4Audio, LPCMand rejects anything else withunsupported codec %T.mediasFromAlwaysAvailableFile(internal/stream/stream.go:24-114) has no*codecs.G711case, so such a track would produce no media even if validation passed.Conversely, removing
alwaysAvailableFilegives up the custom video. G711 support exists only as synthetic silence generation inofflineSubStreamTrack.run(internal/stream/offline_sub_stream_track.go:163-190, emitting0xFFfor µ-law /0xD5for A-law), and that code is unreachable whenever a file is configured — the file branch short-circuits first, for every track:offline_sub_stream.go:45passes the sameAlwaysAvailableFileto every track, so the choice is all-or-nothing across the entire stream rather than per-track.The same split appears in
buildOfflineDesc(internal/stream/stream.go:226-243), which is a plain if/else — when a file is set,alwaysAvailableTracksis never consulted.Impact
This affects any deployment whose live streams carry G711 audio — common with IP cameras and ONVIF/RTSP sources, and in VoIP/intercom setups. Since the offline segment's codecs must match the publisher's for the gapless concatenation the feature is built around, such users cannot substitute AAC or Opus in the offline segment. They are forced to use the built-in "STREAM IS OFFLINE" clip, with no way to show their own branding or message.
Possible solutions
alwaysAvailableFileas the source for the tracks it provides andalwaysAvailableTracksas the declaration for the rest. This is the most flexible and covers the case above directly.filefield on individual entries ofalwaysAvailableTracks, letting video come from an MP4 while audio stays synthetic.Documentation note
docs/2-features/08-always-available.mdpresentsalwaysAvailableFileandalwaysAvailableTracksin separate examples but never states that they cannot be combined, nor that the supported codec set differs between them — G711 is listed as an available value foralwaysAvailableTracks, which is where users will reasonably expect it to keep working. Worth documenting regardless of which fix is chosen.