mirror of
https://github.com/livekit/livekit.git
synced 2026-10-23 23:52:57 +00:00
250 lines
6.7 KiB
Go
250 lines
6.7 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package dynacast
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/livekit/protocol/codecs/mime"
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
)
|
|
|
|
var _ dynacastQuality = (*dynacastQualityVideo)(nil)
|
|
|
|
const (
|
|
initialQualityUpdateWait = 10 * time.Second
|
|
)
|
|
|
|
type dynacastQualityVideoParams struct {
|
|
MimeType mime.MimeType
|
|
Listener dynacastQualityListener
|
|
Logger logger.Logger
|
|
}
|
|
|
|
// dynacastQualityVideo manages max subscribed quality of a single receiver of a media track
|
|
type dynacastQualityVideo struct {
|
|
params dynacastQualityVideoParams
|
|
|
|
// quality level enable/disable
|
|
lock sync.RWMutex
|
|
initialized bool
|
|
maxSubscriberQuality map[livekit.ParticipantID]livekit.VideoQuality
|
|
maxSubscriberNodeQuality map[livekit.NodeID]livekit.VideoQuality
|
|
maxSubscribedQuality livekit.VideoQuality
|
|
maxQualityTimer *time.Timer
|
|
regressTo dynacastQuality
|
|
|
|
dynacastQualityNull
|
|
}
|
|
|
|
func newDynacastQualityVideo(params dynacastQualityVideoParams) dynacastQuality {
|
|
return &dynacastQualityVideo{
|
|
params: params,
|
|
maxSubscriberQuality: make(map[livekit.ParticipantID]livekit.VideoQuality),
|
|
maxSubscriberNodeQuality: make(map[livekit.NodeID]livekit.VideoQuality),
|
|
}
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) Start() {
|
|
d.reset()
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) Restart() {
|
|
d.reset()
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) Stop() {
|
|
d.stopMaxQualityTimer()
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) NotifySubscriberMaxQuality(subscriberID livekit.ParticipantID, quality livekit.VideoQuality) {
|
|
d.params.Logger.Debugw(
|
|
"setting subscriber max quality",
|
|
"mime", d.params.MimeType,
|
|
"subscriberID", subscriberID,
|
|
"quality", quality.String(),
|
|
)
|
|
|
|
d.lock.Lock()
|
|
if r := d.regressTo; r != nil {
|
|
d.lock.Unlock()
|
|
r.NotifySubscriberMaxQuality(subscriberID, quality)
|
|
return
|
|
}
|
|
|
|
if quality == livekit.VideoQuality_OFF {
|
|
delete(d.maxSubscriberQuality, subscriberID)
|
|
} else {
|
|
d.maxSubscriberQuality[subscriberID] = quality
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
d.updateQualityChange(false)
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) NotifySubscriberNodeMaxQuality(nodeID livekit.NodeID, quality livekit.VideoQuality) {
|
|
d.params.Logger.Debugw(
|
|
"setting subscriber node max quality",
|
|
"mime", d.params.MimeType,
|
|
"subscriberNodeID", nodeID,
|
|
"quality", quality.String(),
|
|
)
|
|
|
|
d.lock.Lock()
|
|
if r := d.regressTo; r != nil {
|
|
// the downstream node will synthesize correct quality notify (its dynacast manager has codec regression), just ignore it
|
|
d.params.Logger.Debugw(
|
|
"ignoring node quality change, regressed to another dynacast quality",
|
|
"mime", d.params.MimeType,
|
|
"regressedMime", d.regressTo.Mime(),
|
|
)
|
|
d.lock.Unlock()
|
|
return
|
|
}
|
|
|
|
if quality == livekit.VideoQuality_OFF {
|
|
delete(d.maxSubscriberNodeQuality, nodeID)
|
|
} else {
|
|
d.maxSubscriberNodeQuality[nodeID] = quality
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
d.updateQualityChange(false)
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) ClearSubscriberNodes() {
|
|
d.lock.Lock()
|
|
d.maxSubscriberNodeQuality = make(map[livekit.NodeID]livekit.VideoQuality)
|
|
d.lock.Unlock()
|
|
|
|
d.updateQualityChange(false)
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) Mime() mime.MimeType {
|
|
return d.params.MimeType
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) RegressTo(other dynacastQuality) {
|
|
d.lock.Lock()
|
|
d.regressTo = other
|
|
maxSubscriberQuality := d.maxSubscriberQuality
|
|
maxSubscriberNodeQuality := d.maxSubscriberNodeQuality
|
|
d.maxSubscriberQuality = make(map[livekit.ParticipantID]livekit.VideoQuality)
|
|
d.maxSubscriberNodeQuality = make(map[livekit.NodeID]livekit.VideoQuality)
|
|
d.lock.Unlock()
|
|
|
|
other.Replace(maxSubscriberQuality, maxSubscriberNodeQuality)
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) Replace(
|
|
maxSubscriberQuality map[livekit.ParticipantID]livekit.VideoQuality,
|
|
maxSubscriberNodeQuality map[livekit.NodeID]livekit.VideoQuality,
|
|
) {
|
|
d.lock.Lock()
|
|
for subID, quality := range maxSubscriberQuality {
|
|
if oldQuality, ok := d.maxSubscriberQuality[subID]; ok {
|
|
// no QUALITY_OFF in the map
|
|
if quality > oldQuality {
|
|
d.maxSubscriberQuality[subID] = quality
|
|
}
|
|
} else {
|
|
d.maxSubscriberQuality[subID] = quality
|
|
}
|
|
}
|
|
|
|
for nodeID, quality := range maxSubscriberNodeQuality {
|
|
if oldQuality, ok := d.maxSubscriberNodeQuality[nodeID]; ok {
|
|
// no QUALITY_OFF in the map
|
|
if quality > oldQuality {
|
|
d.maxSubscriberNodeQuality[nodeID] = quality
|
|
}
|
|
} else {
|
|
d.maxSubscriberNodeQuality[nodeID] = quality
|
|
}
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
d.Restart()
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) reset() {
|
|
d.lock.Lock()
|
|
d.initialized = false
|
|
d.lock.Unlock()
|
|
|
|
d.startMaxQualityTimer()
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) updateQualityChange(force bool) {
|
|
d.lock.Lock()
|
|
maxSubscribedQuality := livekit.VideoQuality_OFF
|
|
for _, subQuality := range d.maxSubscriberQuality {
|
|
if maxSubscribedQuality == livekit.VideoQuality_OFF || (subQuality != livekit.VideoQuality_OFF && subQuality > maxSubscribedQuality) {
|
|
maxSubscribedQuality = subQuality
|
|
}
|
|
}
|
|
for _, nodeQuality := range d.maxSubscriberNodeQuality {
|
|
if maxSubscribedQuality == livekit.VideoQuality_OFF || (nodeQuality != livekit.VideoQuality_OFF && nodeQuality > maxSubscribedQuality) {
|
|
maxSubscribedQuality = nodeQuality
|
|
}
|
|
}
|
|
|
|
if maxSubscribedQuality == d.maxSubscribedQuality && d.initialized && !force {
|
|
d.lock.Unlock()
|
|
return
|
|
}
|
|
|
|
d.initialized = true
|
|
d.maxSubscribedQuality = maxSubscribedQuality
|
|
d.params.Logger.Debugw(
|
|
"notifying quality change",
|
|
"mime", d.params.MimeType,
|
|
"maxSubscriberQuality", d.maxSubscriberQuality,
|
|
"maxSubscriberNodeQuality", d.maxSubscriberNodeQuality,
|
|
"maxSubscribedQuality", d.maxSubscribedQuality,
|
|
"force", force,
|
|
)
|
|
d.lock.Unlock()
|
|
|
|
d.params.Listener.OnUpdateMaxQualityForMime(d.params.MimeType, maxSubscribedQuality)
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) startMaxQualityTimer() {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
if d.maxQualityTimer != nil {
|
|
d.maxQualityTimer.Stop()
|
|
d.maxQualityTimer = nil
|
|
}
|
|
|
|
d.maxQualityTimer = time.AfterFunc(initialQualityUpdateWait, func() {
|
|
d.stopMaxQualityTimer()
|
|
d.updateQualityChange(true)
|
|
})
|
|
}
|
|
|
|
func (d *dynacastQualityVideo) stopMaxQualityTimer() {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
if d.maxQualityTimer != nil {
|
|
d.maxQualityTimer.Stop()
|
|
d.maxQualityTimer = nil
|
|
}
|
|
}
|