diff --git a/internal/capture/manager.go b/internal/capture/manager.go index 69408572..51d95425 100644 --- a/internal/capture/manager.go +++ b/internal/capture/manager.go @@ -19,7 +19,8 @@ type CaptureManagerCtx struct { broadcast *BroacastManagerCtx screencast *ScreencastManagerCtx audio *StreamManagerCtx - video *StreamManagerCtx + videos map[string]*StreamManagerCtx + videoIDs []string } func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx { @@ -87,7 +88,10 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt broadcast: broadcastNew(broadcastPipeline), screencast: screencastNew(config.Screencast, screencastPipeline), audio: streamNew(config.AudioCodec, audioPipeline), - video: streamNew(config.VideoCodec, videoPipeline), + videos: map[string]*StreamManagerCtx{ + "hq": streamNew(config.VideoCodec, videoPipeline), + }, + videoIDs: []string{ "hq" }, } } @@ -99,8 +103,10 @@ func (manager *CaptureManagerCtx) Start() { } manager.desktop.OnBeforeScreenSizeChange(func() { - if manager.video.Started() { - manager.video.destroyPipeline() + for _, video := range manager.videos { + if video.Started() { + video.destroyPipeline() + } } if manager.broadcast.Started() { @@ -113,9 +119,11 @@ func (manager *CaptureManagerCtx) Start() { }) manager.desktop.OnAfterScreenSizeChange(func() { - if manager.video.Started() { - if err := manager.video.createPipeline(); err != nil { - manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline") + for _, video := range manager.videos { + if video.Started() { + if err := video.createPipeline(); err != nil { + manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline") + } } } @@ -140,7 +148,11 @@ func (manager *CaptureManagerCtx) Shutdown() error { manager.screencast.shutdown() manager.audio.shutdown() - manager.video.shutdown() + + for _, video := range manager.videos { + video.shutdown() + } + return nil } @@ -156,8 +168,12 @@ func (manager *CaptureManagerCtx) Audio() types.StreamManager { return manager.audio } -func (manager *CaptureManagerCtx) Video() types.StreamManager { - return manager.video +func (manager *CaptureManagerCtx) Video(videoID string) types.StreamManager { + return manager.videos[videoID] +} + +func (manager *CaptureManagerCtx) VideoIDs() []string { + return manager.videoIDs } func (manager *CaptureManagerCtx) StartStream() { @@ -166,14 +182,13 @@ func (manager *CaptureManagerCtx) StartStream() { manager.logger.Info().Msgf("starting stream pipelines") - var err error - err = manager.Video().Start() - if err != nil { - manager.logger.Panic().Err(err).Msg("unable to start video pipeline") + for _, video := range manager.videos { + if err := video.Start(); err != nil { + manager.logger.Panic().Err(err).Msg("unable to start video pipeline") + } } - err = manager.Audio().Start() - if err != nil { + if err := manager.audio.Start(); err != nil { manager.logger.Panic().Err(err).Msg("unable to start audio pipeline") } @@ -186,8 +201,11 @@ func (manager *CaptureManagerCtx) StopStream() { manager.logger.Info().Msgf("stopping stream pipelines") - manager.Video().Stop() - manager.Audio().Stop() + for _, video := range manager.videos { + video.Stop() + } + + manager.audio.Stop() manager.streaming = false } diff --git a/internal/types/capture.go b/internal/types/capture.go index 7cde4ed4..575e52b6 100644 --- a/internal/types/capture.go +++ b/internal/types/capture.go @@ -37,7 +37,8 @@ type CaptureManager interface { Broadcast() BroadcastManager Screencast() ScreencastManager Audio() StreamManager - Video() StreamManager + Video(videoID string) StreamManager + VideoIDs() []string StartStream() StopStream() diff --git a/internal/webrtc/manager.go b/internal/webrtc/manager.go index f8e0f0a2..912b8a67 100644 --- a/internal/webrtc/manager.go +++ b/internal/webrtc/manager.go @@ -19,52 +19,59 @@ import ( func New(desktop types.DesktopManager, capture types.CaptureManager, config *config.WebRTC) *WebRTCManagerCtx { return &WebRTCManagerCtx{ - logger: log.With().Str("module", "webrtc").Logger(), - videoCodec: capture.Video().Codec(), - audioCodec: capture.Audio().Codec(), - desktop: desktop, - capture: capture, - config: config, + logger: log.With().Str("module", "webrtc").Logger(), + defaultVideoID: capture.VideoIDs()[0], + desktop: desktop, + capture: capture, + config: config, } } type WebRTCManagerCtx struct { - logger zerolog.Logger - videoTrack *webrtc.TrackLocalStaticSample - audioTrack *webrtc.TrackLocalStaticSample - videoCodec codec.RTPCodec - audioCodec codec.RTPCodec - desktop types.DesktopManager - capture types.CaptureManager - config *config.WebRTC + logger zerolog.Logger + videoTracks map[string]*webrtc.TrackLocalStaticSample + audioTrack *webrtc.TrackLocalStaticSample + defaultVideoID string + audioCodec codec.RTPCodec + desktop types.DesktopManager + capture types.CaptureManager + config *config.WebRTC } func (manager *WebRTCManagerCtx) Start() { var err error // create audio track - manager.audioTrack, err = webrtc.NewTrackLocalStaticSample(manager.audioCodec.Capability, "audio", "stream") + audio := manager.capture.Audio() + manager.audioTrack, err = webrtc.NewTrackLocalStaticSample(audio.Codec().Capability, "audio", "stream") if err != nil { manager.logger.Panic().Err(err).Msg("unable to create audio track") } - manager.capture.Audio().OnSample(func(sample types.Sample) { + audio.OnSample(func(sample types.Sample) { if err := manager.audioTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe { manager.logger.Warn().Err(err).Msg("audio pipeline failed to write") } }) - // create video track - manager.videoTrack, err = webrtc.NewTrackLocalStaticSample(manager.videoCodec.Capability, "video", "stream") - if err != nil { - manager.logger.Panic().Err(err).Msg("unable to create video track") - } + videoIDs := manager.capture.VideoIDs() + manager.videoTracks = map[string]*webrtc.TrackLocalStaticSample{} + for _, videoID := range videoIDs { + video := manager.capture.Video(videoID) - manager.capture.Video().OnSample(func(sample types.Sample) { - if err := manager.videoTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe { - manager.logger.Warn().Err(err).Msg("video pipeline failed to write") + track, err := webrtc.NewTrackLocalStaticSample(video.Codec().Capability, "video", "stream") + if err != nil { + manager.logger.Panic().Err(err).Msgf("unable to create video (%s) track", videoID) } - }) + + video.OnSample(func(sample types.Sample) { + if err := track.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe { + manager.logger.Warn().Err(err).Msgf("video (%s) pipeline failed to write", videoID) + } + }) + + manager.videoTracks[videoID] = track + } manager.logger.Info(). Str("ice_lite", fmt.Sprintf("%t", manager.config.ICELite)). @@ -137,7 +144,7 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session) (*webrtc.Sess return nil, err } - videoTransceiver, err := connection.AddTransceiverFromTrack(manager.videoTrack, webrtc.RtpTransceiverInit{ + videoTransceiver, err := connection.AddTransceiverFromTrack(manager.videoTracks[manager.defaultVideoID], webrtc.RtpTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionSendonly, }) if err != nil { @@ -209,11 +216,14 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session) (*webrtc.Sess func (manager *WebRTCManagerCtx) mediaEngine() (*webrtc.MediaEngine, error) { engine := &webrtc.MediaEngine{} - if err := manager.videoCodec.Register(engine); err != nil { + // all videos must have the same codec + videoCodec := manager.capture.Video(manager.defaultVideoID).Codec() + if err := videoCodec.Register(engine); err != nil { return nil, err } - if err := manager.audioCodec.Register(engine); err != nil { + audioCodec := manager.capture.Audio().Codec() + if err := audioCodec.Register(engine); err != nil { return nil, err }