
The HTML5 video element already has a well-supported "playbackRate" attribute which can be used to increase playback rate. This change wires up that control to be displayed in the Jellyfish web player. The playback rates offered are between 0.5x and 2x in 0.25x increments, which matches the YouTube player. This change also wires up the ">" and "<" key events to increase and decrease the playback rate, which mirrors the keyboard shortcuts supported by YouTube.
289 lines
8.4 KiB
JavaScript
289 lines
8.4 KiB
JavaScript
import connectionManager from 'connectionManager';
|
|
import actionsheet from 'actionsheet';
|
|
import playbackManager from 'playbackManager';
|
|
import globalize from 'globalize';
|
|
import qualityoptions from 'qualityoptions';
|
|
|
|
function showQualityMenu(player, btn) {
|
|
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
|
|
return stream.Type === 'Video';
|
|
})[0];
|
|
var videoWidth = videoStream ? videoStream.Width : null;
|
|
var videoHeight = videoStream ? videoStream.Height : null;
|
|
|
|
var options = qualityoptions.getVideoQualityOptions({
|
|
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
|
|
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
|
|
videoWidth: videoWidth,
|
|
videoHeight: videoHeight,
|
|
enableAuto: true
|
|
});
|
|
|
|
var menuItems = options.map(function (o) {
|
|
var opt = {
|
|
name: o.name,
|
|
id: o.bitrate,
|
|
asideText: o.secondaryText
|
|
};
|
|
|
|
if (o.selected) {
|
|
opt.selected = true;
|
|
}
|
|
|
|
return opt;
|
|
});
|
|
|
|
var selectedId = options.filter(function (o) {
|
|
return o.selected;
|
|
});
|
|
|
|
selectedId = selectedId.length ? selectedId[0].bitrate : null;
|
|
|
|
return actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: btn
|
|
}).then(function (id) {
|
|
var bitrate = parseInt(id);
|
|
if (bitrate !== selectedId) {
|
|
playbackManager.setMaxStreamingBitrate({
|
|
enableAutomaticBitrateDetection: bitrate ? false : true,
|
|
maxBitrate: bitrate
|
|
}, player);
|
|
}
|
|
});
|
|
}
|
|
|
|
function showRepeatModeMenu(player, btn) {
|
|
var menuItems = [];
|
|
var currentValue = playbackManager.getRepeatMode(player);
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('RepeatAll'),
|
|
id: 'RepeatAll',
|
|
selected: currentValue === 'RepeatAll'
|
|
});
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('RepeatOne'),
|
|
id: 'RepeatOne',
|
|
selected: currentValue === 'RepeatOne'
|
|
});
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('None'),
|
|
id: 'RepeatNone',
|
|
selected: currentValue === 'RepeatNone'
|
|
});
|
|
|
|
return actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: btn
|
|
}).then(function (mode) {
|
|
if (mode) {
|
|
playbackManager.setRepeatMode(mode, player);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getQualitySecondaryText(player) {
|
|
var state = playbackManager.getPlayerState(player);
|
|
|
|
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
|
|
return stream.Type === 'Video';
|
|
})[0];
|
|
|
|
var videoWidth = videoStream ? videoStream.Width : null;
|
|
var videoHeight = videoStream ? videoStream.Height : null;
|
|
|
|
var options = qualityoptions.getVideoQualityOptions({
|
|
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
|
|
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
|
|
videoWidth: videoWidth,
|
|
videoHeight: videoHeight,
|
|
enableAuto: true
|
|
});
|
|
|
|
var selectedOption = options.filter(function (o) {
|
|
return o.selected;
|
|
});
|
|
|
|
if (!selectedOption.length) {
|
|
return null;
|
|
}
|
|
|
|
selectedOption = selectedOption[0];
|
|
var text = selectedOption.name;
|
|
|
|
if (selectedOption.autoText) {
|
|
if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') {
|
|
text += ' - Direct';
|
|
} else {
|
|
text += ' ' + selectedOption.autoText;
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
function showAspectRatioMenu(player, btn) {
|
|
// each has a name and id
|
|
var currentId = playbackManager.getAspectRatio(player);
|
|
var menuItems = playbackManager.getSupportedAspectRatios(player).map(function (i) {
|
|
return {
|
|
id: i.id,
|
|
name: i.name,
|
|
selected: i.id === currentId
|
|
};
|
|
});
|
|
|
|
return actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: btn
|
|
}).then(function (id) {
|
|
if (id) {
|
|
playbackManager.setAspectRatio(id, player);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.reject();
|
|
});
|
|
}
|
|
|
|
function showPlaybackRateMenu(player, btn) {
|
|
// each has a name and id
|
|
const currentId = playbackManager.getPlaybackRate(player);
|
|
const menuItems = playbackManager.getSupportedPlaybackRates(player).map(i => ({
|
|
id: i.id,
|
|
name: i.name,
|
|
selected: i.id === currentId
|
|
}));
|
|
|
|
return actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: btn
|
|
}).then(function (id) {
|
|
if (id) {
|
|
playbackManager.setPlaybackRate(id, player);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.reject();
|
|
});
|
|
}
|
|
|
|
function showWithUser(options, player, user) {
|
|
var supportedCommands = playbackManager.getSupportedCommands(player);
|
|
|
|
var menuItems = [];
|
|
if (supportedCommands.indexOf('SetAspectRatio') !== -1) {
|
|
var currentAspectRatioId = playbackManager.getAspectRatio(player);
|
|
var currentAspectRatio = playbackManager.getSupportedAspectRatios(player).filter(function (i) {
|
|
return i.id === currentAspectRatioId;
|
|
})[0];
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('AspectRatio'),
|
|
id: 'aspectratio',
|
|
asideText: currentAspectRatio ? currentAspectRatio.name : null
|
|
});
|
|
}
|
|
|
|
if (supportedCommands.indexOf('PlaybackRate') !== -1) {
|
|
const currentPlaybackRateId = playbackManager.getPlaybackRate(player);
|
|
const currentPlaybackRate = playbackManager.getSupportedPlaybackRates(player).filter(i => i.id === currentPlaybackRateId)[0];
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('PlaybackRate'),
|
|
id: 'playbackrate',
|
|
asideText: currentPlaybackRate ? currentPlaybackRate.name : null
|
|
});
|
|
}
|
|
|
|
if (user && user.Policy.EnableVideoPlaybackTranscoding) {
|
|
var secondaryQualityText = getQualitySecondaryText(player);
|
|
|
|
menuItems.push({
|
|
name: globalize.translate('Quality'),
|
|
id: 'quality',
|
|
asideText: secondaryQualityText
|
|
});
|
|
}
|
|
|
|
var repeatMode = playbackManager.getRepeatMode(player);
|
|
|
|
if (supportedCommands.indexOf('SetRepeatMode') !== -1 && playbackManager.currentMediaSource(player).RunTimeTicks) {
|
|
menuItems.push({
|
|
name: globalize.translate('RepeatMode'),
|
|
id: 'repeatmode',
|
|
asideText: repeatMode === 'RepeatNone' ? globalize.translate('None') : globalize.translate('' + repeatMode)
|
|
});
|
|
}
|
|
|
|
if (options.suboffset) {
|
|
menuItems.push({
|
|
name: globalize.translate('SubtitleOffset'),
|
|
id: 'suboffset',
|
|
asideText: null
|
|
});
|
|
}
|
|
|
|
if (options.stats) {
|
|
menuItems.push({
|
|
name: globalize.translate('PlaybackData'),
|
|
id: 'stats',
|
|
asideText: null
|
|
});
|
|
}
|
|
|
|
return actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: options.positionTo
|
|
}).then(function (id) {
|
|
return handleSelectedOption(id, options, player);
|
|
});
|
|
}
|
|
|
|
export function show(options) {
|
|
var player = options.player;
|
|
var currentItem = playbackManager.currentItem(player);
|
|
|
|
if (!currentItem || !currentItem.ServerId) {
|
|
return showWithUser(options, player, null);
|
|
}
|
|
|
|
var apiClient = connectionManager.getApiClient(currentItem.ServerId);
|
|
return apiClient.getCurrentUser().then(function (user) {
|
|
return showWithUser(options, player, user);
|
|
});
|
|
}
|
|
|
|
function handleSelectedOption(id, options, player) {
|
|
switch (id) {
|
|
case 'quality':
|
|
return showQualityMenu(player, options.positionTo);
|
|
case 'aspectratio':
|
|
return showAspectRatioMenu(player, options.positionTo);
|
|
case 'playbackrate':
|
|
return showPlaybackRateMenu(player, options.positionTo);
|
|
case 'repeatmode':
|
|
return showRepeatModeMenu(player, options.positionTo);
|
|
case 'stats':
|
|
if (options.onOption) {
|
|
options.onOption('stats');
|
|
}
|
|
return Promise.resolve();
|
|
case 'suboffset':
|
|
if (options.onOption) {
|
|
options.onOption('suboffset');
|
|
}
|
|
return Promise.resolve();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return Promise.reject();
|
|
}
|
|
|
|
export default {
|
|
show: show
|
|
};
|