mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Capture bandwidth switch (#14)
* Handle bitrate change by finding the stream with closest bitrate as peer * Convert video id into bitrate when creating peer or changing bitrate * Try to fix prometheus panic * Revert metrics label name change * minor fixes. * bitrate selector. * skip if moving to the same stream. * no closure for getting target bitrate. * fix: high res switch to lo video, stream bitrate out of range * revert dev config change. * white space. Co-authored-by: Aleksandar Sukovic <aleksandar.sukovic@gmail.com>
This commit is contained in:
parent
e0bee67e85
commit
6067367acd
@ -2,6 +2,8 @@ package capture
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -36,17 +38,17 @@ func (m *BucketsManagerCtx) shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *BucketsManagerCtx) destroyAll() {
|
func (m *BucketsManagerCtx) destroyAll() {
|
||||||
for _, video := range m.streams {
|
for _, stream := range m.streams {
|
||||||
if video.Started() {
|
if stream.Started() {
|
||||||
video.destroyPipeline()
|
stream.destroyPipeline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BucketsManagerCtx) recreateAll() error {
|
func (m *BucketsManagerCtx) recreateAll() error {
|
||||||
for _, video := range m.streams {
|
for _, stream := range m.streams {
|
||||||
if video.Started() {
|
if stream.Started() {
|
||||||
err := video.createPipeline()
|
err := stream.createPipeline()
|
||||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -65,22 +67,39 @@ func (m *BucketsManagerCtx) Codec() codec.RTPCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *BucketsManagerCtx) SetReceiver(receiver types.Receiver) error {
|
func (m *BucketsManagerCtx) SetReceiver(receiver types.Receiver) error {
|
||||||
receiver.OnVideoIdChange(func(videoID string) error {
|
receiver.OnBitrateChange(func(bitrate int) error {
|
||||||
videoStream, ok := m.streams[videoID]
|
stream, ok := m.findNearestStream(bitrate)
|
||||||
if !ok {
|
if !ok {
|
||||||
return types.ErrWebRTCVideoNotFound
|
return fmt.Errorf("no stream found for bitrate %d", bitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
return receiver.SetStream(videoStream)
|
return receiver.SetStream(stream)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Save receiver.
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *BucketsManagerCtx) findNearestStream(bitrate int) (ss *StreamSinkManagerCtx, ok bool) {
|
||||||
|
minDiff := math.MaxInt
|
||||||
|
for _, s := range m.streams {
|
||||||
|
streamBitrate, err := s.Bitrate()
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error().Err(err).Msgf("failed to get bitrate for stream %s", s.ID())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
diffAbs := int(math.Abs(float64(bitrate - streamBitrate)))
|
||||||
|
|
||||||
|
if diffAbs < minDiff {
|
||||||
|
minDiff, ss = diffAbs, s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = ss != nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (m *BucketsManagerCtx) RemoveReceiver(receiver types.Receiver) error {
|
func (m *BucketsManagerCtx) RemoveReceiver(receiver types.Receiver) error {
|
||||||
// TODO: Unsubribe from OnVideoIdChange.
|
receiver.OnBitrateChange(nil)
|
||||||
// TODO: Remove receiver.
|
|
||||||
receiver.RemoveStream()
|
receiver.RemoveStream()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
type CaptureManagerCtx struct {
|
type CaptureManagerCtx struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
desktop types.DesktopManager
|
desktop types.DesktopManager
|
||||||
|
config *config.Capture
|
||||||
|
|
||||||
// sinks
|
// sinks
|
||||||
broadcast *BroacastManagerCtx
|
broadcast *BroacastManagerCtx
|
||||||
@ -66,13 +67,18 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
|||||||
Str("pipeline", pipeline).
|
Str("pipeline", pipeline).
|
||||||
Msg("syntax check for video stream pipeline passed")
|
Msg("syntax check for video stream pipeline passed")
|
||||||
|
|
||||||
|
getVideoBitrate := pipelineConf.GetBitrateFn(desktop.GetScreenSize)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic().Err(err).Msg("unable to get video bitrate")
|
||||||
|
}
|
||||||
// append to videos
|
// append to videos
|
||||||
videos[video_id] = streamSinkNew(config.VideoCodec, createPipeline, video_id)
|
videos[video_id] = streamSinkNew(config.VideoCodec, createPipeline, video_id, getVideoBitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CaptureManagerCtx{
|
return &CaptureManagerCtx{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
desktop: desktop,
|
desktop: desktop,
|
||||||
|
config: config,
|
||||||
|
|
||||||
// sinks
|
// sinks
|
||||||
broadcast: broadcastNew(func(url string) (string, error) {
|
broadcast: broadcastNew(func(url string) (string, error) {
|
||||||
@ -132,7 +138,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
|||||||
"! %s "+
|
"! %s "+
|
||||||
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
||||||
), nil
|
), nil
|
||||||
}, "audio"),
|
}, "audio", nil),
|
||||||
video: bucketsNew(config.VideoCodec, videos, config.VideoIDs),
|
video: bucketsNew(config.VideoCodec, videos, config.VideoIDs),
|
||||||
|
|
||||||
// sources
|
// sources
|
||||||
@ -242,6 +248,15 @@ func (manager *CaptureManagerCtx) Shutdown() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *CaptureManagerCtx) GetBitrateFromVideoID(videoID string) (int, error) {
|
||||||
|
cfg, ok := manager.config.VideoPipelines[videoID]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("video config not found for %s", videoID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.GetBitrateFn(manager.desktop.GetScreenSize)()
|
||||||
|
}
|
||||||
|
|
||||||
func (manager *CaptureManagerCtx) Broadcast() types.BroadcastManager {
|
func (manager *CaptureManagerCtx) Broadcast() types.BroadcastManager {
|
||||||
return manager.broadcast
|
return manager.broadcast
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
var moveSinkListenerMu = sync.Mutex{}
|
var moveSinkListenerMu = sync.Mutex{}
|
||||||
|
|
||||||
type StreamSinkManagerCtx struct {
|
type StreamSinkManagerCtx struct {
|
||||||
|
id string
|
||||||
|
getBitrate func() (int, error)
|
||||||
|
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
@ -37,13 +40,16 @@ type StreamSinkManagerCtx struct {
|
|||||||
pipelinesActive prometheus.Gauge
|
pipelinesActive prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx {
|
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), id string, getBitrate func() (int, error)) *StreamSinkManagerCtx {
|
||||||
logger := log.With().
|
logger := log.With().
|
||||||
Str("module", "capture").
|
Str("module", "capture").
|
||||||
Str("submodule", "stream-sink").
|
Str("submodule", "stream-sink").
|
||||||
Str("video_id", video_id).Logger()
|
Str("id", id).Logger()
|
||||||
|
|
||||||
manager := &StreamSinkManagerCtx{
|
manager := &StreamSinkManagerCtx{
|
||||||
|
id: id,
|
||||||
|
getBitrate: getBitrate,
|
||||||
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
codec: codec,
|
codec: codec,
|
||||||
pipelineFn: pipelineFn,
|
pipelineFn: pipelineFn,
|
||||||
@ -56,7 +62,7 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
|
|||||||
Subsystem: "capture",
|
Subsystem: "capture",
|
||||||
Help: "Current number of listeners for a pipeline.",
|
Help: "Current number of listeners for a pipeline.",
|
||||||
ConstLabels: map[string]string{
|
ConstLabels: map[string]string{
|
||||||
"video_id": video_id,
|
"video_id": id,
|
||||||
"codec_name": codec.Name,
|
"codec_name": codec.Name,
|
||||||
"codec_type": codec.Type.String(),
|
"codec_type": codec.Type.String(),
|
||||||
},
|
},
|
||||||
@ -68,7 +74,7 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
|
|||||||
Help: "Total number of created pipelines.",
|
Help: "Total number of created pipelines.",
|
||||||
ConstLabels: map[string]string{
|
ConstLabels: map[string]string{
|
||||||
"submodule": "streamsink",
|
"submodule": "streamsink",
|
||||||
"video_id": video_id,
|
"video_id": id,
|
||||||
"codec_name": codec.Name,
|
"codec_name": codec.Name,
|
||||||
"codec_type": codec.Type.String(),
|
"codec_type": codec.Type.String(),
|
||||||
},
|
},
|
||||||
@ -80,7 +86,7 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
|
|||||||
Help: "Total number of active pipelines.",
|
Help: "Total number of active pipelines.",
|
||||||
ConstLabels: map[string]string{
|
ConstLabels: map[string]string{
|
||||||
"submodule": "streamsink",
|
"submodule": "streamsink",
|
||||||
"video_id": video_id,
|
"video_id": id,
|
||||||
"codec_name": codec.Name,
|
"codec_name": codec.Name,
|
||||||
"codec_type": codec.Type.String(),
|
"codec_type": codec.Type.String(),
|
||||||
},
|
},
|
||||||
@ -103,6 +109,18 @@ func (manager *StreamSinkManagerCtx) shutdown() {
|
|||||||
manager.wg.Wait()
|
manager.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) ID() string {
|
||||||
|
return manager.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *StreamSinkManagerCtx) Bitrate() (int, error) {
|
||||||
|
if manager.getBitrate == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// recalculate bitrate every time, take screen resolution (and fps) into account
|
||||||
|
return manager.getBitrate()
|
||||||
|
}
|
||||||
|
|
||||||
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
|
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
|
||||||
return manager.codec
|
return manager.codec
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ func (manager *WebRTCManagerCtx) newPeerConnection(codecs []codec.RTPCodec, logg
|
|||||||
return api.NewPeerConnection(manager.webrtcConfiguration)
|
return api.NewPeerConnection(manager.webrtcConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID string) (*webrtc.SessionDescription, error) {
|
func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, bitrate int) (*webrtc.SessionDescription, error) {
|
||||||
id := atomic.AddInt32(&manager.peerId, 1)
|
id := atomic.AddInt32(&manager.peerId, 1)
|
||||||
manager.metrics.NewConnection(session)
|
manager.metrics.NewConnection(session)
|
||||||
|
|
||||||
@ -280,11 +280,12 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default video id
|
// set initial video bitrate
|
||||||
err = videoTrack.SetVideoID(videoID)
|
if err = videoTrack.SetBitrate(bitrate); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoID := videoTrack.stream.ID()
|
||||||
manager.metrics.SetVideoID(session, videoID)
|
manager.metrics.SetVideoID(session, videoID)
|
||||||
|
|
||||||
// data channel
|
// data channel
|
||||||
@ -298,14 +299,19 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
connection: connection,
|
connection: connection,
|
||||||
dataChannel: dataChannel,
|
dataChannel: dataChannel,
|
||||||
changeVideo: func(videoID string) error {
|
changeVideo: func(bitrate int) error {
|
||||||
if err := videoTrack.SetVideoID(videoID); err != nil {
|
if err := videoTrack.SetBitrate(bitrate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoID := videoTrack.stream.ID()
|
||||||
manager.metrics.SetVideoID(session, videoID)
|
manager.metrics.SetVideoID(session, videoID)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
// TODO: Refactor.
|
||||||
|
videoId: func() string {
|
||||||
|
return videoTrack.stream.ID()
|
||||||
|
},
|
||||||
setPaused: func(isPaused bool) {
|
setPaused: func(isPaused bool) {
|
||||||
videoTrack.SetPaused(isPaused)
|
videoTrack.SetPaused(isPaused)
|
||||||
audioTrack.SetPaused(isPaused)
|
audioTrack.SetPaused(isPaused)
|
||||||
@ -418,7 +424,9 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
|
|||||||
connection.Close()
|
connection.Close()
|
||||||
case webrtc.PeerConnectionStateClosed:
|
case webrtc.PeerConnectionStateClosed:
|
||||||
session.SetWebRTCConnected(peer, false)
|
session.SetWebRTCConnected(peer, false)
|
||||||
video.RemoveReceiver(videoTrack)
|
if err = video.RemoveReceiver(videoTrack); err != nil {
|
||||||
|
logger.Err(err).Msg("failed to remove video receiver")
|
||||||
|
}
|
||||||
audioTrack.RemoveStream()
|
audioTrack.RemoveStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ type WebRTCPeerCtx struct {
|
|||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
connection *webrtc.PeerConnection
|
connection *webrtc.PeerConnection
|
||||||
dataChannel *webrtc.DataChannel
|
dataChannel *webrtc.DataChannel
|
||||||
changeVideo func(videoID string) error
|
changeVideo func(bitrate int) error
|
||||||
|
videoId func() string
|
||||||
setPaused func(isPaused bool)
|
setPaused func(isPaused bool)
|
||||||
iceTrickle bool
|
iceTrickle bool
|
||||||
}
|
}
|
||||||
@ -114,7 +115,7 @@ func (peer *WebRTCPeerCtx) SetCandidate(candidate webrtc.ICECandidateInit) error
|
|||||||
return peer.connection.AddICECandidate(candidate)
|
return peer.connection.AddICECandidate(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *WebRTCPeerCtx) SetVideoID(videoID string) error {
|
func (peer *WebRTCPeerCtx) SetVideoBitrate(bitrate int) error {
|
||||||
peer.mu.Lock()
|
peer.mu.Lock()
|
||||||
defer peer.mu.Unlock()
|
defer peer.mu.Unlock()
|
||||||
|
|
||||||
@ -122,8 +123,16 @@ func (peer *WebRTCPeerCtx) SetVideoID(videoID string) error {
|
|||||||
return types.ErrWebRTCConnectionNotFound
|
return types.ErrWebRTCConnectionNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.logger.Info().Str("video_id", videoID).Msg("change video id")
|
peer.logger.Info().Int("bitrate", bitrate).Msg("change video bitrate")
|
||||||
return peer.changeVideo(videoID)
|
return peer.changeVideo(bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor.
|
||||||
|
func (peer *WebRTCPeerCtx) GetVideoId() string {
|
||||||
|
peer.mu.Lock()
|
||||||
|
defer peer.mu.Unlock()
|
||||||
|
|
||||||
|
return peer.videoId()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *WebRTCPeerCtx) SetPaused(isPaused bool) error {
|
func (peer *WebRTCPeerCtx) SetPaused(isPaused bool) error {
|
||||||
|
@ -27,7 +27,7 @@ type Track struct {
|
|||||||
onRtcp func(rtcp.Packet)
|
onRtcp func(rtcp.Packet)
|
||||||
onRtcpMu sync.RWMutex
|
onRtcpMu sync.RWMutex
|
||||||
|
|
||||||
videoIdChange func(string) error
|
bitrateChange func(int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTrack(logger zerolog.Logger, codec codec.RTPCodec, connection *webrtc.PeerConnection) (*Track, error) {
|
func NewTrack(logger zerolog.Logger, codec codec.RTPCodec, connection *webrtc.PeerConnection) (*Track, error) {
|
||||||
@ -140,14 +140,14 @@ func (t *Track) OnRTCP(f func(rtcp.Packet)) {
|
|||||||
t.onRtcp = f
|
t.onRtcp = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) SetVideoID(videoID string) error {
|
func (t *Track) SetBitrate(bitrate int) error {
|
||||||
if t.videoIdChange == nil {
|
if t.bitrateChange == nil {
|
||||||
return fmt.Errorf("video id change not supported")
|
return fmt.Errorf("bitrate change not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.videoIdChange(videoID)
|
return t.bitrateChange(bitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) OnVideoIdChange(f func(string) error) {
|
func (t *Track) OnBitrateChange(f func(int) error) {
|
||||||
t.videoIdChange = f
|
t.bitrateChange = f
|
||||||
}
|
}
|
||||||
|
@ -19,22 +19,35 @@ func (h *MessageHandlerCtx) signalRequest(session types.Session, payload *messag
|
|||||||
payload.Video = videos[0]
|
payload.Video = videos[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
offer, err := h.webrtc.CreatePeer(session, payload.Video)
|
var err error
|
||||||
|
if payload.Bitrate == 0 {
|
||||||
|
// get bitrate from video id
|
||||||
|
payload.Bitrate, err = h.capture.GetBitrateFromVideoID(payload.Video)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offer, err := h.webrtc.CreatePeer(session, payload.Bitrate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
|
||||||
// set webrtc as paused if session has private mode enabled
|
// set webrtc as paused if session has private mode enabled
|
||||||
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil && session.PrivateModeEnabled() {
|
if session.PrivateModeEnabled() {
|
||||||
webrtcPeer.SetPaused(true)
|
webrtcPeer.SetPaused(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload.Video = webrtcPeer.GetVideoId()
|
||||||
|
}
|
||||||
|
|
||||||
session.Send(
|
session.Send(
|
||||||
event.SIGNAL_PROVIDE,
|
event.SIGNAL_PROVIDE,
|
||||||
message.SignalProvide{
|
message.SignalProvide{
|
||||||
SDP: offer.SDP,
|
SDP: offer.SDP,
|
||||||
ICEServers: h.webrtc.ICEServers(),
|
ICEServers: h.webrtc.ICEServers(),
|
||||||
Video: payload.Video,
|
Video: payload.Video, // TODO: Refactor.
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -110,15 +123,24 @@ func (h *MessageHandlerCtx) signalVideo(session types.Session, payload *message.
|
|||||||
return errors.New("webRTC peer does not exist")
|
return errors.New("webRTC peer does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := peer.SetVideoID(payload.Video)
|
var err error
|
||||||
|
if payload.Bitrate == 0 {
|
||||||
|
// get bitrate from video id
|
||||||
|
payload.Bitrate, err = h.capture.GetBitrateFromVideoID(payload.Video)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = peer.SetVideoBitrate(payload.Bitrate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
session.Send(
|
session.Send(
|
||||||
event.SIGNAL_VIDEO,
|
event.SIGNAL_VIDEO,
|
||||||
message.SignalVideo{
|
message.SignalVideo{
|
||||||
Video: payload.Video,
|
Video: peer.GetVideoId(), // TODO: Refactor.
|
||||||
|
Bitrate: payload.Bitrate,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -22,7 +22,7 @@ type Sample media.Sample
|
|||||||
type Receiver interface {
|
type Receiver interface {
|
||||||
SetStream(stream StreamSinkManager) error
|
SetStream(stream StreamSinkManager) error
|
||||||
RemoveStream()
|
RemoveStream()
|
||||||
OnVideoIdChange(f func(string) error)
|
OnBitrateChange(f func(int) error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BucketsManager interface {
|
type BucketsManager interface {
|
||||||
@ -46,6 +46,7 @@ type ScreencastManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamSinkManager interface {
|
type StreamSinkManager interface {
|
||||||
|
ID() string
|
||||||
Codec() codec.RTPCodec
|
Codec() codec.RTPCodec
|
||||||
|
|
||||||
AddListener(listener *func(sample Sample)) error
|
AddListener(listener *func(sample Sample)) error
|
||||||
@ -70,6 +71,8 @@ type CaptureManager interface {
|
|||||||
Start()
|
Start()
|
||||||
Shutdown() error
|
Shutdown() error
|
||||||
|
|
||||||
|
GetBitrateFromVideoID(videoID string) (int, error)
|
||||||
|
|
||||||
Broadcast() BroadcastManager
|
Broadcast() BroadcastManager
|
||||||
Screencast() ScreencastManager
|
Screencast() ScreencastManager
|
||||||
Audio() StreamSinkManager
|
Audio() StreamSinkManager
|
||||||
@ -83,6 +86,7 @@ type VideoConfig struct {
|
|||||||
Width string `mapstructure:"width"` // expression
|
Width string `mapstructure:"width"` // expression
|
||||||
Height string `mapstructure:"height"` // expression
|
Height string `mapstructure:"height"` // expression
|
||||||
Fps string `mapstructure:"fps"` // expression
|
Fps string `mapstructure:"fps"` // expression
|
||||||
|
Bitrate int `mapstructure:"bitrate"` // pipeline bitrate
|
||||||
GstPrefix string `mapstructure:"gst_prefix"` // pipeline prefix, starts with !
|
GstPrefix string `mapstructure:"gst_prefix"` // pipeline prefix, starts with !
|
||||||
GstEncoder string `mapstructure:"gst_encoder"` // gst encoder name
|
GstEncoder string `mapstructure:"gst_encoder"` // gst encoder name
|
||||||
GstParams map[string]string `mapstructure:"gst_params"` // map of expressions
|
GstParams map[string]string `mapstructure:"gst_params"` // map of expressions
|
||||||
@ -173,3 +177,41 @@ func (config *VideoConfig) GetPipeline(screen ScreenSize) (string, error) {
|
|||||||
config.GstSuffix,
|
config.GstSuffix,
|
||||||
}[:], " "), nil
|
}[:], " "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (config *VideoConfig) GetBitrateFn(getScreen func() *ScreenSize) func() (int, error) {
|
||||||
|
return func() (int, error) {
|
||||||
|
if config.Bitrate > 0 {
|
||||||
|
return config.Bitrate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
screen := getScreen()
|
||||||
|
if screen == nil {
|
||||||
|
return 0, fmt.Errorf("screen is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
values := map[string]any{
|
||||||
|
"width": screen.Width,
|
||||||
|
"height": screen.Height,
|
||||||
|
"fps": screen.Rate,
|
||||||
|
}
|
||||||
|
|
||||||
|
language := []gval.Language{
|
||||||
|
gval.Function("round", func(args ...any) (any, error) {
|
||||||
|
return (int)(math.Round(args[0].(float64))), nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is only for vp8.
|
||||||
|
expr, ok := config.GstParams["target-bitrate"]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("target-bitrate not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetBitrate, err := gval.Evaluate(expr, values, language...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetBitrate.(int), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,7 +48,7 @@ type SystemDisconnect struct {
|
|||||||
type SignalProvide struct {
|
type SignalProvide struct {
|
||||||
SDP string `json:"sdp"`
|
SDP string `json:"sdp"`
|
||||||
ICEServers []types.ICEServer `json:"iceservers"`
|
ICEServers []types.ICEServer `json:"iceservers"`
|
||||||
Video string `json:"video"`
|
Video string `json:"video"` // TODO: Refactor.
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignalCandidate struct {
|
type SignalCandidate struct {
|
||||||
@ -60,7 +60,8 @@ type SignalDescription struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignalVideo struct {
|
type SignalVideo struct {
|
||||||
Video string `json:"video"`
|
Video string `json:"video"` // TODO: Refactor.
|
||||||
|
Bitrate int `json:"bitrate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrWebRTCVideoNotFound = errors.New("webrtc video not found")
|
|
||||||
ErrWebRTCDataChannelNotFound = errors.New("webrtc data channel not found")
|
ErrWebRTCDataChannelNotFound = errors.New("webrtc data channel not found")
|
||||||
ErrWebRTCConnectionNotFound = errors.New("webrtc connection not found")
|
ErrWebRTCConnectionNotFound = errors.New("webrtc connection not found")
|
||||||
)
|
)
|
||||||
@ -25,7 +24,8 @@ type WebRTCPeer interface {
|
|||||||
SetAnswer(sdp string) error
|
SetAnswer(sdp string) error
|
||||||
SetCandidate(candidate webrtc.ICECandidateInit) error
|
SetCandidate(candidate webrtc.ICECandidateInit) error
|
||||||
|
|
||||||
SetVideoID(videoID string) error
|
SetVideoBitrate(bitrate int) error
|
||||||
|
GetVideoId() string
|
||||||
SetPaused(isPaused bool) error
|
SetPaused(isPaused bool) error
|
||||||
|
|
||||||
SendCursorPosition(x, y int) error
|
SendCursorPosition(x, y int) error
|
||||||
@ -40,6 +40,6 @@ type WebRTCManager interface {
|
|||||||
|
|
||||||
ICEServers() []ICEServer
|
ICEServers() []ICEServer
|
||||||
|
|
||||||
CreatePeer(session Session, videoID string) (*webrtc.SessionDescription, error)
|
CreatePeer(session Session, bitrate int) (*webrtc.SessionDescription, error)
|
||||||
SetCursorPosition(x, y int)
|
SetCursorPosition(x, y int)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user