diff --git a/muxer.go b/muxer.go index 7d58879..601813c 100644 --- a/muxer.go +++ b/muxer.go @@ -29,6 +29,14 @@ func (w *switchableWriter) Write(p []byte) (int, error) { return w.w.Write(p) } +func isVideo(codec codecs.Codec) bool { + switch codec.(type) { + case *codecs.AV1, *codecs.VP9, *codecs.H265, *codecs.H264: + return true + } + return false +} + // a prefix is needed to prevent usage of cached segments // from previous muxing sessions. func generatePrefix() (string, error) { @@ -117,12 +125,10 @@ type fmp4AugmentedSample struct { // Muxer is a HLS muxer. type Muxer struct { // - // parameters (all optional except VideoTrack or AudioTrack). + // parameters (all optional except Tracks). // - // video track. - VideoTrack *Track - // audio track. - AudioTrack *Track + // tracks. + Tracks []*Track // Variant to use. // It defaults to MuxerVariantLowLatency Variant MuxerVariant @@ -189,23 +195,49 @@ func (m *Muxer) Start() error { m.SegmentMaxSize = 50 * 1024 * 1024 } - if m.VideoTrack == nil && m.AudioTrack == nil { - return fmt.Errorf("one between VideoTrack and AudioTrack is required") + if len(m.Tracks) == 0 { + return fmt.Errorf("at least one track must be provided") } + hasVideo := false + hasAudio := false + if m.Variant == MuxerVariantMPEGTS { - if m.VideoTrack != nil { - if _, ok := m.VideoTrack.Codec.(*codecs.H264); !ok { - return fmt.Errorf( - "the MPEG-TS variant of HLS only supports H264 video. Use the fMP4 or Low-Latency variants instead") + for _, track := range m.Tracks { + if isVideo(track.Codec) { + if hasVideo { + return fmt.Errorf("the MPEG-TS variant of HLS supports only one video track") + } + if _, ok := track.Codec.(*codecs.H264); !ok { + return fmt.Errorf( + "the MPEG-TS variant of HLS only supports H264 video. Use the fMP4 or Low-Latency variants instead") + } + hasVideo = true + } else { + if hasAudio { + return fmt.Errorf("the MPEG-TS variant of HLS supports only one audio track") + } + if _, ok := track.Codec.(*codecs.MPEG4Audio); !ok { + return fmt.Errorf( + "the MPEG-TS variant of HLS only supports MPEG-4 Audio. Use the fMP4 or Low-Latency variants instead") + } + hasAudio = true } } - if m.AudioTrack != nil { - if _, ok := m.AudioTrack.Codec.(*codecs.MPEG4Audio); !ok { - return fmt.Errorf( - "the MPEG-TS variant of HLS only supports MPEG-4 Audio. Use the fMP4 or Low-Latency variants instead") + } else { + for _, track := range m.Tracks { + if isVideo(track.Codec) { + if hasVideo { + return fmt.Errorf("only one video track is currently supported") + } + hasVideo = true + } else { + hasAudio = true } } + if len(m.Tracks) > 1 && !hasVideo { + return fmt.Errorf("multiple tracks are supported only when a video track is present") + } } switch m.Variant { @@ -233,26 +265,15 @@ func (m *Muxer) Start() error { } m.server.initialize() - if m.VideoTrack != nil { - track := &muxerTrack{ - Track: m.VideoTrack, + for i, track := range m.Tracks { + mtrack := &muxerTrack{ + Track: track, variant: m.Variant, - isLeading: true, + isLeading: isVideo(track.Codec) || (!hasVideo && i == 0), } - track.initialize() - m.mtracks = append(m.mtracks, track) - m.mtracksByTrack[m.VideoTrack] = track - } - - if m.AudioTrack != nil { - track := &muxerTrack{ - Track: m.AudioTrack, - variant: m.Variant, - isLeading: m.VideoTrack == nil, - } - track.initialize() - m.mtracks = append(m.mtracks, track) - m.mtracksByTrack[m.AudioTrack] = track + mtrack.initialize() + m.mtracks = append(m.mtracks, mtrack) + m.mtracksByTrack[track] = mtrack } if m.Variant == MuxerVariantMPEGTS { @@ -275,25 +296,32 @@ func (m *Muxer) Start() error { m.streams = append(m.streams, stream) default: - if m.VideoTrack != nil { - videoStream := &muxerStream{ - muxer: m, - tracks: []*muxerTrack{m.mtracksByTrack[m.VideoTrack]}, - id: "video", + defaultRenditionPicked := false + + for i, track := range m.mtracks { + var id string + if isVideo(track.Codec) { + id = "video" + strconv.FormatInt(int64(i+1), 10) + } else { + id = "audio" + strconv.FormatInt(int64(i+1), 10) + } + + isRendition := !track.isLeading + + defaultRendition := isRendition && !defaultRenditionPicked + if defaultRendition { + defaultRenditionPicked = true } - videoStream.initialize() - m.streams = append(m.streams, videoStream) - } - if m.AudioTrack != nil { - audioStream := &muxerStream{ - muxer: m, - tracks: []*muxerTrack{m.mtracksByTrack[m.AudioTrack]}, - id: "audio", - isRendition: m.VideoTrack != nil, + stream := &muxerStream{ + muxer: m, + tracks: []*muxerTrack{track}, + id: id, + isRendition: isRendition, + defaultRendition: defaultRendition, } - audioStream.initialize() - m.streams = append(m.streams, audioStream) + stream.initialize() + m.streams = append(m.streams, stream) } } @@ -327,52 +355,62 @@ func (m *Muxer) Close() { // WriteAV1 writes an AV1 temporal unit. func (m *Muxer) WriteAV1( + track *Track, ntp time.Time, pts time.Duration, tu [][]byte, ) error { - return m.segmenter.writeAV1(ntp, pts, tu) + return m.segmenter.writeAV1(m.mtracksByTrack[track], ntp, pts, tu) } // WriteVP9 writes a VP9 frame. func (m *Muxer) WriteVP9( + track *Track, ntp time.Time, pts time.Duration, frame []byte, ) error { - return m.segmenter.writeVP9(ntp, pts, frame) + return m.segmenter.writeVP9(m.mtracksByTrack[track], ntp, pts, frame) } // WriteH265 writes an H265 access unit. func (m *Muxer) WriteH265( + track *Track, ntp time.Time, pts time.Duration, au [][]byte, ) error { - return m.segmenter.writeH265(ntp, pts, au) + return m.segmenter.writeH265(m.mtracksByTrack[track], ntp, pts, au) } // WriteH264 writes an H264 access unit. func (m *Muxer) WriteH264( + track *Track, ntp time.Time, pts time.Duration, au [][]byte, ) error { - return m.segmenter.writeH264(ntp, pts, au) + return m.segmenter.writeH264(m.mtracksByTrack[track], ntp, pts, au) } // WriteOpus writes Opus packets. func (m *Muxer) WriteOpus( + track *Track, ntp time.Time, pts time.Duration, packets [][]byte, ) error { - return m.segmenter.writeOpus(ntp, pts, packets) + return m.segmenter.writeOpus(m.mtracksByTrack[track], ntp, pts, packets) } // WriteMPEG4Audio writes MPEG-4 Audio access units. -func (m *Muxer) WriteMPEG4Audio(ntp time.Time, pts time.Duration, aus [][]byte) error { - return m.segmenter.writeMPEG4Audio(ntp, pts, aus) +func (m *Muxer) WriteMPEG4Audio( + track *Track, + ntp time.Time, + pts time.Duration, + aus [][]byte, +) error { + return m.segmenter.writeMPEG4Audio(m.mtracksByTrack[track], ntp, pts, aus) } // Handle handles a HTTP request. diff --git a/muxer_segmenter.go b/muxer_segmenter.go index 8e339d3..06ef614 100644 --- a/muxer_segmenter.go +++ b/muxer_segmenter.go @@ -31,12 +31,11 @@ func (s *muxerSegmenter) initialize() { } func (s *muxerSegmenter) writeAV1( + track *muxerTrack, ntp time.Time, pts time.Duration, tu [][]byte, ) error { - track := s.muxer.mtracksByTrack[s.muxer.VideoTrack] - codec := track.Codec.(*codecs.AV1) randomAccess := false @@ -86,12 +85,11 @@ func (s *muxerSegmenter) writeAV1( } func (s *muxerSegmenter) writeVP9( + track *muxerTrack, ntp time.Time, pts time.Duration, frame []byte, ) error { - track := s.muxer.mtracksByTrack[s.muxer.VideoTrack] - var h vp9.Header err := h.Unmarshal(frame) if err != nil { @@ -163,12 +161,11 @@ func (s *muxerSegmenter) writeVP9( } func (s *muxerSegmenter) writeH265( + track *muxerTrack, ntp time.Time, pts time.Duration, au [][]byte, ) error { - track := s.muxer.mtracksByTrack[s.muxer.VideoTrack] - randomAccess := false codec := track.Codec.(*codecs.H265) @@ -245,12 +242,11 @@ func (s *muxerSegmenter) writeH265( } func (s *muxerSegmenter) writeH264( + track *muxerTrack, ntp time.Time, pts time.Duration, au [][]byte, ) error { - track := s.muxer.mtracksByTrack[s.muxer.VideoTrack] - randomAccess := false codec := track.Codec.(*codecs.H264) nonIDRPresent := false @@ -356,12 +352,11 @@ func (s *muxerSegmenter) writeH264( } func (s *muxerSegmenter) writeOpus( + track *muxerTrack, ntp time.Time, pts time.Duration, packets [][]byte, ) error { - track := s.muxer.mtracksByTrack[s.muxer.AudioTrack] - if s.muxer.Variant == MuxerVariantMPEGTS { return fmt.Errorf("unimplemented") } else { @@ -389,11 +384,14 @@ func (s *muxerSegmenter) writeOpus( } } -func (s *muxerSegmenter) writeMPEG4Audio(ntp time.Time, pts time.Duration, aus [][]byte) error { - track := s.muxer.mtracksByTrack[s.muxer.AudioTrack] - +func (s *muxerSegmenter) writeMPEG4Audio( + track *muxerTrack, + ntp time.Time, + pts time.Duration, + aus [][]byte, +) error { if s.muxer.Variant == MuxerVariantMPEGTS { - if s.muxer.VideoTrack == nil { + if track.isLeading { if track.stream.nextSegment == nil { err := s.muxer.createFirstSegment(pts, ntp) if err != nil { @@ -420,7 +418,7 @@ func (s *muxerSegmenter) writeMPEG4Audio(ntp time.Time, pts time.Duration, aus [ return nil } else { - sampleRate := time.Duration(s.muxer.AudioTrack.Codec.(*codecs.MPEG4Audio).Config.SampleRate) + sampleRate := time.Duration(track.Codec.(*codecs.MPEG4Audio).Config.SampleRate) for i, au := range aus { auNTP := ntp.Add(time.Duration(i) * mpeg4audio.SamplesPerAccessUnit * @@ -551,7 +549,7 @@ func (s *muxerSegmenter) fmp4WriteAudio( duration := track.fmp4NextSample.dts - sample.dts sample.Duration = uint32(durationGoToMp4(duration, track.fmp4TimeScale)) - if s.muxer.VideoTrack == nil { + if track.isLeading { // create first segment if track.stream.nextSegment == nil { err := s.muxer.createFirstSegment(sample.dts, sample.ntp) @@ -576,7 +574,7 @@ func (s *muxerSegmenter) fmp4WriteAudio( } // switch segment - if s.muxer.VideoTrack == nil && + if track.isLeading && (track.fmp4NextSample.dts-track.stream.nextSegment.(*muxerSegmentFMP4).startDTS) >= s.muxer.SegmentMinDuration { err = s.muxer.rotateSegments(track.fmp4NextSample.dts, track.fmp4NextSample.ntp, false) if err != nil { diff --git a/muxer_stream.go b/muxer_stream.go index c9f2472..db64070 100644 --- a/muxer_stream.go +++ b/muxer_stream.go @@ -33,16 +33,26 @@ func filterOutHLSParams(rawQuery string) string { return rawQuery } +func containsCodec(cs []string, c string) bool { + for _, c0 := range cs { + if c0 == c { + return true + } + } + return false +} + type generateMediaPlaylistFunc func( isDeltaUpdate bool, rawQuery string, ) ([]byte, error) type muxerStream struct { - muxer *Muxer // TODO: remove - tracks []*muxerTrack - id string - isRendition bool + muxer *Muxer // TODO: remove + tracks []*muxerTrack + id string + isRendition bool + defaultRendition bool generateMediaPlaylist generateMediaPlaylistFunc @@ -97,7 +107,10 @@ func (s *muxerStream) populateMultivariantPlaylist( mv := pl.Variants[0] for _, track := range s.tracks { - mv.Codecs = append(mv.Codecs, codecparams.Marshal(track.Codec)) + codec := codecparams.Marshal(track.Codec) + if !containsCodec(mv.Codecs, codec) { + mv.Codecs = append(mv.Codecs, codec) + } switch codec := track.Codec.(type) { case *codecs.AV1: @@ -157,9 +170,12 @@ func (s *muxerStream) populateMultivariantPlaylist( mv.Audio = "audio" r := &playlist.MultivariantRendition{ - Type: playlist.MultivariantRenditionTypeAudio, - GroupID: "audio", - URI: uri, + Type: playlist.MultivariantRenditionTypeAudio, + GroupID: "audio", + Name: s.id, + Default: s.defaultRendition, + Autoselect: true, + URI: uri, } pl.Renditions = append(pl.Renditions, r) } diff --git a/muxer_test.go b/muxer_test.go index e3769e5..7ffe5ff 100644 --- a/muxer_test.go +++ b/muxer_test.go @@ -116,8 +116,7 @@ func TestMuxerVideoAudio(t *testing.T) { return 3 }(), SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, - AudioTrack: testAudioTrack, + Tracks: []*Track{testVideoTrack, testAudioTrack}, } err := m.Start() @@ -126,14 +125,14 @@ func TestMuxerVideoAudio(t *testing.T) { // access unit without IDR d := 1 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ {1}, // non-IDR }) require.NoError(t, err) // access unit with IDR d = 2 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -141,40 +140,40 @@ func TestMuxerVideoAudio(t *testing.T) { require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) d = 3500 * time.Millisecond - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) // access unit without IDR d = 4 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ {1}, // non-IDR }) require.NoError(t, err) d = 4500 * time.Millisecond - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) // access unit with IDR d = 6 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ {5}, // IDR }) require.NoError(t, err) // access unit with IDR d = 7 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ {5}, // IDR }) require.NoError(t, err) @@ -315,7 +314,7 @@ func TestMuxerVideoAudio(t *testing.T) { }() d = 9 * time.Second - err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ {1}, // non-IDR }) require.NoError(t, err) @@ -343,7 +342,7 @@ func TestMuxerVideoOnly(t *testing.T) { Variant: v, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() @@ -352,7 +351,7 @@ func TestMuxerVideoOnly(t *testing.T) { // access unit with IDR d := 2 * time.Second - err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -361,14 +360,14 @@ func TestMuxerVideoOnly(t *testing.T) { // access unit with IDR d = 6 * time.Second - err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ {5}, // IDR }) require.NoError(t, err) // access unit with IDR d = 7 * time.Second - err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ {5}, // IDR }) require.NoError(t, err) @@ -457,7 +456,7 @@ func TestMuxerAudioOnly(t *testing.T) { Variant: v, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - AudioTrack: testAudioTrack, + Tracks: []*Track{testAudioTrack}, } err := m.Start() @@ -466,20 +465,20 @@ func TestMuxerAudioOnly(t *testing.T) { for i := 0; i < 100; i++ { d := 1 * time.Second - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) } d := 2 * time.Second - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testTime.Add(d-1*time.Second), d, [][]byte{{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ 0x01, 0x02, 0x03, 0x04, }}) require.NoError(t, err) @@ -551,9 +550,9 @@ func TestMuxerCloseBeforeData(t *testing.T) { Variant: MuxerVariantFMP4, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: &Track{ + Tracks: []*Track{{ Codec: &codecs.AV1{}, - }, + }}, } err := m.Start() @@ -577,14 +576,14 @@ func TestMuxerMaxSegmentSize(t *testing.T) { SegmentCount: 3, SegmentMinDuration: 1 * time.Second, SegmentMaxSize: 1, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ testSPS, {5}, // IDR }) @@ -596,21 +595,21 @@ func TestMuxerDoubleRead(t *testing.T) { Variant: MuxerVariantMPEGTS, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteH264(testTime, 0, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 0, [][]byte{ testSPS, {5}, // IDR {1}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ {5}, // IDR {2}, }) @@ -659,27 +658,27 @@ func TestMuxerSaveToDisk(t *testing.T) { Variant: v, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, Directory: dir, } err = m.Start() require.NoError(t, err) - err = m.WriteH264(testTime, 0, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 0, [][]byte{ testSPS, {5}, // IDR {1}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ {5}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 3*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ {5}, // IDR {2}, }) @@ -743,27 +742,27 @@ func TestMuxerDynamicParams(t *testing.T) { Variant: MuxerVariantFMP4, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteH264(testTime, 0, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 0, [][]byte{ testSPS, {5}, // IDR {1}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 1*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 1*time.Second, [][]byte{ {5}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ {5}, // IDR {2}, }) @@ -813,14 +812,14 @@ func TestMuxerDynamicParams(t *testing.T) { 0xcb, } - err = m.WriteH264(testTime, 3*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ testSPS2, {0x65, 0x88, 0x84, 0x00, 0x33, 0xff}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testTime, 5*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 5*time.Second, [][]byte{ {0x65, 0x88, 0x84, 0x00, 0x33, 0xff}, // IDR }) require.NoError(t, err) @@ -867,21 +866,21 @@ func TestMuxerFMP4ZeroDuration(t *testing.T) { Variant: MuxerVariantFMP4, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteH264(time.Now(), 0, [][]byte{ + err = m.WriteH264(testVideoTrack, time.Now(), 0, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR }) require.NoError(t, err) - err = m.WriteH264(time.Now(), 1*time.Nanosecond, [][]byte{ + err = m.WriteH264(testVideoTrack, time.Now(), 1*time.Nanosecond, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -894,65 +893,64 @@ func TestMuxerFMP4NegativeTimestamp(t *testing.T) { Variant: MuxerVariantFMP4, SegmentCount: 3, SegmentMinDuration: 2 * time.Second, - VideoTrack: testVideoTrack, - AudioTrack: testAudioTrack, + Tracks: []*Track{testVideoTrack, testAudioTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteMPEG4Audio(testTime, -9*time.Second, [][]byte{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime, -9*time.Second, [][]byte{ {1, 2, 3, 4}, }) require.NoError(t, err) // this is skipped - err = m.WriteH264(testTime, -11*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, -11*time.Second, [][]byte{ testSPS, {5}, // IDR {1}, }) require.NoError(t, err) - err = m.WriteH264(testTime, -9*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, -9*time.Second, [][]byte{ testSPS, {5}, // IDR {1}, }) require.NoError(t, err) - err = m.WriteH264(testTime, -8*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, -8*time.Second, [][]byte{ {5}, // IDR {2}, }) require.NoError(t, err) // this is skipped - err = m.WriteMPEG4Audio(testTime, -11*time.Second, [][]byte{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime, -11*time.Second, [][]byte{ {1, 2, 3, 4}, }) require.NoError(t, err) - err = m.WriteMPEG4Audio(testTime, -8*time.Second, [][]byte{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime, -8*time.Second, [][]byte{ {5, 6, 7, 8}, }) require.NoError(t, err) - err = m.WriteMPEG4Audio(testTime, -7*time.Second, [][]byte{ + err = m.WriteMPEG4Audio(testAudioTrack, testTime, -7*time.Second, [][]byte{ {9, 10, 11, 12}, }) require.NoError(t, err) // switch segment - err = m.WriteH264(testTime, -7*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, -7*time.Second, [][]byte{ {5}, // IDR {3}, }) require.NoError(t, err) // switch segment - err = m.WriteH264(testTime, -5*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, -5*time.Second, [][]byte{ {5}, // IDR {3}, }) @@ -1039,14 +1037,14 @@ func TestMuxerFMP4SequenceNumber(t *testing.T) { Variant: MuxerVariantLowLatency, SegmentCount: 7, SegmentMinDuration: 2 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() require.NoError(t, err) defer m.Close() - err = m.WriteH264(testTime, 0, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 0, [][]byte{ testSPS, {5}, // IDR {1}, @@ -1054,13 +1052,13 @@ func TestMuxerFMP4SequenceNumber(t *testing.T) { require.NoError(t, err) for i := 0; i < 3; i++ { - err = m.WriteH264(testTime, (1+time.Duration(i))*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, (1+time.Duration(i))*time.Second, [][]byte{ {1}, // non IDR }) require.NoError(t, err) } - err = m.WriteH264(testTime, 4*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 4*time.Second, [][]byte{ {5}, // IDR }) require.NoError(t, err) @@ -1131,7 +1129,7 @@ func TestMuxerInvalidFolder(t *testing.T) { Variant: v, SegmentCount: 7, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, Directory: "/nonexisting", } @@ -1140,7 +1138,7 @@ func TestMuxerInvalidFolder(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testTime, time.Duration(i)*time.Second, [][]byte{ + err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -1161,7 +1159,7 @@ func TestMuxerExpiredSegment(t *testing.T) { Variant: MuxerVariantLowLatency, SegmentCount: 7, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() @@ -1169,7 +1167,7 @@ func TestMuxerExpiredSegment(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testTime, time.Duration(i)*time.Second, [][]byte{ + err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -1201,7 +1199,7 @@ func TestMuxerPreloadHint(t *testing.T) { Variant: MuxerVariantLowLatency, SegmentCount: 7, SegmentMinDuration: 1 * time.Second, - VideoTrack: testVideoTrack, + Tracks: []*Track{testVideoTrack}, } err := m.Start() @@ -1209,7 +1207,7 @@ func TestMuxerPreloadHint(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testTime, time.Duration(i)*time.Second, [][]byte{ + err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -1267,7 +1265,7 @@ func TestMuxerPreloadHint(t *testing.T) { case <-time.After(500 * time.Millisecond): } - err = m.WriteH264(testTime, 3*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ {5}, // IDR }) require.NoError(t, err) diff --git a/pkg/playlist/multivariant.go b/pkg/playlist/multivariant.go index 3352474..b234cff 100644 --- a/pkg/playlist/multivariant.go +++ b/pkg/playlist/multivariant.go @@ -121,12 +121,6 @@ func (m Multivariant) Marshal() ([]byte, error) { ret += m.Start.marshal() } - ret += "\n" - - for _, v := range m.Variants { - ret += v.marshal() - } - if len(m.Renditions) != 0 { ret += "\n" @@ -135,5 +129,11 @@ func (m Multivariant) Marshal() ([]byte, error) { } } + ret += "\n" + + for _, v := range m.Variants { + ret += v.marshal() + } + return []byte(ret), nil }