Merge branch 'master' into audio-normalization

This commit is contained in:
TelepathicWalrus 2023-03-12 13:13:44 +00:00 committed by GitHub
commit 8d5475a21b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
143 changed files with 1557 additions and 1079 deletions

View file

@ -46,6 +46,7 @@ module.exports = {
'keyword-spacing': ['error'],
'no-throw-literal': ['error'],
'max-statements-per-line': ['error'],
'max-params': ['error', 7],
'no-duplicate-imports': ['error'],
'no-empty-function': ['error'],
'no-floating-decimal': ['error'],
@ -67,6 +68,7 @@ module.exports = {
'padded-blocks': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'radix': ['error'],
'@babel/semi': ['error'],
'space-before-blocks': ['error'],
'space-infix-ops': 'error',

View file

@ -21,11 +21,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Initialize CodeQL
uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5
uses: github/codeql-action/init@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5
uses: github/codeql-action/autobuild@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5
uses: github/codeql-action/analyze@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6

View file

@ -60,6 +60,7 @@
- [edvwib](https://github.com/edvwib)
- [Rob Farraher](https://github.com/farraherbg)
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
# Emby Contributors

View file

@ -1,4 +1,4 @@
FROM fedora:37
FROM fedora:38
# Docker build arguments
ARG SOURCE_DIR=/jellyfin

951
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@babel/core": "7.20.12",
"@babel/core": "7.21.0",
"@babel/eslint-parser": "7.19.1",
"@babel/eslint-plugin": "7.19.1",
"@babel/plugin-proposal-class-properties": "7.18.6",
@ -13,14 +13,14 @@
"@babel/plugin-transform-modules-umd": "7.18.6",
"@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@babel/preset-typescript": "7.21.0",
"@types/escape-html": "1.0.2",
"@types/loadable__component": "5.13.4",
"@types/lodash-es": "4.17.6",
"@types/react": "17.0.53",
"@types/react-dom": "17.0.19",
"@typescript-eslint/eslint-plugin": "5.52.0",
"@typescript-eslint/parser": "5.52.0",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.13",
"babel-loader": "9.1.2",
@ -32,7 +32,7 @@
"css-loader": "6.7.3",
"cssnano": "5.1.15",
"es-check": "7.1.0",
"eslint": "8.34.0",
"eslint": "8.35.0",
"eslint-plugin-compat": "4.1.2",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.27.5",
@ -49,11 +49,11 @@
"postcss-loader": "7.0.2",
"postcss-preset-env": "8.0.1",
"postcss-scss": "4.0.6",
"sass": "1.58.1",
"sass": "1.58.3",
"sass-loader": "13.2.0",
"source-map-loader": "4.0.1",
"style-loader": "3.3.1",
"stylelint": "14.16.1",
"stylelint": "15.2.0",
"stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "6.0.2",
@ -77,10 +77,10 @@
"@jellyfin/libass-wasm": "4.1.1",
"@jellyfin/sdk": "unstable",
"@loadable/component": "5.15.3",
"blurhash": "2.0.4",
"blurhash": "2.0.5",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.3.2",
"core-js": "3.28.0",
"core-js": "3.29.0",
"date-fns": "2.29.3",
"dompurify": "2.4.4",
"epubjs": "0.4.2",
@ -89,11 +89,11 @@
"flv.js": "1.6.2",
"headroom.js": "0.12.0",
"history": "5.3.0",
"hls.js": "0.14.17",
"hls.js": "1.3.4",
"intersection-observer": "0.12.2",
"jellyfin-apiclient": "1.10.0",
"jquery": "3.6.3",
"jstree": "3.3.14",
"jstree": "3.3.15",
"libarchive.js": "1.3.0",
"lodash-es": "4.17.21",
"marked": "4.2.12",
@ -102,7 +102,7 @@
"pdfjs-dist": "2.16.105",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "6.8.1",
"react-router-dom": "6.8.2",
"resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2",
"sortablejs": "1.15.0",

View file

@ -1,5 +1,5 @@
import React, { FunctionComponent, useEffect, useState } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import type { ConnectResponse } from 'jellyfin-apiclient';
import alert from './alert';
@ -31,11 +31,11 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
isUserRequired = true
}) => {
const navigate = useNavigate();
const location = useLocation();
const [ isLoading, setIsLoading ] = useState(true);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const bounce = async (connectionResponse: ConnectResponse) => {
switch (connectionResponse.State) {
case ConnectionState.SignedIn:
@ -45,12 +45,12 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
return;
case ConnectionState.ServerSignIn:
// Bounce to the login page
console.debug('[ConnectionRequired] not logged in, redirecting to login page');
navigate(BounceRoutes.Login, {
state: {
serverid: connectionResponse.ApiClient.serverId()
}
});
if (location.pathname === BounceRoutes.Login) {
setIsLoading(false);
} else {
console.debug('[ConnectionRequired] not logged in, redirecting to login page');
navigate(`${BounceRoutes.Login}?serverid=${connectionResponse.ApiClient.serverId()}`);
}
return;
case ConnectionState.ServerSelection:
// Bounce to select server page
@ -144,7 +144,7 @@ const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
};
validateConnection();
}, [ isAdminRequired, isUserRequired, navigate ]);
}, [ isAdminRequired, isUserRequired, location.pathname, navigate ]);
if (isLoading) {
return <Loading />;

View file

@ -19,7 +19,7 @@ import template from './accessSchedule.template.html';
const pct = hours % 1;
if (pct) {
minutes = parseInt(60 * pct);
minutes = parseInt(60 * pct, 10);
}
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));

View file

@ -6,7 +6,7 @@ import dom from '../../scripts/dom';
import '../../elements/emby-button/emby-button';
import './actionSheet.scss';
import 'material-design-icons-iconfont';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../components/listview/listview.scss';
function getOffsets(elems) {

View file

@ -64,10 +64,10 @@ import { toBoolean } from '../utils/string.ts';
function reloadData(instance, elem, apiClient, startIndex, limit) {
if (startIndex == null) {
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0');
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10);
}
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7');
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10);
const minDate = new Date();
const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true);

View file

@ -124,7 +124,7 @@ class AppRouter {
isBack: action === Action.Pop
});
} else {
console.info('[appRouter] "%s" route not found', normalizedPath, location);
// The route is not registered here, so it should be handled by react-router
this.currentRouteInfo = {
route: {},
path: normalizedPath + location.search

View file

@ -663,7 +663,7 @@ import { appRouter } from '../appRouter';
const character = String(str.slice(charIndex, charIndex + 1).charCodeAt());
let sum = 0;
for (let i = 0; i < character.length; i++) {
sum += parseInt(character.charAt(i));
sum += parseInt(character.charAt(i), 10);
}
const index = String(sum).slice(-1);
@ -773,27 +773,24 @@ import { appRouter } from '../appRouter';
* @param {Object} item - Item used to generate the footer text.
* @param {Object} apiClient - API client instance.
* @param {Object} options - Options used to generate the footer text.
* @param {string} showTitle - Flag to show the title in the footer.
* @param {boolean} forceName - Flag to force showing the name of the item.
* @param {boolean} overlayText - Flag to show overlay text.
* @param {Object} imgUrl - Object representing the card's image URL.
* @param {string} footerClass - CSS classes of the footer element.
* @param {string} progressHtml - HTML markup of the progress bar element.
* @param {string} logoUrl - URL of the logo for the item.
* @param {boolean} isOuterFooter - Flag to mark the text as outer footer.
* @param {Object} flags - Various flags for the footer
* @param {Object} urls - Various urls for the footer
* @returns {string} HTML markup of the card's footer text element.
*/
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) {
function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) {
item = item.ProgramInfo || item;
let html = '';
if (logoUrl) {
html += '<div class="lazy cardFooterLogo" data-src="' + logoUrl + '"></div>';
if (urls.logoUrl) {
html += '<div class="lazy cardFooterLogo" data-src="' + urls.logoUrl + '"></div>';
}
const showOtherText = isOuterFooter ? !overlayText : overlayText;
const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder');
const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText;
if (isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') {
if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') {
html += `<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`;
}
@ -805,7 +802,7 @@ import { appRouter } from '../appRouter';
let titleAdded;
if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (item.SeriesId) {
lines.push(getTextActionButton({
Id: item.SeriesId,
@ -835,7 +832,7 @@ import { appRouter } from '../appRouter';
}
let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length);
if (!showMediaTitle && !titleAdded && (showTitle || forceName)) {
if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) {
showMediaTitle = true;
}
@ -856,7 +853,7 @@ import { appRouter } from '../appRouter';
if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) {
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true;
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
@ -991,23 +988,23 @@ import { appRouter } from '../appRouter';
}
}
if ((showTitle || !imgUrl) && forceName && overlayText && lines.length === 1) {
if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) {
lines = [];
}
if (overlayText && showTitle) {
if (flags.overlayText && showTitle) {
lines = [escapeHtml(item.Name)];
}
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
if (progressHtml) {
html += progressHtml;
}
if (html && (!isOuterFooter || logoUrl || options.cardLayout)) {
if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) {
html = '<div class="' + footerClass + '">' + html;
//cardFooter
@ -1217,7 +1214,6 @@ import { appRouter } from '../appRouter';
const forceName = imgInfo.forceName;
const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder');
const overlayText = options.overlayText;
let cardImageContainerClass = 'cardImageContainer';
@ -1265,7 +1261,7 @@ import { appRouter } from '../appRouter';
logoUrl = null;
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
innerCardFooter += getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, false);
innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl });
footerOverlayed = true;
} else if (progressHtml) {
innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">';
@ -1292,7 +1288,7 @@ import { appRouter } from '../appRouter';
logoUrl = null;
}
outerCardFooter = getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, true);
outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl });
}
if (outerCardFooter && !options.cardLayout) {

View file

@ -12,7 +12,7 @@ import '../../elements/emby-input/emby-input';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';

View file

@ -10,7 +10,7 @@ import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-input/emby-input';
import '../formdialog.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import template from './dialog.template.html';
/* eslint-disable indent */

View file

@ -7,7 +7,7 @@ import { toBoolean } from '../../utils/string.ts';
import dom from '../../scripts/dom';
import './dialoghelper.scss';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
/* eslint-disable indent */

View file

@ -8,6 +8,7 @@ import datetime from '../../scripts/datetime';
import globalize from '../../scripts/globalize';
import loading from '../loading/loading';
import skinManager from '../../scripts/themeManager';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-checkbox/emby-checkbox';
@ -35,7 +36,7 @@ import template from './displaySettings.template.html';
function loadScreensavers(context, userSettings) {
const selectScreensaver = context.querySelector('.selectScreensaver');
const options = pluginManager.ofType('screensaver').map(plugin => {
const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => {
return {
name: plugin.name,
value: plugin.id

View file

@ -6,7 +6,7 @@ import imageLoader from './images/imageLoader';
import globalize from '../scripts/globalize';
import layoutManager from './layoutManager';
import { getParameterByName } from '../utils/url.ts';
import '../assets/css/scrollstyles.scss';
import '../styles/scrollstyles.scss';
import '../elements/emby-itemscontainer/emby-itemscontainer';
/* eslint-disable indent */

View file

@ -13,7 +13,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import template from './filtermenu.template.html';

View file

@ -13,7 +13,7 @@ import ServerConnections from './ServerConnections';
const playedIndicator = card.querySelector('.playedIndicator');
const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null;
const options = {
Limit: parseInt(playedIndicatorHtml || '10'),
Limit: parseInt(playedIndicatorHtml || '10', 10),
Fields: 'PrimaryImageAspectRatio,DateCreated',
ParentId: itemId,
GroupItems: false

View file

@ -17,13 +17,13 @@ import dom from '../../scripts/dom';
import './guide.scss';
import './programs.scss';
import 'material-design-icons-iconfont';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-programcell/emby-programcell';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-tabs/emby-tabs';
import '../../elements/emby-scroller/emby-scroller';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import 'webcomponents.js/webcomponents-lite';
import ServerConnections from '../ServerConnections';
import template from './tvguide.template.html';
@ -345,7 +345,9 @@ function Guide(options) {
}
apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) {
renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender);
const guideOptions = { focusProgramOnRender, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs };
renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, guideOptions, apiClient);
hideLoading();
});
@ -667,7 +669,7 @@ function Guide(options) {
return (channelIndex * 10000000) + (start.getTime() / 60000);
}
function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) {
function renderGuide(context, date, channels, programs, renderOptions, guideOptions, apiClient) {
programs.sort(function (a, b) {
return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels);
});
@ -689,11 +691,11 @@ function Guide(options) {
items = {};
renderPrograms(context, date, channels, programs, renderOptions);
if (focusProgramOnRender) {
focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs);
if (guideOptions.focusProgramOnRender) {
focusProgram(context, itemId, channelRowId, guideOptions.focusToTimeMs, guideOptions.startTimeOfDayMs);
}
scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs);
scrollProgramGridToTimeMs(context, guideOptions.scrollToTimeMs, guideOptions.startTimeOfDayMs);
}
function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) {
@ -1147,12 +1149,12 @@ function Guide(options) {
guideContext.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) {
const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button');
const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)];
const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex, 10)];
if (tabButton) {
const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)];
const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex, 10)];
const date = new Date();
date.setTime(parseInt(tabButton.getAttribute('data-date')));
date.setTime(parseInt(tabButton.getAttribute('data-date'), 10));
const scrollWidth = programGrid.scrollWidth;
let scrollToTimeMs;
@ -1164,7 +1166,7 @@ function Guide(options) {
if (previousButton) {
const previousDate = new Date();
previousDate.setTime(parseInt(previousButton.getAttribute('data-date')));
previousDate.setTime(parseInt(previousButton.getAttribute('data-date'), 10));
scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000);
scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000);

View file

@ -96,7 +96,7 @@ import template from './imageeditor.template.html';
return apiClient.getScaledImageUrl(item.Id || item.ItemId, options);
}
function getCardHtml(image, index, numImages, apiClient, imageProviders, imageSize, tagName, enableFooterButtons) {
function getCardHtml(image, apiClient, options) {
// TODO move card creation code to Card component
let html = '';
@ -106,7 +106,7 @@ import template from './imageeditor.template.html';
cssClass += ' backdropCard backdropCard-scalable';
if (tagName === 'button') {
if (options.tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv) {
@ -122,7 +122,7 @@ import template from './imageeditor.template.html';
html += '<div class="' + cssClass + '"';
}
html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + index + '" data-numimages="' + numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + imageProviders.length + '"';
html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + options.index + '" data-numimages="' + options.numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + options.imageProviders.length + '"';
html += '>';
@ -132,7 +132,7 @@ import template from './imageeditor.template.html';
html += '<div class="cardContent">';
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize });
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: options.imageSize });
html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>';
@ -151,23 +151,23 @@ import template from './imageeditor.template.html';
}
html += '</div>';
if (enableFooterButtons) {
if (options.enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
if (image.ImageType === 'Backdrop') {
if (index > 0) {
if (options.index > 0) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
} else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>';
}
if (index < numImages - 1) {
if (options.index < options.numImages - 1) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
} else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
}
} else {
if (imageProviders.length) {
if (options.imageProviders.length) {
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>';
}
}
@ -178,7 +178,7 @@ import template from './imageeditor.template.html';
html += '</div>';
html += '</div>';
html += '</' + tagName + '>';
html += '</' + options.tagName + '>';
return html;
}
@ -226,7 +226,8 @@ import template from './imageeditor.template.html';
for (let i = 0, length = images.length; i < length; i++) {
const image = images[i];
html += getCardHtml(image, i, length, apiClient, imageProviders, imageSize, tagName, enableFooterButtons);
const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons };
html += getCardHtml(image, apiClient, options);
}
elem.innerHTML = html;
@ -277,9 +278,9 @@ import template from './imageeditor.template.html';
const apiClient = ServerConnections.getApiClient(serverId);
const type = imageCard.getAttribute('data-imagetype');
const index = parseInt(imageCard.getAttribute('data-index'));
const providerCount = parseInt(imageCard.getAttribute('data-providers'));
const numImages = parseInt(imageCard.getAttribute('data-numimages'));
const index = parseInt(imageCard.getAttribute('data-index'), 10);
const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10);
const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10);
import('../actionSheet/actionSheet').then(({default: actionSheet}) => {
const commands = [];
@ -384,7 +385,7 @@ import template from './imageeditor.template.html';
addListeners(context, 'btnDeleteImage', 'click', function () {
const type = this.getAttribute('data-imagetype');
let index = this.getAttribute('data-index');
index = index === 'null' ? null : parseInt(index);
index = index === 'null' ? null : parseInt(index, 10);
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
deleteImage(context, currentItem.Id, type, index, apiClient, true);
});

View file

@ -20,7 +20,7 @@ import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import 'material-design-icons-iconfont';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import template from './itemMediaInfo.template.html';
@ -138,7 +138,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
}
if (stream.BitRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000)} kbps`));
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`));
}
if (stream.SampleRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`));

View file

@ -52,7 +52,7 @@ import datetime from '../../scripts/datetime';
if (value) {
if (identifyField[i].type === 'number') {
value = parseInt(value);
value = parseInt(value, 10);
}
lookupInfo[identifyField[i].getAttribute('data-lookup')] = value;
@ -123,7 +123,7 @@ import datetime from '../../scripts/datetime';
elem.innerHTML = html;
function onSearchImageClick() {
const index = parseInt(this.getAttribute('data-index'));
const index = parseInt(this.getAttribute('data-index'), 10);
const currentResult = results[index];

View file

@ -531,7 +531,7 @@ import template from './libraryoptionseditor.template.html';
PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value,
MetadataCountryCode: parent.querySelector('#selectCountry').value,
SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value,
AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value),
AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10),
EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked,
EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked,
EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked,

View file

@ -19,7 +19,7 @@ import '../../elements/emby-select/emby-select';
import '../../elements/emby-toggle/emby-toggle';
import '../listview/listview.scss';
import '../formdialog.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import './style.scss';
import toast from '../toast/toast';
import alert from '../alert';
@ -168,7 +168,7 @@ import template from './mediaLibraryCreator.template.html';
function onRemoveClick(e) {
const button = dom.parentWithClass(e.target, 'btnRemovePath');
const index = parseInt(button.getAttribute('data-index'));
const index = parseInt(button.getAttribute('data-index'), 10);
const location = pathInfos[index].Path;
const locationLower = location.toLowerCase();
pathInfos = pathInfos.filter(p => {

View file

@ -17,7 +17,7 @@ import '../listview/listview.scss';
import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import '../../elements/emby-toggle/emby-toggle';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import './style.scss';
import toast from '../toast/toast';
import confirm from '../confirm/confirm';
@ -93,7 +93,7 @@ import template from './mediaLibraryEditor.template.html';
const listItem = dom.parentWithClass(e.target, 'listItem');
if (listItem) {
const index = parseInt(listItem.getAttribute('data-index'));
const index = parseInt(listItem.getAttribute('data-index'), 10);
const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || [];
const pathInfo = index == null ? {} : pathInfos[index] || {};
const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]);

View file

@ -15,8 +15,8 @@ import '../../elements/emby-textarea/emby-textarea';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import '../../assets/css/clearbutton.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/clearbutton.scss';
import '../../styles/flexstyles.scss';
import './style.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
@ -357,14 +357,14 @@ import template from './metadataEditor.template.html';
let index;
const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson');
if (btnDeletePerson) {
index = parseInt(btnDeletePerson.getAttribute('data-index'));
index = parseInt(btnDeletePerson.getAttribute('data-index'), 10);
currentItem.People.splice(index, 1);
populatePeople(context, currentItem.People);
}
const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson');
if (btnEditPerson) {
index = parseInt(btnEditPerson.getAttribute('data-index'));
index = parseInt(btnEditPerson.getAttribute('data-index'), 10);
editPerson(context, currentItem.People[index], index);
}
});

View file

@ -114,8 +114,8 @@ import shell from '../../scripts/shell';
const itemId = item.Id;
// Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0);
const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0);
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10);
const isPaused = playState.IsPaused || false;
const canSeek = playState.CanSeek || false;
@ -247,7 +247,7 @@ import shell from '../../scripts/shell';
navigator.mediaSession.setActionHandler('seekto', function (object) {
const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0);
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
});

View file

@ -11,6 +11,7 @@ import { appHost } from '../apphost';
import Screenfull from 'screenfull';
import ServerConnections from '../ServerConnections';
import alert from '../alert';
import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts';
const UNLIMITED_ITEMS = -1;
@ -299,20 +300,20 @@ function getAudioMaxValues(deviceProfile) {
}
let startingPlaySession = new Date().getTime();
function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) {
function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiClient, startPosition, maxValues) {
const url = 'Audio/' + item.Id + '/universal';
startingPlaySession++;
return apiClient.getUrl(url, {
UserId: apiClient.getCurrentUserId(),
DeviceId: apiClient.deviceId(),
MaxStreamingBitrate: maxAudioBitrate || maxBitrate,
MaxStreamingBitrate: maxValues.maxAudioBitrate || maxValues.maxBitrate,
Container: directPlayContainers,
TranscodingContainer: transcodingProfile.Container || null,
TranscodingProtocol: transcodingProfile.Protocol || null,
AudioCodec: transcodingProfile.AudioCodec,
MaxAudioSampleRate: maxAudioSampleRate,
MaxAudioBitDepth: maxAudioBitDepth,
MaxAudioSampleRate: maxValues.maxAudioSampleRate,
MaxAudioBitDepth: maxValues.maxAudioBitDepth,
api_key: apiClient.accessToken(),
PlaySessionId: startingPlaySession,
StartTimeTicks: startPosition || 0,
@ -344,7 +345,7 @@ function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, api
const maxValues = getAudioMaxValues(deviceProfile);
return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition);
return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiClient, startPosition, { maxBitrate, ...maxValues });
}
function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) {
@ -377,7 +378,7 @@ function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
let streamUrl;
if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) {
streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition);
streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, apiClient, startPosition, { maxBitrate, ...maxValues });
}
streamUrls.push(streamUrl || '');
@ -408,27 +409,12 @@ function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
});
}
function getPlaybackInfo(player,
apiClient,
item,
deviceProfile,
maxBitrate,
startPosition,
isPlayback,
mediaSourceId,
audioStreamIndex,
subtitleStreamIndex,
liveStreamId,
enableDirectPlay,
enableDirectStream,
allowVideoStreamCopy,
allowAudioStreamCopy,
secondarySubtitleStreamIndex) {
function getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, liveStreamId, options) {
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
return Promise.resolve({
MediaSources: [
{
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition),
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, options.maxBitrate, apiClient, options.startPosition),
Id: item.Id,
MediaStreams: [],
RunTimeTicks: item.RunTimeTicks
@ -446,10 +432,10 @@ function getPlaybackInfo(player,
const query = {
UserId: apiClient.getCurrentUserId(),
StartTimeTicks: startPosition || 0
StartTimeTicks: options.startPosition || 0
};
if (isPlayback) {
if (options.isPlayback) {
query.IsPlayback = true;
query.AutoOpenLiveStream = true;
} else {
@ -457,27 +443,26 @@ function getPlaybackInfo(player,
query.AutoOpenLiveStream = false;
}
if (audioStreamIndex != null) {
query.AudioStreamIndex = audioStreamIndex;
if (options.audioStreamIndex != null) {
query.AudioStreamIndex = options.audioStreamIndex;
}
if (subtitleStreamIndex != null) {
query.SubtitleStreamIndex = subtitleStreamIndex;
if (options.subtitleStreamIndex != null) {
query.SubtitleStreamIndex = options.subtitleStreamIndex;
}
if (secondarySubtitleStreamIndex != null) {
query.SecondarySubtitleStreamIndex = secondarySubtitleStreamIndex;
if (options.secondarySubtitleStreamIndex != null) {
query.SecondarySubtitleStreamIndex = options.secondarySubtitleStreamIndex;
}
if (enableDirectPlay != null) {
query.EnableDirectPlay = enableDirectPlay;
if (options.enableDirectPlay != null) {
query.EnableDirectPlay = options.enableDirectPlay;
}
if (enableDirectStream != null) {
query.EnableDirectStream = enableDirectStream;
if (options.enableDirectStream != null) {
query.EnableDirectStream = options.enableDirectStream;
}
if (allowVideoStreamCopy != null) {
query.AllowVideoStreamCopy = allowVideoStreamCopy;
if (options.allowVideoStreamCopy != null) {
query.AllowVideoStreamCopy = options.allowVideoStreamCopy;
}
if (allowAudioStreamCopy != null) {
query.AllowAudioStreamCopy = allowAudioStreamCopy;
if (options.allowAudioStreamCopy != null) {
query.AllowAudioStreamCopy = options.allowAudioStreamCopy;
}
if (mediaSourceId) {
query.MediaSourceId = mediaSourceId;
@ -485,8 +470,8 @@ function getPlaybackInfo(player,
if (liveStreamId) {
query.LiveStreamId = liveStreamId;
}
if (maxBitrate) {
query.MaxStreamingBitrate = maxBitrate;
if (options.maxBitrate) {
query.MaxStreamingBitrate = options.maxBitrate;
}
if (player.enableMediaProbe && !player.enableMediaProbe(item)) {
query.EnableMediaProbe = false;
@ -537,7 +522,7 @@ function getOptimalMediaSource(apiClient, item, versions) {
});
}
function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) {
function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, mediaSource, options) {
const postData = {
DeviceProfile: deviceProfile,
OpenToken: mediaSource.OpenToken
@ -545,19 +530,19 @@ function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, ma
const query = {
UserId: apiClient.getCurrentUserId(),
StartTimeTicks: startPosition || 0,
StartTimeTicks: options.startPosition || 0,
ItemId: item.Id,
PlaySessionId: playSessionId
};
if (maxBitrate) {
query.MaxStreamingBitrate = maxBitrate;
if (options.maxBitrate) {
query.MaxStreamingBitrate = options.maxBitrate;
}
if (audioStreamIndex != null) {
query.AudioStreamIndex = audioStreamIndex;
if (options.audioStreamIndex != null) {
query.AudioStreamIndex = options.audioStreamIndex;
}
if (subtitleStreamIndex != null) {
query.SubtitleStreamIndex = subtitleStreamIndex;
if (options.subtitleStreamIndex != null) {
query.SubtitleStreamIndex = options.subtitleStreamIndex;
}
// lastly, enforce player overrides for special situations
@ -1706,7 +1691,7 @@ class PlaybackManager {
function changeStream(player, ticks, params) {
if (canPlayerSeek(player) && params == null) {
player.currentTime(parseInt(ticks / 10000));
player.currentTime(parseInt(ticks / 10000, 10));
return;
}
@ -1730,20 +1715,33 @@ class PlaybackManager {
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
if (ticks) {
ticks = parseInt(ticks);
ticks = parseInt(ticks, 10);
}
const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player);
const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions();
getPlaybackInfo(player, apiClient, currentItem, deviceProfile, maxBitrate, ticks, true, currentMediaSource.Id, audioStreamIndex, subtitleStreamIndex, liveStreamId, params.EnableDirectPlay, params.EnableDirectStream, params.AllowVideoStreamCopy, params.AllowAudioStreamCopy).then(function (result) {
const options = {
maxBitrate,
startPosition: ticks,
isPlayback: true,
audioStreamIndex,
subtitleStreamIndex,
enableDirectPlay: params.EnableDirectPlay,
enableDirectStream: params.EnableDirectStream,
allowVideoStreamCopy: params.AllowVideoStreamCopy,
allowAudioStreamCopy: params.AllowAudioStreamCopy
};
getPlaybackInfo(player, apiClient, currentItem, deviceProfile, currentMediaSource.Id, liveStreamId, options).then(function (result) {
if (validatePlaybackInfoResult(self, result)) {
currentMediaSource = result.MediaSources[0];
const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks, player);
streamInfo.fullscreen = currentPlayOptions.fullscreen;
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
streamInfo.resetSubtitleOffset = false;
if (!streamInfo.url) {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream');
@ -2268,7 +2266,7 @@ class PlaybackManager {
function runInterceptors(item, playOptions) {
return new Promise(function (resolve, reject) {
const interceptors = pluginManager.ofType('preplayintercept');
const interceptors = pluginManager.ofType(PluginType.PreplayIntercept);
interceptors.sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
@ -2303,17 +2301,17 @@ class PlaybackManager {
}, reject);
}
function sendPlaybackListToPlayer(player, items, deviceProfile, maxBitrate, apiClient, startPositionTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, startIndex) {
return setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositionTicks).then(function () {
function sendPlaybackListToPlayer(player, items, deviceProfile, apiClient, mediaSourceId, options) {
return setStreamUrls(items, deviceProfile, options.maxBitrate, apiClient, options.startPosition).then(function () {
loading.hide();
return player.play({
items: items,
startPositionTicks: startPositionTicks || 0,
mediaSourceId: mediaSourceId,
audioStreamIndex: audioStreamIndex,
subtitleStreamIndex: subtitleStreamIndex,
startIndex: startIndex
items,
startPositionTicks: options.startPosition || 0,
mediaSourceId,
audioStreamIndex: options.audioStreamIndex,
subtitleStreamIndex: options.subtitleStreamIndex,
startIndex: options.startIndex
});
});
}
@ -2474,15 +2472,27 @@ class PlaybackManager {
const mediaSourceId = playOptions.mediaSourceId;
const audioStreamIndex = playOptions.audioStreamIndex;
const subtitleStreamIndex = playOptions.subtitleStreamIndex;
const options = {
maxBitrate,
startPosition,
isPlayback: null,
audioStreamIndex,
subtitleStreamIndex,
startIndex: playOptions.startIndex,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
if (player && !enableLocalPlaylistManagement(player)) {
return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, maxBitrate, apiClient, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playOptions.startIndex);
return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, apiClient, mediaSourceId, options);
}
// this reference was only needed by sendPlaybackListToPlayer
playOptions.items = null;
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => {
return getPlaybackMediaSource(player, apiClient, deviceProfile, item, mediaSourceId, options).then(async (mediaSource) => {
const user = await apiClient.getCurrentUser();
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
@ -2540,7 +2550,20 @@ class PlaybackManager {
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
return player.getDeviceProfile(item).then(function (deviceProfile) {
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) {
const mediaOptions = {
maxBitrate,
startPosition,
isPlayback: null,
audioStreamIndex: options.audioStreamIndex,
subtitleStreamIndex: options.subtitleStreamIndex,
startIndex: null,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
return getPlaybackMediaSource(player, apiClient, deviceProfile, item, options.mediaSourceId, mediaOptions).then(function (mediaSource) {
return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
});
});
@ -2560,7 +2583,19 @@ class PlaybackManager {
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
return player.getDeviceProfile(item).then(function (deviceProfile) {
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, false, null, null, null, null).then(function (playbackInfoResult) {
const mediaOptions = {
maxBitrate,
startPosition,
isPlayback: true,
audioStreamIndex: null,
subtitleStreamIndex: null,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
return getPlaybackInfo(player, apiClient, item, deviceProfile, null, null, mediaOptions).then(function (playbackInfoResult) {
return playbackInfoResult.MediaSources;
});
});
@ -2701,13 +2736,18 @@ class PlaybackManager {
return tracks;
}
function getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex) {
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, true, mediaSourceId, audioStreamIndex, subtitleStreamIndex, null).then(function (playbackInfoResult) {
function getPlaybackMediaSource(player, apiClient, deviceProfile, item, mediaSourceId, options) {
options.isPlayback = true;
return getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, null, options).then(function (playbackInfoResult) {
if (validatePlaybackInfoResult(self, playbackInfoResult)) {
return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) {
if (mediaSource) {
if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) {
return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, maxBitrate, startPosition, mediaSource, null, null).then(function (openLiveStreamResult) {
options.audioStreamIndex = null;
options.subtitleStreamIndex = null;
return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, mediaSource, options).then(function (openLiveStreamResult) {
return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) {
openLiveStreamResult.MediaSource.enableDirectPlay = result;
return openLiveStreamResult.MediaSource;
@ -3387,12 +3427,12 @@ class PlaybackManager {
}
Events.on(pluginManager, 'registered', function (e, plugin) {
if (plugin.type === 'mediaplayer') {
if (plugin.type === PluginType.MediaPlayer) {
initMediaPlayer(plugin);
}
});
pluginManager.ofType('mediaplayer').forEach(initMediaPlayer);
pluginManager.ofType(PluginType.MediaPlayer).forEach(initMediaPlayer);
function sendProgressUpdate(player, progressEventName, reportPlaylist) {
if (!player) {
@ -3423,12 +3463,6 @@ class PlaybackManager {
streamInfo.lastMediaInfoQuery = new Date().getTime();
const apiClient = ServerConnections.getApiClient(serverId);
if (!apiClient.isMinServerVersion('3.2.70.7')) {
return;
}
ServerConnections.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) {
mediaSource.MediaStreams = info.MediaStreams;
Events.trigger(player, 'mediastreamschange');
@ -3613,7 +3647,7 @@ class PlaybackManager {
percent /= 100;
ticks *= percent;
this.seek(parseInt(ticks), player);
this.seek(parseInt(ticks, 10), player);
}
seekMs(ms, player = this._currentPlayer) {
@ -4000,13 +4034,13 @@ class PlaybackManager {
this.setBrightness(cmd.Arguments.Brightness, player);
break;
case 'SetAudioStreamIndex':
this.setAudioStreamIndex(parseInt(cmd.Arguments.Index), player);
this.setAudioStreamIndex(parseInt(cmd.Arguments.Index, 10), player);
break;
case 'SetSubtitleStreamIndex':
this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index), player);
this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index, 10), player);
break;
case 'SetMaxStreamingBitrate':
this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player);
this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate, 10), player);
break;
case 'ToggleFullscreen':
this.toggleFullscreen(player);

View file

@ -43,7 +43,7 @@ function showQualityMenu(player, btn) {
items: menuItems,
positionTo: btn
}).then(function (id) {
const bitrate = parseInt(id);
const bitrate = parseInt(id, 10);
if (bitrate !== selectedBitrate) {
playbackManager.setMaxStreamingBitrate({
enableAutomaticBitrateDetection: bitrate ? false : true,

View file

@ -1,5 +1,3 @@
/*eslint prefer-const: "error"*/
let currentId = 0;
function addUniquePlaylistItemId(item) {
if (!item.PlaylistItemId) {

View file

@ -8,6 +8,8 @@ import { appRouter } from '../components/appRouter';
import * as inputManager from '../scripts/inputManager';
import toast from '../components/toast/toast';
import confirm from '../components/confirm/confirm';
import * as dashboard from '../utils/dashboard';
import ServerConnections from '../components/ServerConnections';
// TODO: replace with each plugin version
const cacheParam = new Date().getTime();
@ -86,7 +88,9 @@ class PluginManager {
appRouter,
inputManager,
toast,
confirm
confirm,
dashboard,
ServerConnections
});
} else {
console.debug(`Loading plugin (via dynamic import): ${pluginSpec}`);

View file

@ -4,7 +4,7 @@ import globalize from '../../scripts/globalize';
import layoutManager from '../layoutManager';
import loading from '../loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-collapse/emby-collapse';
import '../../elements/emby-input/emby-input';
@ -12,7 +12,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import './recordingcreator.scss';
import 'material-design-icons-iconfont';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import template from './recordingeditor.template.html';

View file

@ -7,13 +7,11 @@ import recordingHelper from './recordinghelper';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import './recordingfields.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './recordingfields.template.html';
/*eslint prefer-const: "error"*/
function loadData(parent, program) {
if (program.IsSeries) {
parent.querySelector('.recordSeriesContainer').classList.remove('hide');

View file

@ -5,8 +5,6 @@ import toast from '../toast/toast';
import confirm from '../confirm/confirm';
import dialog from '../dialog/dialog';
/*eslint prefer-const: "error"*/
function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) {
loading.show();

View file

@ -4,7 +4,7 @@ import layoutManager from '../layoutManager';
import loading from '../loading/loading';
import scrollHelper from '../../scripts/scrollHelper';
import datetime from '../../scripts/datetime';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-input/emby-input';
@ -13,12 +13,10 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss';
import './recordingcreator.scss';
import 'material-design-icons-iconfont';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import template from './seriesrecordingeditor.template.html';
/*eslint prefer-const: "error"*/
let currentDialog;
let recordingUpdated = false;
let recordingDeleted = false;

View file

@ -13,8 +13,6 @@ import '../formdialog.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
/*eslint prefer-const: "error"*/
function getEditorHtml() {
let html = '';

View file

@ -20,8 +20,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import { appRouter } from '../appRouter';
/*eslint prefer-const: "error"*/
let showMuteButton = true;
let showVolumeSlider = true;
@ -46,7 +44,7 @@ function showAudioMenu(context, player, button) {
items: menuItems,
positionTo: button,
callback: function (id) {
playbackManager.setAudioStreamIndex(parseInt(id), player);
playbackManager.setAudioStreamIndex(parseInt(id, 10), player);
}
});
});
@ -78,7 +76,7 @@ function showSubtitleMenu(context, player, button) {
items: menuItems,
positionTo: button,
callback: function (id) {
playbackManager.setSubtitleStreamIndex(parseInt(id), player);
playbackManager.setSubtitleStreamIndex(parseInt(id, 10), player);
}
});
});

View file

@ -7,7 +7,7 @@ import globalize from '../../scripts/globalize';
import 'material-design-icons-iconfont';
import '../../elements/emby-input/emby-input';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import './searchfields.scss';
import layoutManager from '../layoutManager';
import browser from '../../scripts/browser';

View file

@ -150,7 +150,7 @@ import toast from './toast/toast';
StartDate: card.getAttribute('data-startdate'),
EndDate: card.getAttribute('data-enddate'),
UserData: {
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0')
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10)
}
};
}
@ -201,7 +201,7 @@ import toast from './toast/toast';
ServerId: serverId
});
} else if (action === 'play' || action === 'resume') {
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0');
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10);
if (playbackManager.canPlay(item)) {
playbackManager.play({

View file

@ -7,7 +7,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import template from './sortmenu.template.html';
function onSubmit(e) {

View file

@ -14,7 +14,7 @@ import '../formdialog.scss';
import 'material-design-icons-iconfont';
import './subtitleeditor.scss';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import confirm from '../confirm/confirm';

View file

@ -13,7 +13,7 @@ import '../../elements/emby-select/emby-select';
import '../../elements/emby-slider/emby-slider';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import './subtitlesettings.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';

View file

@ -101,14 +101,16 @@
<div class="selectContainer hide">
<select is="emby-select" id="selectTextColor" label="${LabelTextColor}">
<option value="#ffffff">${White}</option>
<option value="#ffff00">${Yellow}</option>
<option value="#008000">${Green}</option>
<option value="#00ffff">${Cyan}</option>
<option value="#0000ff">${Blue}</option>
<option value="#ff00ff">${Magenta}</option>
<option value="#ff0000">${Red}</option>
<option value="#000000">${Black}</option>
<option value="#ffffff">${SubtitleWhite}</option>
<option value="#d3d3d3">${SubtitleLightGray}</option>
<option value="#808080">${SubtitleGray}</option>
<option value="#ffff00">${SubtitleYellow}</option>
<option value="#008000">${SubtitleGreen}</option>
<option value="#00ffff">${SubtitleCyan}</option>
<option value="#0000ff">${SubtitleBlue}</option>
<option value="#ff00ff">${SubtitleMagenta}</option>
<option value="#ff0000">${SubtitleRed}</option>
<option value="#000000">${SubtitleBlack}</option>
</select>
</div>

View file

@ -7,7 +7,7 @@ import '../listview/listview.scss';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import './style.scss';
import Dashboard from '../../utils/dashboard';
import Events from '../../utils/events.ts';

View file

@ -8,7 +8,7 @@ import globalize from '../../scripts/globalize';
import itemHelper from '../itemHelper';
import './upnextdialog.scss';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
/* eslint-disable indent */

View file

@ -28,9 +28,9 @@
.upNextDialog-countdownText {
font-weight: 500;
white-space: nowrap;
}
.upNextDialog-nextVideoText,
.upNextDialog-title {
width: 25.5em;
white-space: nowrap;

View file

@ -12,7 +12,10 @@ const userDataMethods = {
markFavorite: markFavorite
};
function getUserDataButtonHtml(method, itemId, serverId, buttonCssClass, iconCssClass, icon, tooltip, style) {
function getUserDataButtonHtml(method, itemId, serverId, icon, tooltip, style, classes) {
let buttonCssClass = classes.buttonCssClass;
let iconCssClass = classes.iconCssClass;
if (style === 'fab-mini') {
style = 'fab';
buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini';
@ -96,7 +99,7 @@ function getIconsHtml(options) {
}
const iconCssClass = options.iconCssClass;
const classes = { buttonCssClass: btnCssClass, iconCssClass: iconCssClass };
const serverId = item.ServerId;
if (includePlayed !== false) {
@ -104,18 +107,21 @@ function getIconsHtml(options) {
if (itemHelper.canMarkPlayed(item)) {
if (userData.Played) {
html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass + ' btnUserDataOn', iconCssClass, 'check', tooltipPlayed, style);
const buttonCssClass = classes.buttonCssClass + ' btnUserDataOn';
html += getUserDataButtonHtml('markPlayed', itemId, serverId, 'check', tooltipPlayed, style, { buttonCssClass, ...classes });
} else {
html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass, iconCssClass, 'check', tooltipPlayed, style);
html += getUserDataButtonHtml('markPlayed', itemId, serverId, 'check', tooltipPlayed, style, classes);
}
}
}
const tooltipFavorite = globalize.translate('Favorite');
if (userData.IsFavorite) {
html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData btnUserDataOn', iconCssClass, 'favorite', tooltipFavorite, style);
const buttonCssClass = classes.buttonCssClass + ' btnUserData btnUserDataOn';
html += getUserDataButtonHtml('markFavorite', itemId, serverId, 'favorite', tooltipFavorite, style, { buttonCssClass, ...classes });
} else {
html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData', iconCssClass, 'favorite', tooltipFavorite, style);
classes.buttonCssClass += ' btnUserData';
html += getUserDataButtonHtml('markFavorite', itemId, serverId, 'favorite', tooltipFavorite, style, classes);
}
return html;

View file

@ -9,7 +9,7 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import template from './viewSettings.template.html';
function onSubmit(e) {

View file

@ -16,7 +16,7 @@ import imageHelper from '../../scripts/imagehelper';
import indicators from '../../components/indicators/indicators';
import '../../components/listview/listview.scss';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import taskButton from '../../scripts/taskbutton';
import Dashboard from '../../utils/dashboard';

View file

@ -54,15 +54,11 @@ import confirm from '../../../components/confirm/confirm';
}
function showDeviceMenu(view, btn, deviceId) {
const menuItems = [];
if (canEdit) {
menuItems.push({
name: globalize.translate('Edit'),
id: 'open',
icon: 'mode_edit'
});
}
const menuItems = [{
name: globalize.translate('Edit'),
id: 'open',
icon: 'mode_edit'
}];
if (canDelete(deviceId)) {
menuItems.push({
@ -100,7 +96,7 @@ import confirm from '../../../components/confirm/confirm';
deviceHtml += '<div class="cardBox visualCardBox">';
deviceHtml += '<div class="cardScalable">';
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#/device.html?id=' + device.Id : '#'}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
deviceHtml += `<a is="emby-linkbutton" href="#/device.html?id=${device.Id}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
const iconUrl = imageHelper.getDeviceIcon(device);
if (iconUrl) {
@ -114,7 +110,7 @@ import confirm from '../../../components/confirm/confirm';
deviceHtml += '</div>';
deviceHtml += '<div class="cardFooter">';
if (canEdit || canDelete(device.Id)) {
if (canDelete(device.Id)) {
if (globalize.getIsRTL())
deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">';
else
@ -155,7 +151,6 @@ import confirm from '../../../components/confirm/confirm';
});
}
const canEdit = ApiClient.isMinServerVersion('3.4.1.31');
export default function (view) {
view.querySelector('.devicesList').addEventListener('click', function (e) {
const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu');

View file

@ -100,7 +100,7 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>';
const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create');
$('.btnDeleteIdentificationHeader', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index'));
const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.Identification.Headers.splice(itemIndex, 1);
renderIdentificationHeaders(page, currentProfile.Identification.Headers);
});
@ -154,7 +154,7 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>';
const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create');
$('.btnDeleteXmlAttribute', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index'));
const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.XmlRootAttributes.splice(itemIndex, 1);
renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes);
});
@ -198,12 +198,12 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>';
const elem = $('.subtitleProfileList', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index'));
const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.SubtitleProfiles.splice(itemIndex, 1);
renderSubtitleProfiles(page, currentProfile.SubtitleProfiles);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index'));
const itemIndex = parseInt(this.getAttribute('data-index'), 10);
editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]);
});
}
@ -292,7 +292,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteDirectPlayProfile(page, index);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex'));
const index = parseInt(this.getAttribute('data-profileindex'), 10);
editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]);
});
}
@ -353,7 +353,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteTranscodingProfile(page, index);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex'));
const index = parseInt(this.getAttribute('data-profileindex'), 10);
editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]);
});
}
@ -437,7 +437,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteContainerProfile(page, index);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex'));
const index = parseInt(this.getAttribute('data-profileindex'), 10);
editContainerProfile(page, currentProfile.ContainerProfiles[index]);
});
}
@ -509,7 +509,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteCodecProfile(page, index);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex'));
const index = parseInt(this.getAttribute('data-profileindex'), 10);
editCodecProfile(page, currentProfile.CodecProfiles[index]);
});
}
@ -589,7 +589,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteResponseProfile(page, index);
});
$('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex'));
const index = parseInt(this.getAttribute('data-profileindex'), 10);
editResponseProfile(page, currentProfile.ResponseProfiles[index]);
});
}

View file

@ -98,8 +98,8 @@ import alert from '../../components/alert';
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0');
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0');
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10);
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10);
config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value;
config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked;
config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked;

View file

@ -92,7 +92,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
function showCardMenu(page, elem, virtualFolders) {
const card = dom.parentWithClass(elem, 'card');
const index = parseInt(card.getAttribute('data-index'));
const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index];
const menuItems = [];
menuItems.push({
@ -192,7 +192,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
});
$('.editLibrary', divVirtualFolders).on('click', function () {
const card = $(this).parents('.card')[0];
const index = parseInt(card.getAttribute('data-index'));
const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index];
if (virtualFolder.ItemId) {

View file

@ -3,7 +3,7 @@ import loading from '../../components/loading/loading';
import globalize from '../../scripts/globalize';
import '../../elements/emby-button/emby-button';
import '../../components/listview/listview.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';

View file

@ -26,7 +26,7 @@
<div class="fieldDescription">${LabelDummyChapterDurationHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="valDummyChapterCount" label="${LabelDummyChapterCount}" min="1"></input>
<input is="emby-input" type="number" id="valDummyChapterCount" label="${LabelDummyChapterCount}" min="0"></input>
<div class="fieldDescription">${LabelDummyChapterCountHelp}</div>
</div>
<div class="selectContainer">

View file

@ -68,7 +68,7 @@ function renderPackage(pkg, installedPlugins, page) {
if (installedPlugin) {
const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>');
$('#pCurrentVersion', page).show().text(currentVersionText);
$('#pCurrentVersion', page).show().html(currentVersionText);
} else {
$('#pCurrentVersion', page).hide().text('');
}

View file

@ -235,7 +235,7 @@ import { getParameterByName } from '../../../utils/url.ts';
const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger');
if (btnDeleteTrigger) {
ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index')));
ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10));
}
});
view.addEventListener('viewshow', function () {

View file

@ -15,7 +15,7 @@ import Dashboard from '../../utils/dashboard';
loading.show();
const form = this;
ApiClient.getServerConfiguration().then(function (config) {
config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'));
config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10);
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
});

View file

@ -1,5 +1,6 @@
import { intervalToDuration } from 'date-fns';
import DOMPurify from 'dompurify';
import { marked } from 'marked';
import escapeHtml from 'escape-html';
import isEqual from 'lodash-es/isEqual';
@ -22,7 +23,7 @@ import libraryMenu from '../../scripts/libraryMenu';
import globalize from '../../scripts/globalize';
import browser from '../../scripts/browser';
import { playbackManager } from '../../components/playback/playbackmanager';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-button/emby-button';
@ -877,7 +878,7 @@ function renderOverview(page, item) {
const overviewElements = page.querySelectorAll('.overview');
if (overviewElements.length > 0) {
const overview = DOMPurify.sanitize(item.Overview || '');
const overview = DOMPurify.sanitize(marked(item.Overview || ''));
if (overview) {
for (const overviewElemnt of overviewElements) {
@ -1063,7 +1064,7 @@ function renderTagline(page, item) {
}
}
function renderDetails(page, item, apiClient, context, isStatic) {
function renderDetails(page, item, apiClient, context) {
renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient);
@ -1083,7 +1084,7 @@ function renderDetails(page, item, apiClient, context, isStatic) {
}
renderTags(page, item);
renderSeriesAirTime(page, item, isStatic);
renderSeriesAirTime(page, item);
}
function enableScrollX() {
@ -1156,12 +1157,7 @@ function renderMoreFromArtist(view, item, apiClient) {
const section = view.querySelector('.moreFromArtistSection');
if (section) {
if (item.Type === 'MusicArtist') {
if (!apiClient.isMinServerVersion('3.4.1.19')) {
section.classList.add('hide');
return;
}
} else if (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length) {
if (item.Type !== 'MusicArtist' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) {
section.classList.add('hide');
return;
}
@ -1262,7 +1258,7 @@ function renderSimilarItems(page, item, context) {
}
}
function renderSeriesAirTime(page, item, isStatic) {
function renderSeriesAirTime(page, item) {
const seriesAirTime = page.querySelector('#seriesAirTime');
if (item.Type != 'Series') {
seriesAirTime.classList.add('hide');
@ -1281,19 +1277,6 @@ function renderSeriesAirTime(page, item, isStatic) {
if (item.AirTime) {
html += ' at ' + item.AirTime;
}
if (item.Studios.length) {
if (isStatic) {
html += ' on ' + escapeHtml(item.Studios[0].Name);
} else {
const context = inferContext(item);
const href = appRouter.getRouteUrl(item.Studios[0], {
context: context,
itemType: 'Studio',
serverId: item.ServerId
});
html += ' on <a class="textlink button-link" is="emby-linkbutton" href="' + href + '">' + escapeHtml(item.Studios[0].Name) + '</a>';
}
}
if (html) {
html = (item.Status == 'Ended' ? 'Aired ' : 'Airs ') + html;
seriesAirTime.innerHTML = html;

View file

@ -6,7 +6,7 @@ import globalize from '../../scripts/globalize';
import * as mainTabsManager from '../../components/maintabsmanager';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import imageLoader from '../../components/images/imageLoader';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../elements/emby-tabs/emby-tabs';
import '../../elements/emby-button/emby-button';
@ -222,17 +222,17 @@ export default function (view, params) {
}
function onBeforeTabChange(evt) {
preLoadTab(view, parseInt(evt.detail.selectedTabIndex));
preLoadTab(view, parseInt(evt.detail.selectedTabIndex, 10));
}
function onTabChange(evt) {
const previousTabController = tabControllers[parseInt(evt.detail.previousIndex)];
const previousTabController = tabControllers[parseInt(evt.detail.previousIndex, 10)];
if (previousTabController && previousTabController.onHide) {
previousTabController.onHide();
}
loadTab(view, parseInt(evt.detail.selectedTabIndex));
loadTab(view, parseInt(evt.detail.selectedTabIndex, 10));
}
function getTabContainers() {
@ -339,7 +339,7 @@ export default function (view, params) {
let isViewRestored;
const self = this;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'));
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'), 10);
let initialTabIndex = currentTabIndex;
let lastFullRender = 0;
[].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) {

View file

@ -7,7 +7,7 @@ import layoutManager from '../components/layoutManager';
import loading from '../components/loading/loading';
import browser from '../scripts/browser';
import '../components/listview/listview.scss';
import '../assets/css/flexstyles.scss';
import '../styles/flexstyles.scss';
import '../elements/emby-itemscontainer/emby-itemscontainer';
import '../components/cardbuilder/card.scss';
import 'material-design-icons-iconfont';

View file

@ -9,11 +9,11 @@ import imageLoader from '../../components/images/imageLoader';
import libraryMenu from '../../scripts/libraryMenu';
import * as mainTabsManager from '../../components/maintabsmanager';
import globalize from '../../scripts/globalize';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../elements/emby-tabs/emby-tabs';
import '../../elements/emby-button/emby-button';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import Dashboard from '../../utils/dashboard';
/* eslint-disable indent */
@ -245,11 +245,11 @@ import Dashboard from '../../utils/dashboard';
}
function onBeforeTabChange(e) {
preLoadTab(view, parseInt(e.detail.selectedTabIndex));
preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10));
}
function onTabChange(e) {
loadTab(view, parseInt(e.detail.selectedTabIndex));
loadTab(view, parseInt(e.detail.selectedTabIndex, 10));
}
function getTabContainers() {
@ -350,7 +350,7 @@ import Dashboard from '../../utils/dashboard';
}
}
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId));
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10);
const suggestionsTabIndex = 1;
this.initTab = function () {

View file

@ -15,10 +15,10 @@ import { appHost } from '../../../components/apphost';
import layoutManager from '../../../components/layoutManager';
import * as userSettings from '../../../scripts/settings/userSettings';
import keyboardnavigation from '../../../scripts/keyboardNavigation';
import '../../../assets/css/scrollstyles.scss';
import '../../../styles/scrollstyles.scss';
import '../../../elements/emby-slider/emby-slider';
import '../../../elements/emby-button/paper-icon-button-light';
import '../../../assets/css/videoosd.scss';
import '../../../styles/videoosd.scss';
import ServerConnections from '../../../components/ServerConnections';
import shell from '../../../scripts/shell';
import SubtitleSync from '../../../components/subtitlesync/subtitlesync';
@ -975,7 +975,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
title: globalize.translate('Audio'),
positionTo: positionTo
}).then(function (id) {
const index = parseInt(id);
const index = parseInt(id, 10);
if (index !== currentIndex) {
playbackManager.setAudioStreamIndex(index, player);
@ -1022,7 +1022,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
positionTo
}).then(function (id) {
if (id) {
const index = parseInt(id);
const index = parseInt(id, 10);
if (index !== currentIndex) {
playbackManager.setSecondarySubtitleStreamIndex(index, player);
}
@ -1099,7 +1099,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
console.error(e);
}
} else {
const index = parseInt(id);
const index = parseInt(id, 10);
if (index !== currentIndex) {
playbackManager.setSubtitleStreamIndex(index, player);
@ -1633,7 +1633,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
ms /= 100;
ms *= value;
ms += programStartDateMs;
return '<h1 class="sliderBubbleText">' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms)), true) + '</h1>';
return '<h1 class="sliderBubbleText">' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '</h1>';
}
return '--:--';

View file

@ -10,7 +10,7 @@ import actionSheet from '../../../components/actionSheet/actionSheet';
import dom from '../../../scripts/dom';
import browser from '../../../scripts/browser';
import 'material-design-icons-iconfont';
import '../../../assets/css/flexstyles.scss';
import '../../../styles/flexstyles.scss';
import '../../../elements/emby-scroller/emby-scroller';
import '../../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../../components/cardbuilder/card.scss';

View file

@ -9,7 +9,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
import { playbackManager } from '../../components/playback/playbackmanager';
import * as mainTabsManager from '../../components/maintabsmanager';
import globalize from '../../scripts/globalize';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../elements/emby-button/emby-button';
import Dashboard from '../../utils/dashboard';
@ -224,11 +224,11 @@ import autoFocuser from '../../components/autoFocuser';
export default function (view, params) {
function onBeforeTabChange(e) {
preLoadTab(view, parseInt(e.detail.selectedTabIndex));
preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10));
}
function onTabChange(e) {
const newIndex = parseInt(e.detail.selectedTabIndex);
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
loadTab(view, newIndex);
}
@ -340,7 +340,7 @@ import autoFocuser from '../../components/autoFocuser';
}
const self = this;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId));
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10);
const suggestionsTabIndex = 1;
self.initTab = function () {

View file

@ -4,7 +4,7 @@ import datetime from '../../scripts/datetime';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import imageLoader from '../../components/images/imageLoader';
import globalize from '../../scripts/globalize';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
/* eslint-disable indent */

View file

@ -1,6 +1,6 @@
import loading from '../../../components/loading/loading';
import globalize from '../../../scripts/globalize';
import '../../../assets/css/dashboard.scss';
import '../../../styles/dashboard.scss';
import '../../../elements/emby-input/emby-input';
import '../../../elements/emby-button/emby-button';
import Dashboard from '../../../utils/dashboard';

View file

@ -414,7 +414,7 @@ import Sortable from 'sortablejs';
clearRefreshInterval(itemsContainer);
if (!intervalMs) {
intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0');
intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10);
}
if (intervalMs) {

View file

@ -3,8 +3,8 @@
const ProgressBarPrototype = Object.create(HTMLDivElement.prototype);
function onAutoTimeProgress() {
const start = parseInt(this.getAttribute('data-starttime'));
const end = parseInt(this.getAttribute('data-endtime'));
const start = parseInt(this.getAttribute('data-starttime'), 10);
const end = parseInt(this.getAttribute('data-endtime'), 10);
const now = new Date().getTime();
const total = end - start;

View file

@ -91,7 +91,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
return 0;
}
value = parseInt(value);
value = parseInt(value, 10);
if (isNaN(value)) {
return 0;
}

View file

@ -75,7 +75,7 @@ const Scroller: FC<ScrollerProps> = ({
return 0;
}
if (isNaN(parseInt(value))) {
if (isNaN(parseInt(value, 10))) {
return 0;
}

View file

@ -5,7 +5,7 @@ import browser from '../../scripts/browser';
import focusManager from '../../components/focusManager';
import layoutManager from '../../components/layoutManager';
import './emby-tabs.scss';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
/* eslint-disable indent */
const EmbyTabs = Object.create(HTMLDivElement.prototype);
@ -75,11 +75,11 @@ import '../../assets/css/scrollstyles.scss';
current.classList.remove(activeButtonClass);
}
const previousIndex = current ? parseInt(current.getAttribute('data-index')) : null;
const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null;
setActiveTabButton(tabButton);
const index = parseInt(tabButton.getAttribute('data-index'));
const index = parseInt(tabButton.getAttribute('data-index'), 10);
triggerBeforeTabChange(tabs, index, previousIndex);
@ -194,7 +194,7 @@ import '../../assets/css/scrollstyles.scss';
initScroller(this);
const current = this.querySelector('.' + activeButtonClass);
const currentIndex = current ? parseInt(current.getAttribute('data-index')) : parseInt(this.getAttribute('data-index') || '0');
const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10);
if (currentIndex !== -1) {
this.selectedTabIndex = currentIndex;

View file

@ -14,7 +14,7 @@ function calculateOffset(textarea) {
let offset = 0;
for (let i = 0; i < props.length; i++) {
offset += parseInt(style[props[i]]);
offset += parseInt(style[props[i]], 10);
}
return offset;
}

View file

@ -6,7 +6,7 @@ import 'intersection-observer';
import 'classlist.js';
import 'whatwg-fetch';
import 'resize-observer-polyfill';
import './assets/css/site.scss';
import './styles/site.scss';
import React, { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import Events from './utils/events.ts';
@ -24,7 +24,6 @@ import { appRouter, history } from './components/appRouter';
import './elements/emby-button/emby-button';
import './scripts/autoThemes';
import './scripts/libraryMenu';
import './scripts/routes';
import './components/themeMediaPlayer';
import './scripts/autoBackdrops';
import { pageClassOn, serverAddress } from './utils/dashboard';
@ -39,6 +38,10 @@ import { currentSettings } from './scripts/settings/userSettings';
import taskButton from './scripts/taskbutton';
import App from './App.tsx';
import './styles/livetv.scss';
import './styles/dashboard.scss';
import './styles/detailtable.scss';
function loadCoreDictionary() {
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es_419', 'es-ar', 'es_do', 'es-mx', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
const translations = languages.map(function (language) {
@ -89,13 +92,13 @@ function onGlobalizeInit() {
if (browser.tv && !browser.android) {
console.debug('using system fonts with explicit sizes');
import('./assets/css/fonts.sized.scss');
import('./styles/fonts.sized.scss');
} else {
console.debug('using default fonts');
import('./assets/css/fonts.scss');
import('./styles/fonts.scss');
}
import('./assets/css/librarybrowser.scss');
import('./styles/librarybrowser.scss');
loadPlugins().then(onAppReady);
}
@ -135,7 +138,7 @@ async function onAppReady() {
console.debug('onAppReady: loading dependencies');
if (browser.iOS) {
import('./assets/css/ios.scss');
import('./styles/ios.scss');
}
Events.on(appHost, 'resume', () => {

View file

@ -6,7 +6,7 @@
import browser from '../../scripts/browser';
import dom from '../../scripts/dom';
import './navdrawer.scss';
import '../../assets/css/scrollstyles.scss';
import '../../styles/scrollstyles.scss';
import globalize from '../../scripts/globalize';
function getTouches(e) {

View file

@ -8,7 +8,7 @@ import layoutManager from '../components/layoutManager';
import dom from '../scripts/dom';
import focusManager from '../components/focusManager';
import ResizeObserver from 'resize-observer-polyfill';
import '../assets/css/scrollstyles.scss';
import '../styles/scrollstyles.scss';
import globalize from '../scripts/globalize';
/**

View file

@ -1,10 +1,11 @@
/* eslint-disable indent */
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
class BackdropScreensaver {
constructor() {
this.name = 'Backdrop ScreenSaver';
this.type = 'screensaver';
this.type = PluginType.Screensaver;
this.id = 'backdropscreensaver';
this.supportsAnonymous = false;
}

View file

@ -9,6 +9,7 @@ import TableOfContents from './tableOfContents';
import dom from '../../scripts/dom';
import { translateHtml } from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import '../../elements/emby-button/paper-icon-button-light';
@ -19,7 +20,7 @@ import './style.scss';
export class BookPlayer {
constructor() {
this.name = 'Book Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'bookplayer';
this.priority = 1;

View file

@ -5,6 +5,7 @@ import globalize from '../../scripts/globalize';
import castSenderApiLoader from './castSenderApi';
import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js
@ -569,7 +570,7 @@ class ChromecastPlayer {
constructor() {
// playbackManager needs this
this.name = PlayerName;
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'chromecast';
this.isLocalPlayer = false;
this.lastPlayerData = {};
@ -683,7 +684,7 @@ class ChromecastPlayer {
}
seek(position) {
position = parseInt(position);
position = parseInt(position, 10);
position = position / 10000000;

View file

@ -6,13 +6,14 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
import { appRouter } from '../../components/appRouter';
import ServerConnections from '../../components/ServerConnections';
import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import './style.scss';
export class ComicsPlayer {
constructor() {
this.name = 'Comics Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'comicsplayer';
this.priority = 1;
this.imageMap = new Map();

View file

@ -2,6 +2,7 @@ import globalize from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings';
import { appHost } from '../../components/apphost';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
// TODO: Replace with date-fns
// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
@ -46,7 +47,7 @@ function showIsoMessage() {
class ExpirementalPlaybackWarnings {
constructor() {
this.name = 'Experimental playback warnings';
this.type = 'preplayintercept';
this.type = PluginType.PreplayIntercept;
this.id = 'expirementalplaybackwarnings';
}

View file

@ -3,6 +3,7 @@ import { appHost } from '../../components/apphost';
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
import profileBuilder from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
function getDefaultProfile() {
@ -48,6 +49,9 @@ function supportsFade() {
function requireHlsPlayer(callback) {
import('hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
});
@ -85,7 +89,7 @@ class HtmlAudioPlayer {
const self = this;
self.name = 'Html Audio Player';
self.type = 'mediaplayer';
self.type = PluginType.MediaPlayer;
self.id = 'htmlaudioplayer';
// Let any players created by plugins take priority

View file

@ -30,8 +30,10 @@ import ServerConnections from '../../components/ServerConnections';
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import { includesAny } from '../../utils/container.ts';
import debounce from 'lodash-es/debounce';
/**
* Returns resolved URL.
@ -106,6 +108,9 @@ function tryRemoveElement(elem) {
function requireHlsPlayer(callback) {
import('hls.js').then(({default: hls}) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
});
@ -166,7 +171,7 @@ function tryRemoveElement(elem) {
/**
* @type {string}
*/
type = 'mediaplayer';
type = PluginType.MediaPlayer;
/**
* @type {string}
*/
@ -378,7 +383,7 @@ function tryRemoveElement(elem) {
this.#currentTime = null;
this.resetSubtitleOffset();
if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset();
return this.createMediaElement(options).then(elem => {
return this.updateVideoUrl(options).then(() => {
@ -571,7 +576,12 @@ function tryRemoveElement(elem) {
}
}
setSubtitleOffset(offset) {
setSubtitleOffset = debounce(this._setSubtitleOffset, 100);
/**
* @private
*/
_setSubtitleOffset(offset) {
const offsetValue = parseFloat(offset);
// if .ass currently rendering
@ -620,6 +630,41 @@ function tryRemoveElement(elem) {
return relativeOffset;
}
/**
* @private
* These browsers will not clear the existing active cue when setting an offset
* for native TextTracks.
* Any previous text tracks that are on the screen when the offset changes will remain next
* to the new tracks until they reach the end time of the new offset's instance of the track.
*/
requiresHidingActiveCuesOnOffsetChange() {
return !!browser.firefox;
}
/**
* @private
*/
hideTextTrackWithActiveCues(currentTrack) {
if (currentTrack.activeCues) {
currentTrack.mode = 'hidden';
}
}
/**
* Forces the active cue to clear by disabling then re-enabling the track.
* The track mode is reverted inside of a 0ms timeout to free up the track
* and allow it to disable and clear the active cue.
* @private
*/
forceClearTextTrackActiveCues(currentTrack) {
if (currentTrack.activeCues) {
currentTrack.mode = 'disabled';
setTimeout(() => {
currentTrack.mode = 'showing';
}, 0);
}
}
/**
* @private
*/
@ -629,11 +674,21 @@ function tryRemoveElement(elem) {
if (offsetValue === 0) {
return;
}
const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange();
if (shouldClearActiveCues) {
this.hideTextTrackWithActiveCues(currentTrack);
}
Array.from(currentTrack.cues)
.forEach(function (cue) {
cue.startTime -= offsetValue;
cue.endTime -= offsetValue;
});
if (shouldClearActiveCues) {
this.forceClearTextTrackActiveCues(currentTrack);
}
}
}
@ -771,6 +826,8 @@ function tryRemoveElement(elem) {
}
destroy() {
this.setSubtitleOffset.cancel();
destroyHlsPlayer(this);
destroyFlvPlayer(this);

View file

@ -1,8 +1,10 @@
import { PluginType } from '../../types/plugin.ts';
export default function () {
const self = this;
self.name = 'Logo ScreenSaver';
self.type = 'screensaver';
self.type = PluginType.Screensaver;
self.id = 'logoscreensaver';
self.supportsAnonymous = true;

View file

@ -4,6 +4,7 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import dom from '../../scripts/dom';
import { appRouter } from '../../components/appRouter';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
import './style.scss';
@ -12,7 +13,7 @@ import '../../elements/emby-button/paper-icon-button-light';
export class PdfPlayer {
constructor() {
this.name = 'PDF Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'pdfplayer';
this.priority = 1;
@ -261,7 +262,7 @@ export class PdfPlayer {
for (const page of pages) {
if (!this.pages[page]) {
this.pages[page] = document.createElement('canvas');
this.renderPage(this.pages[page], parseInt(page.slice(4)));
this.renderPage(this.pages[page], parseInt(page.slice(4), 10));
}
}

View file

@ -1,9 +1,10 @@
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
export default class PhotoPlayer {
constructor() {
this.name = 'Photo Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'photoplayer';
this.priority = 1;
}

View file

@ -1,6 +1,7 @@
import globalize from '../../scripts/globalize';
import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
function showErrorMessage() {
return alert(globalize.translate('MessagePlayAccessRestricted'));
@ -9,7 +10,7 @@ function showErrorMessage() {
class PlayAccessValidation {
constructor() {
this.name = 'Playback validation';
this.type = 'preplayintercept';
this.type = PluginType.PreplayIntercept;
this.id = 'playaccessvalidation';
this.order = -2;
}

View file

@ -1,6 +1,7 @@
import { playbackManager } from '../../components/playback/playbackmanager';
import serverNotifications from '../../scripts/serverNotifications';
import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
function getActivePlayerId() {
@ -181,7 +182,7 @@ class SessionPlayer {
const self = this;
this.name = 'Remote Control';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.isLocalPlayer = false;
this.id = 'remoteplayer';

View file

@ -254,7 +254,7 @@ class Manager {
if (typeof cmd.When === 'string') {
cmd.When = new Date(cmd.When);
cmd.EmittedAt = new Date(cmd.EmittedAt);
cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks) : null;
cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks, 10) : null;
}
if (!this.isSyncPlayEnabled()) {

View file

@ -5,8 +5,9 @@ import SyncPlay from './core';
import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer';
import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer';
import { Plugin, PluginType } from '../../types/plugin';
class SyncPlayPlugin {
class SyncPlayPlugin implements Plugin {
name: string;
id: string;
type: string;
@ -17,7 +18,7 @@ class SyncPlayPlugin {
this.id = 'syncplay';
// NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but
// SyncPlay needs refactored so it does not have an independent playback manager.
this.type = 'syncplay';
this.type = PluginType.SyncPlay;
this.priority = 1;
this.init();

View file

@ -2,6 +2,7 @@ import browser from '../../scripts/browser';
import { appRouter } from '../../components/appRouter';
import loading from '../../components/loading/loading';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
/* globals YT */
@ -197,7 +198,7 @@ function setCurrentSrc(instance, elem, options) {
class YoutubePlayer {
constructor() {
this.name = 'Youtube Player';
this.type = 'mediaplayer';
this.type = PluginType.MediaPlayer;
this.id = 'youtubeplayer';
// Let any players created by plugins take priority

View file

@ -4,7 +4,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES, toAsyncPageRoute } from './asyncRoutes';
import ConnectionRequired from '../components/ConnectionRequired';
import ServerContentPage from '../components/ServerContentPage';
import { LEGACY_ADMIN_ROUTES, LEGACY_USER_ROUTES, toViewManagerPageRoute } from './legacyRoutes';
import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES, toViewManagerPageRoute } from './legacyRoutes';
const AppRoutes = () => (
<Routes>
@ -28,6 +28,8 @@ const AppRoutes = () => (
{/* Public routes */}
<Route path='/' element={<ConnectionRequired isUserRequired={false} />}>
<Route index element={<Navigate replace to='/home.html' />} />
{LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
</Route>
{/* Suppress warnings for unhandled routes */}

View file

@ -21,4 +21,5 @@ export function toViewManagerPageRoute(route: LegacyRoute) {
}
export * from './admin';
export * from './public';
export * from './user';

View file

@ -0,0 +1,81 @@
import { LegacyRoute } from '.';
export const LEGACY_PUBLIC_ROUTES: LegacyRoute[] = [
{
path: 'addserver.html',
pageProps: {
controller: 'session/addServer/index',
view: 'session/addServer/index.html'
}
},
{
path: 'selectserver.html',
pageProps: {
controller: 'session/selectServer/index',
view: 'session/selectServer/index.html'
}
},
{
path: 'login.html',
pageProps: {
controller: 'session/login/index',
view: 'session/login/index.html'
}
},
{
path: 'forgotpassword.html',
pageProps: {
controller: 'session/forgotPassword/index',
view: 'session/forgotPassword/index.html'
}
},
{
path: 'forgotpasswordpin.html',
pageProps: {
controller: 'session/resetPassword/index',
view: 'session/resetPassword/index.html'
}
},
{
path: 'wizardremoteaccess.html',
pageProps: {
controller: 'wizard/remote/index',
view: 'wizard/remote/index.html'
}
},
{
path: 'wizardfinish.html',
pageProps: {
controller: 'wizard/finish/index',
view: 'wizard/finish/index.html'
}
},
{
path: 'wizardlibrary.html',
pageProps: {
controller: 'dashboard/library',
view: 'wizard/library.html'
}
},
{
path: 'wizardsettings.html',
pageProps: {
controller: 'wizard/settings/index',
view: 'wizard/settings/index.html'
}
},
{
path: 'wizardstart.html',
pageProps: {
controller: 'wizard/start/index',
view: 'wizard/start/index.html'
}
},
{
path: 'wizarduser.html',
pageProps: {
controller: 'wizard/user/index',
view: 'wizard/user/index.html'
}
}
];

View file

@ -55,7 +55,7 @@ const getTabs = () => {
const Movies: FC = () => {
const [ searchParams ] = useSearchParams();
const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString());
const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString(), 10);
const [ selectedIndex, setSelectedIndex ] = useState(currentTabIndex);
const element = useRef<HTMLDivElement>(null);
@ -95,7 +95,7 @@ const Movies: FC = () => {
};
const onTabChange = useCallback((e: { detail: { selectedTabIndex: string; }; }) => {
const newIndex = parseInt(e.detail.selectedTabIndex);
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
setSelectedIndex(newIndex);
}, []);

View file

@ -228,8 +228,8 @@ const UserEdit: FunctionComponent = () => {
user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked;
user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked;
user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat((page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value || '0'));
user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0');
user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0');
user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0', 10);
user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0', 10);
user.Policy.AuthenticationProviderId = (page.querySelector('#selectLoginProvider') as HTMLSelectElement).value;
user.Policy.PasswordResetProviderId = (page.querySelector('#selectPasswordResetProvider') as HTMLSelectElement).value;
user.Policy.EnableContentDeletion = (page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).checked;

View file

@ -11,7 +11,7 @@ import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../components/cardbuilder/card.scss';
import '../../components/indicators/indicators.scss';
import '../../assets/css/flexstyles.scss';
import '../../styles/flexstyles.scss';
import Page from '../../components/Page';
type MenuEntry = {

View file

@ -224,7 +224,7 @@ const uaMatch = function (ua) {
version = version || match[2] || '0';
let versionMajor = parseInt(version.split('.')[0]);
let versionMajor = parseInt(version.split('.')[0], 10);
if (isNaN(versionMajor)) {
versionMajor = 0;
@ -295,7 +295,7 @@ if (browser.web0s) {
delete browser.safari;
const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/);
browser.tizenVersion = parseInt(v[1]);
browser.tizenVersion = parseInt(v[1], 10);
} else {
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
}

View file

@ -303,7 +303,7 @@ import { getParameterByName } from '../utils/url.ts';
updateEditorNode(this, item);
}).on('pagebeforeshow', '.metadataEditorPage', function () {
/* eslint-disable-next-line @babel/no-unused-expressions */
import('../assets/css/metadataeditor.scss');
import('../styles/metadataeditor.scss');
}).on('pagebeforeshow', '.metadataEditorPage', function () {
const page = this;
Dashboard.getCurrentUser().then(function (user) {

Some files were not shown because too many files have changed in this diff Show more