1717 *
1818 */
1919
20+ import { PerformanceSample } from 'Repositories/media/backgroundEffects/helper/samples' ;
21+ import {
22+ getBestMatchingQualityTier ,
23+ QualityMode ,
24+ QualityTier ,
25+ Resolution ,
26+ resolutionIsGreaterThanOrEqualTo ,
27+ TIER_DEFINITIONS ,
28+ } from 'Repositories/media/backgroundEffects/quality/definitions' ;
29+ import { QUALITY_TIERS , QualityController } from 'Repositories/media/backgroundEffects/quality/qualityController' ;
2030import { BackgroundSource } from 'Repositories/media/VideoBackgroundEffects' ;
2131import { getLogger , Logger } from 'Util/logger' ;
2232
23- import {
24- type CapabilityInfo ,
25- type EffectMode ,
26- type Metrics ,
27- QualityMode ,
28- type QualityTier ,
29- } from './backgroundEffectsWorkerTypes' ;
33+ import { type CapabilityInfo , type EffectMode , type Metrics , Mode } from './backgroundEffectsWorkerTypes' ;
3034import { detectCapabilities } from './helper/capability' ;
3135import {
3236 defaultOpts ,
@@ -41,10 +45,15 @@ import {runSegmenter, updateSegmenterOptions} from './pipe/segmenter';
4145// The shader's blur radius is 30 px, so a sigma in the ~10–20 px range gives
4246// visually useful blur. Multiply by this factor to get from the 0–1 range.
4347const BLUR_SIGMA_SCALE = 15 ;
48+ const DEFAULT_FRAME_RATE = 15 ;
49+ const MAX_WIDTH = 256 ;
50+ const MAX_HEIGHT = 144 ;
4451
4552export class BackgroundEffectsController {
4653 private readonly logger : Logger ;
4754
55+ private inputTrack : MediaStreamTrack | null = null ;
56+
4857 private worker : Worker | null = null ;
4958 private options : ProcessVideoTrackOptions = defaultOpts ;
5059
@@ -57,7 +66,10 @@ export class BackgroundEffectsController {
5766 requestVideoFrameCallback : false ,
5867 } ;
5968
60- private maxQualityTier : QualityTier = 'superhigh' ;
69+ private qualityController : QualityController | null = null ;
70+ private qualitySampleQueue = Promise . resolve ( ) ;
71+ private maxResolution : Resolution = TIER_DEFINITIONS . hd . resolution ;
72+ private maxQualityTier : QualityTier = 'hd' ;
6173 private refcount = 0 ;
6274
6375 /**
@@ -79,8 +91,21 @@ export class BackgroundEffectsController {
7991 const trackCapabilities = inputTrack . getCapabilities ( ) ;
8092 const trackSettings = inputTrack . getSettings ( ) ;
8193 const trackConstraints = inputTrack . getConstraints ( ) ;
82- const { width, height, frameRate} = trackSettings ;
94+ let { width, height, frameRate} = trackSettings ;
95+ if ( ! width ) {
96+ width = TIER_DEFINITIONS [ QUALITY_TIERS . HD ] . resolution . width ;
97+ }
98+ if ( ! height ) {
99+ height = TIER_DEFINITIONS [ QUALITY_TIERS . HD ] . resolution . height ;
100+ }
101+ if ( ! frameRate ) {
102+ frameRate = DEFAULT_FRAME_RATE ;
103+ }
104+ this . maxResolution = { width, height} ;
105+ this . maxQualityTier = getBestMatchingQualityTier ( this . maxResolution ) . tier ;
106+ this . qualityController = new QualityController ( frameRate , this . maxQualityTier ) ;
83107
108+ this . inputTrack = inputTrack ;
84109 this . logger . info ( `start: ${ width } x${ height } ${ frameRate } fps` , {
85110 trackCapabilities,
86111 trackSettings,
@@ -90,6 +115,8 @@ export class BackgroundEffectsController {
90115 const { readable} = new TrackProcessor ( { track : inputTrack } ) ;
91116
92117 const canvas = document . createElement ( 'canvas' ) ;
118+ canvas . width = MAX_WIDTH ;
119+ canvas . height = MAX_HEIGHT ;
93120 const outputTrack = canvas . captureStream ( frameRate ) . getVideoTracks ( ) [ 0 ] ;
94121 const offscreen = canvas . transferControlToOffscreen ( ) ;
95122
@@ -109,16 +136,30 @@ export class BackgroundEffectsController {
109136 transferables ,
110137 ) ;
111138 onWorkerMessage = ( { data} : MessageEvent ) => {
112- const { name, stats } = data as { name : string ; stats : Metrics } ;
139+ const { name} = data as { name : string } ;
113140 if ( name === 'stats' && this . onMetrics ) {
141+ const { stats} = data as { stats : Metrics } ;
114142 this . onMetrics ( stats ) ;
115143 }
144+
145+ if ( name === 'performanceSample' && this . qualityController !== null ) {
146+ const { sample, mode} = data as { sample : PerformanceSample ; mode : Mode } ;
147+ this . enqueuePerformanceSample ( sample , mode ) ;
148+ }
116149 } ;
117150
118151 this . worker . addEventListener ( 'message' , onWorkerMessage ) ;
119152 } else {
120153 const { options : workerOptions } = getWorkerOptions ( resolved ) ;
121- await runSegmenter ( offscreen , readable , workerOptions , stats => this . onMetrics ?.( stats ) ) ;
154+ await runSegmenter (
155+ offscreen ,
156+ readable ,
157+ workerOptions ,
158+ stats => this . onMetrics ?.( stats ) ,
159+ ( sample : PerformanceSample , mode : Mode ) => {
160+ this . enqueuePerformanceSample ( sample , mode ) ;
161+ } ,
162+ ) ;
122163 }
123164
124165 outputTrack . stop = ( ) => {
@@ -176,9 +217,15 @@ export class BackgroundEffectsController {
176217 }
177218 }
178219
179- public setQuality ( quality : QualityMode ) : void {
220+ public async setQuality ( quality : QualityMode ) : Promise < void > {
180221 this . logger . info ( 'setQuality' , quality ) ;
181- this . options = { ...this . options , quality} ;
222+
223+ let requestedQuality = quality ;
224+ if ( quality !== 'auto' ) {
225+ requestedQuality = await this . changeResolution ( quality ) ;
226+ }
227+
228+ this . options = { ...this . options , quality : requestedQuality } ;
182229 this . pushOptionsUpdate ( ) ;
183230 }
184231
@@ -235,6 +282,64 @@ export class BackgroundEffectsController {
235282 updateSegmenterOptions ( finalOptions ) ;
236283 }
237284 }
285+
286+ private async changeResolution ( quality : QualityMode ) : Promise < QualityMode > {
287+ if ( ! this . inputTrack || quality === 'auto' ) {
288+ return quality ;
289+ }
290+
291+ const mediaConstraints = this . inputTrack . getConstraints ( ) ;
292+
293+ let newResolution : Resolution ;
294+ let newQualityTier : QualityTier ;
295+
296+ if ( quality === 'bypass' ) {
297+ newResolution = this . maxResolution ;
298+ newQualityTier = quality ;
299+ } else {
300+ const requestedResolution = TIER_DEFINITIONS [ quality ] . resolution ;
301+
302+ if ( this . maxResolution === null || resolutionIsGreaterThanOrEqualTo ( this . maxResolution , requestedResolution ) ) {
303+ newResolution = requestedResolution ;
304+ newQualityTier = quality ;
305+ } else {
306+ newResolution = this . maxResolution ;
307+ newQualityTier = this . maxQualityTier ;
308+ }
309+ }
310+
311+ mediaConstraints . width = { ideal : newResolution . width } ;
312+ mediaConstraints . height = { ideal : newResolution . height } ;
313+
314+ try {
315+ await this . inputTrack . applyConstraints ( mediaConstraints ) ;
316+ return newQualityTier ;
317+ } catch ( error : unknown ) {
318+ this . logger . warn ( 'Failed to change resolution' , error ) ;
319+ return this . maxQualityTier ;
320+ }
321+ }
322+
323+ private enqueuePerformanceSample ( sample : PerformanceSample , mode : Mode ) : void {
324+ this . qualitySampleQueue = this . qualitySampleQueue
325+ . then ( ( ) => this . onPerformanceSample ( sample , mode ) )
326+ . catch ( ( error : unknown ) => {
327+ this . logger ?. error ?.( 'onPerformanceSample failed' , { error} ) ;
328+ } ) ;
329+ }
330+
331+ private async onPerformanceSample ( sample : PerformanceSample , mode : Mode ) : Promise < void > {
332+ if ( this . qualityController === null ) {
333+ this . logger . warn ( 'onPerformanceSample: qualityController is null' ) ;
334+ return ;
335+ }
336+ const tier = this . qualityController . update ( sample , mode ) ;
337+
338+ if ( tier . tier === this . qualityController . getCurrentTier ( ) ) {
339+ return ;
340+ }
341+ return this . setQuality ( tier . tier ) ;
342+ }
238343}
239344
240345// ---------------------------------------------------------------------------
0 commit comments