1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
jellyfin-web/src/controllers/playback/video/index.js

2072 lines
74 KiB
JavaScript
Raw Normal View History

2022-01-30 00:27:26 +03:00
import escapeHtml from 'escape-html';
import debounce from 'lodash-es/debounce';
2020-08-16 20:24:45 +02:00
import { playbackManager } from '../../../components/playback/playbackmanager';
2021-03-29 15:05:31 +03:00
import browser from '../../../scripts/browser';
2020-08-14 08:46:34 +02:00
import dom from '../../../scripts/dom';
import inputManager from '../../../scripts/inputManager';
import mouseManager from '../../../scripts/mouseManager';
import datetime from '../../../scripts/datetime';
import itemHelper from '../../../components/itemHelper';
import mediaInfo from '../../../components/mediainfo/mediainfo';
import focusManager from '../../../components/focusManager';
import Events from '../../../utils/events.ts';
2024-08-14 13:31:34 -04:00
import globalize from '../../../lib/globalize';
2020-08-16 20:24:45 +02:00
import { appHost } from '../../../components/apphost';
2020-08-14 08:46:34 +02:00
import layoutManager from '../../../components/layoutManager';
import * as userSettings from '../../../scripts/settings/userSettings';
import keyboardnavigation from '../../../scripts/keyboardNavigation';
import '../../../styles/scrollstyles.scss';
2020-08-14 08:46:34 +02:00
import '../../../elements/emby-slider/emby-slider';
import '../../../elements/emby-button/paper-icon-button-light';
import '../../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../../styles/videoosd.scss';
import ServerConnections from '../../../components/ServerConnections';
2020-10-18 13:53:12 +01:00
import shell from '../../../scripts/shell';
import SubtitleSync from '../../../components/subtitlesync/subtitlesync';
2023-05-01 10:04:13 -04:00
import { appRouter } from '../../../components/router/appRouter';
2022-03-31 14:36:09 -04:00
import LibraryMenu from '../../../scripts/libraryMenu';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
import { pluginManager } from '../../../components/pluginManager';
import { PluginType } from '../../../types/plugin.ts';
import { EventType } from 'types/eventType';
2024-10-19 20:08:27 +03:00
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
2024-10-24 12:37:38 +03:00
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
2023-04-19 01:56:05 -04:00
function getOpenedDialog() {
return document.querySelector('.dialogContainer .dialog.opened');
}
export default function (view) {
function getDisplayItem(item) {
if (item.Type === 'TvChannel') {
const apiClient = ServerConnections.getApiClient(item.ServerId);
return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) {
return {
originalItem: refreshedItem,
displayItem: refreshedItem.CurrentProgram
};
2019-02-02 13:51:03 -05:00
});
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return Promise.resolve({
originalItem: item
});
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function updateRecordingButton(item) {
if (!item || item.Type !== 'Program') {
if (recordingButtonManager) {
recordingButtonManager.destroy();
recordingButtonManager = null;
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
view.querySelector('.btnRecord').classList.add('hide');
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(function (user) {
if (user.Policy.EnableLiveTvManagement) {
import('../../../components/recordingcreator/recordingbutton').then(({ default: RecordingButton }) => {
if (recordingButtonManager) {
recordingButtonManager.refreshItem(item);
return;
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
recordingButtonManager = new RecordingButton({
item: item,
button: view.querySelector('.btnRecord')
});
view.querySelector('.btnRecord').classList.remove('hide');
});
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
});
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function updateDisplayItem(itemInfo) {
const item = itemInfo.originalItem;
currentItem = item;
const displayItem = itemInfo.displayItem || item;
updateRecordingButton(displayItem);
let parentName = displayItem.SeriesName || displayItem.Album;
if (displayItem.EpisodeTitle || displayItem.IsSeries) {
parentName = displayItem.Name;
}
setTitle(displayItem, parentName);
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
const secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo');
const secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, {
startDate: false,
programTime: false
});
secondaryMediaInfo.innerHTML = secondaryMediaInfoHtml;
if (secondaryMediaInfoHtml) {
secondaryMediaInfo.classList.remove('hide');
} else {
secondaryMediaInfo.classList.add('hide');
}
if (enableProgressByTimeOfDay) {
setDisplayTime(startTimeText, displayItem.StartDate);
setDisplayTime(endTimeText, displayItem.EndDate);
startTimeText.classList.remove('hide');
endTimeText.classList.remove('hide');
programStartDateMs = displayItem.StartDate ? datetime.parseISO8601Date(displayItem.StartDate).getTime() : 0;
programEndDateMs = displayItem.EndDate ? datetime.parseISO8601Date(displayItem.EndDate).getTime() : 0;
} else {
startTimeText.classList.add('hide');
endTimeText.classList.add('hide');
startTimeText.innerHTML = '';
endTimeText.innerHTML = '';
programStartDateMs = 0;
programEndDateMs = 0;
2019-02-02 13:51:03 -05:00
}
// Set currently playing item for favorite button
const btnUserRating = view.querySelector('.btnUserRating');
if (itemHelper.canRate(currentItem)) {
btnUserRating.classList.remove('hide');
btnUserRating.setItem(currentItem);
} else {
btnUserRating.classList.add('hide');
btnUserRating.setItem(null);
}
// Update trickplay data
trickplayResolution = null;
const mediaSourceId = currentPlayer.streamInfo.mediaSource.Id;
2024-03-23 09:31:07 -07:00
const trickplayResolutions = item.Trickplay?.[mediaSourceId];
if (trickplayResolutions) {
// Prefer highest resolution <= 20% of total screen resolution width
let bestWidth;
const maxWidth = window.screen.width * window.devicePixelRatio * 0.2;
for (const [, info] of Object.entries(trickplayResolutions)) {
if (!bestWidth
|| (info.Width < bestWidth && bestWidth > maxWidth) // Objects not guaranteed to be sorted in any order, first width might be > maxWidth.
2024-03-23 09:31:07 -07:00
|| (info.Width > bestWidth && info.Width <= maxWidth)) {
bestWidth = info.Width;
}
}
if (bestWidth) trickplayResolution = trickplayResolutions[bestWidth];
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function getDisplayTimeWithoutAmPm(date, showSeconds) {
if (showSeconds) {
return datetime.toLocaleTimeString(date, {
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
}).toLowerCase().replace('am', '').replace('pm', '').trim();
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return datetime.getDisplayTime(date).toLowerCase().replace('am', '').replace('pm', '').trim();
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function setDisplayTime(elem, date) {
let html;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (date) {
date = datetime.parseISO8601Date(date);
html = getDisplayTimeWithoutAmPm(date);
2018-10-23 01:05:09 +03:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
elem.innerHTML = html || '';
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function shouldEnableProgressByTimeOfDay(item) {
return !(item.Type !== 'TvChannel' || !item.CurrentProgram);
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function updateNowPlayingInfo(player, state) {
const item = state.NowPlayingItem;
currentItem = item;
if (!item) {
updateRecordingButton(null);
LibraryMenu.setTitle('');
nowPlayingVolumeSlider.disabled = true;
nowPlayingPositionSlider.disabled = true;
btnFastForward.disabled = true;
btnRewind.disabled = true;
view.querySelector('.btnSubtitles').classList.add('hide');
view.querySelector('.btnAudio').classList.add('hide');
view.querySelector('.osdTitle').innerHTML = '';
view.querySelector('.osdMediaInfo').innerHTML = '';
return;
}
enableProgressByTimeOfDay = shouldEnableProgressByTimeOfDay(item);
getDisplayItem(item).then(updateDisplayItem);
nowPlayingVolumeSlider.disabled = false;
nowPlayingPositionSlider.disabled = false;
btnFastForward.disabled = false;
btnRewind.disabled = false;
if (playbackManager.subtitleTracks(player).length) {
view.querySelector('.btnSubtitles').classList.remove('hide');
toggleSubtitleSync();
} else {
view.querySelector('.btnSubtitles').classList.add('hide');
toggleSubtitleSync('forceToHide');
}
if (playbackManager.audioTracks(player).length > 1) {
view.querySelector('.btnAudio').classList.remove('hide');
} else {
view.querySelector('.btnAudio').classList.add('hide');
}
if (currentItem.Chapters?.length > 1) {
view.querySelector('.btnPreviousChapter').classList.remove('hide');
view.querySelector('.btnNextChapter').classList.remove('hide');
} else {
view.querySelector('.btnPreviousChapter').classList.add('hide');
view.querySelector('.btnNextChapter').classList.add('hide');
}
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function setTitle(item, parentName) {
let itemName = itemHelper.getDisplayName(item, {
includeParentInfo: item.Type !== 'Program',
includeIndexNumber: item.Type !== 'Program'
});
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (itemName && parentName) {
itemName = `${parentName} - ${itemName}`;
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (!itemName) {
itemName = parentName || '';
}
2023-04-19 01:56:05 -04:00
// Display the item with its premiere date if it has one
let title = itemName;
if (item.Type == 'Movie' && item.ProductionYear) {
title += ` (${datetime.toLocaleString(item.ProductionYear, { useGrouping: false })})`;
} else if (item.PremiereDate) {
2023-04-19 01:56:05 -04:00
try {
const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false });
title += ` (${year})`;
} catch (e) {
console.error(e);
}
2018-10-23 01:05:09 +03:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
LibraryMenu.setTitle(title);
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
const documentTitle = parentName || (item ? item.Name : null);
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (documentTitle) {
document.title = documentTitle;
}
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
let mouseIsDown = false;
function showOsd(focusElement) {
Events.trigger(document, EventType.SHOW_VIDEO_OSD, [ true ]);
2023-04-19 01:56:05 -04:00
slideDownToShow(headerElement);
showMainOsdControls(focusElement);
2023-04-19 01:56:05 -04:00
resetIdle();
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function hideOsd() {
Events.trigger(document, EventType.SHOW_VIDEO_OSD, [ false ]);
2023-04-19 01:56:05 -04:00
slideUpToHide(headerElement);
hideMainOsdControls();
mouseManager.hideCursor();
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function toggleOsd() {
if (currentVisibleMenu === 'osd') {
hideOsd();
} else if (!currentVisibleMenu) {
showOsd();
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function startOsdHideTimer() {
stopOsdHideTimer();
osdHideTimeout = setTimeout(hideOsd, 3e3);
}
2023-04-19 01:56:05 -04:00
function stopOsdHideTimer() {
if (osdHideTimeout) {
clearTimeout(osdHideTimeout);
osdHideTimeout = null;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function slideDownToShow(elem) {
clearHideAnimationEventListeners(elem);
elem.classList.remove('hide');
2023-04-19 01:56:05 -04:00
elem.classList.remove('osdHeader-hidden');
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function slideUpToHide(elem) {
clearHideAnimationEventListeners(elem);
2023-04-19 01:56:05 -04:00
elem.classList.add('osdHeader-hidden');
elem.addEventListener(transitionEndEventName, onHideAnimationComplete);
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function clearHideAnimationEventListeners(elem) {
elem.removeEventListener(transitionEndEventName, onHideAnimationComplete);
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onHideAnimationComplete(e) {
const elem = e.target;
if (elem !== osdBottomElement && elem !== headerElement) return;
2023-04-19 01:56:05 -04:00
elem.classList.add('hide');
elem.removeEventListener(transitionEndEventName, onHideAnimationComplete);
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
const _focus = debounce((focusElement) => focusManager.focus(focusElement), 50);
function showMainOsdControls(focusElement) {
2023-04-19 01:56:05 -04:00
if (!currentVisibleMenu) {
const elem = osdBottomElement;
currentVisibleMenu = 'osd';
clearHideAnimationEventListeners(elem);
elem.classList.remove('hide');
elem.classList.remove('videoOsdBottom-hidden');
2019-02-02 10:20:27 -05:00
focusElement ||= elem.querySelector('.btnPause');
2023-04-19 01:56:05 -04:00
if (!layoutManager.mobile) {
_focus(focusElement);
2023-04-19 01:56:05 -04:00
}
toggleSubtitleSync();
} else if (currentVisibleMenu === 'osd' && !layoutManager.mobile) {
// If no focus element is provided, try to keep current focus if it's valid,
// otherwise default to pause button
if (!focusElement) {
const currentFocus = document.activeElement;
if (!currentFocus || !focusManager.isCurrentlyFocusable(currentFocus)) {
focusElement = osdBottomElement.querySelector('.btnPause');
}
}
if (focusElement) _focus(focusElement);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function hideMainOsdControls() {
if (currentVisibleMenu === 'osd') {
const elem = osdBottomElement;
clearHideAnimationEventListeners(elem);
elem.classList.add('videoOsdBottom-hidden');
2019-02-02 13:51:03 -05:00
elem.addEventListener(transitionEndEventName, onHideAnimationComplete);
2023-04-19 01:56:05 -04:00
currentVisibleMenu = null;
toggleSubtitleSync('hide');
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
// Firefox does not blur by itself
if (osdBottomElement.contains(document.activeElement)
|| headerElement.contains(document.activeElement)) {
2023-04-19 01:56:05 -04:00
document.activeElement.blur();
2018-10-23 01:05:09 +03:00
}
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
// TODO: Move all idle-related code to `inputManager` or `idleManager` or `idleHelper` (per dialog thing) and listen event from there.
2023-04-19 01:56:05 -04:00
function resetIdle() {
// Restart hide timer if OSD is currently visible and there is no opened dialog
if (currentVisibleMenu && !mouseIsDown && !getOpenedDialog()) {
startOsdHideTimer();
} else {
stopOsdHideTimer();
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onPointerMove(e) {
if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') {
const eventX = e.screenX || e.clientX || 0;
const eventY = e.screenY || e.clientY || 0;
const obj = lastPointerMoveData;
2020-07-09 19:05:24 +03:00
2023-04-19 01:56:05 -04:00
if (!obj) {
lastPointerMoveData = {
x: eventX,
y: eventY
};
return;
2020-07-09 19:05:24 +03:00
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) {
return;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
obj.x = eventX;
obj.y = eventY;
showOsd();
}
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function onInputCommand(e) {
const player = currentPlayer;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
switch (e.detail.command) {
case 'left':
if (currentVisibleMenu === 'osd') {
showOsd();
2023-09-12 17:02:06 -04:00
} else if (!currentVisibleMenu) {
e.preventDefault();
playbackManager.rewind(player);
2023-04-19 01:56:05 -04:00
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
break;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
case 'right':
if (currentVisibleMenu === 'osd') {
showOsd();
2023-04-19 01:56:05 -04:00
} else if (!currentVisibleMenu) {
e.preventDefault();
playbackManager.fastForward(player);
}
break;
case 'pageup':
playbackManager.nextChapter(player);
break;
case 'pagedown':
playbackManager.previousChapter(player);
break;
case 'up':
case 'down':
case 'select':
case 'menu':
case 'info':
case 'play':
case 'playpause':
case 'pause':
case 'fastforward':
case 'rewind':
case 'next':
case 'previous':
showOsd();
break;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
case 'record':
onRecordingCommand();
showOsd();
break;
2021-12-05 19:04:43 +03:00
2023-04-19 01:56:05 -04:00
case 'togglestats':
toggleStats();
break;
case 'back':
// Ignore command when some dialog is opened
if (currentVisibleMenu === 'osd' && !getOpenedDialog()) {
hideOsd();
e.preventDefault();
}
break;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onRecordingCommand() {
const btnRecord = view.querySelector('.btnRecord');
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (!btnRecord.classList.contains('hide')) {
btnRecord.click();
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onFullscreenChanged() {
if (currentPlayer.forcedFullscreen && !playbackManager.isFullscreen(currentPlayer)) {
appRouter.back();
return;
}
2023-04-19 01:56:05 -04:00
updateFullscreenIcon();
}
2020-05-02 14:02:44 +03:00
2023-04-19 01:56:05 -04:00
function updateFullscreenIcon() {
const button = view.querySelector('.btnFullscreen');
const icon = button.querySelector('.material-icons');
2020-05-02 14:02:44 +03:00
2023-04-19 01:56:05 -04:00
icon.classList.remove('fullscreen_exit', 'fullscreen');
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (playbackManager.isFullscreen(currentPlayer)) {
button.setAttribute('title', globalize.translate('ExitFullscreen') + ' (F)');
2023-04-19 01:56:05 -04:00
icon.classList.add('fullscreen_exit');
} else {
button.setAttribute('title', globalize.translate('Fullscreen') + ' (F)');
2023-04-19 01:56:05 -04:00
icon.classList.add('fullscreen');
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onPlayerChange() {
bindToPlayer(playbackManager.getCurrentPlayer());
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onStateChanged(event, state) {
const player = this;
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (state.NowPlayingItem) {
isEnabled = true;
updatePlayerStateInternal(event, player, state);
updatePlaylist();
enableStopOnBack(true);
updatePlaybackRate(player);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onPlayPauseStateChanged() {
if (isEnabled) {
updatePlayPauseState(this.paused());
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onVolumeChanged() {
if (isEnabled) {
const player = this;
2023-04-19 01:56:05 -04:00
updatePlayerVolumeState(player, player.isMuted(), player.getVolume());
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onPlaybackStart(e, state) {
console.debug('nowplaying event: ' + e.type);
const player = this;
onStateChanged.call(player, e, state);
resetUpNextDialog();
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function resetUpNextDialog() {
comingUpNextDisplayed = false;
const dlg = currentUpNextDialog;
if (dlg) {
dlg.destroy();
currentUpNextDialog = null;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onPlaybackStopped(e, state) {
currentRuntimeTicks = null;
resetUpNextDialog();
console.debug('nowplaying event: ' + e.type);
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (state.NextMediaType !== 'Video') {
view.removeEventListener('viewbeforehide', onViewHideStopPlayback);
appRouter.back();
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onMediaStreamsChanged() {
const player = this;
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function onBeginFetch() {
view.querySelector('.osdMediaStatus').classList.remove('hide');
2023-04-19 01:56:05 -04:00
}
2019-09-12 21:24:16 +02:00
2023-04-19 01:56:05 -04:00
function onEndFetch() {
view.querySelector('.osdMediaStatus').classList.add('hide');
2023-04-19 01:56:05 -04:00
}
function bindToPlayer(player) {
if (player !== currentPlayer) {
releaseCurrentPlayer();
currentPlayer = player;
if (!player) return;
}
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
Events.on(player, 'playbackstart', onPlaybackStart);
Events.on(player, 'playbackstop', onPlaybackStopped);
2024-10-24 12:37:38 +03:00
Events.on(player, PlayerEvent.PromptSkip, onPromptSkip);
2023-04-19 01:56:05 -04:00
Events.on(player, 'volumechange', onVolumeChanged);
Events.on(player, 'pause', onPlayPauseStateChanged);
Events.on(player, 'unpause', onPlayPauseStateChanged);
Events.on(player, 'timeupdate', onTimeUpdate);
Events.on(player, 'fullscreenchange', onFullscreenChanged);
Events.on(player, 'mediastreamschange', onMediaStreamsChanged);
Events.on(player, 'beginFetch', onBeginFetch);
Events.on(player, 'endFetch', onEndFetch);
resetUpNextDialog();
if (player.isFetching) {
onBeginFetch();
2019-09-12 21:24:16 +02:00
}
2023-04-19 01:56:05 -04:00
}
2019-09-12 21:24:16 +02:00
2023-04-19 01:56:05 -04:00
function releaseCurrentPlayer() {
destroyStats();
destroySubtitleSync();
resetUpNextDialog();
const player = currentPlayer;
if (player) {
Events.off(player, 'playbackstart', onPlaybackStart);
Events.off(player, 'playbackstop', onPlaybackStopped);
2024-10-24 12:37:38 +03:00
Events.off(player, PlayerEvent.PromptSkip, onPromptSkip);
2023-04-19 01:56:05 -04:00
Events.off(player, 'volumechange', onVolumeChanged);
Events.off(player, 'pause', onPlayPauseStateChanged);
Events.off(player, 'unpause', onPlayPauseStateChanged);
Events.off(player, 'timeupdate', onTimeUpdate);
Events.off(player, 'fullscreenchange', onFullscreenChanged);
Events.off(player, 'mediastreamschange', onMediaStreamsChanged);
currentPlayer = null;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function onTimeUpdate() {
// Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint
if (isEnabled && currentItem) {
const now = new Date().getTime();
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (now - lastUpdateTime >= 700) {
lastUpdateTime = now;
const player = this;
currentRuntimeTicks = playbackManager.duration(player);
const currentTime = playbackManager.currentTime(player) * 10000;
updateTimeDisplay(currentTime, currentRuntimeTicks, playbackManager.playbackStartTime(player), playbackManager.getPlaybackRate(player), playbackManager.getBufferedRanges(player));
const item = currentItem;
refreshProgramInfoIfNeeded(player, item);
showComingUpNextIfNeeded(player, item, currentTime, currentRuntimeTicks);
2019-02-02 13:51:03 -05:00
}
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2024-10-19 20:08:27 +03:00
function onPromptSkip(e, mediaSegment) {
const player = this;
if (mediaSegment && player && mediaSegment.EndTicks != null
&& mediaSegment.EndTicks >= playbackManager.duration(player)
&& playbackManager.getNextItem()
&& userSettings.enableNextVideoInfoOverlay()
2024-10-19 20:08:27 +03:00
) {
showComingUpNext(player);
}
}
2023-04-19 01:56:05 -04:00
function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) {
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) {
let showAtSecondsLeft = 30;
if (runtimeTicks >= 50 * TICKS_PER_MINUTE) {
showAtSecondsLeft = 40;
} else if (runtimeTicks >= 40 * TICKS_PER_MINUTE) {
showAtSecondsLeft = 35;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
const showAtTicks = runtimeTicks - showAtSecondsLeft * TICKS_PER_SECOND;
const timeRemainingTicks = runtimeTicks - currentTimeTicks;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (currentTimeTicks >= showAtTicks && runtimeTicks >= (10 * TICKS_PER_MINUTE) && timeRemainingTicks >= (20 * TICKS_PER_SECOND)) {
showComingUpNext(player);
2019-02-02 13:51:03 -05:00
}
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function onUpNextHidden() {
if (currentVisibleMenu === 'upnext') {
currentVisibleMenu = null;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function showComingUpNext(player) {
import('../../../components/upnextdialog/upnextdialog').then(({ default: UpNextDialog }) => {
if (!(currentVisibleMenu || currentUpNextDialog)) {
currentVisibleMenu = 'upnext';
comingUpNextDisplayed = true;
playbackManager.nextItem(player).then(function (nextItem) {
currentUpNextDialog = new UpNextDialog({
parent: view.querySelector('.upNextContainer'),
player: player,
nextItem: nextItem
});
Events.on(currentUpNextDialog, 'hide', onUpNextHidden);
}, onUpNextHidden);
}
});
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function refreshProgramInfoIfNeeded(player, item) {
if (item.Type === 'TvChannel') {
const program = item.CurrentProgram;
2019-02-02 13:51:03 -05:00
2023-07-06 13:39:48 -04:00
if (program?.EndDate) {
2023-04-19 01:56:05 -04:00
try {
const endDate = datetime.parseISO8601Date(program.EndDate);
if (new Date().getTime() >= endDate.getTime()) {
console.debug('program info needs to be refreshed');
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
} catch (e) {
console.error('error parsing date: ' + program.EndDate);
2019-02-02 13:51:03 -05:00
}
}
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function updatePlayPauseState(isPaused) {
const btnPlayPause = view.querySelector('.btnPause');
const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons');
2020-05-02 14:02:44 +03:00
2023-04-19 01:56:05 -04:00
btnPlayPauseIcon.classList.remove('play_arrow', 'pause');
2022-01-18 14:52:53 +03:00
2023-04-19 01:56:05 -04:00
let icon;
let title;
2022-01-18 14:52:53 +03:00
2023-04-19 01:56:05 -04:00
if (isPaused) {
icon = 'play_arrow';
title = globalize.translate('Play');
} else {
icon = 'pause';
title = globalize.translate('ButtonPause');
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
btnPlayPauseIcon.classList.add(icon);
dom.setElementTitle(btnPlayPause, title + ' (K)', title);
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function updatePlayerStateInternal(event, player, state) {
const playState = state.PlayState || {};
updatePlayPauseState(playState.IsPaused);
const supportedCommands = playbackManager.getSupportedCommands(player);
currentPlayerSupportedCommands = supportedCommands;
updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel);
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
nowPlayingPositionSlider.disabled = !playState.CanSeek;
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
btnFastForward.disabled = !playState.CanSeek;
btnRewind.disabled = !playState.CanSeek;
const nowPlayingItem = state.NowPlayingItem || {};
playbackStartTimeTicks = playState.PlaybackStartTimeTicks;
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []);
updateNowPlayingInfo(player, state);
2023-04-19 01:56:05 -04:00
const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null;
nowPlayingPositionSlider.setIsClear(isProgressClear);
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (nowPlayingItem.RunTimeTicks) {
nowPlayingPositionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / nowPlayingItem.RunTimeTicks,
userSettings.skipForwardLength() * 1000000 / nowPlayingItem.RunTimeTicks);
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (supportedCommands.indexOf('ToggleFullscreen') === -1 || player.isLocalPlayer && layoutManager.tv && playbackManager.isFullscreen(player)) {
view.querySelector('.btnFullscreen').classList.add('hide');
} else {
view.querySelector('.btnFullscreen').classList.remove('hide');
}
2020-01-10 11:31:03 -05:00
2023-04-19 01:56:05 -04:00
if (supportedCommands.indexOf('PictureInPicture') === -1) {
view.querySelector('.btnPip').classList.add('hide');
} else {
view.querySelector('.btnPip').classList.remove('hide');
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
if (supportedCommands.indexOf('AirPlay') === -1) {
view.querySelector('.btnAirPlay').classList.add('hide');
} else {
view.querySelector('.btnAirPlay').classList.remove('hide');
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
onFullscreenChanged();
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs) {
return (currentTimeMs - programStartDateMs) / programRuntimeMs * 100;
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function updateTimeDisplay(positionTicks, runtimeTicks, playbackStartTimeTicks, playbackRate, bufferedRanges) {
if (enableProgressByTimeOfDay) {
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
if (programStartDateMs && programEndDateMs) {
const currentTimeMs = (playbackStartTimeTicks + (positionTicks || 0)) / 1e4;
const programRuntimeMs = programEndDateMs - programStartDateMs;
nowPlayingPositionSlider.value = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs);
if (bufferedRanges.length) {
const rangeStart = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].start || 0)) / 1e4);
const rangeEnd = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].end || 0)) / 1e4);
nowPlayingPositionSlider.setBufferedRanges([{
start: rangeStart,
end: rangeEnd
}]);
2019-02-02 13:51:03 -05:00
} else {
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.setBufferedRanges([]);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
} else {
nowPlayingPositionSlider.value = 0;
nowPlayingPositionSlider.setBufferedRanges([]);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
nowPlayingPositionText.innerHTML = '';
nowPlayingDurationText.innerHTML = '';
} else {
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
if (runtimeTicks) {
let pct = positionTicks / runtimeTicks;
pct *= 100;
nowPlayingPositionSlider.value = pct;
2020-11-17 22:34:02 +08:00
} else {
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.value = 0;
2020-11-17 22:34:02 +08:00
}
2023-04-19 01:56:05 -04:00
if (runtimeTicks && positionTicks != null && currentRuntimeTicks && !enableProgressByTimeOfDay && currentItem.RunTimeTicks && currentItem.Type !== 'Recording' && playbackRate !== null) {
endsAtText.innerHTML = '&nbsp;&nbsp;&nbsp;&nbsp;' + mediaInfo.getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, true);
2020-11-17 22:34:02 +08:00
} else {
2023-04-19 01:56:05 -04:00
endsAtText.innerHTML = '';
2020-11-17 22:34:02 +08:00
}
2019-02-02 13:51:03 -05:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (nowPlayingPositionSlider) {
nowPlayingPositionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
if (positionTicks >= 0) {
updateTimeText(nowPlayingPositionText, positionTicks);
nowPlayingPositionText.classList.remove('hide');
} else {
nowPlayingPositionText.classList.add('hide');
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (userSettings.enableVideoRemainingTime()) {
const leftTicks = runtimeTicks - positionTicks;
if (leftTicks >= 0) {
updateTimeText(nowPlayingDurationText, leftTicks);
nowPlayingDurationText.innerHTML = '-' + nowPlayingDurationText.innerHTML;
nowPlayingDurationText.classList.remove('hide');
} else {
nowPlayingPositionText.classList.add('hide');
}
} else {
updateTimeText(nowPlayingDurationText, runtimeTicks);
nowPlayingDurationText.classList.remove('hide');
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function updatePlayerVolumeState(player, isMuted, volumeLevel) {
const supportedCommands = currentPlayerSupportedCommands;
let showMuteButton = true;
let showVolumeSlider = true;
2020-05-02 14:02:44 +03:00
2023-04-19 01:56:05 -04:00
if (supportedCommands.indexOf('Mute') === -1) {
showMuteButton = false;
}
2020-05-02 14:02:44 +03:00
2023-04-19 01:56:05 -04:00
if (supportedCommands.indexOf('SetVolume') === -1) {
showVolumeSlider = false;
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
showMuteButton = false;
showVolumeSlider = false;
2018-10-23 01:05:09 +03:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
const buttonMute = view.querySelector('.buttonMute');
const buttonMuteIcon = buttonMute.querySelector('.material-icons');
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
buttonMuteIcon.classList.remove('volume_off', 'volume_up');
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (isMuted) {
buttonMute.setAttribute('title', globalize.translate('Unmute') + ' (M)');
2023-04-19 01:56:05 -04:00
buttonMuteIcon.classList.add('volume_off');
} else {
buttonMute.setAttribute('title', globalize.translate('Mute') + ' (M)');
2023-04-19 01:56:05 -04:00
buttonMuteIcon.classList.add('volume_up');
2019-02-02 13:51:03 -05:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (showMuteButton) {
buttonMute.classList.remove('hide');
} else {
buttonMute.classList.add('hide');
}
2023-04-19 01:56:05 -04:00
if (nowPlayingVolumeSlider) {
if (showVolumeSlider) {
nowPlayingVolumeSliderContainer.classList.remove('hide');
} else {
nowPlayingVolumeSliderContainer.classList.add('hide');
}
2023-04-19 01:56:05 -04:00
if (!nowPlayingVolumeSlider.dragging) {
nowPlayingVolumeSlider.value = volumeLevel || 0;
}
}
}
2024-10-22 16:34:25 -04:00
async function updatePlaylist() {
try {
const playlist = await playbackManager.getPlaylist();
if (playlist && playlist.length > 1) {
const btnPreviousTrack = view.querySelector('.btnPreviousTrack');
const btnNextTrack = view.querySelector('.btnNextTrack');
btnPreviousTrack.classList.remove('hide');
btnNextTrack.classList.remove('hide');
btnPreviousTrack.disabled = false;
btnNextTrack.disabled = false;
}
} catch (err) {
console.error('[VideoPlayer] failed to get playlist', err);
}
2023-04-19 01:56:05 -04:00
}
2020-07-10 15:15:46 +03:00
2023-04-19 01:56:05 -04:00
function updateTimeText(elem, ticks, divider) {
if (ticks == null) {
elem.innerHTML = '';
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
let html = datetime.getDisplayRunningTime(ticks);
if (divider) {
html = '&nbsp;/&nbsp;' + html;
2019-02-02 13:51:03 -05:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
elem.innerHTML = html;
}
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
function nowPlayingDurationTextClick() {
userSettings.enableVideoRemainingTime(!userSettings.enableVideoRemainingTime());
// immediately update the text, without waiting for the next tick update or if the player is paused
const state = playbackManager.getPlayerState(currentPlayer);
const playState = state.PlayState;
const nowPlayingItem = state.NowPlayingItem;
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []);
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function onSettingsButtonClick() {
const btn = this;
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
import('../../../components/playback/playersettingsmenu').then((playerSettingsMenu) => {
const player = currentPlayer;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (player) {
const state = playbackManager.getPlayerState(player);
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
// show subtitle offset feature only if player and media support it
const showSubOffset = playbackManager.supportSubtitleOffset(player)
&& playbackManager.canHandleOffsetOnCurrentSubtitle(player);
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
playerSettingsMenu.show({
mediaType: 'Video',
player: player,
positionTo: btn,
quality: state.MediaSource?.SupportsTranscoding,
stats: true,
suboffset: showSubOffset,
onOption: onSettingsOption
2020-07-10 15:15:46 +03:00
}).finally(() => {
resetIdle();
2019-02-02 13:51:03 -05:00
});
2020-07-10 15:15:46 +03:00
setTimeout(resetIdle, 0);
2023-04-19 01:56:05 -04:00
}
});
}
function onSettingsOption(selectedOption) {
if (selectedOption === 'stats') {
toggleStats();
} else if (selectedOption === 'suboffset') {
const player = currentPlayer;
if (player) {
playbackManager.enableShowingSubtitleOffset(player);
toggleSubtitleSync();
}
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function toggleStats() {
import('../../../components/playerstats/playerstats').then(({ default: PlayerStats }) => {
const player = currentPlayer;
2023-04-19 01:56:05 -04:00
if (player) {
if (statsOverlay) {
statsOverlay.toggle();
} else {
statsOverlay = new PlayerStats({
player: player
});
}
}
2023-04-19 01:56:05 -04:00
});
}
2023-04-19 01:56:05 -04:00
function destroyStats() {
if (statsOverlay) {
statsOverlay.destroy();
statsOverlay = null;
}
}
2023-04-19 01:56:05 -04:00
function showAudioTrackSelection() {
const player = currentPlayer;
const audioTracks = playbackManager.audioTracks(player);
const currentIndex = playbackManager.getAudioStreamIndex(player);
const menuItems = audioTracks.map(function (stream) {
const opt = {
name: stream.DisplayTitle,
id: stream.Index
};
2023-04-19 01:56:05 -04:00
if (stream.Index === currentIndex) {
opt.selected = true;
}
2023-04-19 01:56:05 -04:00
return opt;
});
const positionTo = this;
2023-04-19 01:56:05 -04:00
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
actionsheet.show({
items: menuItems,
2023-04-19 01:56:05 -04:00
title: globalize.translate('Audio'),
positionTo: positionTo
}).then(function (id) {
2023-04-19 01:56:05 -04:00
const index = parseInt(id, 10);
if (index !== currentIndex) {
playbackManager.setAudioStreamIndex(index, player);
}
2023-04-19 01:56:05 -04:00
}).finally(() => {
resetIdle();
});
setTimeout(resetIdle, 0);
2023-04-19 01:56:05 -04:00
});
}
function showSecondarySubtitlesMenu(actionsheet, positionTo) {
const player = currentPlayer;
if (!playbackManager.playerHasSecondarySubtitleSupport(player)) return;
let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player);
const streams = playbackManager.secondarySubtitleTracks(player);
if (currentIndex == null) {
currentIndex = -1;
}
2023-04-19 01:56:05 -04:00
streams.unshift({
Index: -1,
DisplayTitle: globalize.translate('Off')
});
const menuItems = streams.map(function (stream) {
const opt = {
name: stream.DisplayTitle,
id: stream.Index
};
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (stream.Index === currentIndex) {
opt.selected = true;
2019-02-02 13:51:03 -05:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
return opt;
});
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
actionsheet.show({
title: globalize.translate('SecondarySubtitles'),
items: menuItems,
positionTo
}).then(function (id) {
if (id) {
const index = parseInt(id, 10);
if (index !== currentIndex) {
playbackManager.setSecondarySubtitleStreamIndex(index, player);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
})
.finally(() => {
resetIdle();
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
setTimeout(resetIdle, 0);
}
function showSubtitleTrackSelection() {
const player = currentPlayer;
const streams = playbackManager.subtitleTracks(player);
const secondaryStreams = playbackManager.secondarySubtitleTracks(player);
let currentIndex = playbackManager.getSubtitleStreamIndex(player);
if (currentIndex == null) {
currentIndex = -1;
}
streams.unshift({
Index: -1,
DisplayTitle: globalize.translate('Off')
});
const menuItems = streams.map(function (stream) {
const opt = {
name: stream.DisplayTitle,
id: stream.Index
};
if (stream.Index === currentIndex) {
opt.selected = true;
}
return opt;
});
/**
2022-11-13 20:40:02 -05:00
* Only show option if:
* - player has support
* - has more than 1 subtitle track
* - has valid secondary tracks
* - primary subtitle is not off
* - primary subtitle has support
2022-11-13 20:40:02 -05:00
*/
2023-04-19 01:56:05 -04:00
const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player)
&& streams.length > 1
&& secondaryStreams.length > 0
&& currentIndex !== -1
&& playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player);
2022-11-13 20:40:02 -05:00
2023-04-19 01:56:05 -04:00
if (currentTrackCanAddSecondarySubtitle) {
const secondarySubtitleMenuItem = {
name: globalize.translate('SecondarySubtitles'),
id: 'secondarysubtitle'
};
menuItems.unshift(secondarySubtitleMenuItem);
}
2023-04-19 01:56:05 -04:00
const positionTo = this;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
actionsheet.show({
title: globalize.translate('Subtitles'),
items: menuItems,
positionTo: positionTo
}).then(function (id) {
if (id === 'secondarysubtitle') {
try {
showSecondarySubtitlesMenu(actionsheet, positionTo);
} catch (e) {
console.error(e);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
} else {
const index = parseInt(id, 10);
2023-04-19 01:56:05 -04:00
if (index !== currentIndex) {
playbackManager.setSubtitleStreamIndex(index, player);
}
}
2020-07-10 15:15:46 +03:00
2023-04-19 01:56:05 -04:00
toggleSubtitleSync();
}).finally(() => {
resetIdle();
2019-02-02 13:51:03 -05:00
});
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
setTimeout(resetIdle, 0);
});
}
function toggleSubtitleSync(action) {
const player = currentPlayer;
if (subtitleSyncOverlay) {
subtitleSyncOverlay.toggle(action);
} else if (player) {
subtitleSyncOverlay = new SubtitleSync(player);
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
function destroySubtitleSync() {
if (subtitleSyncOverlay) {
subtitleSyncOverlay.destroy();
subtitleSyncOverlay = null;
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
/**
* Clicked element.
* To skip 'click' handling on Firefox/Edge.
*/
2023-04-19 01:56:05 -04:00
let clickedElement;
2023-04-19 01:56:05 -04:00
function onClickCapture(e) {
// Firefox/Edge emits `click` even if `preventDefault` was used on `keydown`
// Ignore 'click' if another element was originally clicked
if (!e.target.contains(clickedElement)) {
e.preventDefault();
e.stopPropagation();
return false;
}
}
function onKeyDown(e) {
clickedElement = e.target;
const isKeyModified = e.ctrlKey || e.altKey || e.metaKey;
2024-09-10 03:53:43 +03:00
// Skip modified keys
if (isKeyModified) return;
const key = keyboardnavigation.getKeyName(e);
const btnPlayPause = osdBottomElement.querySelector('.btnPause');
2023-04-19 01:56:05 -04:00
if (e.keyCode === 32) {
if (e.target.tagName !== 'BUTTON' || !layoutManager.tv) {
playbackManager.playPause(currentPlayer);
showOsd(btnPlayPause);
2021-03-29 15:05:31 +03:00
e.preventDefault();
e.stopPropagation();
2023-04-19 01:56:05 -04:00
// Trick Firefox with a null element to skip next click
clickedElement = null;
} else {
showOsd();
2021-03-29 15:05:31 +03:00
}
2023-04-19 01:56:05 -04:00
return;
2021-03-29 15:05:31 +03:00
}
if (layoutManager.tv && !currentVisibleMenu) {
// Change the behavior of some keys when the OSD is hidden
switch (key) {
case 'ArrowLeft':
case 'ArrowRight':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
showOsd(nowPlayingPositionSlider);
nowPlayingPositionSlider.dispatchEvent(new KeyboardEvent(e.type, e));
}
return;
case 'Enter':
if (e.target.tagName !== 'BUTTON') {
playbackManager.playPause(currentPlayer);
showOsd(btnPlayPause);
}
return;
}
}
2023-04-19 01:56:05 -04:00
if (layoutManager.tv && keyboardnavigation.isNavigationKey(key)) {
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) showOsd();
2023-04-19 01:56:05 -04:00
return;
}
2023-04-19 01:56:05 -04:00
switch (key) {
case 'Enter':
showOsd();
break;
case 'Escape':
case 'Back':
// Ignore key when some dialog is opened
if (currentVisibleMenu === 'osd' && !getOpenedDialog()) {
hideOsd();
2021-03-29 15:05:31 +03:00
e.stopPropagation();
}
2023-04-19 01:56:05 -04:00
break;
case 'k':
case 'K':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.playPause(currentPlayer);
showOsd(btnPlayPause);
}
2023-04-19 01:56:05 -04:00
break;
case 'ArrowUp':
case 'Up':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.volumeUp(currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case 'ArrowDown':
case 'Down':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.volumeDown(currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case 'l':
case 'L':
2023-04-19 01:56:05 -04:00
case 'ArrowRight':
case 'Right':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.fastForward(currentPlayer);
showOsd(btnFastForward);
}
2023-04-19 01:56:05 -04:00
break;
case 'j':
case 'J':
2023-04-19 01:56:05 -04:00
case 'ArrowLeft':
case 'Left':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.rewind(currentPlayer);
showOsd(btnRewind);
}
2023-04-19 01:56:05 -04:00
break;
case 'f':
case 'F':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
2023-12-05 23:20:35 -05:00
e.preventDefault();
2023-04-19 01:56:05 -04:00
playbackManager.toggleFullscreen(currentPlayer);
}
break;
case 'm':
case 'M':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.toggleMute(currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case 'p':
case 'P':
if (e.shiftKey) {
2023-12-05 23:20:35 -05:00
e.preventDefault();
2023-04-19 01:56:05 -04:00
playbackManager.previousTrack(currentPlayer);
}
break;
case 'n':
case 'N':
if (e.shiftKey) {
2023-12-05 23:20:35 -05:00
e.preventDefault();
2023-04-19 01:56:05 -04:00
playbackManager.nextTrack(currentPlayer);
}
break;
case 'NavigationLeft':
case 'GamepadDPadLeft':
case 'GamepadLeftThumbstickLeft':
// Ignores gamepad events that are always triggered, even when not focused.
if (document.hasFocus()) { /* eslint-disable-line compat/compat */
playbackManager.rewind(currentPlayer);
showOsd(btnRewind);
2023-04-19 01:56:05 -04:00
}
break;
case 'NavigationRight':
case 'GamepadDPadRight':
case 'GamepadLeftThumbstickRight':
// Ignores gamepad events that are always triggered, even when not focused.
if (document.hasFocus()) { /* eslint-disable-line compat/compat */
playbackManager.fastForward(currentPlayer);
showOsd(btnFastForward);
}
2023-04-19 01:56:05 -04:00
break;
case 'Home':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.seekPercent(0, currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case 'End':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.seekPercent(100, currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
2024-09-10 03:53:43 +03:00
case '9': { // no Shift
e.preventDefault();
const percent = parseInt(key, 10) * 10;
playbackManager.seekPercent(percent, currentPlayer);
2023-04-19 01:56:05 -04:00
break;
}
2024-09-10 03:53:43 +03:00
case '>': // Shift+.
2023-12-05 23:20:35 -05:00
e.preventDefault();
2023-04-19 01:56:05 -04:00
playbackManager.increasePlaybackRate(currentPlayer);
break;
2024-09-10 03:53:43 +03:00
case '<': // Shift+,
2023-12-05 23:20:35 -05:00
e.preventDefault();
2023-04-19 01:56:05 -04:00
playbackManager.decreasePlaybackRate(currentPlayer);
break;
case 'PageUp':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.nextChapter(currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
case 'PageDown':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
playbackManager.previousChapter(currentPlayer);
}
2023-04-19 01:56:05 -04:00
break;
2024-02-09 22:31:12 +01:00
case 'g':
case 'G':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
subtitleSyncOverlay?.decrementOffset();
}
2024-02-09 22:31:12 +01:00
break;
case 'h':
case 'H':
2024-09-10 03:53:43 +03:00
if (!e.shiftKey) {
e.preventDefault();
subtitleSyncOverlay?.incrementOffset();
}
2024-02-09 22:31:12 +01:00
break;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function onKeyDownCapture() {
resetIdle();
}
2020-06-28 23:46:27 +03:00
2023-04-19 01:56:05 -04:00
function onWheel(e) {
if (getOpenedDialog()) return;
2023-04-19 01:56:05 -04:00
if (e.deltaY < 0) {
playbackManager.volumeUp(currentPlayer);
2023-03-17 10:52:38 +13:00
}
2023-04-19 01:56:05 -04:00
if (e.deltaY > 0) {
playbackManager.volumeDown(currentPlayer);
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
function onWindowMouseDown(e) {
clickedElement = e.target;
mouseIsDown = true;
resetIdle();
}
2023-04-19 01:56:05 -04:00
function onWindowMouseUp() {
mouseIsDown = false;
resetIdle();
}
2023-04-19 01:56:05 -04:00
function onWindowDragEnd() {
// mousedown -> dragstart -> dragend !!! no mouseup :(
mouseIsDown = false;
resetIdle();
}
2019-02-02 10:20:27 -05:00
2024-02-11 20:34:30 -08:00
function updateTrickplayBubbleHtml(apiClient, trickplayInfo, item, mediaSourceId, bubble, positionTicks) {
let doFullUpdate = false;
let chapterThumbContainer = bubble.querySelector('.chapterThumbContainer');
let chapterThumb;
let chapterThumbText;
let chapterThumbName;
2024-02-11 20:34:30 -08:00
// Create bubble elements if they don't already exist
if (chapterThumbContainer) {
chapterThumb = chapterThumbContainer.querySelector('.chapterThumbWrapper');
chapterThumbText = chapterThumbContainer.querySelector('h2.chapterThumbText');
chapterThumbName = chapterThumbContainer.querySelector('div.chapterThumbText');
2024-02-11 20:34:30 -08:00
} else {
doFullUpdate = true;
chapterThumbContainer = document.createElement('div');
chapterThumbContainer.classList.add('chapterThumbContainer');
chapterThumbContainer.style.overflow = 'hidden';
chapterThumb = document.createElement('div');
chapterThumb.classList.add('chapterThumbWrapper');
chapterThumb.style.overflow = 'hidden';
chapterThumb.style.width = trickplayInfo.Width + 'px';
chapterThumb.style.height = trickplayInfo.Height + 'px';
chapterThumbContainer.appendChild(chapterThumb);
2024-02-11 20:34:30 -08:00
const chapterThumbTextContainer = document.createElement('div');
chapterThumbTextContainer.classList.add('chapterThumbTextContainer');
chapterThumbContainer.appendChild(chapterThumbTextContainer);
chapterThumbName = document.createElement('div');
chapterThumbName.classList.add('chapterThumbText', 'chapterThumbText-dim');
chapterThumbTextContainer.appendChild(chapterThumbName);
2024-02-11 20:34:30 -08:00
chapterThumbText = document.createElement('h2');
chapterThumbText.classList.add('chapterThumbText');
chapterThumbTextContainer.appendChild(chapterThumbText);
}
let chapter;
for (const currentChapter of item.Chapters || []) {
if (positionTicks < currentChapter.StartPositionTicks) {
break;
}
chapter = currentChapter;
}
2024-02-11 20:34:30 -08:00
// Update trickplay values
const currentTimeMs = positionTicks / 10_000;
const currentTile = Math.floor(currentTimeMs / trickplayInfo.Interval);
const tileSize = trickplayInfo.TileWidth * trickplayInfo.TileHeight;
const tileOffset = currentTile % tileSize;
const index = Math.floor(currentTile / tileSize);
const tileOffsetX = tileOffset % trickplayInfo.TileWidth;
const tileOffsetY = Math.floor(tileOffset / trickplayInfo.TileWidth);
const offsetX = -(tileOffsetX * trickplayInfo.Width);
const offsetY = -(tileOffsetY * trickplayInfo.Height);
const imgSrc = apiClient.getUrl('Videos/' + item.Id + '/Trickplay/' + trickplayInfo.Width + '/' + index + '.jpg', {
api_key: apiClient.accessToken(),
MediaSourceId: mediaSourceId
});
chapterThumb.style.backgroundImage = `url('${imgSrc}')`;
chapterThumb.style.backgroundPositionX = offsetX + 'px';
chapterThumb.style.backgroundPositionY = offsetY + 'px';
2024-02-11 20:34:30 -08:00
chapterThumbText.textContent = datetime.getDisplayRunningTime(positionTicks);
chapterThumbName.textContent = chapter?.Name || '';
2024-02-11 20:34:30 -08:00
// Set bubble innerHTML if container isn't part of DOM
if (doFullUpdate) {
bubble.innerHTML = chapterThumbContainer.outerHTML;
}
return true;
}
2023-04-19 01:56:05 -04:00
function getImgUrl(item, chapter, index, maxWidth, apiClient) {
if (chapter.ImageTag) {
return apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth,
tag: chapter.ImageTag,
type: 'Chapter',
index: index
});
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return null;
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function getChapterBubbleHtml(apiClient, item, chapters, positionTicks) {
let chapter;
let index = -1;
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
for (let i = 0, length = chapters.length; i < length; i++) {
const currentChapter = chapters[i];
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (positionTicks >= currentChapter.StartPositionTicks) {
chapter = currentChapter;
index = i;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (!chapter) {
2019-02-02 13:51:03 -05:00
return null;
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
const src = getImgUrl(item, chapter, index, 400, apiClient);
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (src) {
let html = '<div class="chapterThumbContainer">';
html += '<img class="chapterThumb" src="' + src + '" />';
html += '<div class="chapterThumbTextContainer">';
html += '<div class="chapterThumbText chapterThumbText-dim">';
html += escapeHtml(chapter.Name);
html += '</div>';
html += '<h2 class="chapterThumbText">';
html += datetime.getDisplayRunningTime(positionTicks);
html += '</h2>';
html += '</div>';
return html + '</div>';
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return null;
}
let playPauseClickTimeout;
function onViewHideStopPlayback() {
if (playbackManager.isPlayingVideo()) {
shell.disableFullscreen();
clearTimeout(playPauseClickTimeout);
const player = currentPlayer;
2020-05-04 12:44:12 +02:00
view.removeEventListener('viewbeforehide', onViewHideStopPlayback);
2023-04-19 01:56:05 -04:00
releaseCurrentPlayer();
playbackManager.stop(player);
}
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function enableStopOnBack(enabled) {
view.removeEventListener('viewbeforehide', onViewHideStopPlayback);
if (enabled && playbackManager.isPlayingVideo(currentPlayer)) {
view.addEventListener('viewbeforehide', onViewHideStopPlayback);
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
function updatePlaybackRate(player) {
// Restore playback speed control, if it exists in the session.
const playbackRateSpeed = sessionStorage.getItem('playbackRateSpeed');
if (playbackRateSpeed !== null) {
player.setPlaybackRate(playbackRateSpeed);
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
shell.enableFullscreen();
let currentPlayer;
let comingUpNextDisplayed;
let currentUpNextDialog;
let isEnabled;
let currentItem;
let recordingButtonManager;
let enableProgressByTimeOfDay;
let currentVisibleMenu;
let statsOverlay;
let osdHideTimeout;
let lastPointerMoveData;
const self = this;
let currentPlayerSupportedCommands = [];
let currentRuntimeTicks = 0;
let lastUpdateTime = 0;
let programStartDateMs = 0;
let programEndDateMs = 0;
let playbackStartTimeTicks = 0;
let subtitleSyncOverlay;
let trickplayResolution = null;
2023-04-19 01:56:05 -04:00
const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider');
const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer');
const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider');
const nowPlayingPositionText = view.querySelector('.osdPositionText');
const nowPlayingDurationText = view.querySelector('.osdDurationText');
const startTimeText = view.querySelector('.startTimeText');
const endTimeText = view.querySelector('.endTimeText');
const endsAtText = view.querySelector('.endsAtText');
const btnRewind = view.querySelector('.btnRewind');
const btnFastForward = view.querySelector('.btnFastForward');
const transitionEndEventName = dom.whichTransitionEvent();
const headerElement = document.querySelector('.skinHeader');
const osdBottomElement = view.querySelector('.videoOsdBottom-maincontrols');
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.enableKeyboardDragging();
nowPlayingVolumeSlider.enableKeyboardDragging();
if (layoutManager.tv) {
nowPlayingPositionSlider.classList.add('focusable');
}
nowPlayingDurationText.addEventListener('click', nowPlayingDurationTextClick);
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
view.addEventListener('viewbeforeshow', function () {
headerElement.classList.add('osdHeader');
setBackdropTransparency(TRANSPARENCY_LEVEL.Full);
});
view.addEventListener('viewshow', function () {
try {
Events.on(playbackManager, 'playerchange', onPlayerChange);
bindToPlayer(playbackManager.getCurrentPlayer());
/* eslint-disable-next-line compat/compat */
dom.addEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, {
passive: true
});
showOsd();
inputManager.on(window, onInputCommand);
document.addEventListener('keydown', onKeyDown);
dom.addEventListener(document, 'keydown', onKeyDownCapture, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
document.addEventListener('wheel', onWheel);
2020-07-05 12:54:25 +02:00
/* eslint-disable-next-line compat/compat */
2023-04-19 01:56:05 -04:00
dom.addEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
});
2020-07-05 12:54:25 +02:00
/* eslint-disable-next-line compat/compat */
2023-04-19 01:56:05 -04:00
dom.addEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
});
2023-04-19 01:56:05 -04:00
dom.addEventListener(window, 'touchstart', onWindowMouseDown, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
});
['touchend', 'touchcancel'].forEach((event) => {
2023-04-19 01:56:05 -04:00
dom.addEventListener(window, event, onWindowMouseUp, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
});
});
2023-04-19 01:56:05 -04:00
dom.addEventListener(window, 'dragend', onWindowDragEnd, {
2020-07-09 19:05:24 +03:00
capture: true,
passive: true
});
2021-03-29 15:05:31 +03:00
if (browser.firefox || browser.edge) {
2023-04-19 01:56:05 -04:00
dom.addEventListener(document, 'click', onClickCapture, { capture: true });
2021-03-29 15:05:31 +03:00
}
2023-04-19 01:56:05 -04:00
} catch (e) {
setBackdropTransparency(TRANSPARENCY_LEVEL.None); // reset state set in viewbeforeshow
2023-04-19 01:56:05 -04:00
appRouter.goHome();
}
});
view.addEventListener('viewbeforehide', function () {
if (statsOverlay) {
statsOverlay.enabled(false);
}
document.removeEventListener('keydown', onKeyDown);
dom.removeEventListener(document, 'keydown', onKeyDownCapture, {
capture: true,
passive: true
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
document.removeEventListener('wheel', onWheel);
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, {
capture: true,
passive: true
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, {
capture: true,
passive: true
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
dom.removeEventListener(window, 'touchstart', onWindowMouseDown, {
capture: true,
passive: true
2020-01-10 11:31:03 -05:00
});
2023-04-19 01:56:05 -04:00
['touchend', 'touchcancel'].forEach((event) => {
dom.removeEventListener(window, event, onWindowMouseUp, {
capture: true,
passive: true
});
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
dom.removeEventListener(window, 'dragend', onWindowDragEnd, {
capture: true,
passive: true
2019-02-02 13:51:03 -05:00
});
2023-04-19 01:56:05 -04:00
if (browser.firefox || browser.edge) {
dom.removeEventListener(document, 'click', onClickCapture, { capture: true });
}
stopOsdHideTimer();
headerElement.classList.remove('osdHeader');
headerElement.classList.remove('osdHeader-hidden');
2020-07-05 12:54:25 +02:00
/* eslint-disable-next-line compat/compat */
2023-04-19 01:56:05 -04:00
dom.removeEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, {
passive: true
});
inputManager.off(window, onInputCommand);
Events.off(playbackManager, 'playerchange', onPlayerChange);
releaseCurrentPlayer();
});
view.querySelector('.btnFullscreen').addEventListener('click', function () {
playbackManager.toggleFullscreen(currentPlayer);
});
view.querySelector('.btnPip').addEventListener('click', function () {
playbackManager.togglePictureInPicture(currentPlayer);
});
view.querySelector('.btnAirPlay').addEventListener('click', function () {
playbackManager.toggleAirPlay(currentPlayer);
});
view.querySelector('.btnVideoOsdSettings').addEventListener('click', onSettingsButtonClick);
view.addEventListener('viewhide', function () {
headerElement.classList.remove('hide');
});
view.addEventListener('viewdestroy', function () {
if (self.touchHelper) {
self.touchHelper.destroy();
self.touchHelper = null;
}
if (recordingButtonManager) {
recordingButtonManager.destroy();
recordingButtonManager = null;
}
destroyStats();
destroySubtitleSync();
});
let lastPointerDown = 0;
/* eslint-disable-next-line compat/compat */
dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) {
if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) {
showOsd();
return;
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
const now = new Date().getTime();
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
switch (pointerType) {
case 'touch':
if (now - lastPointerDown > 300) {
lastPointerDown = now;
toggleOsd();
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
break;
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
case 'mouse':
if (!e.button) {
if (playPauseClickTimeout) {
clearTimeout(playPauseClickTimeout);
playPauseClickTimeout = 0;
} else {
playPauseClickTimeout = setTimeout(function() {
playbackManager.playPause(currentPlayer);
showOsd();
playPauseClickTimeout = 0;
2023-04-19 01:56:05 -04:00
}, 300);
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
break;
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
default:
playbackManager.playPause(currentPlayer);
showOsd();
}
}, {
passive: true
});
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
dom.addEventListener(view, 'dblclick', (e) => {
if (e.target !== view) return;
playbackManager.toggleFullscreen(currentPlayer);
});
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
view.querySelector('.buttonMute').addEventListener('click', function () {
playbackManager.toggleMute(currentPlayer);
});
2023-04-19 01:56:05 -04:00
nowPlayingVolumeSlider.addEventListener('input', (e) => {
playbackManager.setVolume(e.target.value, currentPlayer);
});
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.addEventListener('change', function () {
const player = currentPlayer;
2019-02-02 13:51:03 -05:00
2023-04-19 01:56:05 -04:00
if (player) {
const newPercent = parseFloat(this.value);
2019-02-02 10:20:27 -05:00
2019-02-12 10:01:11 -05:00
if (enableProgressByTimeOfDay) {
2023-04-19 01:56:05 -04:00
let seekAirTimeTicks = newPercent / 100 * (programEndDateMs - programStartDateMs) * 1e4;
seekAirTimeTicks += 1e4 * programStartDateMs;
seekAirTimeTicks -= playbackStartTimeTicks;
playbackManager.seek(seekAirTimeTicks, player);
} else {
playbackManager.seekPercent(newPercent, player);
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
});
2019-02-02 10:20:27 -05:00
nowPlayingPositionSlider.addEventListener('keydown', function (e) {
if (e.defaultPrevented) return;
const key = keyboardnavigation.getKeyName(e);
if (key === 'Enter') {
playbackManager.playPause(currentPlayer);
}
});
2024-02-11 20:34:30 -08:00
nowPlayingPositionSlider.updateBubbleHtml = function(bubble, value) {
showOsd();
const item = currentItem;
2024-03-22 18:19:30 -07:00
const ticks = currentRuntimeTicks * value / 100;
2024-02-11 20:34:30 -08:00
if (trickplayResolution && item?.Trickplay) {
2024-02-11 20:34:30 -08:00
return updateTrickplayBubbleHtml(
ServerConnections.getApiClient(item.ServerId),
trickplayResolution,
2024-02-11 20:34:30 -08:00
item,
currentPlayer.streamInfo.mediaSource.Id,
2024-02-11 20:34:30 -08:00
bubble,
ticks);
}
return false;
};
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.getBubbleHtml = function (value) {
showOsd();
if (enableProgressByTimeOfDay) {
if (programStartDateMs && programEndDateMs) {
let ms = programEndDateMs - programStartDateMs;
ms /= 100;
ms *= value;
ms += programStartDateMs;
return '<h1 class="sliderBubbleText">' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '</h1>';
2019-02-02 13:51:03 -05:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return '--:--';
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
if (!currentRuntimeTicks) {
return '--:--';
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
let ticks = currentRuntimeTicks;
ticks /= 100;
ticks *= value;
const item = currentItem;
2023-07-06 13:39:48 -04:00
if (item?.Chapters?.length && item.Chapters[0].ImageTag) {
2023-04-19 01:56:05 -04:00
const html = getChapterBubbleHtml(ServerConnections.getApiClient(item.ServerId), item, item.Chapters, ticks);
if (html) {
return html;
2019-02-02 13:51:03 -05:00
}
2023-04-19 01:56:05 -04:00
}
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
return '<h1 class="sliderBubbleText">' + datetime.getDisplayRunningTime(ticks) + '</h1>';
};
2019-02-02 10:20:27 -05:00
2023-04-19 01:56:05 -04:00
nowPlayingPositionSlider.getMarkerInfo = function () {
// use markers based on chapters
return currentItem?.Chapters?.map(currentChapter => ({
name: currentChapter.Name,
progress: currentChapter.StartPositionTicks / currentItem.RunTimeTicks
})) || [];
2023-04-19 01:56:05 -04:00
};
view.querySelector('.btnPreviousTrack').addEventListener('click', function () {
playbackManager.previousTrack(currentPlayer);
});
view.querySelector('.btnPreviousChapter').addEventListener('click', function () {
playbackManager.previousChapter(currentPlayer);
});
view.querySelector('.btnPause').addEventListener('click', function () {
playbackManager.playPause(currentPlayer);
});
view.querySelector('.btnNextChapter').addEventListener('click', function () {
playbackManager.nextChapter(currentPlayer);
});
view.querySelector('.btnNextTrack').addEventListener('click', function () {
playbackManager.nextTrack(currentPlayer);
});
btnRewind.addEventListener('click', function () {
playbackManager.rewind(currentPlayer);
});
btnFastForward.addEventListener('click', function () {
playbackManager.fastForward(currentPlayer);
});
view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection);
view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection);
// HACK: Remove `emby-button` from the rating button to make it look like the other buttons
view.querySelector('.btnUserRating').classList.remove('emby-button');
2023-04-19 01:56:05 -04:00
// Register to SyncPlay playback events and show big animated icon
const showIcon = (action) => {
2024-10-17 01:23:38 -04:00
let primaryIconName = '';
let secondaryIconName = '';
let animationClass = 'oneShotPulse';
2023-04-19 01:56:05 -04:00
let iconVisibilityTime = 1500;
const syncPlayIcon = view.querySelector('#syncPlayIcon');
switch (action) {
case 'schedule-play':
2024-10-17 01:23:38 -04:00
primaryIconName = 'sync spin';
secondaryIconName = 'play_arrow centered';
animationClass = 'infinitePulse';
2023-04-19 01:56:05 -04:00
iconVisibilityTime = -1;
hideOsd();
break;
case 'unpause':
2024-10-17 01:23:38 -04:00
primaryIconName = 'play_circle_outline';
2023-04-19 01:56:05 -04:00
break;
case 'pause':
2024-10-17 01:23:38 -04:00
primaryIconName = 'pause_circle_outline';
2023-04-19 01:56:05 -04:00
showOsd();
break;
case 'seek':
2024-10-17 01:23:38 -04:00
primaryIconName = 'update';
animationClass = 'infinitePulse';
2023-04-19 01:56:05 -04:00
iconVisibilityTime = -1;
break;
case 'buffering':
2024-10-17 01:23:38 -04:00
primaryIconName = 'schedule';
animationClass = 'infinitePulse';
2023-04-19 01:56:05 -04:00
iconVisibilityTime = -1;
break;
case 'wait-pause':
2024-10-17 01:23:38 -04:00
primaryIconName = 'schedule';
secondaryIconName = 'pause shifted';
animationClass = 'infinitePulse';
2023-04-19 01:56:05 -04:00
iconVisibilityTime = -1;
break;
case 'wait-unpause':
2024-10-17 01:23:38 -04:00
primaryIconName = 'schedule';
secondaryIconName = 'play_arrow shifted';
animationClass = 'infinitePulse';
2023-04-19 01:56:05 -04:00
iconVisibilityTime = -1;
break;
default: {
syncPlayIcon.style.visibility = 'hidden';
return;
}
2023-04-19 01:56:05 -04:00
}
2024-10-17 01:23:38 -04:00
syncPlayIcon.setAttribute('class', 'syncPlayIconCircle ' + animationClass);
2023-04-19 01:56:05 -04:00
const primaryIcon = syncPlayIcon.querySelector('.primary-icon');
2024-10-17 01:23:38 -04:00
primaryIcon.setAttribute('class', 'primary-icon material-icons ' + primaryIconName);
2023-04-19 01:56:05 -04:00
const secondaryIcon = syncPlayIcon.querySelector('.secondary-icon');
2024-10-17 01:23:38 -04:00
secondaryIcon.setAttribute('class', 'secondary-icon material-icons ' + secondaryIconName);
2023-04-19 01:56:05 -04:00
const clone = syncPlayIcon.cloneNode(true);
clone.style.visibility = 'visible';
syncPlayIcon.parentNode.replaceChild(clone, syncPlayIcon);
2023-04-19 01:56:05 -04:00
if (iconVisibilityTime < 0) {
return;
}
2023-04-19 01:56:05 -04:00
setTimeout(() => {
clone.style.visibility = 'hidden';
}, iconVisibilityTime);
};
2023-04-19 01:56:05 -04:00
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
if (SyncPlay) {
Events.on(SyncPlay.Manager, 'enabled', (_event, enabled) => {
if (!enabled) {
const syncPlayIcon = view.querySelector('#syncPlayIcon');
syncPlayIcon.style.visibility = 'hidden';
}
2023-04-19 01:56:05 -04:00
});
2023-04-19 01:56:05 -04:00
Events.on(SyncPlay.Manager, 'notify-osd', (_event, action) => {
showIcon(action);
});
2023-04-19 01:56:05 -04:00
Events.on(SyncPlay.Manager, 'group-state-update', (_event, state, reason) => {
if (state === 'Playing' && reason === 'Unpause') {
showIcon('schedule-play');
} else if (state === 'Playing' && reason === 'Ready') {
showIcon('schedule-play');
} else if (state === 'Paused' && reason === 'Pause') {
showIcon('pause');
} else if (state === 'Paused' && reason === 'Ready') {
showIcon('clear');
} else if (state === 'Waiting' && reason === 'Seek') {
showIcon('seek');
} else if (state === 'Waiting' && reason === 'Buffer') {
showIcon('buffering');
} else if (state === 'Waiting' && reason === 'Pause') {
showIcon('wait-pause');
} else if (state === 'Waiting' && reason === 'Unpause') {
showIcon('wait-unpause');
}
});
}
2023-04-19 01:56:05 -04:00
}