Description
Containers like Ogg and MP4 can contain multiple tracks. Somewhat similarly, Ogg streams can be "chained" into a single file, so beyond multiple tracks in one container, you then have multiple containers, each one with one or more tracks. Before continuing with more decoder work, I would like to define how we will deal with that.
Current behavior seems emergent - which is a nice word for saying "not by design" 😄
Though I did not test any of it yet, from a code review I am fairly confident the following is true today:
Symphonia
Track selection: For containers with multiple tracks, the Symphonia decoder will load the default track. This will usually be the first track in the container, but does not have to be. After finishing this default track, it will proceed onto the next track in the container, if any. This will succeed if and only if the decoder specification (codec, channels, sample rate) is equal to the default track, in which case the iterator will continue. If the specification is different, no attempt is made to reset the decoder.
total_duration: For the default track only. So in the case of multiple tracks with the same spec, the actual playback time may exceed the reported total_duration
.
Seeking: Hard-coded to work with the default track. Calling try_seek
with a track playing beyond the default track, will make the demuxer reload the default track and seek in it.
Saturating seeks: Rodio's logic for saturating seeks will saturate on the duration of the default track. So if you've got three 5 minute tracks and you seek to 7'03'' then in effect you will seek to the end of the default track ==
the start of the second track.
Chained streams: As with tracks, but now for the demuxer instead of the decoder: will succeed if and only if the specification is equal. If the specification is different, no attempt is made to recreate the decoder.
lewton
Track selection: lewton
(and ogg
underneath it) do not provide high-level functions for determining track numbering, sequencing, or defaults. It will play the first track it encounters. After that, when there are more tracks, it is not certain but likely to stop (depending on whether the packet that comes other only contains headers or also samples).
total_duration: Not available. May be possible by parsing Ogg headers manually, but I don't intend to go there (just use Symphonia).
Seeking: Currently not implemented. Could be re-implemented probably with coarse seeking, in which case it would be relative to the entire container. As the number of tracks increases, so will the headers in between, and the seeking error will increase because you have to seek a number of pages and you don't know how many pages of headers you have. So if you seek to 7'03'' (with the same three 5 minute tracks) you will land somewhere before 7'03'' (so 2'03'' into the second track).
Saturating seeks: Not available, because no total_duration
.
Chained streams: As with tracks.
Proposal
First:
- Implement
DecoderBuilder::with_track_id
to optionally specify a track to load - Change the decoders to by default play a single track only
Then:
- Implement
DecoderBuilder::with_multi_track
to:
- enable multi-track processing mode
- report
total_duration
(if available) for all tracks - seek relative to the entire container
- saturate seeks relative to the entire duration
This last step will not be perfect for chained streams, because you cannot determine total_duration
until you have read each stream. That is not desirable nor possible, given that chained streams are literally designed for streaming. Such streams should not allow seeking anyway, so best to have the user call with_seekable(false)
on them and forget about total_duration
.