mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into fix_long_getItems_request_URL
This commit is contained in:
commit
f516e18fb9
16 changed files with 293 additions and 160 deletions
|
@ -46,6 +46,7 @@ module.exports = {
|
||||||
'keyword-spacing': ['error'],
|
'keyword-spacing': ['error'],
|
||||||
'no-throw-literal': ['error'],
|
'no-throw-literal': ['error'],
|
||||||
'max-statements-per-line': ['error'],
|
'max-statements-per-line': ['error'],
|
||||||
|
'max-params': ['error', 7],
|
||||||
'no-duplicate-imports': ['error'],
|
'no-duplicate-imports': ['error'],
|
||||||
'no-empty-function': ['error'],
|
'no-empty-function': ['error'],
|
||||||
'no-floating-decimal': ['error'],
|
'no-floating-decimal': ['error'],
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
- [Vankerkom](https://github.com/vankerkom)
|
- [Vankerkom](https://github.com/vankerkom)
|
||||||
- [edvwib](https://github.com/edvwib)
|
- [edvwib](https://github.com/edvwib)
|
||||||
- [Rob Farraher](https://github.com/farraherbg)
|
- [Rob Farraher](https://github.com/farraherbg)
|
||||||
|
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
|
||||||
- [Merlin Sievers](https://github.com/dann-merlin)
|
- [Merlin Sievers](https://github.com/dann-merlin)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
38
package-lock.json
generated
38
package-lock.json
generated
|
@ -30,7 +30,7 @@
|
||||||
"flv.js": "1.6.2",
|
"flv.js": "1.6.2",
|
||||||
"headroom.js": "0.12.0",
|
"headroom.js": "0.12.0",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"hls.js": "0.14.17",
|
"hls.js": "1.3.4",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"jellyfin-apiclient": "1.10.0",
|
"jellyfin-apiclient": "1.10.0",
|
||||||
"jquery": "3.6.3",
|
"jquery": "3.6.3",
|
||||||
|
@ -7301,7 +7301,8 @@
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
|
@ -8467,13 +8468,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hls.js": {
|
"node_modules/hls.js": {
|
||||||
"version": "0.14.17",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz",
|
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.4.tgz",
|
||||||
"integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==",
|
"integrity": "sha512-iFEwVqtEDk6sKotcTwtJ5OMo/nuDTk9PrpB8FI2J2WYf8EriTVfR4FaK0aNyYtwbYeRSWCXJKlz23xeREdlNYg=="
|
||||||
"dependencies": {
|
|
||||||
"eventemitter3": "^4.0.3",
|
|
||||||
"url-toolkit": "^2.1.6"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
|
@ -18229,11 +18226,6 @@
|
||||||
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
|
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/url-toolkit": {
|
|
||||||
"version": "2.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.3.tgz",
|
|
||||||
"integrity": "sha512-Da75SQoxsZ+2wXS56CZBrj2nukQ4nlGUZUP/dqUBG5E1su5GKThgT94Q00x81eVII7AyS1Pn+CtTTZ4Z0pLUtQ=="
|
|
||||||
},
|
|
||||||
"node_modules/use": {
|
"node_modules/use": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||||
|
@ -24492,7 +24484,8 @@
|
||||||
"eventemitter3": {
|
"eventemitter3": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
|
@ -25405,13 +25398,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hls.js": {
|
"hls.js": {
|
||||||
"version": "0.14.17",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz",
|
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.4.tgz",
|
||||||
"integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==",
|
"integrity": "sha512-iFEwVqtEDk6sKotcTwtJ5OMo/nuDTk9PrpB8FI2J2WYf8EriTVfR4FaK0aNyYtwbYeRSWCXJKlz23xeREdlNYg=="
|
||||||
"requires": {
|
|
||||||
"eventemitter3": "^4.0.3",
|
|
||||||
"url-toolkit": "^2.1.6"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"hoist-non-react-statics": {
|
"hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
|
@ -32698,11 +32687,6 @@
|
||||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"url-toolkit": {
|
|
||||||
"version": "2.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.3.tgz",
|
|
||||||
"integrity": "sha512-Da75SQoxsZ+2wXS56CZBrj2nukQ4nlGUZUP/dqUBG5E1su5GKThgT94Q00x81eVII7AyS1Pn+CtTTZ4Z0pLUtQ=="
|
|
||||||
},
|
|
||||||
"use": {
|
"use": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||||
|
|
|
@ -89,7 +89,7 @@
|
||||||
"flv.js": "1.6.2",
|
"flv.js": "1.6.2",
|
||||||
"headroom.js": "0.12.0",
|
"headroom.js": "0.12.0",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"hls.js": "0.14.17",
|
"hls.js": "1.3.4",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"jellyfin-apiclient": "1.10.0",
|
"jellyfin-apiclient": "1.10.0",
|
||||||
"jquery": "3.6.3",
|
"jquery": "3.6.3",
|
||||||
|
|
|
@ -773,27 +773,24 @@ import { appRouter } from '../appRouter';
|
||||||
* @param {Object} item - Item used to generate the footer text.
|
* @param {Object} item - Item used to generate the footer text.
|
||||||
* @param {Object} apiClient - API client instance.
|
* @param {Object} apiClient - API client instance.
|
||||||
* @param {Object} options - Options used to generate the footer text.
|
* @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} footerClass - CSS classes of the footer element.
|
||||||
* @param {string} progressHtml - HTML markup of the progress bar element.
|
* @param {string} progressHtml - HTML markup of the progress bar element.
|
||||||
* @param {string} logoUrl - URL of the logo for the item.
|
* @param {Object} flags - Various flags for the footer
|
||||||
* @param {boolean} isOuterFooter - Flag to mark the text as outer footer.
|
* @param {Object} urls - Various urls for the footer
|
||||||
* @returns {string} HTML markup of the card's footer text element.
|
* @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;
|
item = item.ProgramInfo || item;
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
if (logoUrl) {
|
if (urls.logoUrl) {
|
||||||
html += '<div class="lazy cardFooterLogo" data-src="' + logoUrl + '"></div>';
|
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>`;
|
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;
|
let titleAdded;
|
||||||
|
|
||||||
if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
|
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) {
|
if (item.SeriesId) {
|
||||||
lines.push(getTextActionButton({
|
lines.push(getTextActionButton({
|
||||||
Id: item.SeriesId,
|
Id: item.SeriesId,
|
||||||
|
@ -835,7 +832,7 @@ import { appRouter } from '../appRouter';
|
||||||
}
|
}
|
||||||
|
|
||||||
let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length);
|
let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length);
|
||||||
if (!showMediaTitle && !titleAdded && (showTitle || forceName)) {
|
if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) {
|
||||||
showMediaTitle = true;
|
showMediaTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,7 +853,7 @@ import { appRouter } from '../appRouter';
|
||||||
|
|
||||||
if (showOtherText) {
|
if (showOtherText) {
|
||||||
if (options.showParentTitle && parentTitleUnderneath) {
|
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].Type = 'MusicArtist';
|
||||||
item.AlbumArtists[0].IsFolder = true;
|
item.AlbumArtists[0].IsFolder = true;
|
||||||
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
|
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 = [];
|
lines = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overlayText && showTitle) {
|
if (flags.overlayText && showTitle) {
|
||||||
lines = [escapeHtml(item.Name)];
|
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) {
|
if (progressHtml) {
|
||||||
html += progressHtml;
|
html += progressHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (html && (!isOuterFooter || logoUrl || options.cardLayout)) {
|
if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) {
|
||||||
html = '<div class="' + footerClass + '">' + html;
|
html = '<div class="' + footerClass + '">' + html;
|
||||||
|
|
||||||
//cardFooter
|
//cardFooter
|
||||||
|
@ -1217,7 +1214,6 @@ import { appRouter } from '../appRouter';
|
||||||
|
|
||||||
const forceName = imgInfo.forceName;
|
const forceName = imgInfo.forceName;
|
||||||
|
|
||||||
const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder');
|
|
||||||
const overlayText = options.overlayText;
|
const overlayText = options.overlayText;
|
||||||
|
|
||||||
let cardImageContainerClass = 'cardImageContainer';
|
let cardImageContainerClass = 'cardImageContainer';
|
||||||
|
@ -1265,7 +1261,7 @@ import { appRouter } from '../appRouter';
|
||||||
logoUrl = null;
|
logoUrl = null;
|
||||||
|
|
||||||
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
|
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;
|
footerOverlayed = true;
|
||||||
} else if (progressHtml) {
|
} else if (progressHtml) {
|
||||||
innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">';
|
innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">';
|
||||||
|
@ -1292,7 +1288,7 @@ import { appRouter } from '../appRouter';
|
||||||
logoUrl = null;
|
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) {
|
if (outerCardFooter && !options.cardLayout) {
|
||||||
|
|
|
@ -345,7 +345,9 @@ function Guide(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) {
|
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();
|
hideLoading();
|
||||||
});
|
});
|
||||||
|
@ -667,7 +669,7 @@ function Guide(options) {
|
||||||
return (channelIndex * 10000000) + (start.getTime() / 60000);
|
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) {
|
programs.sort(function (a, b) {
|
||||||
return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels);
|
return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels);
|
||||||
});
|
});
|
||||||
|
@ -689,11 +691,11 @@ function Guide(options) {
|
||||||
items = {};
|
items = {};
|
||||||
renderPrograms(context, date, channels, programs, renderOptions);
|
renderPrograms(context, date, channels, programs, renderOptions);
|
||||||
|
|
||||||
if (focusProgramOnRender) {
|
if (guideOptions.focusProgramOnRender) {
|
||||||
focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs);
|
focusProgram(context, itemId, channelRowId, guideOptions.focusToTimeMs, guideOptions.startTimeOfDayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs);
|
scrollProgramGridToTimeMs(context, guideOptions.scrollToTimeMs, guideOptions.startTimeOfDayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) {
|
function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) {
|
||||||
|
|
|
@ -96,7 +96,7 @@ import template from './imageeditor.template.html';
|
||||||
return apiClient.getScaledImageUrl(item.Id || item.ItemId, options);
|
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
|
// TODO move card creation code to Card component
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
|
@ -106,7 +106,7 @@ import template from './imageeditor.template.html';
|
||||||
|
|
||||||
cssClass += ' backdropCard backdropCard-scalable';
|
cssClass += ' backdropCard backdropCard-scalable';
|
||||||
|
|
||||||
if (tagName === 'button') {
|
if (options.tagName === 'button') {
|
||||||
cssClass += ' btnImageCard';
|
cssClass += ' btnImageCard';
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
if (layoutManager.tv) {
|
||||||
|
@ -122,7 +122,7 @@ import template from './imageeditor.template.html';
|
||||||
html += '<div class="' + cssClass + '"';
|
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 += '>';
|
html += '>';
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ import template from './imageeditor.template.html';
|
||||||
|
|
||||||
html += '<div class="cardContent">';
|
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>';
|
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>';
|
html += '</div>';
|
||||||
|
|
||||||
if (enableFooterButtons) {
|
if (options.enableFooterButtons) {
|
||||||
html += '<div class="cardText cardTextCentered">';
|
html += '<div class="cardText cardTextCentered">';
|
||||||
|
|
||||||
if (image.ImageType === 'Backdrop') {
|
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>';
|
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 {
|
} 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>';
|
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>';
|
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 {
|
} 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>';
|
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 {
|
} 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>';
|
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 += '</div>';
|
html += '</div>';
|
||||||
html += '</' + tagName + '>';
|
html += '</' + options.tagName + '>';
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,8 @@ import template from './imageeditor.template.html';
|
||||||
|
|
||||||
for (let i = 0, length = images.length; i < length; i++) {
|
for (let i = 0, length = images.length; i < length; i++) {
|
||||||
const image = images[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;
|
elem.innerHTML = html;
|
||||||
|
|
|
@ -302,20 +302,20 @@ function getAudioMaxValues(deviceProfile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let startingPlaySession = new Date().getTime();
|
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';
|
const url = 'Audio/' + item.Id + '/universal';
|
||||||
|
|
||||||
startingPlaySession++;
|
startingPlaySession++;
|
||||||
return apiClient.getUrl(url, {
|
return apiClient.getUrl(url, {
|
||||||
UserId: apiClient.getCurrentUserId(),
|
UserId: apiClient.getCurrentUserId(),
|
||||||
DeviceId: apiClient.deviceId(),
|
DeviceId: apiClient.deviceId(),
|
||||||
MaxStreamingBitrate: maxAudioBitrate || maxBitrate,
|
MaxStreamingBitrate: maxValues.maxAudioBitrate || maxValues.maxBitrate,
|
||||||
Container: directPlayContainers,
|
Container: directPlayContainers,
|
||||||
TranscodingContainer: transcodingProfile.Container || null,
|
TranscodingContainer: transcodingProfile.Container || null,
|
||||||
TranscodingProtocol: transcodingProfile.Protocol || null,
|
TranscodingProtocol: transcodingProfile.Protocol || null,
|
||||||
AudioCodec: transcodingProfile.AudioCodec,
|
AudioCodec: transcodingProfile.AudioCodec,
|
||||||
MaxAudioSampleRate: maxAudioSampleRate,
|
MaxAudioSampleRate: maxValues.maxAudioSampleRate,
|
||||||
MaxAudioBitDepth: maxAudioBitDepth,
|
MaxAudioBitDepth: maxValues.maxAudioBitDepth,
|
||||||
api_key: apiClient.accessToken(),
|
api_key: apiClient.accessToken(),
|
||||||
PlaySessionId: startingPlaySession,
|
PlaySessionId: startingPlaySession,
|
||||||
StartTimeTicks: startPosition || 0,
|
StartTimeTicks: startPosition || 0,
|
||||||
|
@ -347,7 +347,7 @@ function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, api
|
||||||
|
|
||||||
const maxValues = getAudioMaxValues(deviceProfile);
|
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) {
|
function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) {
|
||||||
|
@ -380,7 +380,7 @@ function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
|
||||||
let streamUrl;
|
let streamUrl;
|
||||||
|
|
||||||
if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) {
|
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 || '');
|
streamUrls.push(streamUrl || '');
|
||||||
|
@ -411,27 +411,12 @@ function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlaybackInfo(player,
|
function getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, liveStreamId, options) {
|
||||||
apiClient,
|
|
||||||
item,
|
|
||||||
deviceProfile,
|
|
||||||
maxBitrate,
|
|
||||||
startPosition,
|
|
||||||
isPlayback,
|
|
||||||
mediaSourceId,
|
|
||||||
audioStreamIndex,
|
|
||||||
subtitleStreamIndex,
|
|
||||||
liveStreamId,
|
|
||||||
enableDirectPlay,
|
|
||||||
enableDirectStream,
|
|
||||||
allowVideoStreamCopy,
|
|
||||||
allowAudioStreamCopy,
|
|
||||||
secondarySubtitleStreamIndex) {
|
|
||||||
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
|
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
MediaSources: [
|
MediaSources: [
|
||||||
{
|
{
|
||||||
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition),
|
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, options.maxBitrate, apiClient, options.startPosition),
|
||||||
Id: item.Id,
|
Id: item.Id,
|
||||||
MediaStreams: [],
|
MediaStreams: [],
|
||||||
RunTimeTicks: item.RunTimeTicks
|
RunTimeTicks: item.RunTimeTicks
|
||||||
|
@ -449,10 +434,10 @@ function getPlaybackInfo(player,
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
UserId: apiClient.getCurrentUserId(),
|
UserId: apiClient.getCurrentUserId(),
|
||||||
StartTimeTicks: startPosition || 0
|
StartTimeTicks: options.startPosition || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isPlayback) {
|
if (options.isPlayback) {
|
||||||
query.IsPlayback = true;
|
query.IsPlayback = true;
|
||||||
query.AutoOpenLiveStream = true;
|
query.AutoOpenLiveStream = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -460,27 +445,26 @@ function getPlaybackInfo(player,
|
||||||
query.AutoOpenLiveStream = false;
|
query.AutoOpenLiveStream = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioStreamIndex != null) {
|
if (options.audioStreamIndex != null) {
|
||||||
query.AudioStreamIndex = audioStreamIndex;
|
query.AudioStreamIndex = options.audioStreamIndex;
|
||||||
}
|
}
|
||||||
if (subtitleStreamIndex != null) {
|
if (options.subtitleStreamIndex != null) {
|
||||||
query.SubtitleStreamIndex = subtitleStreamIndex;
|
query.SubtitleStreamIndex = options.subtitleStreamIndex;
|
||||||
}
|
}
|
||||||
if (secondarySubtitleStreamIndex != null) {
|
if (options.secondarySubtitleStreamIndex != null) {
|
||||||
query.SecondarySubtitleStreamIndex = secondarySubtitleStreamIndex;
|
query.SecondarySubtitleStreamIndex = options.secondarySubtitleStreamIndex;
|
||||||
}
|
}
|
||||||
if (enableDirectPlay != null) {
|
if (options.enableDirectPlay != null) {
|
||||||
query.EnableDirectPlay = enableDirectPlay;
|
query.EnableDirectPlay = options.enableDirectPlay;
|
||||||
}
|
}
|
||||||
|
if (options.enableDirectStream != null) {
|
||||||
if (enableDirectStream != null) {
|
query.EnableDirectStream = options.enableDirectStream;
|
||||||
query.EnableDirectStream = enableDirectStream;
|
|
||||||
}
|
}
|
||||||
if (allowVideoStreamCopy != null) {
|
if (options.allowVideoStreamCopy != null) {
|
||||||
query.AllowVideoStreamCopy = allowVideoStreamCopy;
|
query.AllowVideoStreamCopy = options.allowVideoStreamCopy;
|
||||||
}
|
}
|
||||||
if (allowAudioStreamCopy != null) {
|
if (options.allowAudioStreamCopy != null) {
|
||||||
query.AllowAudioStreamCopy = allowAudioStreamCopy;
|
query.AllowAudioStreamCopy = options.allowAudioStreamCopy;
|
||||||
}
|
}
|
||||||
if (mediaSourceId) {
|
if (mediaSourceId) {
|
||||||
query.MediaSourceId = mediaSourceId;
|
query.MediaSourceId = mediaSourceId;
|
||||||
|
@ -488,8 +472,8 @@ function getPlaybackInfo(player,
|
||||||
if (liveStreamId) {
|
if (liveStreamId) {
|
||||||
query.LiveStreamId = liveStreamId;
|
query.LiveStreamId = liveStreamId;
|
||||||
}
|
}
|
||||||
if (maxBitrate) {
|
if (options.maxBitrate) {
|
||||||
query.MaxStreamingBitrate = maxBitrate;
|
query.MaxStreamingBitrate = options.maxBitrate;
|
||||||
}
|
}
|
||||||
if (player.enableMediaProbe && !player.enableMediaProbe(item)) {
|
if (player.enableMediaProbe && !player.enableMediaProbe(item)) {
|
||||||
query.EnableMediaProbe = false;
|
query.EnableMediaProbe = false;
|
||||||
|
@ -540,7 +524,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 = {
|
const postData = {
|
||||||
DeviceProfile: deviceProfile,
|
DeviceProfile: deviceProfile,
|
||||||
OpenToken: mediaSource.OpenToken
|
OpenToken: mediaSource.OpenToken
|
||||||
|
@ -548,19 +532,19 @@ function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, ma
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
UserId: apiClient.getCurrentUserId(),
|
UserId: apiClient.getCurrentUserId(),
|
||||||
StartTimeTicks: startPosition || 0,
|
StartTimeTicks: options.startPosition || 0,
|
||||||
ItemId: item.Id,
|
ItemId: item.Id,
|
||||||
PlaySessionId: playSessionId
|
PlaySessionId: playSessionId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (maxBitrate) {
|
if (options.maxBitrate) {
|
||||||
query.MaxStreamingBitrate = maxBitrate;
|
query.MaxStreamingBitrate = options.maxBitrate;
|
||||||
}
|
}
|
||||||
if (audioStreamIndex != null) {
|
if (options.audioStreamIndex != null) {
|
||||||
query.AudioStreamIndex = audioStreamIndex;
|
query.AudioStreamIndex = options.audioStreamIndex;
|
||||||
}
|
}
|
||||||
if (subtitleStreamIndex != null) {
|
if (options.subtitleStreamIndex != null) {
|
||||||
query.SubtitleStreamIndex = subtitleStreamIndex;
|
query.SubtitleStreamIndex = options.subtitleStreamIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastly, enforce player overrides for special situations
|
// lastly, enforce player overrides for special situations
|
||||||
|
@ -1740,7 +1724,19 @@ class PlaybackManager {
|
||||||
|
|
||||||
const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions();
|
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)) {
|
if (validatePlaybackInfoResult(self, result)) {
|
||||||
currentMediaSource = result.MediaSources[0];
|
currentMediaSource = result.MediaSources[0];
|
||||||
|
|
||||||
|
@ -2306,17 +2302,17 @@ class PlaybackManager {
|
||||||
}, reject);
|
}, reject);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendPlaybackListToPlayer(player, items, deviceProfile, maxBitrate, apiClient, startPositionTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, startIndex) {
|
function sendPlaybackListToPlayer(player, items, deviceProfile, apiClient, mediaSourceId, options) {
|
||||||
return setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositionTicks).then(function () {
|
return setStreamUrls(items, deviceProfile, options.maxBitrate, apiClient, options.startPosition).then(function () {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
|
|
||||||
return player.play({
|
return player.play({
|
||||||
items: items,
|
items,
|
||||||
startPositionTicks: startPositionTicks || 0,
|
startPositionTicks: options.startPosition || 0,
|
||||||
mediaSourceId: mediaSourceId,
|
mediaSourceId,
|
||||||
audioStreamIndex: audioStreamIndex,
|
audioStreamIndex: options.audioStreamIndex,
|
||||||
subtitleStreamIndex: subtitleStreamIndex,
|
subtitleStreamIndex: options.subtitleStreamIndex,
|
||||||
startIndex: startIndex
|
startIndex: options.startIndex
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2477,15 +2473,27 @@ class PlaybackManager {
|
||||||
const mediaSourceId = playOptions.mediaSourceId;
|
const mediaSourceId = playOptions.mediaSourceId;
|
||||||
const audioStreamIndex = playOptions.audioStreamIndex;
|
const audioStreamIndex = playOptions.audioStreamIndex;
|
||||||
const subtitleStreamIndex = playOptions.subtitleStreamIndex;
|
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)) {
|
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
|
// this reference was only needed by sendPlaybackListToPlayer
|
||||||
playOptions.items = null;
|
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();
|
const user = await apiClient.getCurrentUser();
|
||||||
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
|
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
|
||||||
|
|
||||||
|
@ -2543,7 +2551,20 @@ class PlaybackManager {
|
||||||
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
|
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
|
||||||
|
|
||||||
return player.getDeviceProfile(item).then(function (deviceProfile) {
|
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);
|
return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2563,7 +2584,19 @@ class PlaybackManager {
|
||||||
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
|
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
|
||||||
|
|
||||||
return player.getDeviceProfile(item).then(function (deviceProfile) {
|
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;
|
return playbackInfoResult.MediaSources;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2704,13 +2737,18 @@ class PlaybackManager {
|
||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex) {
|
function getPlaybackMediaSource(player, apiClient, deviceProfile, item, mediaSourceId, options) {
|
||||||
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, true, mediaSourceId, audioStreamIndex, subtitleStreamIndex, null).then(function (playbackInfoResult) {
|
options.isPlayback = true;
|
||||||
|
|
||||||
|
return getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, null, options).then(function (playbackInfoResult) {
|
||||||
if (validatePlaybackInfoResult(self, playbackInfoResult)) {
|
if (validatePlaybackInfoResult(self, playbackInfoResult)) {
|
||||||
return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) {
|
return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) {
|
||||||
if (mediaSource) {
|
if (mediaSource) {
|
||||||
if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) {
|
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) {
|
return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) {
|
||||||
openLiveStreamResult.MediaSource.enableDirectPlay = result;
|
openLiveStreamResult.MediaSource.enableDirectPlay = result;
|
||||||
return openLiveStreamResult.MediaSource;
|
return openLiveStreamResult.MediaSource;
|
||||||
|
|
|
@ -12,7 +12,10 @@ const userDataMethods = {
|
||||||
markFavorite: markFavorite
|
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') {
|
if (style === 'fab-mini') {
|
||||||
style = 'fab';
|
style = 'fab';
|
||||||
buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini';
|
buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini';
|
||||||
|
@ -96,7 +99,7 @@ function getIconsHtml(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconCssClass = options.iconCssClass;
|
const iconCssClass = options.iconCssClass;
|
||||||
|
const classes = { buttonCssClass: btnCssClass, iconCssClass: iconCssClass };
|
||||||
const serverId = item.ServerId;
|
const serverId = item.ServerId;
|
||||||
|
|
||||||
if (includePlayed !== false) {
|
if (includePlayed !== false) {
|
||||||
|
@ -104,18 +107,21 @@ function getIconsHtml(options) {
|
||||||
|
|
||||||
if (itemHelper.canMarkPlayed(item)) {
|
if (itemHelper.canMarkPlayed(item)) {
|
||||||
if (userData.Played) {
|
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 {
|
} 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');
|
const tooltipFavorite = globalize.translate('Favorite');
|
||||||
if (userData.IsFavorite) {
|
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 {
|
} 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;
|
return html;
|
||||||
|
|
|
@ -48,6 +48,9 @@ function supportsFade() {
|
||||||
|
|
||||||
function requireHlsPlayer(callback) {
|
function requireHlsPlayer(callback) {
|
||||||
import('hls.js').then(({ default: hls }) => {
|
import('hls.js').then(({ default: hls }) => {
|
||||||
|
hls.DefaultConfig.lowLatencyMode = false;
|
||||||
|
hls.DefaultConfig.backBufferLength = Infinity;
|
||||||
|
hls.DefaultConfig.liveBackBufferLength = 90;
|
||||||
window.Hls = hls;
|
window.Hls = hls;
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import { includesAny } from '../../utils/container.ts';
|
import { includesAny } from '../../utils/container.ts';
|
||||||
|
import debounce from 'lodash-es/debounce';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns resolved URL.
|
* Returns resolved URL.
|
||||||
|
@ -106,6 +107,9 @@ function tryRemoveElement(elem) {
|
||||||
|
|
||||||
function requireHlsPlayer(callback) {
|
function requireHlsPlayer(callback) {
|
||||||
import('hls.js').then(({default: hls}) => {
|
import('hls.js').then(({default: hls}) => {
|
||||||
|
hls.DefaultConfig.lowLatencyMode = false;
|
||||||
|
hls.DefaultConfig.backBufferLength = Infinity;
|
||||||
|
hls.DefaultConfig.liveBackBufferLength = 90;
|
||||||
window.Hls = hls;
|
window.Hls = hls;
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
@ -571,7 +575,12 @@ function tryRemoveElement(elem) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubtitleOffset(offset) {
|
setSubtitleOffset = debounce(this._setSubtitleOffset, 100);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setSubtitleOffset(offset) {
|
||||||
const offsetValue = parseFloat(offset);
|
const offsetValue = parseFloat(offset);
|
||||||
|
|
||||||
// if .ass currently rendering
|
// if .ass currently rendering
|
||||||
|
@ -620,6 +629,41 @@ function tryRemoveElement(elem) {
|
||||||
return relativeOffset;
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -629,11 +673,21 @@ function tryRemoveElement(elem) {
|
||||||
if (offsetValue === 0) {
|
if (offsetValue === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange();
|
||||||
|
if (shouldClearActiveCues) {
|
||||||
|
this.hideTextTrackWithActiveCues(currentTrack);
|
||||||
|
}
|
||||||
|
|
||||||
Array.from(currentTrack.cues)
|
Array.from(currentTrack.cues)
|
||||||
.forEach(function (cue) {
|
.forEach(function (cue) {
|
||||||
cue.startTime -= offsetValue;
|
cue.startTime -= offsetValue;
|
||||||
cue.endTime -= offsetValue;
|
cue.endTime -= offsetValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (shouldClearActiveCues) {
|
||||||
|
this.forceClearTextTrackActiveCues(currentTrack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,6 +825,8 @@ function tryRemoveElement(elem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
this.setSubtitleOffset.cancel();
|
||||||
|
|
||||||
destroyHlsPlayer(this);
|
destroyHlsPlayer(this);
|
||||||
destroyFlvPlayer(this);
|
destroyFlvPlayer(this);
|
||||||
|
|
||||||
|
|
|
@ -571,7 +571,7 @@
|
||||||
"LabelSelectVersionToInstall": "Seleccionar versión a instalar:",
|
"LabelSelectVersionToInstall": "Seleccionar versión a instalar:",
|
||||||
"LabelSelectUsers": "Seleccionar usuarios:",
|
"LabelSelectUsers": "Seleccionar usuarios:",
|
||||||
"LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.",
|
"LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.",
|
||||||
"LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:",
|
"LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como «Películas», «Música» y «TV»:",
|
||||||
"LabelSeasonNumber": "Temporada número:",
|
"LabelSeasonNumber": "Temporada número:",
|
||||||
"LabelScreensaver": "Protector de pantalla:",
|
"LabelScreensaver": "Protector de pantalla:",
|
||||||
"LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.",
|
"LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.",
|
||||||
|
@ -1006,7 +1006,7 @@
|
||||||
"LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.",
|
"LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.",
|
||||||
"LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:",
|
"LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:",
|
||||||
"LabelMaxParentalRating": "Máxima clasificación parental permitida:",
|
"LabelMaxParentalRating": "Máxima clasificación parental permitida:",
|
||||||
"LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:",
|
"LabelMaxChromecastBitrate": "Calidad de transmisión de Google Cast:",
|
||||||
"LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:",
|
"LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:",
|
||||||
"LabelMatchType": "Tipo de coincidencia:",
|
"LabelMatchType": "Tipo de coincidencia:",
|
||||||
"LabelManufacturerUrl": "URL del fabricante:",
|
"LabelManufacturerUrl": "URL del fabricante:",
|
||||||
|
@ -1354,7 +1354,7 @@
|
||||||
"MessagePluginInstallError": "Ocurrió un error instalando el complemento.",
|
"MessagePluginInstallError": "Ocurrió un error instalando el complemento.",
|
||||||
"PlaybackRate": "Tasa de reproducción",
|
"PlaybackRate": "Tasa de reproducción",
|
||||||
"Data": "Datos",
|
"Data": "Datos",
|
||||||
"ButtonUseQuickConnect": "Usar conexión rápida",
|
"ButtonUseQuickConnect": "Usar «Conexión rápida»",
|
||||||
"ButtonActivate": "Activar",
|
"ButtonActivate": "Activar",
|
||||||
"Authorize": "Autorizar",
|
"Authorize": "Autorizar",
|
||||||
"LabelCurrentStatus": "Estado actual:",
|
"LabelCurrentStatus": "Estado actual:",
|
||||||
|
@ -1366,7 +1366,7 @@
|
||||||
"TonemappingAlgorithmHelp": "El mapeo de tonos puede ser modificado. Si no estas familiarizado con estas opciones, solo manten el predeterminado. El valor recomendado es Hable.",
|
"TonemappingAlgorithmHelp": "El mapeo de tonos puede ser modificado. Si no estas familiarizado con estas opciones, solo manten el predeterminado. El valor recomendado es Hable.",
|
||||||
"LabelTonemappingThresholdHelp": "El algoritmo de mapeo de tonos puede ser finamente ajustado para cada escena. Y el umbral es usado para detectar si la escena ha cambiado. Si los valores entre el promedio de brillo del fotograma actual y el promedio actual excede el valor de umbral, se vuelve a calcular el brillo promedio y máximo. Los valores recomendados y por defecto son entre 0.2 y 0.8.",
|
"LabelTonemappingThresholdHelp": "El algoritmo de mapeo de tonos puede ser finamente ajustado para cada escena. Y el umbral es usado para detectar si la escena ha cambiado. Si los valores entre el promedio de brillo del fotograma actual y el promedio actual excede el valor de umbral, se vuelve a calcular el brillo promedio y máximo. Los valores recomendados y por defecto son entre 0.2 y 0.8.",
|
||||||
"ThumbCard": "Miniatura",
|
"ThumbCard": "Miniatura",
|
||||||
"LabelMaxMuxingQueueSizeHelp": "Numero máximo de paquetes que pueden estar en búfer mientras se espera a que se inicialicen todas las trasmisiones. Intenta incrementarlos si encuentras el error \"Too many packets buffered for output stream\" en los registros de ffmpeg. El valor recomendado es 2048.",
|
"LabelMaxMuxingQueueSizeHelp": "Numero máximo de paquetes que pueden estar en búfer mientras se espera a que se inicialicen todas las trasmisiones. Intenta incrementarlos si encuentras el error «Too many packets buffered for output stream» en los registros de ffmpeg. El valor recomendado es 2048.",
|
||||||
"LabelMaxMuxingQueueSize": "Tamaño máximo cola de mezclado:",
|
"LabelMaxMuxingQueueSize": "Tamaño máximo cola de mezclado:",
|
||||||
"LabelTonemappingParamHelp": "Modifica el algoritmo de mapeo de tonos. Los valores recomendados y por defecto son NaN. Se puede dejar en blanco.",
|
"LabelTonemappingParamHelp": "Modifica el algoritmo de mapeo de tonos. Los valores recomendados y por defecto son NaN. Se puede dejar en blanco.",
|
||||||
"LabelTonemappingParam": "Parámetro Mapeo de tonos:",
|
"LabelTonemappingParam": "Parámetro Mapeo de tonos:",
|
||||||
|
@ -1380,7 +1380,7 @@
|
||||||
"LabelTonemappingAlgorithm": "Seleccione el algoritmo de mapeo de tonos:",
|
"LabelTonemappingAlgorithm": "Seleccione el algoritmo de mapeo de tonos:",
|
||||||
"AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR manteniendo la calidad de imagen y los colores, que son datos muy importantes para mostrar la imagen original. Actualmente solo funciona con videos HDR10 o HLG. Esto requiere los tiempos de ejecución OpenCL o CUDA correspondientes.",
|
"AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR manteniendo la calidad de imagen y los colores, que son datos muy importantes para mostrar la imagen original. Actualmente solo funciona con videos HDR10 o HLG. Esto requiere los tiempos de ejecución OpenCL o CUDA correspondientes.",
|
||||||
"EnableTonemapping": "Habilitar mapeo de tonos",
|
"EnableTonemapping": "Habilitar mapeo de tonos",
|
||||||
"LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se utiliza para el mapeo de tonos. El lado izquierdo del punto es el número de plataforma y el lado derecho es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere la aplicación ffmpeg que contiene el método de aceleración de hardware OpenCL.",
|
"LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se utiliza para el mapeo de tonos. El lado izquierdo del punto es el número de plataforma y el lado derecho es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere la aplicación FFmpeg que contiene el método de aceleración de hardware OpenCL.",
|
||||||
"LabelOpenclDevice": "Dispositivo OpenCL:",
|
"LabelOpenclDevice": "Dispositivo OpenCL:",
|
||||||
"LabelColorPrimaries": "Colores primarios:",
|
"LabelColorPrimaries": "Colores primarios:",
|
||||||
"LabelColorTransfer": "Transferencia de Color:",
|
"LabelColorTransfer": "Transferencia de Color:",
|
||||||
|
@ -1400,22 +1400,22 @@
|
||||||
"SelectServer": "Seleccionar Servidor",
|
"SelectServer": "Seleccionar Servidor",
|
||||||
"Restart": "Reiniciar",
|
"Restart": "Reiniciar",
|
||||||
"ResetPassword": "Resetear contraseña",
|
"ResetPassword": "Resetear contraseña",
|
||||||
"QuickConnectNotActive": "Conexión Rápida no esta habilitado en el servidor",
|
"QuickConnectNotActive": "«Conexión rápida» no está habilitado en este servidor",
|
||||||
"QuickConnectNotAvailable": "Pregunte al administrador del servidor para habilitar Conexión Rápida",
|
"QuickConnectNotAvailable": "Pide al administrador del servidor que habilite la «Conexión rápida»",
|
||||||
"QuickConnectInvalidCode": "Código Conexión Rápida inválido",
|
"QuickConnectInvalidCode": "Código de «Conexión rápida» inválido",
|
||||||
"QuickConnectDescription": "Para ingresar con Conexión Rápida, seleccione el botón Conexión Rápida en el dispositivo del cual estas ingresando e ingrese el siguiente código.",
|
"QuickConnectDescription": "Para ingresar con «Conexión rápida», seleccione el botón «Conexión rápida» en el dispositivo del cual estas ingresando e ingrese el código mostrado a continuación.",
|
||||||
"QuickConnectDeactivated": "Conexión Rápida se desactivó antes de que se pudiera aprobar la solicitud de inicio de sesión",
|
"QuickConnectDeactivated": "La «Conexión rápida» se desactivó antes de que se pudiera aprobar la solicitud de inicio de sesión",
|
||||||
"QuickConnectAuthorizeFail": "Código Conexión Rápida desconocido",
|
"QuickConnectAuthorizeFail": "Código de «Conexión rápida» desconocido",
|
||||||
"QuickConnectAuthorizeSuccess": "Requiere autorización",
|
"QuickConnectAuthorizeSuccess": "Solicitud autorizada",
|
||||||
"QuickConnectAuthorizeCode": "Ingrese el código {0} para acceder",
|
"QuickConnectAuthorizeCode": "Ingrese el código {0} para acceder",
|
||||||
"QuickConnectActivationSuccessful": "Activación Exitosa",
|
"QuickConnectActivationSuccessful": "Activado exitosamente",
|
||||||
"QuickConnect": "Conexión Rápida",
|
"QuickConnect": "Conexión rápida",
|
||||||
"Profile": "Perfil",
|
"Profile": "Perfil",
|
||||||
"PosterCard": "Imagen del Cartel",
|
"PosterCard": "Imagen del Cartel",
|
||||||
"Poster": "Cartel",
|
"Poster": "Cartel",
|
||||||
"Photo": "Foto",
|
"Photo": "Foto",
|
||||||
"MusicVideos": "Videos musicales",
|
"MusicVideos": "Videos musicales",
|
||||||
"LabelQuickConnectCode": "Código conexión rápida:",
|
"LabelQuickConnectCode": "Código de «Conexión rápida»:",
|
||||||
"LabelKnownProxies": "Proxies conocidos:",
|
"LabelKnownProxies": "Proxies conocidos:",
|
||||||
"LabelIconMaxResHelp": "Resolución maxima de los iconos por medio de la función 'upnp:icon'.",
|
"LabelIconMaxResHelp": "Resolución maxima de los iconos por medio de la función 'upnp:icon'.",
|
||||||
"EnableAutoCast": "Establecer como Predeterminado",
|
"EnableAutoCast": "Establecer como Predeterminado",
|
||||||
|
@ -1519,7 +1519,7 @@
|
||||||
"LabelSlowResponseEnabled": "Log de alarma si la respuesta del servidor fue lenta",
|
"LabelSlowResponseEnabled": "Log de alarma si la respuesta del servidor fue lenta",
|
||||||
"UseEpisodeImagesInNextUpHelp": "Las secciones Siguiente y Continuar viendo utilizaran imagenes del episodio como miniaturas en lugar de miniaturas del show.",
|
"UseEpisodeImagesInNextUpHelp": "Las secciones Siguiente y Continuar viendo utilizaran imagenes del episodio como miniaturas en lugar de miniaturas del show.",
|
||||||
"UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"",
|
"UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"",
|
||||||
"LabelLocalCustomCss": "El CSS personalizado aplica solo a este cliente. Puede quieras deshabilitar el CSS del servidor.",
|
"LabelLocalCustomCss": "Código CSS personalizado para estilo que aplica solo a este cliente. Puede que quiera deshabilitar el código CSS personalizado del servidor.",
|
||||||
"LabelDisableCustomCss": "Desactivar el código CSS personalizado para la tematización/marca proporcionada desde el servidor.",
|
"LabelDisableCustomCss": "Desactivar el código CSS personalizado para la tematización/marca proporcionada desde el servidor.",
|
||||||
"DisableCustomCss": "Deshabilitar CSS proveniente del servidor",
|
"DisableCustomCss": "Deshabilitar CSS proveniente del servidor",
|
||||||
"LabelAutomaticallyAddToCollectionHelp": "Cuando al menos 2 películas tengan el mismo nombre de colección, se añadirán automáticamente a dicha colección.",
|
"LabelAutomaticallyAddToCollectionHelp": "Cuando al menos 2 películas tengan el mismo nombre de colección, se añadirán automáticamente a dicha colección.",
|
||||||
|
@ -1574,5 +1574,6 @@
|
||||||
"LabelDummyChapterDurationHelp": "El intervalo de la extracción de las imágenes de los capítulos, en segundos.",
|
"LabelDummyChapterDurationHelp": "El intervalo de la extracción de las imágenes de los capítulos, en segundos.",
|
||||||
"LabelDummyChapterCount": "Límite:",
|
"LabelDummyChapterCount": "Límite:",
|
||||||
"LabelChapterImageResolution": "Resolución:",
|
"LabelChapterImageResolution": "Resolución:",
|
||||||
"LabelChapterImageResolutionHelp": "La resolución de las imágenes de capítulos extraídas."
|
"LabelChapterImageResolutionHelp": "La resolución de las imágenes de capítulos extraídas.",
|
||||||
|
"LabelMaxDaysForNextUp": "Días máximos en «A continuación»:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,7 @@
|
||||||
"DownloadsValue": "{0} téléchargements",
|
"DownloadsValue": "{0} téléchargements",
|
||||||
"DisplayModeHelp": "Sélectionner l'agencement que vous désirez pour l'interface.",
|
"DisplayModeHelp": "Sélectionner l'agencement que vous désirez pour l'interface.",
|
||||||
"DisplayMissingEpisodesWithinSeasons": "Afficher les épisodes manquants dans les saisons",
|
"DisplayMissingEpisodesWithinSeasons": "Afficher les épisodes manquants dans les saisons",
|
||||||
"DisplayInOtherHomeScreenSections": "Afficher dans les sections de l’écran d’accueil comme « Ajouts récents » et reprendre le visionnement",
|
"DisplayInOtherHomeScreenSections": "Afficher dans les sections de l’écran d’accueil comme « Ajouts récents » et «Reprendre le visionnement»",
|
||||||
"Directors": "Réalisateurs",
|
"Directors": "Réalisateurs",
|
||||||
"Director": "Réalisateur",
|
"Director": "Réalisateur",
|
||||||
"DetectingDevices": "Détection des appareils",
|
"DetectingDevices": "Détection des appareils",
|
||||||
|
@ -349,7 +349,7 @@
|
||||||
"HeaderCodecProfile": "Profil de codec",
|
"HeaderCodecProfile": "Profil de codec",
|
||||||
"HeaderChapterImages": "Images des chapitres",
|
"HeaderChapterImages": "Images des chapitres",
|
||||||
"HeaderChannelAccess": "Accès aux chaînes",
|
"HeaderChannelAccess": "Accès aux chaînes",
|
||||||
"LatestFromLibrary": "{0}, ajouts récents",
|
"LatestFromLibrary": "{0} ajouts récents",
|
||||||
"HideWatchedContentFromLatestMedia": "Masquer le contenu déjà vu dans les 'Derniers Médias'",
|
"HideWatchedContentFromLatestMedia": "Masquer le contenu déjà vu dans les 'Derniers Médias'",
|
||||||
"HeaderLatestRecordings": "Derniers enregistrements",
|
"HeaderLatestRecordings": "Derniers enregistrements",
|
||||||
"HeaderLatestMusic": "Dernière musique",
|
"HeaderLatestMusic": "Dernière musique",
|
||||||
|
@ -1020,5 +1020,25 @@
|
||||||
"LabelMaxParentalRating": "Classification parentale maximale :",
|
"LabelMaxParentalRating": "Classification parentale maximale :",
|
||||||
"SpecialFeatures": "Bonus",
|
"SpecialFeatures": "Bonus",
|
||||||
"Sort": "Trier",
|
"Sort": "Trier",
|
||||||
"SortByValue": "Trier par"
|
"SortByValue": "Trier par",
|
||||||
|
"LabelMovieCategories": "Catégories de films:",
|
||||||
|
"LabelNewPassword": "Nouveau mot de passe:",
|
||||||
|
"LabelOriginalName": "Nom original:",
|
||||||
|
"LabelPasswordRecoveryPinCode": "Code NIP:",
|
||||||
|
"LabelOriginalTitle": "Titre original:",
|
||||||
|
"LabelMaxStreamingBitrate": "Qualité maximale de streaming:",
|
||||||
|
"LabelNotificationEnabled": "Activer cette notification",
|
||||||
|
"LabelNewName": "Nouveau nom:",
|
||||||
|
"LabelNewPasswordConfirm": "Confirmer le nouveau mot de passe:",
|
||||||
|
"LabelPersonRole": "Rôle:",
|
||||||
|
"LabelPasswordConfirm": "Confirmer le mot de passe:",
|
||||||
|
"LabelPersonRoleHelp": "Exemple: Conducteur de camion à crème glacée",
|
||||||
|
"LabelPleaseRestart": "Les changements prendront effets après avoir rechargé manuellement le client web.",
|
||||||
|
"LabelMinAudiobookResumeHelp": "Les titres sont considérés comme non joué s'ils sont arrêtés avant cette durée.",
|
||||||
|
"LabelPassword": "Mot de passe:",
|
||||||
|
"LabelPath": "Chemin:",
|
||||||
|
"LabelMetadataPath": "Chemin des métadonnées:",
|
||||||
|
"LabelDummyChapterDuration": "Intervalle:",
|
||||||
|
"LabelDummyChapterCount": "Limite:",
|
||||||
|
"LabelChapterImageResolution": "Résolution:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,5 +455,28 @@
|
||||||
"ButtonActivate": "Virkja",
|
"ButtonActivate": "Virkja",
|
||||||
"ButtonClose": "Loka",
|
"ButtonClose": "Loka",
|
||||||
"ButtonSpace": "Bil",
|
"ButtonSpace": "Bil",
|
||||||
"Authorize": "Heimila"
|
"Authorize": "Heimila",
|
||||||
|
"ChangingMetadataImageSettingsNewContent": "Breytingar á niðurhali lýsigögnum eða myndum mun aðeins gilda um ný efni sem bætt hafa verið í safnið. Til að breyta núverandi titla þarftu að endurnýja lýsigögnin handvirkt.",
|
||||||
|
"ColorTransfer": "Litaflutningur",
|
||||||
|
"Data": "Gögn",
|
||||||
|
"ClearQueue": "Hreinsa biðröð",
|
||||||
|
"DailyAt": "Daglega um {0}",
|
||||||
|
"DashboardServerName": "Þjónn: {0}",
|
||||||
|
"DashboardVersionNumber": "Útgáfa: {0}",
|
||||||
|
"ColorSpace": "Litarými",
|
||||||
|
"Copied": "Aftritað",
|
||||||
|
"Copy": "Afrita",
|
||||||
|
"CopyFailed": "Gat ekki afritað",
|
||||||
|
"ButtonExitApp": "Fara úr forriti",
|
||||||
|
"EnableFasterAnimations": "Hraðari hreyfimyndir",
|
||||||
|
"EnablePlugin": "Virkja",
|
||||||
|
"Bwdif": "BWDIF",
|
||||||
|
"DisablePlugin": "Slökkva",
|
||||||
|
"EnableAutoCast": "Setja sem Sjálfgefið",
|
||||||
|
"EnableDetailsBanner": "Upplýsingar borði",
|
||||||
|
"DeleteAll": "Eyða Öllu",
|
||||||
|
"ButtonBackspace": "Backspace",
|
||||||
|
"ButtonUseQuickConnect": "Nota hraðtengingu",
|
||||||
|
"Digital": "Stafrænt",
|
||||||
|
"DownloadAll": "Sækja allt"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1439,7 +1439,7 @@
|
||||||
"SubtitleDownloadersHelp": "Увімкніть та оцініть бажані завантажувачі субтитрів у порядку пріоритету.",
|
"SubtitleDownloadersHelp": "Увімкніть та оцініть бажані завантажувачі субтитрів у порядку пріоритету.",
|
||||||
"SubtitleAppearanceSettingsDisclaimer": "Наведені нижче налаштування не застосовуються до графічних субтитрів, згаданих вище, або субтитрів ASS/SSA, які вбудовують власні стилі.",
|
"SubtitleAppearanceSettingsDisclaimer": "Наведені нижче налаштування не застосовуються до графічних субтитрів, згаданих вище, або субтитрів ASS/SSA, які вбудовують власні стилі.",
|
||||||
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Ці налаштування також застосовуються до будь-якого відтворення Google Cast, запущеного цим пристроєм.",
|
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Ці налаштування також застосовуються до будь-якого відтворення Google Cast, запущеного цим пристроєм.",
|
||||||
"Subtitle": "Підзаголовок",
|
"Subtitle": "Субтитри",
|
||||||
"Studios": "Студії",
|
"Studios": "Студії",
|
||||||
"StopRecording": "Зупинити запис",
|
"StopRecording": "Зупинити запис",
|
||||||
"StopPlayback": "Зупинити відтворення",
|
"StopPlayback": "Зупинити відтворення",
|
||||||
|
|
|
@ -158,6 +158,7 @@ const config = {
|
||||||
path.resolve(__dirname, 'node_modules/dom7'),
|
path.resolve(__dirname, 'node_modules/dom7'),
|
||||||
path.resolve(__dirname, 'node_modules/epubjs'),
|
path.resolve(__dirname, 'node_modules/epubjs'),
|
||||||
path.resolve(__dirname, 'node_modules/flv.js'),
|
path.resolve(__dirname, 'node_modules/flv.js'),
|
||||||
|
path.resolve(__dirname, 'node_modules/hls.js'),
|
||||||
path.resolve(__dirname, 'node_modules/libarchive.js'),
|
path.resolve(__dirname, 'node_modules/libarchive.js'),
|
||||||
path.resolve(__dirname, 'node_modules/marked'),
|
path.resolve(__dirname, 'node_modules/marked'),
|
||||||
path.resolve(__dirname, 'node_modules/react-router'),
|
path.resolve(__dirname, 'node_modules/react-router'),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue