diff --git a/go.mod b/go.mod index f2718d90e14..0a3d8e1f497 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/abema/go-mp4 v1.2.0 github.com/alecthomas/kong v1.2.1 github.com/asticode/go-astits v1.13.0 - github.com/bluenviron/gohlslib v1.4.0 + github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8 github.com/bluenviron/gortsplib/v4 v4.10.6 github.com/bluenviron/mediacommon v1.12.4 github.com/datarhei/gosrt v0.7.0 diff --git a/go.sum b/go.sum index 2f81d800d29..ba26b795af0 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,10 @@ github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwf github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4= github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI= -github.com/bluenviron/gohlslib v1.4.0 h1:3a9W1x8eqlxJUKt1sJCunPGtti5ALIY2ik4GU0RVe7E= -github.com/bluenviron/gohlslib v1.4.0/go.mod h1:q5ZElzNw5GRbV1VEI45qkcPbKBco6BP58QEY5HyFsmo= +github.com/bluenviron/gohlslib/v2 v2.0.0-20240922120058-777eb7502c6d h1:9A3/zdCFSAJLtza+6pVjY68gfje4FC82BDYYPOnE3s4= +github.com/bluenviron/gohlslib/v2 v2.0.0-20240922120058-777eb7502c6d/go.mod h1:DVvQIj+MjYydWuYDCgP+s0/GplDgUSpDNXCA/BVLhu4= +github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8 h1:OQeYfxJg5otVKa33HWJ63E+IxCJ5Ty0qwCBPD2JcIso= +github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8/go.mod h1:DVvQIj+MjYydWuYDCgP+s0/GplDgUSpDNXCA/BVLhu4= github.com/bluenviron/gortsplib/v4 v4.10.6 h1:KMvVcU21xxQQu1Jqn6D/z/FoIMn+QEKE1dBDWt4aWvg= github.com/bluenviron/gortsplib/v4 v4.10.6/go.mod h1:/7C8qoGEsIQupuVw8YnXANpqBMNBpZ+51xFreLGiN2g= github.com/bluenviron/mediacommon v1.12.4 h1:7VrA/W/iDB7VELquXqRjgjzUSJT3llZYgXjFN9WkByo= diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 7fc83bf58ed..0c861293393 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/auth" diff --git a/internal/conf/hls_variant.go b/internal/conf/hls_variant.go index d9f970a4f5e..6fd364a2680 100644 --- a/internal/conf/hls_variant.go +++ b/internal/conf/hls_variant.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" ) // HLSVariant is the hlsVariant parameter. diff --git a/internal/protocols/hls/from_stream.go b/internal/protocols/hls/from_stream.go index 75e033df709..dd9b7761e80 100644 --- a/internal/protocols/hls/from_stream.go +++ b/internal/protocols/hls/from_stream.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - "github.com/bluenviron/gohlslib" - "github.com/bluenviron/gohlslib/pkg/codecs" + "github.com/bluenviron/gohlslib/v2" + "github.com/bluenviron/gohlslib/v2/pkg/codecs" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/asyncwriter" "github.com/bluenviron/mediamtx/internal/logger" @@ -22,11 +22,18 @@ func setupVideoTrack( stream *stream.Stream, writer *asyncwriter.Writer, muxer *gohlslib.Muxer, -) format.Format { + setuppedFormats map[format.Format]struct{}, +) { var videoFormatAV1 *format.AV1 videoMedia := stream.Desc().FindFormat(&videoFormatAV1) if videoFormatAV1 != nil { + track := &gohlslib.Track{ + Codec: &codecs.AV1{}, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[videoFormatAV1] = struct{}{} + stream.AddReader(writer, videoMedia, videoFormatAV1, func(u unit.Unit) error { tunit := u.(*unit.AV1) @@ -34,7 +41,7 @@ func setupVideoTrack( return nil } - err := muxer.WriteAV1(tunit.NTP, tunit.PTS, tunit.TU) + err := muxer.WriteAV1(track, tunit.NTP, tunit.PTS, tunit.TU) if err != nil { return fmt.Errorf("muxer error: %w", err) } @@ -42,16 +49,19 @@ func setupVideoTrack( return nil }) - muxer.VideoTrack = &gohlslib.Track{ - Codec: &codecs.AV1{}, - } - return videoFormatAV1 + return } var videoFormatVP9 *format.VP9 videoMedia = stream.Desc().FindFormat(&videoFormatVP9) if videoFormatVP9 != nil { + track := &gohlslib.Track{ + Codec: &codecs.VP9{}, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[videoFormatVP9] = struct{}{} + stream.AddReader(writer, videoMedia, videoFormatVP9, func(u unit.Unit) error { tunit := u.(*unit.VP9) @@ -59,7 +69,7 @@ func setupVideoTrack( return nil } - err := muxer.WriteVP9(tunit.NTP, tunit.PTS, tunit.Frame) + err := muxer.WriteVP9(track, tunit.NTP, tunit.PTS, tunit.Frame) if err != nil { return fmt.Errorf("muxer error: %w", err) } @@ -67,16 +77,24 @@ func setupVideoTrack( return nil }) - muxer.VideoTrack = &gohlslib.Track{ - Codec: &codecs.VP9{}, - } - return videoFormatVP9 + return } var videoFormatH265 *format.H265 videoMedia = stream.Desc().FindFormat(&videoFormatH265) if videoFormatH265 != nil { + vps, sps, pps := videoFormatH265.SafeParams() + track := &gohlslib.Track{ + Codec: &codecs.H265{ + VPS: vps, + SPS: sps, + PPS: pps, + }, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[videoFormatH265] = struct{}{} + stream.AddReader(writer, videoMedia, videoFormatH265, func(u unit.Unit) error { tunit := u.(*unit.H265) @@ -84,7 +102,7 @@ func setupVideoTrack( return nil } - err := muxer.WriteH265(tunit.NTP, tunit.PTS, tunit.AU) + err := muxer.WriteH265(track, tunit.NTP, tunit.PTS, tunit.AU) if err != nil { return fmt.Errorf("muxer error: %w", err) } @@ -92,22 +110,23 @@ func setupVideoTrack( return nil }) - vps, sps, pps := videoFormatH265.SafeParams() - - muxer.VideoTrack = &gohlslib.Track{ - Codec: &codecs.H265{ - VPS: vps, - SPS: sps, - PPS: pps, - }, - } - return videoFormatH265 + return } var videoFormatH264 *format.H264 videoMedia = stream.Desc().FindFormat(&videoFormatH264) if videoFormatH264 != nil { + sps, pps := videoFormatH264.SafeParams() + track := &gohlslib.Track{ + Codec: &codecs.H264{ + SPS: sps, + PPS: pps, + }, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[videoFormatH264] = struct{}{} + stream.AddReader(writer, videoMedia, videoFormatH264, func(u unit.Unit) error { tunit := u.(*unit.H264) @@ -115,7 +134,7 @@ func setupVideoTrack( return nil } - err := muxer.WriteH264(tunit.NTP, tunit.PTS, tunit.AU) + err := muxer.WriteH264(track, tunit.NTP, tunit.PTS, tunit.AU) if err != nil { return fmt.Errorf("muxer error: %w", err) } @@ -123,85 +142,76 @@ func setupVideoTrack( return nil }) - sps, pps := videoFormatH264.SafeParams() - - muxer.VideoTrack = &gohlslib.Track{ - Codec: &codecs.H264{ - SPS: sps, - PPS: pps, - }, - } - return videoFormatH264 + return } - - return nil } -func setupAudioTrack( +func setupAudioTracks( stream *stream.Stream, writer *asyncwriter.Writer, muxer *gohlslib.Muxer, -) format.Format { - var audioFormatOpus *format.Opus - audioMedia := stream.Desc().FindFormat(&audioFormatOpus) - - if audioFormatOpus != nil { - stream.AddReader(writer, audioMedia, audioFormatOpus, func(u unit.Unit) error { - tunit := u.(*unit.Opus) - - err := muxer.WriteOpus( - tunit.NTP, - tunit.PTS, - tunit.Packets) - if err != nil { - return fmt.Errorf("muxer error: %w", err) - } - - return nil - }) - - muxer.AudioTrack = &gohlslib.Track{ - Codec: &codecs.Opus{ - ChannelCount: audioFormatOpus.ChannelCount, - }, - } - return audioFormatOpus - } + setuppedFormats map[format.Format]struct{}, +) { + for _, media := range stream.Desc().Medias { + for _, forma := range media.Formats { + switch forma := forma.(type) { + case *format.Opus: + track := &gohlslib.Track{ + Codec: &codecs.Opus{ + ChannelCount: forma.ChannelCount, + }, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[forma] = struct{}{} - var audioFormatMPEG4Audio *format.MPEG4Audio - audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4Audio) + stream.AddReader(writer, media, forma, func(u unit.Unit) error { + tunit := u.(*unit.Opus) - if audioFormatMPEG4Audio != nil { - co := audioFormatMPEG4Audio.GetConfig() - if co != nil { - stream.AddReader(writer, audioMedia, audioFormatMPEG4Audio, func(u unit.Unit) error { - tunit := u.(*unit.MPEG4Audio) + err := muxer.WriteOpus( + track, + tunit.NTP, + tunit.PTS, + tunit.Packets) + if err != nil { + return fmt.Errorf("muxer error: %w", err) + } - if tunit.AUs == nil { return nil + }) + + case *format.MPEG4Audio: + co := forma.GetConfig() + if co != nil { + track := &gohlslib.Track{ + Codec: &codecs.MPEG4Audio{ + Config: *co, + }, + } + muxer.Tracks = append(muxer.Tracks, track) + setuppedFormats[forma] = struct{}{} + + stream.AddReader(writer, media, forma, func(u unit.Unit) error { + tunit := u.(*unit.MPEG4Audio) + + if tunit.AUs == nil { + return nil + } + + err := muxer.WriteMPEG4Audio( + track, + tunit.NTP, + tunit.PTS, + tunit.AUs) + if err != nil { + return fmt.Errorf("muxer error: %w", err) + } + + return nil + }) } - - err := muxer.WriteMPEG4Audio( - tunit.NTP, - tunit.PTS, - tunit.AUs) - if err != nil { - return fmt.Errorf("muxer error: %w", err) - } - - return nil - }) - - muxer.AudioTrack = &gohlslib.Track{ - Codec: &codecs.MPEG4Audio{ - Config: *co, - }, } - return audioFormatMPEG4Audio } } - - return nil } // FromStream maps a MediaMTX stream to a HLS muxer. @@ -211,27 +221,31 @@ func FromStream( muxer *gohlslib.Muxer, l logger.Writer, ) error { - videoFormat := setupVideoTrack( + setuppedFormats := make(map[format.Format]struct{}) + + setupVideoTrack( stream, writer, muxer, + setuppedFormats, ) - audioFormat := setupAudioTrack( + setupAudioTracks( stream, writer, muxer, + setuppedFormats, ) - if videoFormat == nil && audioFormat == nil { + if len(muxer.Tracks) == 0 { return ErrNoSupportedCodecs } n := 1 for _, media := range stream.Desc().Medias { for _, forma := range media.Formats { - if forma != videoFormat && forma != audioFormat { - l.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec()) + if _, ok := setuppedFormats[forma]; !ok { + l.Log(logger.Warn, "skipping track with codec %s", forma.Codec()) } n++ } diff --git a/internal/protocols/hls/from_stream_test.go b/internal/protocols/hls/from_stream_test.go index 3b8ee7416df..4f0153358a1 100644 --- a/internal/protocols/hls/from_stream_test.go +++ b/internal/protocols/hls/from_stream_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/asyncwriter" diff --git a/internal/protocols/hls/to_stream.go b/internal/protocols/hls/to_stream.go index 703c6e01665..9b003e182b3 100644 --- a/internal/protocols/hls/to_stream.go +++ b/internal/protocols/hls/to_stream.go @@ -3,8 +3,8 @@ package hls import ( "time" - "github.com/bluenviron/gohlslib" - "github.com/bluenviron/gohlslib/pkg/codecs" + "github.com/bluenviron/gohlslib/v2" + "github.com/bluenviron/gohlslib/v2/pkg/codecs" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/stream" diff --git a/internal/protocols/hls/to_stream_test.go b/internal/protocols/hls/to_stream_test.go index bea6daaea4c..811f3607974 100644 --- a/internal/protocols/hls/to_stream_test.go +++ b/internal/protocols/hls/to_stream_test.go @@ -3,7 +3,7 @@ package hls import ( "testing" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" "github.com/stretchr/testify/require" ) diff --git a/internal/servers/hls/index.html b/internal/servers/hls/index.html index cd14504dadd..0dd7265d796 100644 --- a/internal/servers/hls/index.html +++ b/internal/servers/hls/index.html @@ -36,12 +36,41 @@ box-sizing: border-box; text-shadow: 0 0 5px black; } +#lang-icon { + display: none; + position: absolute; + top: 20px; + right: 20px; + width: 30px; + height: 30px; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0iI2ZmZiIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48cGF0aCBkPSJNMzguNSAzMy45bC0xLjktMS42YzIuNS0yLjkgMy44LTYuMyAzLjgtOS45IDAtMy4xLTEtNi4xLTIuOS04LjhsMi4xLTEuNWMyLjIgMy4xIDMuNCA2LjYgMy40IDEwLjItLjEgNC4zLTEuNiA4LjMtNC41IDExLjZ6TTUuNiAyMy4yaC0zYy0uNSAwLTEgLjUtMSAxLjF2MTAuNWMwIC42LjQgMS4xIDEgMS4xaDNjLjIgMCAuMyAwIC40LjFsMTMuOCA3LjhjLjYuNCAxLjQtLjIgMS40LTFWMTYuM2MwLS44LS44LTEuMy0xLjQtMUw2LjEgMjMuMWMtLjIuMS0uMy4xLS41LjF6bTIxLTE2LjlMMTIuOCAxNGMtLjEuMS0uMy4xLS40LjFoLTNjLS41IDAtMSAuNS0xIDEuMVYyMGwxMi4yLTYuOGExLjM2IDEuMzYgMCAwIDEgMS41IDBjLjUuMy44LjguOCAxLjV2MTcuOWwzLjcgMi4xYy42LjQgMS40LS4yIDEuNC0xVjcuMmMuMS0uOC0uNy0xLjMtMS40LS45em0xNi41IDMwLjJsLTEuOS0xLjZjMy4xLTMuNyA0LjctOCA0LjctMTIuNSAwLTQtMS4zLTcuOC0zLjctMTEuMmwyLjEtMS41YzIuNyAzLjggNC4yIDguMiA0LjIgMTIuNy0uMiA1LjEtMiA5LjktNS40IDE0LjF6TTM1IDMxLjFsLTItMS42YzEuNy0yLjEgMi42LTQuNiAyLjYtNy4yIDAtMi40LS44LTQuNy0yLjItNi43bDItMS41YzEuOCAyLjUgMi43IDUuMyAyLjcgOC4yIDAgMy4yLTEuMSA2LjItMy4xIDguOHoiLz48L3N2Zz4="); + background-size: 80%; + background-position: center; + background-repeat: no-repeat; + cursor: pointer; +} +#lang-list { + display: none; + position: absolute; + top: 100%; + right: 0; + background: rgb(190, 190, 190); + color: black; +} +#lang-icon:hover #lang-list { + display: block; +} +#lang-list div { + border-bottom: 1px solid black; + padding: 5px 15px; +}
+
@@ -51,6 +80,8 @@ const video = document.getElementById('video'); const message = document.getElementById('message'); +const langIcon = document.getElementById('lang-icon'); +const langList = document.getElementById('lang-list'); let defaultControls = false; @@ -83,8 +114,11 @@ if (data.fatal) { hls.destroy(); + langIcon.style.display = 'none'; + langList.innerHTML = ''; + if (data.details === 'manifestIncompatibleCodecsError') { - setMessage('stream makes use of codecs which are incompatible with this browser or operative system'); + setMessage('stream makes use of codecs which are not compatible with this browser or operative system'); } else if (data.response && data.response.code === 404) { setMessage('stream not found, retrying in some seconds'); } else { @@ -99,7 +133,19 @@ hls.loadSource('index.m3u8' + window.location.search); }); - hls.on(Hls.Events.MANIFEST_PARSED, () => { + hls.on(Hls.Events.MANIFEST_LOADED, () => { + if (hls.audioTracks.length > 1) { + for (const track of hls.audioTracks) { + const div = document.createElement('DIV'); + div.innerText = track.name; + div.addEventListener('click', () => { + hls.audioTrack = track.id; + }); + langList.appendChild(div); + } + langIcon.style.display = 'block'; + } + setMessage(''); video.play(); }); diff --git a/internal/servers/hls/muxer_instance.go b/internal/servers/hls/muxer_instance.go index 5ed2379d875..aeb9de0fc33 100644 --- a/internal/servers/hls/muxer_instance.go +++ b/internal/servers/hls/muxer_instance.go @@ -5,7 +5,7 @@ import ( "path/filepath" "time" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/mediamtx/internal/asyncwriter" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/defs" @@ -42,12 +42,15 @@ func (mi *muxerInstance) initialize() error { } mi.hmuxer = &gohlslib.Muxer{ - Variant: gohlslib.MuxerVariant(mi.variant), - SegmentCount: mi.segmentCount, - SegmentDuration: time.Duration(mi.segmentDuration), - PartDuration: time.Duration(mi.partDuration), - SegmentMaxSize: uint64(mi.segmentMaxSize), - Directory: muxerDirectory, + Variant: gohlslib.MuxerVariant(mi.variant), + SegmentCount: mi.segmentCount, + SegmentMinDuration: time.Duration(mi.segmentDuration), + PartMinDuration: time.Duration(mi.partDuration), + SegmentMaxSize: uint64(mi.segmentMaxSize), + Directory: muxerDirectory, + OnEncodeError: func(err error) { + mi.Log(logger.Warn, err.Error()) + }, } err := hls.FromStream(mi.stream, mi.writer, mi.hmuxer, mi) diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go index b014702b038..fa9bfd97a25 100644 --- a/internal/servers/hls/server_test.go +++ b/internal/servers/hls/server_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/bluenviron/gohlslib" - "github.com/bluenviron/gohlslib/pkg/codecs" + "github.com/bluenviron/gohlslib/v2" + "github.com/bluenviron/gohlslib/v2/pkg/codecs" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/defs" diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go index 095ed7a7cf5..da6765d233e 100644 --- a/internal/staticsources/hls/source.go +++ b/internal/staticsources/hls/source.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - "github.com/bluenviron/gohlslib" + "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/mediamtx/internal/conf"