Skip to content

Commit 2c52076

Browse files
feat: Consider object-fit when selecting playlist by player size (#1051)
* Make simple playlist selecor easier to extend Refactor from positional parameters to a single `settings` argument. This makes it clearer what each argument means in calls of the function. Additional parameters can now be added without making the argument list overly long. Key names were chosen to match those of `minRebufferMaxBandwidthSelector` to align the signatures. * Make simpleSelector test easier to understand Inline the passed bandwidth value instead of referencing the config constant since the concrete value is needed to understand why the expected playlist is chosen. Also if the config constant should ever change the test will fail for no good reason. * Consider object-fit when selecting playlist by player size So far, when `limitRenditionByPlayerDimensions` is `true`, `simpleSelector` tried to either find a rendition with a resolution that matches the size of the player exactly or, if that does not exist, a rendition with the smallest resolution that has either greater width or greater height than the player. This makes sense since by default the video will be scaled to fit inside the media element. So every resolution that exceeds player size in at least one dimension will be scaled down. Most browsers support [1] customizing this scaling behavior via the `object-fit` CSS property [2]. If it set to `cover`, the video will instead be scaled up if video and player aspect ratio do not match. The previous behavior caused renditions with low resolution to be selected for players with small width (e.g. portrait phone aspect ratio) even when videos were then scaled up to cover the whole player. We therefore detect if `object-fit` is set to `cover` and instead select the smallest rendition with a resolution that exceeds player dimensions in both width and height. [1] https://caniuse.com/?search=object-fit [2] https://developer.mozilla.org/de/docs/Web/CSS/object-fit * Add usePlayerObjectFit option Only consider `object-fit` CSS property when selecting playlists based on play size when `usePlayerObjectFit` option is `true` to make new behavior an opt-in. * chore: add object-fit option to the demo page --------- Co-authored-by: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com>
1 parent c1d3186 commit 2c52076

7 files changed

Lines changed: 196 additions & 45 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Video.js Compatibility: 7.x, 8.x
4949
- [enableLowInitialPlaylist](#enablelowinitialplaylist)
5050
- [limitRenditionByPlayerDimensions](#limitrenditionbyplayerdimensions)
5151
- [useDevicePixelRatio](#usedevicepixelratio)
52+
- [usePlayerObjectFit](#useplayerobjectfit)
5253
- [customPixelRatio](#custompixelratio)
5354
- [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow)
5455
- [customTagParsers](#customtagparsers)
@@ -414,6 +415,17 @@ This setting is `true` by default.
414415
If true, this will take the device pixel ratio into account when doing rendition switching. This means that if you have a player with the width of `540px` in a high density display with a device pixel ratio of 2, a rendition of `1080p` will be allowed.
415416
This setting is `false` by default.
416417

418+
419+
##### usePlayerObjectFit
420+
* Type: `boolean`
421+
* can be used as an initialization option.
422+
423+
If true, the video element's `object-fit` CSS property will be taken
424+
into account when doing rendition switching. This ensures that a
425+
suitable rendition is selected for videos that are scaled up to cover
426+
the media element. This setting is `false` by default.
427+
428+
417429
##### customPixelRatio
418430
* Type: `number`
419431
* can be used as an initialization option.
@@ -426,6 +438,7 @@ It is worth noting that if the player dimension multiplied by the custom pixel r
426438

427439
If `useDevicePixelRatio` is set to `true`, the custom pixel ratio will be prioritized and overwrite any previous pixel ratio.
428440

441+
429442
##### allowSeeksWithinUnsafeLiveWindow
430443
* Type: `boolean`
431444
* can be used as a source option

index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@
172172
<label class="form-check-label" for="pixel-diff-selector">[EXPERIMENTAL] Use the Pixel difference resolution selector (reloads player)</label>
173173
</div>
174174

175+
<div class="form-check">
176+
<input id=object-fit type="checkbox" class="form-check-input">
177+
<label class="form-check-label" for="object-fit">Account Object-fit for resolution selection (reloads player)</label>
178+
</div>
179+
175180
<div class="form-check">
176181
<input id=override-native type="checkbox" class="form-check-input" checked>
177182
<label class="form-check-label" for="override-native">Override Native (reloads player)</label>

scripts/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@
471471
'network-info',
472472
'dts-offset',
473473
'override-native',
474+
'object-fit',
474475
'use-mms',
475476
'preload',
476477
'mirror-source',
@@ -523,6 +524,7 @@
523524
'llhls',
524525
'buffer-water',
525526
'override-native',
527+
'object-fit',
526528
'use-mms',
527529
'liveui',
528530
'pixel-diff-selector',
@@ -615,7 +617,8 @@
615617
leastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']),
616618
useNetworkInformationApi: getInputValue(stateEls['network-info']),
617619
useDtsForTimestampOffset: getInputValue(stateEls['dts-offset']),
618-
useForcedSubtitles: getInputValue(stateEls['forced-subtitles'])
620+
useForcedSubtitles: getInputValue(stateEls['forced-subtitles']),
621+
usePlayerObjectFit: getInputValue(stateEls['object-fit'])
619622
}
620623
}
621624
});

src/playlist-selectors.js

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -133,30 +133,38 @@ export const comparePlaylistResolution = function(left, right) {
133133
/**
134134
* Chooses the appropriate media playlist based on bandwidth and player size
135135
*
136-
* @param {Object} main
136+
* @param {Object} settings
137+
* Object of information required to use this selector
138+
* @param {Object} settings.main
137139
* Object representation of the main manifest
138-
* @param {number} playerBandwidth
140+
* @param {number} settings.bandwidth
139141
* Current calculated bandwidth of the player
140-
* @param {number} playerWidth
142+
* @param {number} settings.playerWidth
141143
* Current width of the player element (should account for the device pixel ratio)
142-
* @param {number} playerHeight
144+
* @param {number} settings.playerHeight
143145
* Current height of the player element (should account for the device pixel ratio)
144-
* @param {boolean} limitRenditionByPlayerDimensions
146+
* @param {number} settings.playerObjectFit
147+
* Current value of the video element's object-fit CSS property. Allows taking into
148+
* account that the video might be scaled up to cover the media element when selecting
149+
* media playlists based on player size.
150+
* @param {boolean} settings.limitRenditionByPlayerDimensions
145151
* True if the player width and height should be used during the selection, false otherwise
146-
* @param {Object} playlistController
152+
* @param {Object} settings.playlistController
147153
* the current playlistController object
148154
* @return {Playlist} the highest bitrate playlist less than the
149155
* currently detected bandwidth, accounting for some amount of
150156
* bandwidth variance
151157
*/
152-
export let simpleSelector = function(
153-
main,
154-
playerBandwidth,
155-
playerWidth,
156-
playerHeight,
157-
limitRenditionByPlayerDimensions,
158-
playlistController
159-
) {
158+
export let simpleSelector = function(settings) {
159+
const {
160+
main,
161+
bandwidth: playerBandwidth,
162+
playerWidth,
163+
playerHeight,
164+
playerObjectFit,
165+
limitRenditionByPlayerDimensions,
166+
playlistController
167+
} = settings;
160168

161169
// If we end up getting called before `main` is available, exit early
162170
if (!main) {
@@ -271,7 +279,18 @@ export let simpleSelector = function(
271279
// find the smallest variant that is larger than the player
272280
// if there is no match of exact resolution
273281
if (!resolutionBestRep) {
274-
resolutionPlusOneList = haveResolution.filter((rep) => rep.width > playerWidth || rep.height > playerHeight);
282+
resolutionPlusOneList = haveResolution.filter((rep) => {
283+
if (playerObjectFit === 'cover') {
284+
// video will be scaled up to cover the player. We need to
285+
// make sure rendition is at least as wide and as high as the
286+
// player.
287+
return rep.width > playerWidth && rep.height > playerHeight;
288+
}
289+
290+
// video will be scaled down to fit inside the player soon as
291+
// its resolution exceeds player size in at least one dimension.
292+
return rep.width > playerWidth || rep.height > playerHeight;
293+
});
275294

276295
// find all the variants have the same smallest resolution
277296
resolutionPlusOneSmallest = resolutionPlusOneList.filter((rep) => rep.width === resolutionPlusOneList[0].width &&
@@ -370,14 +389,15 @@ export const lastBandwidthSelector = function() {
370389
pixelRatio = this.customPixelRatio;
371390
}
372391

373-
return simpleSelector(
374-
this.playlists.main,
375-
this.systemBandwidth,
376-
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
377-
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
378-
this.limitRenditionByPlayerDimensions,
379-
this.playlistController_
380-
);
392+
return simpleSelector({
393+
main: this.playlists.main,
394+
bandwidth: this.systemBandwidth,
395+
playerWidth: parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
396+
playerHeight: parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
397+
playerObjectFit: this.usePlayerObjectFit ? safeGetComputedStyle(this.tech_.el(), 'objectFit') : '',
398+
limitRenditionByPlayerDimensions: this.limitRenditionByPlayerDimensions,
399+
playlistController: this.playlistController_
400+
});
381401
};
382402

383403
/**
@@ -425,14 +445,15 @@ export const movingAverageBandwidthSelector = function(decay) {
425445
lastSystemBandwidth = this.systemBandwidth;
426446
}
427447

428-
return simpleSelector(
429-
this.playlists.main,
430-
average,
431-
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
432-
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
433-
this.limitRenditionByPlayerDimensions,
434-
this.playlistController_
435-
);
448+
return simpleSelector({
449+
main: this.playlists.main,
450+
bandwidth: average,
451+
playerWidth: parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
452+
playerHeight: parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
453+
playerObjectFit: this.usePlayerObjectFit ? safeGetComputedStyle(this.tech_.el(), 'objectFit') : '',
454+
limitRenditionByPlayerDimensions: this.limitRenditionByPlayerDimensions,
455+
playlistController: this.playlistController_
456+
});
436457
};
437458
};
438459

src/videojs-http-streaming.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ class VhsHandler extends Component {
691691
this.options_.withCredentials = this.options_.withCredentials || false;
692692
this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
693693
this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
694+
this.options_.usePlayerObjectFit = this.options_.usePlayerObjectFit || false;
694695
this.options_.useBandwidthFromLocalStorage =
695696
typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ?
696697
this.source_.useBandwidthFromLocalStorage :
@@ -739,6 +740,7 @@ class VhsHandler extends Component {
739740
[
740741
'withCredentials',
741742
'useDevicePixelRatio',
743+
'usePlayerObjectFit',
742744
'customPixelRatio',
743745
'limitRenditionByPlayerDimensions',
744746
'bandwidth',
@@ -763,6 +765,7 @@ class VhsHandler extends Component {
763765

764766
this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
765767
this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
768+
this.usePlayerObjectFit = this.options_.usePlayerObjectFit;
766769

767770
const customPixelRatio = this.options_.customPixelRatio;
768771

test/configuration.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ const options = [{
3131
default: false,
3232
test: true,
3333
alt: false
34+
}, {
35+
name: 'usePlayerObjectFit',
36+
default: false,
37+
test: true,
38+
alt: false
39+
}, {
3440
},
3541
{
3642
name: 'customPixelRatio',

0 commit comments

Comments
 (0)