Add latency compensator to player. Closes #1931
This commit is contained in:
parent
ff968616ba
commit
8624358dde
@ -8,12 +8,17 @@ import { getLocalStorage, setLocalStorage } from '../../utils/localStorage';
|
|||||||
import { isVideoPlayingAtom, clockSkewAtom } from '../stores/ClientConfigStore';
|
import { isVideoPlayingAtom, clockSkewAtom } from '../stores/ClientConfigStore';
|
||||||
import PlaybackMetrics from './metrics/playback';
|
import PlaybackMetrics from './metrics/playback';
|
||||||
import createVideoSettingsMenuButton from './settings-menu';
|
import createVideoSettingsMenuButton from './settings-menu';
|
||||||
|
import LatencyCompensator from './latencyCompensator';
|
||||||
|
|
||||||
const VIDEO_CONFIG_URL = '/api/video/variants';
|
const VIDEO_CONFIG_URL = '/api/video/variants';
|
||||||
const PLAYER_VOLUME = 'owncast_volume';
|
const PLAYER_VOLUME = 'owncast_volume';
|
||||||
|
const LATENCY_COMPENSATION_ENABLED = 'latencyCompensatorEnabled';
|
||||||
|
|
||||||
const ping = new ViewerPing();
|
const ping = new ViewerPing();
|
||||||
let playbackMetrics = null;
|
let playbackMetrics = null;
|
||||||
|
let latencyCompensator = null;
|
||||||
|
let latencyCompensatorEnabled = false;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source: string;
|
source: string;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
@ -73,6 +78,50 @@ export default function OwncastPlayer(props: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setLatencyCompensatorItemTitle = title => {
|
||||||
|
const item = document.querySelector('.latency-toggle-item > .vjs-menu-item-text');
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.innerHTML = title;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startLatencyCompensator = () => {
|
||||||
|
if (latencyCompensator) {
|
||||||
|
latencyCompensator.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
latencyCompensatorEnabled = true;
|
||||||
|
|
||||||
|
latencyCompensator = new LatencyCompensator(playerRef.current);
|
||||||
|
latencyCompensator.setClockSkew(clockSkew);
|
||||||
|
latencyCompensator.enable();
|
||||||
|
setLocalStorage(LATENCY_COMPENSATION_ENABLED, true);
|
||||||
|
|
||||||
|
setLatencyCompensatorItemTitle('disable minimized latency');
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopLatencyCompensator = () => {
|
||||||
|
if (latencyCompensator) {
|
||||||
|
latencyCompensator.disable();
|
||||||
|
}
|
||||||
|
latencyCompensator = null;
|
||||||
|
latencyCompensatorEnabled = false;
|
||||||
|
setLocalStorage(LATENCY_COMPENSATION_ENABLED, false);
|
||||||
|
setLatencyCompensatorItemTitle(
|
||||||
|
'<span style="font-size: 0.8em">enable minimized latency (experimental)</span>',
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleLatencyCompensator = () => {
|
||||||
|
if (latencyCompensatorEnabled) {
|
||||||
|
stopLatencyCompensator();
|
||||||
|
} else {
|
||||||
|
startLatencyCompensator();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Register keyboard shortcut for the space bar to toggle playback
|
// Register keyboard shortcut for the space bar to toggle playback
|
||||||
useHotkeys('space', togglePlayback, {
|
useHotkeys('space', togglePlayback, {
|
||||||
enableOnContentEditable: false,
|
enableOnContentEditable: false,
|
||||||
@ -133,6 +182,23 @@ export default function OwncastPlayer(props: Props) {
|
|||||||
playerRef.current = player;
|
playerRef.current = player;
|
||||||
setSavedVolume();
|
setSavedVolume();
|
||||||
|
|
||||||
|
const setupLatencyCompensator = () => {
|
||||||
|
const tech = player.tech({ IWillNotUseThisInPlugins: true });
|
||||||
|
|
||||||
|
// VHS is required.
|
||||||
|
if (!tech || !tech.vhs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latencyCompensatorEnabledSaved = getLocalStorage(LATENCY_COMPENSATION_ENABLED);
|
||||||
|
|
||||||
|
if (latencyCompensatorEnabledSaved === 'true' && tech && tech.vhs) {
|
||||||
|
startLatencyCompensator();
|
||||||
|
} else {
|
||||||
|
stopLatencyCompensator();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// You can handle player events here, for example:
|
// You can handle player events here, for example:
|
||||||
player.on('waiting', () => {
|
player.on('waiting', () => {
|
||||||
player.log('player is waiting');
|
player.log('player is waiting');
|
||||||
@ -170,14 +236,19 @@ export default function OwncastPlayer(props: Props) {
|
|||||||
|
|
||||||
const createSettings = async () => {
|
const createSettings = async () => {
|
||||||
const videoQualities = await getVideoSettings();
|
const videoQualities = await getVideoSettings();
|
||||||
const menuButton = createVideoSettingsMenuButton(player, videojs, videoQualities);
|
const menuButton = createVideoSettingsMenuButton(
|
||||||
|
player,
|
||||||
|
videojs,
|
||||||
|
videoQualities,
|
||||||
|
toggleLatencyCompensator,
|
||||||
|
);
|
||||||
player.controlBar.addChild(
|
player.controlBar.addChild(
|
||||||
menuButton,
|
menuButton,
|
||||||
{},
|
{},
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
player.controlBar.children_.length - 2,
|
player.controlBar.children_.length - 2,
|
||||||
);
|
);
|
||||||
// this.latencyCompensatorToggleButton = lowLatencyItem;
|
setupLatencyCompensator();
|
||||||
};
|
};
|
||||||
|
|
||||||
createSettings();
|
createSettings();
|
||||||
|
@ -44,6 +44,28 @@ const MAX_JUMP_FREQUENCY = 20 * 1000; // How often we'll allow a time jump.
|
|||||||
const MAX_ACTIONABLE_LATENCY = 80 * 1000; // If latency is seen to be greater than this then something is wrong.
|
const MAX_ACTIONABLE_LATENCY = 80 * 1000; // If latency is seen to be greater than this then something is wrong.
|
||||||
const STARTUP_WAIT_TIME = 10 * 1000; // The amount of time after we start up that we'll allow monitoring to occur.
|
const STARTUP_WAIT_TIME = 10 * 1000; // The amount of time after we start up that we'll allow monitoring to occur.
|
||||||
|
|
||||||
|
function getCurrentlyPlayingSegment(tech) {
|
||||||
|
const targetMedia = tech.vhs.playlists.media();
|
||||||
|
const snapshotTime = tech.currentTime();
|
||||||
|
let segment;
|
||||||
|
|
||||||
|
// Iterate trough available segments and get first within which snapshot_time is
|
||||||
|
// eslint-disable-next-line no-plusplus
|
||||||
|
for (let i = 0, l = targetMedia.segments.length; i < l; i++) {
|
||||||
|
// Note: segment.end may be undefined or is not properly set
|
||||||
|
if (snapshotTime < targetMedia.segments[i].end) {
|
||||||
|
segment = targetMedia.segments[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!segment) {
|
||||||
|
[segment] = targetMedia.segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
class LatencyCompensator {
|
class LatencyCompensator {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
@ -143,10 +165,12 @@ class LatencyCompensator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tech.vhs.stats.buffered.forEach((buffer) => {
|
tech.vhs.stats.buffered.forEach(buffer => {
|
||||||
totalBuffered += buffer.end - buffer.start;
|
totalBuffered += buffer.end - buffer.start;
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
// Determine how much of the current playlist's bandwidth requirements
|
// Determine how much of the current playlist's bandwidth requirements
|
||||||
// we're utilizing. If it's too high then we can't afford to push
|
// we're utilizing. If it's too high then we can't afford to push
|
||||||
@ -164,10 +188,7 @@ class LatencyCompensator {
|
|||||||
|
|
||||||
// If we're downloading media fast enough or we feel like we have a large
|
// If we're downloading media fast enough or we feel like we have a large
|
||||||
// enough buffer then continue. Otherwise timeout for a bit.
|
// enough buffer then continue. Otherwise timeout for a bit.
|
||||||
if (
|
if (bandwidthRatio < REQUIRED_BANDWIDTH_RATIO && totalBuffered < segment.duration * 6) {
|
||||||
bandwidthRatio < REQUIRED_BANDWIDTH_RATIO &&
|
|
||||||
totalBuffered < segment.duration * 6
|
|
||||||
) {
|
|
||||||
this.timeout();
|
this.timeout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -175,30 +196,24 @@ class LatencyCompensator {
|
|||||||
// How far away from live edge do we stop the compensator.
|
// How far away from live edge do we stop the compensator.
|
||||||
const computedMinLatencyThreshold = Math.max(
|
const computedMinLatencyThreshold = Math.max(
|
||||||
MIN_LATENCY,
|
MIN_LATENCY,
|
||||||
segment.duration * 1000 * LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER
|
segment.duration * 1000 * LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create an array of all the buffering events in the past along with
|
// Create an array of all the buffering events in the past along with
|
||||||
// the computed min latency above.
|
// the computed min latency above.
|
||||||
const targetLatencies = this.bufferedAtLatency.concat([
|
const targetLatencies = this.bufferedAtLatency.concat([computedMinLatencyThreshold]);
|
||||||
computedMinLatencyThreshold,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Determine if we need to reduce the minimum latency we computed
|
// Determine if we need to reduce the minimum latency we computed
|
||||||
// above based on buffering events that have taken place in the past by
|
// above based on buffering events that have taken place in the past by
|
||||||
// creating an array of all the buffering events and the above computed
|
// creating an array of all the buffering events and the above computed
|
||||||
// minimum latency target and averaging all those values.
|
// minimum latency target and averaging all those values.
|
||||||
const minLatencyThreshold =
|
const minLatencyThreshold =
|
||||||
targetLatencies.reduce((sum, current) => sum + current, 0) /
|
targetLatencies.reduce((sum, current) => sum + current, 0) / targetLatencies.length;
|
||||||
targetLatencies.length;
|
|
||||||
|
|
||||||
// How far away from live edge do we start the compensator.
|
// How far away from live edge do we start the compensator.
|
||||||
let maxLatencyThreshold = Math.max(
|
let maxLatencyThreshold = Math.max(
|
||||||
minLatencyThreshold * 1.4,
|
minLatencyThreshold * 1.4,
|
||||||
Math.min(
|
Math.min(segment.duration * 1000 * HIGHEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER, MAX_LATENCY),
|
||||||
segment.duration * 1000 * HIGHEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER,
|
|
||||||
MAX_LATENCY
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// If this newly adjusted minimum latency ends up being greater than
|
// If this newly adjusted minimum latency ends up being greater than
|
||||||
@ -228,10 +243,7 @@ class LatencyCompensator {
|
|||||||
if (latency > maxLatencyThreshold) {
|
if (latency > maxLatencyThreshold) {
|
||||||
// If the current latency exceeds the max jump amount then
|
// If the current latency exceeds the max jump amount then
|
||||||
// force jump into the future, skipping all the video in between.
|
// force jump into the future, skipping all the video in between.
|
||||||
if (
|
if (this.shouldJumpToLive() && latency > maxLatencyThreshold + MAX_JUMP_LATENCY) {
|
||||||
this.shouldJumpToLive() &&
|
|
||||||
latency > maxLatencyThreshold + MAX_JUMP_LATENCY
|
|
||||||
) {
|
|
||||||
const jumpAmount = latency / 1000 - segment.duration * 3;
|
const jumpAmount = latency / 1000 - segment.duration * 3;
|
||||||
const seekPosition = this.player.currentTime() + jumpAmount;
|
const seekPosition = this.player.currentTime() + jumpAmount;
|
||||||
console.info(
|
console.info(
|
||||||
@ -242,17 +254,13 @@ class LatencyCompensator {
|
|||||||
'to live from ',
|
'to live from ',
|
||||||
this.player.currentTime(),
|
this.player.currentTime(),
|
||||||
' to ',
|
' to ',
|
||||||
seekPosition
|
seekPosition,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify we have the seek position buffered before jumping.
|
// Verify we have the seek position buffered before jumping.
|
||||||
const availableBufferedTimeEnd = tech.vhs.stats.buffered[0].end;
|
const availableBufferedTimeEnd = tech.vhs.stats.buffered[0].end;
|
||||||
const availableBufferedTimeStart = tech.vhs.stats.buffered[0].start;
|
const availableBufferedTimeStart = tech.vhs.stats.buffered[0].start;
|
||||||
if (
|
if (seekPosition > availableBufferedTimeStart < availableBufferedTimeEnd) {
|
||||||
seekPosition >
|
|
||||||
availableBufferedTimeStart <
|
|
||||||
availableBufferedTimeEnd
|
|
||||||
) {
|
|
||||||
this.jump(seekPosition);
|
this.jump(seekPosition);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -260,13 +268,10 @@ class LatencyCompensator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Using our bandwidth ratio determine a wide guess at how fast we can play.
|
// Using our bandwidth ratio determine a wide guess at how fast we can play.
|
||||||
var proposedPlaybackRate = bandwidthRatio * 0.33;
|
let proposedPlaybackRate = bandwidthRatio * 0.33;
|
||||||
|
|
||||||
// But limit the playback rate to a max value.
|
// But limit the playback rate to a max value.
|
||||||
proposedPlaybackRate = Math.max(
|
proposedPlaybackRate = Math.max(Math.min(proposedPlaybackRate, MAX_SPEEDUP_RATE), 1.0);
|
||||||
Math.min(proposedPlaybackRate, MAX_SPEEDUP_RATE),
|
|
||||||
1.0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (proposedPlaybackRate > this.playbackRate + MAX_SPEEDUP_RAMP) {
|
if (proposedPlaybackRate > this.playbackRate + MAX_SPEEDUP_RAMP) {
|
||||||
// If this proposed speed is substantially faster than the current rate,
|
// If this proposed speed is substantially faster than the current rate,
|
||||||
@ -275,8 +280,7 @@ class LatencyCompensator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit to 3 decimal places of precision.
|
// Limit to 3 decimal places of precision.
|
||||||
proposedPlaybackRate =
|
proposedPlaybackRate = Math.round(proposedPlaybackRate * 10 ** 3) / 10 ** 3;
|
||||||
Math.round(proposedPlaybackRate * Math.pow(10, 3)) / Math.pow(10, 3);
|
|
||||||
|
|
||||||
// Otherwise start the playback rate adjustment.
|
// Otherwise start the playback rate adjustment.
|
||||||
this.start(proposedPlaybackRate);
|
this.start(proposedPlaybackRate);
|
||||||
@ -300,7 +304,7 @@ class LatencyCompensator {
|
|||||||
'skew: ',
|
'skew: ',
|
||||||
this.clockSkewMs,
|
this.clockSkewMs,
|
||||||
'rebuffer events: ',
|
'rebuffer events: ',
|
||||||
this.bufferingCounter
|
this.bufferingCounter,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.error(err);
|
// console.error(err);
|
||||||
@ -325,12 +329,7 @@ class LatencyCompensator {
|
|||||||
|
|
||||||
this.lastJumpOccurred = new Date();
|
this.lastJumpOccurred = new Date();
|
||||||
|
|
||||||
console.info(
|
console.info('current time', this.player.currentTime(), 'seeking to', seekPosition);
|
||||||
'current time',
|
|
||||||
this.player.currentTime(),
|
|
||||||
'seeking to',
|
|
||||||
seekPosition
|
|
||||||
);
|
|
||||||
this.player.currentTime(seekPosition);
|
this.player.currentTime(seekPosition);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -435,7 +434,7 @@ class LatencyCompensator {
|
|||||||
this.disable();
|
this.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(e) {
|
handleError() {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -444,7 +443,7 @@ class LatencyCompensator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
countBufferingEvent() {
|
countBufferingEvent() {
|
||||||
this.bufferingCounter++;
|
this.bufferingCounter += 1;
|
||||||
|
|
||||||
if (this.bufferingCounter > REBUFFER_EVENT_LIMIT) {
|
if (this.bufferingCounter > REBUFFER_EVENT_LIMIT) {
|
||||||
this.disable();
|
this.disable();
|
||||||
@ -457,13 +456,13 @@ class LatencyCompensator {
|
|||||||
'latency compensation timeout due to buffering:',
|
'latency compensation timeout due to buffering:',
|
||||||
this.bufferingCounter,
|
this.bufferingCounter,
|
||||||
'buffering events of',
|
'buffering events of',
|
||||||
REBUFFER_EVENT_LIMIT
|
REBUFFER_EVENT_LIMIT,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allow us to forget about old buffering events if enough time goes by.
|
// Allow us to forget about old buffering events if enough time goes by.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.bufferingCounter > 0) {
|
if (this.bufferingCounter > 0) {
|
||||||
this.bufferingCounter--;
|
this.bufferingCounter -= 1;
|
||||||
}
|
}
|
||||||
}, BUFFERING_AMNESTY_DURATION);
|
}, BUFFERING_AMNESTY_DURATION);
|
||||||
}
|
}
|
||||||
@ -487,26 +486,4 @@ class LatencyCompensator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentlyPlayingSegment(tech) {
|
|
||||||
var target_media = tech.vhs.playlists.media();
|
|
||||||
var snapshot_time = tech.currentTime();
|
|
||||||
|
|
||||||
var segment;
|
|
||||||
|
|
||||||
// Iterate trough available segments and get first within which snapshot_time is
|
|
||||||
for (var i = 0, l = target_media.segments.length; i < l; i++) {
|
|
||||||
// Note: segment.end may be undefined or is not properly set
|
|
||||||
if (snapshot_time < target_media.segments[i].end) {
|
|
||||||
segment = target_media.segments[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment) {
|
|
||||||
segment = target_media.segments[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return segment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LatencyCompensator;
|
export default LatencyCompensator;
|
@ -1,32 +1,37 @@
|
|||||||
export default function createVideoSettingsMenuButton(player, videojs, qualities): any {
|
export default function createVideoSettingsMenuButton(
|
||||||
// const VjsMenuItem = videojs.getComponent('MenuItem');
|
player,
|
||||||
|
videojs,
|
||||||
|
qualities,
|
||||||
|
latencyItemPressed,
|
||||||
|
): any {
|
||||||
|
const VjsMenuItem = videojs.getComponent('MenuItem');
|
||||||
const MenuItem = videojs.getComponent('MenuItem');
|
const MenuItem = videojs.getComponent('MenuItem');
|
||||||
const MenuButtonClass = videojs.getComponent('MenuButton');
|
const MenuButtonClass = videojs.getComponent('MenuButton');
|
||||||
|
|
||||||
// class MenuSeparator extends VjsMenuItem {
|
class MenuSeparator extends VjsMenuItem {
|
||||||
// // eslint-disable-next-line no-useless-constructor
|
// eslint-disable-next-line no-useless-constructor
|
||||||
// constructor(p: any, options: { selectable: boolean }) {
|
constructor(p: any, options: { selectable: boolean }) {
|
||||||
// super(p, options);
|
super(p, options);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// createEl(tag = 'button', props = {}, attributes = {}) {
|
createEl(tag = 'button', props = {}, attributes = {}) {
|
||||||
// const el = super.createEl(tag, props, attributes);
|
const el = super.createEl(tag, props, attributes);
|
||||||
// el.innerHTML = '<hr style="opacity: 0.3; margin-left: 10px; margin-right: 10px;" />';
|
el.innerHTML = '<hr style="opacity: 0.3; margin-left: 10px; margin-right: 10px;" />';
|
||||||
// return el;
|
return el;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
const lowLatencyItem = new MenuItem(player, {
|
const lowLatencyItem = new MenuItem(player, {
|
||||||
selectable: true,
|
selectable: true,
|
||||||
});
|
});
|
||||||
lowLatencyItem.setAttribute('class', 'latency-toggle-item');
|
lowLatencyItem.setAttribute('class', 'latency-toggle-item');
|
||||||
lowLatencyItem.on('click', () => {
|
lowLatencyItem.on('click', () => {
|
||||||
this.toggleLatencyCompensator();
|
latencyItemPressed();
|
||||||
});
|
});
|
||||||
|
|
||||||
// const separator = new MenuSeparator(player, {
|
const separator = new MenuSeparator(player, {
|
||||||
// selectable: false,
|
selectable: false,
|
||||||
// });
|
});
|
||||||
|
|
||||||
const MenuButton = videojs.extend(MenuButtonClass, {
|
const MenuButton = videojs.extend(MenuButtonClass, {
|
||||||
// The `init()` method will also work for constructor logic here, but it is
|
// The `init()` method will also work for constructor logic here, but it is
|
||||||
@ -75,16 +80,16 @@ export default function createVideoSettingsMenuButton(player, videojs, qualities
|
|||||||
defaultAutoItem.selected(false);
|
defaultAutoItem.selected(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const supportsLatencyCompensator = false; // !!tech && !!tech.vhs;
|
const supportsLatencyCompensator = !!tech && !!tech.vhs;
|
||||||
|
|
||||||
// Only show the quality selector if there is more than one option.
|
// Only show the quality selector if there is more than one option.
|
||||||
// if (qualities.length < 2 && supportsLatencyCompensator) {
|
if (qualities.length < 2 && supportsLatencyCompensator) {
|
||||||
// return [lowLatencyItem];
|
return [lowLatencyItem];
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (qualities.length > 1 && supportsLatencyCompensator) {
|
if (qualities.length > 1 && supportsLatencyCompensator) {
|
||||||
// return [defaultAutoItem, ...items, separator, lowLatencyItem];
|
return [defaultAutoItem, ...items, separator, lowLatencyItem];
|
||||||
// }
|
}
|
||||||
if (!supportsLatencyCompensator && qualities.length === 1) {
|
if (!supportsLatencyCompensator && qualities.length === 1) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user