Skip to content

Commit f8ad58a

Browse files
committed
Use ResizeObserver to cap level to player size
1 parent 2ef42ba commit f8ad58a

File tree

2 files changed

+53
-25
lines changed

2 files changed

+53
-25
lines changed

src/controller/cap-level-controller.ts

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,18 @@ type RestrictedLevel = { width: number; height: number; bitrate: number };
1919
class CapLevelController implements ComponentAPI {
2020
private hls: Hls;
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,7 +44,7 @@ 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
}
5250
this.media = null;
@@ -94,9 +92,17 @@ class CapLevelController implements ComponentAPI {
9492
event: Events.MEDIA_ATTACHING,
9593
data: MediaAttachingData,
9694
) {
97-
this.media = data.media instanceof HTMLVideoElement ? data.media : null;
95+
const media = data.media;
9896
this.clientRect = null;
99-
if (this.timer && this.hls.levels.length) {
97+
if (media instanceof HTMLVideoElement) {
98+
this.media = media;
99+
if (this.hls.config.capLevelToPlayerSize) {
100+
this.observe();
101+
}
102+
} else {
103+
this.media = null;
104+
}
105+
if ((this.timer || this.observer) && this.hls.levels.length) {
100106
this.detectPlayerSize();
101107
}
102108
}
@@ -107,7 +113,6 @@ class CapLevelController implements ComponentAPI {
107113
) {
108114
const hls = this.hls;
109115
this.restrictedLevels = [];
110-
this.firstLevel = data.firstLevel;
111116
if (hls.config.capLevelToPlayerSize && data.video) {
112117
// Start capping immediately if the manifest has signaled video codecs
113118
this.startCapping();
@@ -118,7 +123,10 @@ class CapLevelController implements ComponentAPI {
118123
event: Events.LEVELS_UPDATED,
119124
data: LevelsUpdatedData,
120125
) {
121-
if (this.timer && Number.isFinite(this.autoLevelCapping)) {
126+
if (
127+
(this.timer || this.observer) &&
128+
Number.isFinite(this.autoLevelCapping)
129+
) {
122130
this.detectPlayerSize();
123131
}
124132
}
@@ -183,34 +191,58 @@ class CapLevelController implements ComponentAPI {
183191
const validLevels = levels.filter(
184192
(level, index) => this.isLevelAllowed(level) && index <= capLevelIndex,
185193
);
186-
187-
this.clientRect = null;
194+
if (!this.observer) {
195+
this.clientRect = null;
196+
}
188197
return CapLevelController.getMaxLevelByMediaSize(
189198
validLevels,
190199
this.mediaWidth,
191200
this.mediaHeight,
192201
);
193202
}
194203

204+
private observe() {
205+
const ResizeObserver = self.ResizeObserver;
206+
if (ResizeObserver) {
207+
this.observer = new ResizeObserver((entries) => {
208+
const bounds = entries[0]?.contentRect;
209+
if (bounds) {
210+
this.clientRect = bounds;
211+
this.detectPlayerSize();
212+
}
213+
});
214+
}
215+
if (this.observer && this.media) {
216+
this.observer.observe(this.media);
217+
}
218+
}
219+
195220
startCapping() {
196-
if (this.timer) {
221+
if (this.timer || this.observer) {
197222
// Don't reset capping if started twice; this can happen if the manifest signals a video codec
198223
return;
199224
}
200-
this.autoLevelCapping = Number.POSITIVE_INFINITY;
201225
self.clearInterval(this.timer);
202-
this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000);
226+
this.timer = undefined;
227+
this.autoLevelCapping = Number.POSITIVE_INFINITY;
228+
this.observe();
229+
if (!this.observer) {
230+
this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000);
231+
}
203232
this.detectPlayerSize();
204233
}
205234

206235
stopCapping() {
207236
this.restrictedLevels = [];
208-
this.firstLevel = -1;
209237
this.autoLevelCapping = Number.POSITIVE_INFINITY;
210238
if (this.timer) {
211239
self.clearInterval(this.timer);
212240
this.timer = undefined;
213241
}
242+
if (this.observer) {
243+
this.observer.disconnect();
244+
this.observer = undefined;
245+
}
214246
}
215247

216248
getDimensions(): { width: number; height: number } {

tests/unit/controller/cap-level-controller.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,15 @@ describe('CapLevelController', function () {
181181
});
182182

183183
describe('start and stop', function () {
184-
it('immediately caps and sets a timer for monitoring size size', function () {
184+
it('immediately caps and begins monitoring size', function () {
185185
const detectPlayerSizeSpy = sinon.spy(
186186
capLevelController,
187187
'detectPlayerSize',
188188
);
189189
capLevelController.startCapping();
190190

191-
expect(capLevelController.timer).to.exist;
191+
expect(capLevelController.timer || capLevelController.observer).to
192+
.exist;
192193
expect(detectPlayerSizeSpy.callCount).to.equal(1);
193194
});
194195

@@ -201,7 +202,6 @@ describe('CapLevelController', function () {
201202
Number.POSITIVE_INFINITY,
202203
);
203204
expect(capLevelController.restrictedLevels).to.be.empty;
204-
expect(capLevelController.firstLevel).to.equal(-1);
205205
expect(capLevelController.timer).to.not.exist;
206206
});
207207
});
@@ -232,15 +232,11 @@ describe('CapLevelController', function () {
232232
expect(startCappingSpy.calledOnce).to.be.true;
233233
});
234234

235-
it('receives level information from the MANIFEST_PARSED event', function () {
235+
it('resets restrictedLevels on MANIFEST_PARSED', function () {
236236
capLevelController.restrictedLevels = [1];
237-
const data = {
237+
capLevelController.onManifestParsed(Events.MANIFEST_PARSED, {
238238
levels: [{ foo: 'bar' }],
239-
firstLevel: 0,
240-
};
241-
242-
capLevelController.onManifestParsed(Events.MANIFEST_PARSED, data);
243-
expect(capLevelController.firstLevel).to.equal(data.firstLevel);
239+
});
244240
expect(capLevelController.restrictedLevels).to.be.empty;
245241
});
246242

0 commit comments

Comments
 (0)