@@ -17,22 +17,20 @@ import type { Level } from '../types/level';
1717
1818type RestrictedLevel = { width : number ; height : number ; bitrate : number } ;
1919class CapLevelController implements ComponentAPI {
20- private hls : Hls ;
20+ private hls : Hls | null = null ;
2121 private autoLevelCapping : number ;
22- private firstLevel : number ;
2322 private media : HTMLVideoElement | null ;
2423 private restrictedLevels : RestrictedLevel [ ] ;
25- private timer : number | undefined ;
24+ private timer ?: number ;
25+ private observer ?: ResizeObserver ;
2626 private clientRect : { width : number ; height : number } | null ;
2727 private streamController ?: StreamController ;
2828
2929 constructor ( hls : Hls ) {
3030 this . hls = hls ;
3131 this . autoLevelCapping = Number . POSITIVE_INFINITY ;
32- this . firstLevel = - 1 ;
3332 this . media = null ;
3433 this . restrictedLevels = [ ] ;
35- this . timer = undefined ;
3634 this . clientRect = null ;
3735
3836 this . registerListeners ( ) ;
@@ -46,39 +44,45 @@ class CapLevelController implements ComponentAPI {
4644 if ( this . hls ) {
4745 this . unregisterListener ( ) ;
4846 }
49- if ( this . timer ) {
47+ if ( this . timer || this . observer ) {
5048 this . stopCapping ( ) ;
5149 }
52- this . media = null ;
53- this . clientRect = null ;
50+ this . media = this . clientRect = this . hls = null ;
5451 // @ts -ignore
55- this . hls = this . streamController = null ;
52+ this . streamController = undefined ;
5653 }
5754
5855 protected registerListeners ( ) {
5956 const { hls } = this ;
60- hls . on ( Events . FPS_DROP_LEVEL_CAPPING , this . onFpsDropLevelCapping , this ) ;
61- hls . on ( Events . MEDIA_ATTACHING , this . onMediaAttaching , this ) ;
62- hls . on ( Events . MANIFEST_PARSED , this . onManifestParsed , this ) ;
63- hls . on ( Events . LEVELS_UPDATED , this . onLevelsUpdated , this ) ;
64- hls . on ( Events . BUFFER_CODECS , this . onBufferCodecs , this ) ;
65- hls . on ( Events . MEDIA_DETACHING , this . onMediaDetaching , this ) ;
57+ if ( hls ) {
58+ hls . on ( Events . FPS_DROP_LEVEL_CAPPING , this . onFpsDropLevelCapping , this ) ;
59+ hls . on ( Events . MEDIA_ATTACHING , this . onMediaAttaching , this ) ;
60+ hls . on ( Events . MANIFEST_PARSED , this . onManifestParsed , this ) ;
61+ hls . on ( Events . LEVELS_UPDATED , this . onLevelsUpdated , this ) ;
62+ hls . on ( Events . BUFFER_CODECS , this . onBufferCodecs , this ) ;
63+ hls . on ( Events . MEDIA_DETACHING , this . onMediaDetaching , this ) ;
64+ }
6665 }
6766
6867 protected unregisterListener ( ) {
6968 const { hls } = this ;
70- hls . off ( Events . FPS_DROP_LEVEL_CAPPING , this . onFpsDropLevelCapping , this ) ;
71- hls . off ( Events . MEDIA_ATTACHING , this . onMediaAttaching , this ) ;
72- hls . off ( Events . MANIFEST_PARSED , this . onManifestParsed , this ) ;
73- hls . off ( Events . LEVELS_UPDATED , this . onLevelsUpdated , this ) ;
74- hls . off ( Events . BUFFER_CODECS , this . onBufferCodecs , this ) ;
75- hls . off ( Events . MEDIA_DETACHING , this . onMediaDetaching , this ) ;
69+ if ( hls ) {
70+ hls . off ( Events . FPS_DROP_LEVEL_CAPPING , this . onFpsDropLevelCapping , this ) ;
71+ hls . off ( Events . MEDIA_ATTACHING , this . onMediaAttaching , this ) ;
72+ hls . off ( Events . MANIFEST_PARSED , this . onManifestParsed , this ) ;
73+ hls . off ( Events . LEVELS_UPDATED , this . onLevelsUpdated , this ) ;
74+ hls . off ( Events . BUFFER_CODECS , this . onBufferCodecs , this ) ;
75+ hls . off ( Events . MEDIA_DETACHING , this . onMediaDetaching , this ) ;
76+ }
7677 }
7778
7879 protected onFpsDropLevelCapping (
7980 event : Events . FPS_DROP_LEVEL_CAPPING ,
8081 data : FPSDropLevelCappingData ,
8182 ) {
83+ if ( ! this . hls ) {
84+ return ;
85+ }
8286 // Don't add a restricted level more than once
8387 const level = this . hls . levels [ data . droppedLevel ] ;
8488 if ( this . isLevelAllowed ( level ) ) {
@@ -94,9 +98,20 @@ class CapLevelController implements ComponentAPI {
9498 event : Events . MEDIA_ATTACHING ,
9599 data : MediaAttachingData ,
96100 ) {
97- this . media = data . media instanceof HTMLVideoElement ? data . media : null ;
101+ const media = data . media ;
98102 this . clientRect = null ;
99- if ( this . timer && this . hls . levels . length ) {
103+ if ( ! this . hls ) {
104+ return ;
105+ }
106+ if ( media instanceof HTMLVideoElement ) {
107+ this . media = media ;
108+ if ( this . hls . config . capLevelToPlayerSize ) {
109+ this . observe ( ) ;
110+ }
111+ } else {
112+ this . media = null ;
113+ }
114+ if ( ( this . timer || this . observer ) && this . hls . levels . length ) {
100115 this . detectPlayerSize ( ) ;
101116 }
102117 }
@@ -107,8 +122,7 @@ class CapLevelController implements ComponentAPI {
107122 ) {
108123 const hls = this . hls ;
109124 this . restrictedLevels = [ ] ;
110- this . firstLevel = data . firstLevel ;
111- if ( hls . config . capLevelToPlayerSize && data . video ) {
125+ if ( hls ?. config . capLevelToPlayerSize && data . video ) {
112126 // Start capping immediately if the manifest has signaled video codecs
113127 this . startCapping ( ) ;
114128 }
@@ -118,7 +132,10 @@ class CapLevelController implements ComponentAPI {
118132 event : Events . LEVELS_UPDATED ,
119133 data : LevelsUpdatedData ,
120134 ) {
121- if ( this . timer && Number . isFinite ( this . autoLevelCapping ) ) {
135+ if (
136+ ( this . timer || this . observer ) &&
137+ Number . isFinite ( this . autoLevelCapping )
138+ ) {
122139 this . detectPlayerSize ( ) ;
123140 }
124141 }
@@ -130,7 +147,7 @@ class CapLevelController implements ComponentAPI {
130147 data : BufferCodecsData ,
131148 ) {
132149 const hls = this . hls ;
133- if ( hls . config . capLevelToPlayerSize && data . video ) {
150+ if ( hls ? .config . capLevelToPlayerSize && data . video ) {
134151 // If the manifest did not signal a video codec capping has been deferred until we're certain video is present
135152 this . startCapping ( ) ;
136153 }
@@ -143,7 +160,7 @@ class CapLevelController implements ComponentAPI {
143160
144161 detectPlayerSize ( ) {
145162 if ( this . media ) {
146- if ( this . mediaHeight <= 0 || this . mediaWidth <= 0 ) {
163+ if ( this . mediaHeight <= 0 || this . mediaWidth <= 0 || ! this . hls ) {
147164 this . clientRect = null ;
148165 return ;
149166 }
@@ -175,6 +192,9 @@ class CapLevelController implements ComponentAPI {
175192 * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
176193 */
177194 getMaxLevel ( capLevelIndex : number ) : number {
195+ if ( ! this . hls ) {
196+ return - 1 ;
197+ }
178198 const levels = this . hls . levels ;
179199 if ( ! levels . length ) {
180200 return - 1 ;
@@ -183,34 +203,58 @@ class CapLevelController implements ComponentAPI {
183203 const validLevels = levels . filter (
184204 ( level , index ) => this . isLevelAllowed ( level ) && index <= capLevelIndex ,
185205 ) ;
186-
187- this . clientRect = null ;
206+ if ( ! this . observer ) {
207+ this . clientRect = null ;
208+ }
188209 return CapLevelController . getMaxLevelByMediaSize (
189210 validLevels ,
190211 this . mediaWidth ,
191212 this . mediaHeight ,
192213 ) ;
193214 }
194215
216+ private observe ( ) {
217+ const ResizeObserver = self . ResizeObserver ;
218+ if ( ResizeObserver ) {
219+ this . observer = new ResizeObserver ( ( entries ) => {
220+ const bounds = entries [ 0 ] ?. contentRect ;
221+ if ( bounds ) {
222+ this . clientRect = bounds ;
223+ this . detectPlayerSize ( ) ;
224+ }
225+ } ) ;
226+ }
227+ if ( this . observer && this . media ) {
228+ this . observer . observe ( this . media ) ;
229+ }
230+ }
231+
195232 startCapping ( ) {
196- if ( this . timer ) {
233+ if ( this . timer || this . observer ) {
197234 // Don't reset capping if started twice; this can happen if the manifest signals a video codec
198235 return ;
199236 }
200- this . autoLevelCapping = Number . POSITIVE_INFINITY ;
201237 self . clearInterval ( this . timer ) ;
202- this . timer = self . setInterval ( this . detectPlayerSize . bind ( this ) , 1000 ) ;
238+ this . timer = undefined ;
239+ this . autoLevelCapping = Number . POSITIVE_INFINITY ;
240+ this . observe ( ) ;
241+ if ( ! this . observer ) {
242+ this . timer = self . setInterval ( this . detectPlayerSize . bind ( this ) , 1000 ) ;
243+ }
203244 this . detectPlayerSize ( ) ;
204245 }
205246
206247 stopCapping ( ) {
207248 this . restrictedLevels = [ ] ;
208- this . firstLevel = - 1 ;
209249 this . autoLevelCapping = Number . POSITIVE_INFINITY ;
210250 if ( this . timer ) {
211251 self . clearInterval ( this . timer ) ;
212252 this . timer = undefined ;
213253 }
254+ if ( this . observer ) {
255+ this . observer . disconnect ( ) ;
256+ this . observer = undefined ;
257+ }
214258 }
215259
216260 getDimensions ( ) : { width : number ; height : number } {
@@ -250,15 +294,18 @@ class CapLevelController implements ComponentAPI {
250294
251295 get contentScaleFactor ( ) : number {
252296 let pixelRatio = 1 ;
253- if ( ! this . hls . config . ignoreDevicePixelRatio ) {
297+ if ( ! this . hls ) {
298+ return pixelRatio ;
299+ }
300+ const { ignoreDevicePixelRatio, maxDevicePixelRatio } = this . hls . config ;
301+ if ( ! ignoreDevicePixelRatio ) {
254302 try {
255303 pixelRatio = self . devicePixelRatio ;
256304 } catch ( e ) {
257305 /* no-op */
258306 }
259307 }
260-
261- return Math . min ( pixelRatio , this . hls . config . maxDevicePixelRatio ) ;
308+ return Math . min ( pixelRatio , maxDevicePixelRatio ) ;
262309 }
263310
264311 private isLevelAllowed ( level : Level ) : boolean {
0 commit comments