1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge branch 'es6' into es6-shortcuts

This commit is contained in:
dkanada 2020-06-25 15:41:36 +09:00 committed by GitHub
commit ed3a820c21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
166 changed files with 6500 additions and 3182 deletions

View file

@ -1,6 +1,6 @@
<div data-role="page" class="page standalonePage">
<div class="padded-left padded-right padded-bottom-page">
<form class="addServerForm" style="margin: 0 auto;">
<form class="addServerForm" style="margin: 0 auto;" novalidate>
<h1>${HeaderConnectToServer}</h1>
<div class="inputContainer">
<input is="emby-input" type="url" id="txtServerHost" required="required" label="${LabelServerHost}"/>

View file

@ -597,6 +597,7 @@
.detailImageContainer {
position: relative;
margin-top: -25vh;
margin-bottom: 10vh;
float: left;
width: 25vw;
z-index: 3;
@ -641,7 +642,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.itemDetailGalleryLink.defaultCardBackground {
height: 23vw; /* Dirty hack to get it to look somewhat square. Less than ideal. */
/* Dirty hack to get it to look somewhat square. Less than ideal. */
height: 23vw;
}
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
@ -797,9 +799,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.detailImageProgressContainer {
position: absolute;
bottom: 0;
width: 22.786458333333332vw;
margin-top: -0.4vw;
width: 100%;
}
.detailButton-text {

View file

@ -121,6 +121,11 @@ div[data-role=page] {
transform: translateY(-100%);
}
.drawerContent {
/* make sure the bottom of the drawer is visible when music is playing */
padding-bottom: 4em;
}
.force-scroll {
overflow-y: scroll;
}

View file

@ -16,6 +16,12 @@ _define('fetch', function() {
return fetch;
});
// Blurhash
var blurhash = require('blurhash');
_define('blurhash', function() {
return blurhash;
});
// query-string
var query = require('query-string');
_define('queryString', function() {

View file

@ -1,9 +1,20 @@
define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-button-light', 'formDialogStyle'], function (dialogHelper, datetime, globalize) {
'use strict';
/* eslint-disable indent */
/**
* Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule
*/
import dialogHelper from 'dialogHelper';
import datetime from 'datetime';
import globalize from 'globalize';
import 'emby-select';
import 'paper-icon-button-light';
import 'formDialogStyle';
function getDisplayTime(hours) {
var minutes = 0;
var pct = hours % 1;
let minutes = 0;
const pct = hours % 1;
if (pct) {
minutes = parseInt(60 * pct);
@ -13,25 +24,25 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt
}
function populateHours(context) {
var html = '';
let html = '';
for (var i = 0; i < 24; i++) {
html += '<option value="' + i + '">' + getDisplayTime(i) + '</option>';
for (let i = 0; i < 24; i++) {
html += `<option value="${i}">${getDisplayTime(i)}</option>`;
}
html += '<option value="24">' + getDisplayTime(0) + '</option>';
html += `<option value="24">${getDisplayTime(0)}</option>`;
context.querySelector('#selectStart').innerHTML = html;
context.querySelector('#selectEnd').innerHTML = html;
}
function loadSchedule(context, schedule) {
context.querySelector('#selectDay').value = schedule.DayOfWeek || 'Sunday';
context.querySelector('#selectStart').value = schedule.StartHour || 0;
context.querySelector('#selectEnd').value = schedule.EndHour || 0;
function loadSchedule(context, {DayOfWeek, StartHour, EndHour}) {
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday';
context.querySelector('#selectStart').value = StartHour || 0;
context.querySelector('#selectEnd').value = EndHour || 0;
}
function submitSchedule(context, options) {
var updatedSchedule = {
const updatedSchedule = {
DayOfWeek: context.querySelector('#selectDay').value,
StartHour: context.querySelector('#selectStart').value,
EndHour: context.querySelector('#selectEnd').value
@ -46,44 +57,42 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt
dialogHelper.close(context);
}
return {
show: function (options) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'components/accessSchedule/accessSchedule.template.html', true);
xhr.onload = function (e) {
var template = this.response;
var dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
var html = '';
html += globalize.translateDocument(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', function () {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', function (event) {
submitSchedule(dlg, options);
event.preventDefault();
return false;
});
};
xhr.send();
export function show(options) {
return new Promise((resolve, reject) => {
// TODO: remove require
require(['text!./components/accessSchedule/accessSchedule.template.html'], template => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateDocument(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', () => {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', event => {
submitSchedule(dlg, options);
event.preventDefault();
return false;
});
});
}
};
});
});
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -192,9 +192,14 @@ button::-moz-focus-inner {
/* Needed in case this is a button */
display: block;
/* Needed in case this is a button */
margin: 0 !important;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
font-family: inherit;
font-size: inherit;
/* Needed in safari */
height: 100%;
@ -203,19 +208,12 @@ button::-moz-focus-inner {
contain: strict;
}
.cardContent-button {
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
.cardContent:not(.defaultCardBackground) {
background-color: transparent;
}
.cardContent-button:not(.defaultCardBackground) {
background-color: transparent;
.cardBox:not(.visualCardBox) .cardPadder {
background-color: #242424;
}
.visualCardBox .cardContent {
@ -223,7 +221,8 @@ button::-moz-focus-inner {
border-bottom-right-radius: 0;
}
.cardContent-shadow {
.cardContent-shadow,
.cardBox:not(.visualCardBox) .cardPadder {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
}

View file

@ -368,9 +368,7 @@ import 'programStyles';
let apiClient;
let lastServerId;
for (let i = 0; i < items.length; i++) {
let item = items[i];
for (const [i, item] of items.entries()) {
let serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId) {
@ -503,94 +501,49 @@ import 'programStyles';
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
let forceName = false;
let imgUrl = null;
let imgTag = null;
let coverImage = false;
let uiAspect = null;
let imgType = null;
let itemId = null;
if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Banner',
maxWidth: width,
tag: item.ImageTags.Banner
});
imgType = 'Banner';
imgTag = item.ImageTags.Banner;
} else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Disc',
maxWidth: width,
tag: item.ImageTags.Disc
});
imgType = 'Disc';
imgTag = item.ImageTags.Disc;
} else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Logo',
maxWidth: width,
tag: item.ImageTags.Logo
});
imgType = 'Logo';
imgTag = item.ImageTags.Logo;
} else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) {
imgUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, {
type: 'Logo',
maxWidth: width,
tag: item.ParentLogoImageTag
});
imgType = 'Logo';
imgTag = item.ParentLogoImageTag;
itemId = item.ParentLogoItemId;
} else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Thumb',
maxWidth: width,
tag: item.SeriesThumbImageTag
});
imgType = 'Thumb';
imgTag = item.SeriesThumbImageTag;
itemId = item.SeriesId;
} else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') {
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: 'Thumb',
maxWidth: width,
tag: item.ParentThumbImageTag
});
imgType = 'Thumb';
imgTag = item.ParentThumbImageTag;
itemId = item.ParentThumbItemId;
} else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.BackdropImageTags[0];
forceName = true;
} else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') {
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: 'Backdrop',
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
});
} else if (item.ImageTags && item.ImageTags.Primary) {
imgType = 'Backdrop';
imgTag = item.ParentBackdropImageTags[0];
itemId = item.ParentBackdropItemId;
} else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) {
imgType = 'Primary';
imgTag = item.ImageTags.Primary;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.ImageTags.Primary
});
if (options.preferThumb && options.showTitle !== false) {
forceName = true;
}
@ -601,18 +554,16 @@ import 'programStyles';
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.PrimaryImageTag) {
imgType = 'Primary';
imgTag = item.PrimaryImageTag;
itemId = item.PrimaryImageItemId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.PrimaryImageTag
});
if (options.preferThumb && options.showTitle !== false) {
forceName = true;
}
@ -624,30 +575,15 @@ import 'programStyles';
}
}
} else if (item.ParentPrimaryImageTag) {
imgUrl = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, {
type: 'Primary',
maxWidth: width,
tag: item.ParentPrimaryImageTag
});
} else if (item.SeriesPrimaryImageTag) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Primary',
maxWidth: width,
tag: item.SeriesPrimaryImageTag
});
imgType = 'Primary';
imgTag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.AlbumId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.AlbumPrimaryImageTag
});
if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape);
if (uiAspect) {
@ -655,57 +591,46 @@ import 'programStyles';
}
}
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.BackdropImageTags[0];
} else if (item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if (item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Thumb',
maxWidth: width,
tag: item.SeriesThumbImageTag
});
imgType = 'Thumb';
imgTag = item.SeriesThumbImageTag;
itemId = item.SeriesId;
} else if (item.ParentThumbItemId && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: 'Thumb',
maxWidth: width,
tag: item.ParentThumbImageTag
});
imgType = 'Thumb';
imgTag = item.ParentThumbImageTag;
itemId = item.ParentThumbItemId;
} else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: 'Backdrop',
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.ParentBackdropImageTags[0];
itemId = item.ParentBackdropItemId;
}
if (!itemId) {
itemId = item.Id;
}
if (imgTag && imgType) {
imgUrl = apiClient.getScaledImageUrl(itemId, {
type: imgType,
maxHeight: height,
maxWidth: width,
tag: imgTag
});
}
let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
return {
imgUrl: imgUrl,
blurhash: (blurHashes[imgType] || {})[imgTag],
forceName: forceName,
coverImage: coverImage
};
@ -1321,6 +1246,7 @@ import 'programStyles';
const imgInfo = getCardImageUrl(item, apiClient, options, shape);
const imgUrl = imgInfo.imgUrl;
const blurhash = imgInfo.blurhash;
const forceName = imgInfo.forceName;
@ -1441,26 +1367,27 @@ import 'programStyles';
let cardScalableClose = '';
let cardContentClass = 'cardContent';
if (!options.cardLayout) {
cardContentClass += ' cardContent-shadow';
let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
blurhashAttrib = 'data-blurhash="' + blurhash + '"';
}
if (layoutManager.tv) {
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
cardImageContainerClose = '</div>';
} else {
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '">') : ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerClose = '</button>';
}
let cardScalableClass = 'cardScalable';
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>';
cardScalableClose = '</div>';

View file

@ -1,13 +1,23 @@
define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browser'], function (datetime, imageLoader, connectionManager, layoutManager, browser) {
'use strict';
/* eslint-disable indent */
var enableFocusTransform = !browser.slow && !browser.edge;
/**
* Module for building cards from item data.
* @module components/cardBuilder/chaptercardbuilder
*/
function buildChapterCardsHtml(item, chapters, options) {
import datetime from 'datetime';
import imageLoader from 'imageLoader';
import connectionManager from 'connectionManager';
import layoutManager from 'layoutManager';
import browser from 'browser';
const enableFocusTransform = !browser.slow && !browser.edge;
function buildChapterCardsHtml(item, chapters, options) {
// TODO move card creation code to Card component
var className = 'card itemAction chapterCard';
let className = 'card itemAction chapterCard';
if (layoutManager.tv) {
className += ' show-focus';
@ -17,12 +27,12 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
}
}
var mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
var videoStream = mediaStreams.filter(function (i) {
return i.Type === 'Video';
const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
const videoStream = mediaStreams.filter(({Type}) => {
return Type === 'Video';
})[0] || {};
var shape = (options.backdropShape || 'backdrop');
let shape = (options.backdropShape || 'backdrop');
if (videoStream.Width && videoStream.Height) {
@ -31,24 +41,24 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
}
}
className += ' ' + shape + 'Card';
className += ` ${shape}Card`;
if (options.block || options.rows) {
className += ' block';
}
var html = '';
var itemsInRow = 0;
let html = '';
let itemsInRow = 0;
var apiClient = connectionManager.getApiClient(item.ServerId);
const apiClient = connectionManager.getApiClient(item.ServerId);
for (var i = 0, length = chapters.length; i < length; i++) {
for (let i = 0, length = chapters.length; i < length; i++) {
if (options.rows && itemsInRow === 0) {
html += '<div class="cardColumn">';
}
var chapter = chapters[i];
const chapter = chapters[i];
html += buildChapterCard(item, apiClient, chapter, i, options, className, shape);
itemsInRow++;
@ -62,50 +72,50 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
return html;
}
function getImgUrl(item, chapter, index, maxWidth, apiClient) {
function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) {
if (chapter.ImageTag) {
if (ImageTag) {
return apiClient.getScaledImageUrl(item.Id, {
return apiClient.getScaledImageUrl(Id, {
maxWidth: maxWidth * 2,
tag: chapter.ImageTag,
tag: ImageTag,
type: 'Chapter',
index: index
index
});
}
return null;
}
function buildChapterCard(item, apiClient, chapter, index, options, className, shape) {
function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) {
var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient);
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
var cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
if (options.coverImage) {
let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
if (coverImage) {
cardImageContainerClass += ' coveredImage';
}
var dataAttributes = ' data-action="play" data-isfolder="' + item.IsFolder + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-type="' + item.Type + '" data-mediatype="' + item.MediaType + '" data-positionticks="' + chapter.StartPositionTicks + '"';
var cardImageContainer = imgUrl ? ('<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + '">');
const dataAttributes = ` data-action="play" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-positionticks="${chapter.StartPositionTicks}"`;
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
if (!imgUrl) {
cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
}
var nameHtml = '';
nameHtml += '<div class="cardText">' + chapter.Name + '</div>';
nameHtml += '<div class="cardText">' + datetime.getDisplayRunningTime(chapter.StartPositionTicks) + '</div>';
let nameHtml = '';
nameHtml += `<div class="cardText">${chapter.Name}</div>`;
nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
var cardBoxCssClass = 'cardBox';
var cardScalableClass = 'cardScalable';
const cardBoxCssClass = 'cardBox';
const cardScalableClass = 'cardScalable';
var html = '<button type="button" class="' + className + '"' + dataAttributes + '><div class="' + cardBoxCssClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainer + '</div><div class="innerCardFooter">' + nameHtml + '</div></div></div></button>';
const html = `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
return html;
}
function buildChapterCards(item, chapters, options) {
export function buildChapterCards(item, chapters, options) {
if (options.parentContainer) {
// Abort if the container has been disposed
@ -121,15 +131,16 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
}
}
var html = buildChapterCardsHtml(item, chapters, options);
const html = buildChapterCardsHtml(item, chapters, options);
options.itemsContainer.innerHTML = html;
imageLoader.lazyChildren(options.itemsContainer);
}
return {
buildChapterCards: buildChapterCards
};
/* eslint-enable indent */
export default {
buildChapterCards: buildChapterCards
};
});

View file

@ -1,7 +1,13 @@
define(['cardBuilder'], function (cardBuilder) {
'use strict';
/* eslint-disable indent */
function buildPeopleCards(items, options) {
/**
* Module for building cards from item data.
* @module components/cardBuilder/peoplecardbuilder
*/
import cardBuilder from 'cardBuilder';
export function buildPeopleCards(items, options) {
options = Object.assign(options || {}, {
cardLayout: false,
@ -15,8 +21,8 @@ define(['cardBuilder'], function (cardBuilder) {
cardBuilder.buildCards(items, options);
}
return {
buildPeopleCards: buildPeopleCards
};
/* eslint-enable indent */
});
export default {
buildPeopleCards: buildPeopleCards
};

View file

@ -4,12 +4,8 @@
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:1em;padding-bottom: 1em; text-align: center;">
<div class="text">
</div>
<div class="text"></div>
</div>
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="padding-bottom: 1.5em;">
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="margin:1em"></div>

View file

@ -181,6 +181,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
@ -223,6 +224,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
userSettingsInstance.skin(context.querySelector('.selectSkin').value);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);

View file

@ -143,20 +143,28 @@
<select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select>
</div>
<div class="inputContainer inputContainer-withDescription fldFadein">
<div class="inputContainer inputContainer-withDescription">
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldFadein">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
<span>${EnableFastImageFadeIn}</span>
<span>${EnableFasterAnimations}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableFastImageFadeInHelp}</div>
<div class="fieldDescription checkboxFieldDescription">${EnableFasterAnimationsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldDetailsBanner">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBlurhash" />
<span>${EnableBlurhash}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableBlurhashHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
<span>${EnableDetailsBanner}</span>

View file

@ -1,21 +1,19 @@
define([], function () {
'use strict';
/* eslint-disable indent */
export function getFetchPromise(request) {
function getFetchPromise(request) {
var headers = request.headers || {};
const headers = request.headers || {};
if (request.dataType === 'json') {
headers.accept = 'application/json';
}
var fetchRequest = {
const fetchRequest = {
headers: headers,
method: request.type,
credentials: 'same-origin'
};
var contentType = request.contentType;
let contentType = request.contentType;
if (request.data) {
@ -33,12 +31,12 @@ define([], function () {
headers['Content-Type'] = contentType;
}
var url = request.url;
let url = request.url;
if (request.query) {
var paramString = paramsToString(request.query);
const paramString = paramsToString(request.query);
if (paramString) {
url += '?' + paramString;
url += `?${paramString}`;
}
}
@ -51,11 +49,11 @@ define([], function () {
function fetchWithTimeout(url, options, timeoutMs) {
console.debug('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url);
console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);
return new Promise(function (resolve, reject) {
var timeout = setTimeout(reject, timeoutMs);
const timeout = setTimeout(reject, timeoutMs);
options = options || {};
options.credentials = 'same-origin';
@ -63,50 +61,47 @@ define([], function () {
fetch(url, options).then(function (response) {
clearTimeout(timeout);
console.debug('fetchWithTimeout: succeeded connecting to url: ' + url);
console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`);
resolve(response);
}, function (error) {
clearTimeout(timeout);
console.debug('fetchWithTimeout: timed out connecting to url: ' + url);
console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`);
reject();
reject(error);
});
});
}
/**
* @param params {Record<string, string | number | boolean>}
* @returns {string} Query string
*/
function paramsToString(params) {
var values = [];
for (var key in params) {
var value = params[key];
if (value !== null && value !== undefined && value !== '') {
values.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
}
}
return values.join('&');
return Object.entries(params)
// eslint-disable-next-line no-unused-vars
.filter(([_, v]) => v !== null && v !== undefined && v !== '')
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
}
function ajax(request) {
export function ajax(request) {
if (!request) {
throw new Error('Request cannot be null');
}
request.headers = request.headers || {};
console.debug('requesting url: ' + request.url);
console.debug(`requesting url: ${request.url}`);
return getFetchPromise(request).then(function (response) {
console.debug('response status: ' + response.status + ', url: ' + request.url);
console.debug(`response status: ${response.status}, url: ${request.url}`);
if (response.status < 400) {
if (request.dataType === 'json' || request.headers.accept === 'application/json') {
return response.json();
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) {
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().startsWith('text/')) {
return response.text();
} else {
return response;
@ -115,12 +110,8 @@ define([], function () {
return Promise.reject(response);
}
}, function (err) {
console.error('request failed to url: ' + request.url);
console.error(`request failed to url: ${request.url}`);
throw err;
});
}
return {
getFetchPromise: getFetchPromise,
ajax: ajax
};
});
/* eslint-enable indent */

View file

@ -1,21 +1,28 @@
define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'browser', 'require', 'emby-checkbox', 'emby-collapse', 'css!./style'], function (dom, dialogHelper, globalize, connectionManager, events, browser, require) {
'use strict';
import dom from 'dom';
import dialogHelper from 'dialogHelper';
import globalize from 'globalize';
import connectionManager from 'connectionManager';
import events from 'events';
import 'emby-checkbox';
import 'emby-collapse';
import 'css!./style.css';
/* eslint-disable indent */
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
const elem = context.querySelector(selector);
if (items.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
var html = '';
let html = '';
html += '<div class="checkboxList">';
html += items.map(function (filter) {
var itemHtml = '';
var checkedHtml = isCheckedFn(filter) ? ' checked' : '';
let itemHtml = '';
const checkedHtml = isCheckedFn(filter) ? 'checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + filter + '</span>';
itemHtml += `<input is="emby-checkbox" type="checkbox" ${checkedHtml} data-filter="${filter}" class="${cssClass}"/>`;
itemHtml += `<span>${filter}</span>`;
itemHtml += '</label>';
return itemHtml;
}).join('');
@ -24,21 +31,24 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
}
function renderFilters(context, result, query) {
if (result.Tags) {
result.Tags.length = Math.min(result.Tags.length, 50);
}
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
var delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) {
var delimeter = '|';
return (delimeter + (query.OfficialRatings || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) {
var delimeter = '|';
return (delimeter + (query.Tags || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) {
var delimeter = ',';
return (delimeter + (query.Years || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = ',';
return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter);
});
}
@ -52,59 +62,58 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
});
}
/**
* @param context {HTMLDivElement} Dialog
* @param options {any} Options
*/
function updateFilterControls(context, options) {
var elems;
var i;
var length;
var query = options.query;
const query = options.query;
if (options.mode == 'livetvchannels') {
context.querySelector('.chkFavorite').checked = query.IsFavorite == true;
context.querySelector('.chkLikes').checked = query.IsLiked == true;
context.querySelector('.chkDislikes').checked = query.IsDisliked == true;
if (options.mode === 'livetvchannels') {
context.querySelector('.chkFavorite').checked = query.IsFavorite === true;
context.querySelector('.chkLikes').checked = query.IsLiked === true;
context.querySelector('.chkDislikes').checked = query.IsDisliked === true;
} else {
elems = context.querySelectorAll('.chkStandardFilter');
for (i = 0, length = elems.length; i < length; i++) {
var chkStandardFilter = elems[i];
var filters = ',' + (query.Filters || '');
var filterName = chkStandardFilter.getAttribute('data-filter');
chkStandardFilter.checked = filters.indexOf(',' + filterName) != -1;
for (const elem of context.querySelectorAll('.chkStandardFilter')) {
const filters = `,${query.Filters || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
var chkVideoTypeFilter = elems[i];
var filters = ',' + (query.VideoTypes || '');
var filterName = chkVideoTypeFilter.getAttribute('data-filter');
chkVideoTypeFilter.checked = filters.indexOf(',' + filterName) != -1;
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
const filters = `,${query.VideoTypes || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
context.querySelector('.chk3DFilter').checked = query.Is3D == true;
context.querySelector('.chkHDFilter').checked = query.IsHD == true;
context.querySelector('.chk4KFilter').checked = query.Is4K == true;
context.querySelector('.chkSDFilter').checked = query.IsHD == true;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles == true;
context.querySelector('#chkTrailer').checked = query.HasTrailer == true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong == true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo == true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature == true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber == 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing == true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired == true;
for (i = 0, length = elems.length; i < length; i++) {
var chkStatus = elems[i];
var filters = ',' + (query.SeriesStatus || '');
var filterName = chkStatus.getAttribute('data-filter');
chkStatus.checked = filters.indexOf(',' + filterName) != -1;
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === true;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true;
for (const elem of context.querySelectorAll('.chkStatus')) {
const filters = `,${query.SeriesStatus || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
/**
* @param instance {FilterDialog} An instance of FilterDialog
*/
function triggerChange(instance) {
events.trigger(instance, 'filterchange');
}
function setVisibility(context, options) {
if (options.mode == 'livetvchannels' || options.mode == 'albums' || options.mode == 'artists' || options.mode == 'albumartists' || options.mode == 'songs') {
if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') {
hideByClass(context, 'videoStandard');
}
@ -115,263 +124,287 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
context.querySelector('.yearFilters').classList.remove('hide');
}
if (options.mode == 'movies' || options.mode == 'episodes') {
if (options.mode === 'movies' || options.mode === 'episodes') {
context.querySelector('.videoTypeFilters').classList.remove('hide');
}
if (options.mode == 'movies' || options.mode == 'series' || options.mode == 'episodes') {
if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') {
context.querySelector('.features').classList.remove('hide');
}
if (options.mode == 'series') {
if (options.mode === 'series') {
context.querySelector('.seriesStatus').classList.remove('hide');
}
if (options.mode == 'episodes') {
if (options.mode === 'episodes') {
showByClass(context, 'episodeFilter');
}
}
function showByClass(context, className) {
var elems = context.querySelectorAll('.' + className);
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].classList.remove('hide');
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.remove('hide');
}
}
function hideByClass(context, className) {
var elems = context.querySelectorAll('.' + className);
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].classList.add('hide');
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.add('hide');
}
}
function enableDynamicFilters(mode) {
return mode == 'movies' || mode == 'series' || mode == 'albums' || mode == 'albumartists' || mode == 'artists' || mode == 'songs' || mode == 'episodes';
return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes';
}
return function (options) {
function onFavoriteChange() {
var query = options.query;
query.StartIndex = 0;
query.IsFavorite = !!this.checked || null;
triggerChange(self);
class FilterDialog {
constructor(options) {
/**
* @private
*/
this.options = options;
}
function onStandardFilterChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.Filters || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onFavoriteChange(elem) {
const query = this.options.query;
query.StartIndex = 0;
query.IsFavorite = !!elem.checked || null;
triggerChange(this);
}
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
/**
* @private
*/
onStandardFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.Filters || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.Filters = filters;
triggerChange(self);
triggerChange(this);
}
function onVideoTypeFilterChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.VideoTypes || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onVideoTypeFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.VideoTypes || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.VideoTypes = filters;
triggerChange(self);
triggerChange(this);
}
function onStatusChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.SeriesStatus || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onStatusChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.SeriesStatus || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.SeriesStatus = filters;
query.StartIndex = 0;
triggerChange(self);
triggerChange(this);
}
function bindEvents(context) {
var elems;
var i;
var length;
var query = options.query;
/**
* @param context {HTMLDivElement} The dialog
*/
bindEvents(context) {
const query = this.options.query;
if (options.mode == 'livetvchannels') {
elems = context.querySelectorAll('.chkFavorite');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onFavoriteChange);
if (this.options.mode === 'livetvchannels') {
for (const elem of context.querySelectorAll('.chkFavorite')) {
elem.addEventListener('change', () => this.onFavoriteChange(elem));
}
context.querySelector('.chkLikes').addEventListener('change', function () {
const chkLikes = context.querySelector('.chkLikes');
chkLikes.addEventListener('change', () => {
query.StartIndex = 0;
query.IsLiked = this.checked ? true : null;
triggerChange(self);
query.IsLiked = chkLikes.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkDislikes').addEventListener('change', function () {
const chkDislikes = context.querySelector('.chkDislikes');
chkDislikes.addEventListener('change', () => {
query.StartIndex = 0;
query.IsDisliked = this.checked ? true : null;
triggerChange(self);
query.IsDisliked = chkDislikes.checked ? true : null;
triggerChange(this);
});
} else {
elems = context.querySelectorAll('.chkStandardFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onStandardFilterChange);
for (const elem of context.querySelectorAll('.chkStandardFilter')) {
elem.addEventListener('change', () => this.onStandardFilterChange(elem));
}
}
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onVideoTypeFilterChange);
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem));
}
context.querySelector('.chk3DFilter').addEventListener('change', function () {
const chk3DFilter = context.querySelector('.chk3DFilter');
chk3DFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.Is3D = this.checked ? true : null;
triggerChange(self);
query.Is3D = chk3DFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chk4KFilter').addEventListener('change', function () {
const chk4KFilter = context.querySelector('.chk4KFilter');
chk4KFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.Is4K = this.checked ? true : null;
triggerChange(self);
query.Is4K = chk4KFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkHDFilter').addEventListener('change', function () {
const chkHDFilter = context.querySelector('.chkHDFilter');
chkHDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = this.checked ? true : null;
triggerChange(self);
query.IsHD = chkHDFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkSDFilter').addEventListener('change', function () {
const chkSDFilter = context.querySelector('.chkSDFilter');
chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = this.checked ? false : null;
triggerChange(self);
query.IsHD = chkSDFilter.checked ? false : null;
triggerChange(this);
});
elems = context.querySelectorAll('.chkStatus');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onStatusChange);
for (const elem of context.querySelectorAll('.chkStatus')) {
elem.addEventListener('change', () => this.onStatusChange(elem));
}
context.querySelector('#chkTrailer').addEventListener('change', function () {
const chkTrailer = context.querySelector('#chkTrailer');
chkTrailer.addEventListener('change', () => {
query.StartIndex = 0;
query.HasTrailer = this.checked ? true : null;
triggerChange(self);
query.HasTrailer = chkTrailer.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkThemeSong').addEventListener('change', function () {
const chkThemeSong = context.querySelector('#chkThemeSong');
chkThemeSong.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeSong = this.checked ? true : null;
triggerChange(self);
query.HasThemeSong = chkThemeSong.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkSpecialFeature').addEventListener('change', function () {
const chkSpecialFeature = context.querySelector('#chkSpecialFeature');
chkSpecialFeature.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSpecialFeature = this.checked ? true : null;
triggerChange(self);
query.HasSpecialFeature = chkSpecialFeature.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkThemeVideo').addEventListener('change', function () {
const chkThemeVideo = context.querySelector('#chkThemeVideo');
chkThemeVideo.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeVideo = this.checked ? true : null;
triggerChange(self);
query.HasThemeVideo = chkThemeVideo.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkMissingEpisode').addEventListener('change', function () {
const chkMissingEpisode = context.querySelector('#chkMissingEpisode');
chkMissingEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.IsMissing = this.checked ? true : false;
triggerChange(self);
query.IsMissing = !!chkMissingEpisode.checked;
triggerChange(this);
});
context.querySelector('#chkSpecialEpisode').addEventListener('change', function () {
const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode');
chkSpecialEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.ParentIndexNumber = this.checked ? 0 : null;
triggerChange(self);
query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null;
triggerChange(this);
});
context.querySelector('#chkFutureEpisode').addEventListener('change', function () {
const chkFutureEpisode = context.querySelector('#chkFutureEpisode');
chkFutureEpisode.addEventListener('change', () => {
query.StartIndex = 0;
if (this.checked) {
if (chkFutureEpisode.checked) {
query.IsUnaired = true;
query.IsVirtualUnaired = null;
} else {
query.IsUnaired = null;
query.IsVirtualUnaired = false;
}
triggerChange(self);
triggerChange(this);
});
context.querySelector('#chkSubtitle').addEventListener('change', function () {
const chkSubtitle = context.querySelector('#chkSubtitle');
chkSubtitle.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSubtitles = this.checked ? true : null;
triggerChange(self);
query.HasSubtitles = chkSubtitle.checked ? true : null;
triggerChange(this);
});
context.addEventListener('change', function (e) {
var chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
context.addEventListener('change', (e) => {
const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
if (chkGenreFilter) {
var filterName = chkGenreFilter.getAttribute('data-filter');
var filters = query.Genres || '';
var delimiter = '|';
const filterName = chkGenreFilter.getAttribute('data-filter');
let filters = query.Genres || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkGenreFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Genres = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
if (chkTagFilter) {
var filterName = chkTagFilter.getAttribute('data-filter');
var filters = query.Tags || '';
var delimiter = '|';
const filterName = chkTagFilter.getAttribute('data-filter');
let filters = query.Tags || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkTagFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Tags = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
if (chkYearFilter) {
var filterName = chkYearFilter.getAttribute('data-filter');
var filters = query.Years || '';
var delimiter = ',';
const filterName = chkYearFilter.getAttribute('data-filter');
let filters = query.Years || '';
const delimiter = ',';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkYearFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Years = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
if (chkOfficialRatingFilter) {
var filterName = chkOfficialRatingFilter.getAttribute('data-filter');
var filters = query.OfficialRatings || '';
var delimiter = '|';
const filterName = chkOfficialRatingFilter.getAttribute('data-filter');
let filters = query.OfficialRatings || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkOfficialRatingFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.OfficialRatings = filters;
triggerChange(self);
return;
triggerChange(this);
}
});
}
var self = this;
self.show = function () {
return new Promise(function (resolve, reject) {
require(['text!./filterdialog.template.html'], function (template) {
var dlg = dialogHelper.createDialog({
show() {
return import('text!./filterdialog.template.html').then(({default: template}) => {
return new Promise((resolve) => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
modal: false
});
@ -380,18 +413,21 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
dlg.classList.add('formDialog');
dlg.classList.add('filterDialog');
dlg.innerHTML = globalize.translateDocument(template);
setVisibility(dlg, options);
setVisibility(dlg, this.options);
dialogHelper.open(dlg);
dlg.addEventListener('close', resolve);
updateFilterControls(dlg, options);
bindEvents(dlg);
if (enableDynamicFilters(options.mode)) {
updateFilterControls(dlg, this.options);
this.bindEvents(dlg);
if (enableDynamicFilters(this.options.mode)) {
dlg.classList.add('dynamicFilterDialog');
var apiClient = connectionManager.getApiClient(options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), options.query);
const apiClient = connectionManager.getApiClient(this.options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query);
}
});
});
};
};
});
}
}
/* eslint-enable indent */
export default FilterDialog;

View file

@ -55,7 +55,7 @@
/* Without this emby-checkbox is able to appear on top */
z-index: 1;
align-items: flex-end;
justify-content: flex-end;
justify-content: center;
flex-wrap: wrap;
}

View file

@ -168,7 +168,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getPortraitShape() {
return enableScrollX() ? 'autooverflow' : 'auto';
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
function getLibraryButtonsHtml(items) {
@ -254,7 +254,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
return cardBuilder.getCardsHtml({
items: items,
shape: shape,
preferThumb: viewType !== 'movies' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
showUnplayedIndicator: false,
showChildCountIndicator: true,
context: 'home',

View file

@ -58,6 +58,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
var html = ['<img style="max-width:100%;max-height:100%;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
page.querySelector('#imageOutput').innerHTML = html;
page.querySelector('#dropImageText').classList.add('hide');
page.querySelector('#fldUpload').classList.remove('hide');
};
})(file);

View file

@ -20,7 +20,7 @@
</div>
<div>
<div class="imageEditor-dropZone fieldDescription">
<div>${LabelDropImageHere}</div>
<div id="dropImageText">${LabelDropImageHere}</div>
<output id="imageOutput" class="flex align-items-center justify-content-center" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;"></output>
<input type="file" accept="image/*" id="uploadImage" name="uploadImage" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;opacity:0;" />
</div>

View file

@ -1,5 +1,6 @@
import * as lazyLoader from 'lazyLoader';
import * as userSettings from 'userSettings';
import * as blurhash from 'blurhash';
import 'css!./style';
/* eslint-disable indent */
@ -11,47 +12,111 @@ import 'css!./style';
fillImageElement(elem, source);
}
async function itemBlurhashing(target, blurhashstr) {
if (blurhash.isBlurhashValid(blurhashstr)) {
// Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us,
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
// Lower values had more visible pixelation
let width = 18;
let height = 18;
let pixels;
try {
pixels = blurhash.decode(blurhashstr, width, height);
} catch (err) {
console.error('Blurhash decode error: ', err);
target.classList.add('non-blurhashable');
return;
}
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
let imgData = ctx.createImageData(width, height);
imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0);
let child = target.appendChild(canvas);
child.classList.add('blurhash-canvas');
child.style.opacity = 1;
if (userSettings.enableFastFadein()) {
child.classList.add('lazy-blurhash-fadein-fast');
} else {
child.classList.add('lazy-blurhash-fadein');
}
target.classList.add('blurhashed');
target.removeAttribute('data-blurhash');
}
}
function switchCanvas(elem) {
let child = elem.getElementsByClassName('blurhash-canvas')[0];
if (child) {
child.style.opacity = elem.getAttribute('data-src') ? 1 : 0;
}
}
export function fillImage(entry) {
if (!entry) {
throw new Error('entry cannot be null');
}
let target = entry.target;
var source = undefined;
if (entry.target) {
source = entry.target.getAttribute('data-src');
if (target) {
source = target.getAttribute('data-src');
var blurhashstr = target.getAttribute('data-blurhash');
} else {
source = entry;
}
if (userSettings.enableBlurhash()) {
if (!target.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) {
itemBlurhashing(target, blurhashstr);
} else if (!blurhashstr && !target.classList.contains('blurhashed')) {
target.classList.add('non-blurhashable');
}
}
if (entry.intersectionRatio > 0) {
if (source) fillImageElement(entry.target, source);
if (source) fillImageElement(target, source);
} else if (!source) {
emptyImageElement(entry.target);
emptyImageElement(target);
}
}
function fillImageElement(elem, url) {
if (url === undefined) {
throw new Error('url cannot be undefined');
throw new TypeError('url cannot be undefined');
}
let preloaderImg = new Image();
preloaderImg.src = url;
// This is necessary here, so changing blurhash settings without reloading the page works
if (!userSettings.enableBlurhash() || elem.classList.contains('non-blurhashable')) {
elem.classList.add('lazy-hidden');
}
preloaderImg.addEventListener('load', () => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
}
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
elem.removeAttribute('data-src');
if (elem.classList.contains('non-blurhashable') || !userSettings.enableBlurhash()) {
elem.classList.remove('lazy-hidden');
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
} else {
switchCanvas(elem);
}
});
}
@ -65,11 +130,14 @@ import 'css!./style';
url = elem.getAttribute('src');
elem.setAttribute('src', '');
}
elem.setAttribute('data-src', url);
elem.classList.remove('lazy-image-fadein-fast');
elem.classList.remove('lazy-image-fadein');
if (elem.classList.contains('non-blurhashable') || !userSettings.enableBlurhash()) {
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
} else {
switchCanvas(elem);
}
}
export function lazyChildren(elem) {

View file

@ -1,13 +1,33 @@
.cardImageContainer.lazy {
.lazy-image-fadein {
opacity: 1;
transition: opacity 0.5s;
}
.lazy-image-fadein-fast {
opacity: 1;
transition: opacity 0.1s;
}
.lazy-hidden {
opacity: 0;
}
.cardImageContainer.lazy.lazy-image-fadein {
opacity: 1;
.lazy-blurhash-fadein-fast {
transition: opacity 0.2s;
}
.lazy-blurhash-fadein {
transition: opacity 0.7s;
}
.cardImageContainer.lazy.lazy-image-fadein-fast {
opacity: 1;
transition: opacity 0.2s;
.blurhash-canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
pointer-events: none;
}

View file

@ -403,7 +403,7 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter',
break;
case 'moremediainfo':
require(['itemMediaInfo'], function (itemMediaInfo) {
itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id));
});
break;
case 'refresh':

View file

@ -11,9 +11,9 @@
(entries) => {
entries.forEach(entry => {
callback(entry);
},
{rootMargin: '50%'});
});
});
},
{rootMargin: '25%'});
this.observer = observer;
}

View file

@ -70,6 +70,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
function getImageUrl(item, width) {
var apiClient = connectionManager.getApiClient(item.ServerId);
let itemId;
var options = {
maxWidth: width * 2,
@ -77,45 +78,45 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
};
if (item.ImageTags && item.ImageTags.Primary) {
options.tag = item.ImageTags.Primary;
return apiClient.getScaledImageUrl(item.Id, options);
}
if (item.AlbumId && item.AlbumPrimaryImageTag) {
itemId = item.Id;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
return apiClient.getScaledImageUrl(item.AlbumId, options);
itemId = item.AlbumId;
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
options.tag = item.SeriesPrimaryImageTag;
return apiClient.getScaledImageUrl(item.SeriesId, options);
itemId = item.SeriesId;
} else if (item.ParentPrimaryImageTag) {
options.tag = item.ParentPrimaryImageTag;
return apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, options);
itemId = item.ParentPrimaryImageItemId;
}
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type] || {})[options.tag];
if (itemId) {
return { url: apiClient.getScaledImageUrl(itemId, options), blurhash: blurhashstr };
}
return null;
}
function getChannelImageUrl(item, width) {
var apiClient = connectionManager.getApiClient(item.ServerId);
var options = {
maxWidth: width * 2,
type: 'Primary'
};
if (item.ChannelId && item.ChannelPrimaryImageTag) {
options.tag = item.ChannelPrimaryImageTag;
return apiClient.getScaledImageUrl(item.ChannelId, options);
}
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type])[options.tag];
return null;
if (item.ChannelId) {
return { url: apiClient.getScaledImageUrl(item.ChannelId, options), blurhash: blurhashstr };
}
}
function getTextLinesHtml(textlines, isLargeStyle) {
@ -268,8 +269,10 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
}
if (options.image !== false) {
var imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
var imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
let imgData = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
let imgUrl = imgData.url;
let blurhash = imgData.blurhash;
let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
if (isLargeStyle && layoutManager.tv) {
imageClass += ' listItemImage-large-tv';
@ -283,8 +286,13 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan
var imageAction = playOnImageClick ? 'resume' : action;
let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
blurhashAttrib = 'data-blurhash="' + blurhash + '"';
}
if (imgUrl) {
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" item-icon>';
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + ' item-icon>';
} else {
html += '<div class="' + imageClass + '">';
}

View file

@ -1,92 +0,0 @@
define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
'use strict';
function showDialog(instance, options, template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
var dlg = dialogHelper.createDialog(dialogOptions);
var configuredButtons = [];
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-items-center');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.style['flex-grow'] = 'initial';
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
if (enableTvLayout) {
scrollHelper.centerFocus.on(formDialogContent, false);
dlg.querySelector('.formDialogHeader').style.marginTop = '15%';
} else {
dlg.classList.add('dialog-fullscreen-lowres');
}
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
// dialogHelper.close(dlg);
//});
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title;
dlg.querySelector('.text').innerHTML = options.text;
instance.dlg = dlg;
return dialogHelper.open(dlg).then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
loading.hide();
});
}
function LoadingDialog(options) {
this.options = options;
}
LoadingDialog.prototype.show = function () {
var instance = this;
loading.show();
return new Promise(function (resolve, reject) {
require(['text!./../dialog/dialog.template.html'], function (template) {
showDialog(instance, instance.options, template);
resolve();
});
});
};
LoadingDialog.prototype.setTitle = function (title) {
};
LoadingDialog.prototype.setText = function (text) {
};
LoadingDialog.prototype.hide = function () {
if (this.dlg) {
dialogHelper.close(this.dlg);
this.dlg = null;
}
};
LoadingDialog.prototype.destroy = function () {
this.dlg = null;
this.options = null;
};
return LoadingDialog;
});

View file

@ -273,7 +273,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater
}
}
if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && !showFolderRuntime && options.runtime !== false) {
if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) {
if (item.Type === 'Audio') {

View file

@ -308,8 +308,8 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi
}
});
context.removeEventListener('submit', onEditorClick);
context.addEventListener('submit', onEditorClick);
context.removeEventListener('click', onEditorClick);
context.addEventListener('click', onEditorClick);
var form = context.querySelector('form');
form.removeEventListener('submit', onSubmit);

View file

@ -7,7 +7,6 @@
<div class="formDialogContent smoothScrollY" style="padding-top:2em;">
<form class="popupEditPersonForm dialogContentInner dialog-content-centered">
<div class="inputContainer">
<input type="text" is="emby-input" class="txtPersonName" required="required" label="${LabelName}" />
</div>
@ -23,6 +22,7 @@
<option value="Writer">${Writer}</option>
</select>
</div>
<div class="inputContainer fldRole hide">
<input is="emby-input" type="text" class="txtPersonRole" label="${LabelPersonRole}" />
<div class="fieldDescription">${LabelPersonRoleHelp}</div>
@ -33,6 +33,5 @@
<span>${Save}</span>
</button>
</div>
</form>
</div>

View file

@ -1,171 +1,171 @@
define(['events', 'playbackManager', 'dom', 'browser', 'css!./iconosd', 'material-icons'], function (events, playbackManager, dom, browser) {
'use strict';
import events from 'events';
import playbackManager from 'playbackManager';
import dom from 'dom';
import browser from 'browser';
import 'css!./iconosd';
import 'material-icons';
var currentPlayer;
var osdElement;
var iconElement;
var progressElement;
var currentPlayer;
var osdElement;
var iconElement;
var progressElement;
var enableAnimation;
var enableAnimation;
function getOsdElementHtml() {
var html = '';
function getOsdElementHtml() {
var html = '';
html += '<span class="material-icons iconOsdIcon brightness_high"></span>';
html += '<span class="material-icons iconOsdIcon brightness_high"></span>';
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner brightnessOsdProgressInner"></div></div>';
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner brightnessOsdProgressInner"></div></div>';
return html;
return html;
}
function ensureOsdElement() {
var elem = osdElement;
if (!elem) {
enableAnimation = browser.supportsCssAnimation();
elem = document.createElement('div');
elem.classList.add('hide');
elem.classList.add('iconOsd');
elem.classList.add('iconOsd-hidden');
elem.classList.add('brightnessOsd');
elem.innerHTML = getOsdElementHtml();
iconElement = elem.querySelector('.material-icons');
progressElement = elem.querySelector('.iconOsdProgressInner');
document.body.appendChild(elem);
osdElement = elem;
}
}
function ensureOsdElement() {
function onHideComplete() {
this.classList.add('hide');
}
var elem = osdElement;
if (!elem) {
var hideTimeout;
function showOsd() {
enableAnimation = browser.supportsCssAnimation();
clearHideTimeout();
elem = document.createElement('div');
elem.classList.add('hide');
elem.classList.add('iconOsd');
elem.classList.add('iconOsd-hidden');
elem.classList.add('brightnessOsd');
elem.innerHTML = getOsdElementHtml();
var elem = osdElement;
iconElement = elem.querySelector('.material-icons');
progressElement = elem.querySelector('.iconOsdProgressInner');
document.body.appendChild(elem);
osdElement = elem;
}
}
function onHideComplete() {
this.classList.add('hide');
}
var hideTimeout;
function showOsd() {
clearHideTimeout();
var elem = osdElement;
dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
elem.classList.remove('hide');
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.remove('iconOsd-hidden');
hideTimeout = setTimeout(hideOsd, 3000);
});
}
function clearHideTimeout() {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
}
function hideOsd() {
clearHideTimeout();
var elem = osdElement;
if (elem) {
if (enableAnimation) {
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.add('iconOsd-hidden');
dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
});
} else {
onHideComplete.call(elem);
}
}
}
function setIcon(iconElement, icon) {
iconElement.classList.remove('brightness_high');
iconElement.classList.remove('brightness_medium');
iconElement.classList.remove('brightness_low');
iconElement.classList.add(icon);
}
function updateElementsFromPlayer(brightness) {
if (iconElement) {
if (brightness >= 80) {
setIcon(iconElement, 'brightness_high');
} else if (brightness >= 20) {
setIcon(iconElement, 'brightness_medium');
} else {
setIcon(iconElement, 'brightness_low');
}
}
if (progressElement) {
progressElement.style.width = (brightness || 0) + '%';
}
}
function releaseCurrentPlayer() {
var player = currentPlayer;
if (player) {
events.off(player, 'brightnesschange', onBrightnessChanged);
events.off(player, 'playbackstop', hideOsd);
currentPlayer = null;
}
}
function onBrightnessChanged(e) {
var player = this;
ensureOsdElement();
updateElementsFromPlayer(playbackManager.getBrightness(player));
showOsd();
}
function bindToPlayer(player) {
if (player === currentPlayer) {
return;
}
releaseCurrentPlayer();
currentPlayer = player;
if (!player) {
return;
}
hideOsd();
events.on(player, 'brightnesschange', onBrightnessChanged);
events.on(player, 'playbackstop', hideOsd);
}
events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer());
dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
bindToPlayer(playbackManager.getCurrentPlayer());
elem.classList.remove('hide');
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.remove('iconOsd-hidden');
hideTimeout = setTimeout(hideOsd, 3000);
});
}
function clearHideTimeout() {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
}
function hideOsd() {
clearHideTimeout();
var elem = osdElement;
if (elem) {
if (enableAnimation) {
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.add('iconOsd-hidden');
dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
});
} else {
onHideComplete.call(elem);
}
}
}
function setIcon(iconElement, icon) {
iconElement.classList.remove('brightness_high', 'brightness_medium', 'brightness_low');
iconElement.classList.add(icon);
}
function updateElementsFromPlayer(brightness) {
if (iconElement) {
if (brightness >= 80) {
setIcon(iconElement, 'brightness_high');
} else if (brightness >= 20) {
setIcon(iconElement, 'brightness_medium');
} else {
setIcon(iconElement, 'brightness_low');
}
}
if (progressElement) {
progressElement.style.width = (brightness || 0) + '%';
}
}
function releaseCurrentPlayer() {
var player = currentPlayer;
if (player) {
events.off(player, 'brightnesschange', onBrightnessChanged);
events.off(player, 'playbackstop', hideOsd);
currentPlayer = null;
}
}
function onBrightnessChanged(e) {
var player = this;
ensureOsdElement();
updateElementsFromPlayer(playbackManager.getBrightness(player));
showOsd();
}
function bindToPlayer(player) {
if (player === currentPlayer) {
return;
}
releaseCurrentPlayer();
currentPlayer = player;
if (!player) {
return;
}
hideOsd();
events.on(player, 'brightnesschange', onBrightnessChanged);
events.on(player, 'playbackstop', hideOsd);
}
events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer());
});
bindToPlayer(playbackManager.getCurrentPlayer());

View file

@ -241,6 +241,15 @@ import connectionManager from 'connectionManager';
navigator.mediaSession.setActionHandler('seekforward', function () {
execute('fastForward');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekto', function (object) {
let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms
let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0);
let wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
});
}
events.on(playbackManager, 'playerchange', function () {

View file

@ -1,86 +1,82 @@
define([], function () {
'use strict';
export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
var topItem = nowPlayingItem;
var bottomItem = null;
var topText = nowPlayingItem.Name;
var topItem = nowPlayingItem;
var bottomItem = null;
var topText = nowPlayingItem.Name;
if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') {
topItem = {
Id: nowPlayingItem.AlbumId,
Name: nowPlayingItem.Album,
Type: 'MusicAlbum',
IsFolder: true
};
}
if (nowPlayingItem.MediaType === 'Video') {
if (nowPlayingItem.IndexNumber != null) {
topText = nowPlayingItem.IndexNumber + ' - ' + topText;
}
if (nowPlayingItem.ParentIndexNumber != null) {
topText = nowPlayingItem.ParentIndexNumber + '.' + topText;
}
}
var bottomText = '';
if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) {
bottomItem = {
Id: nowPlayingItem.ArtistItems[0].Id,
Name: nowPlayingItem.ArtistItems[0].Name,
Type: 'MusicArtist',
IsFolder: true
};
bottomText = nowPlayingItem.ArtistItems.map(function (a) {
return a.Name;
}).join(', ');
} else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) {
bottomText = nowPlayingItem.Artists.join(', ');
} else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) {
bottomText = topText;
topText = nowPlayingItem.SeriesName || nowPlayingItem.Album;
bottomItem = topItem;
if (nowPlayingItem.SeriesId) {
topItem = {
Id: nowPlayingItem.SeriesId,
Name: nowPlayingItem.SeriesName,
Type: 'Series',
IsFolder: true
};
} else {
topItem = null;
}
} else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) {
bottomText = nowPlayingItem.ProductionYear;
}
var list = [];
list.push({
text: topText,
item: topItem
});
if (bottomText) {
list.push({
text: bottomText,
item: bottomItem
});
}
return list;
if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') {
topItem = {
Id: nowPlayingItem.AlbumId,
Name: nowPlayingItem.Album,
Type: 'MusicAlbum',
IsFolder: true
};
}
return {
getNowPlayingNames: getNowPlayingNames
};
});
if (nowPlayingItem.MediaType === 'Video') {
if (nowPlayingItem.IndexNumber != null) {
topText = nowPlayingItem.IndexNumber + ' - ' + topText;
}
if (nowPlayingItem.ParentIndexNumber != null) {
topText = nowPlayingItem.ParentIndexNumber + '.' + topText;
}
}
var bottomText = '';
if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) {
bottomItem = {
Id: nowPlayingItem.ArtistItems[0].Id,
Name: nowPlayingItem.ArtistItems[0].Name,
Type: 'MusicArtist',
IsFolder: true
};
bottomText = nowPlayingItem.ArtistItems.map(function (a) {
return a.Name;
}).join(', ');
} else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) {
bottomText = nowPlayingItem.Artists.join(', ');
} else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) {
bottomText = topText;
topText = nowPlayingItem.SeriesName || nowPlayingItem.Album;
bottomItem = topItem;
if (nowPlayingItem.SeriesId) {
topItem = {
Id: nowPlayingItem.SeriesId,
Name: nowPlayingItem.SeriesName,
Type: 'Series',
IsFolder: true
};
} else {
topItem = null;
}
} else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) {
bottomText = nowPlayingItem.ProductionYear;
}
var list = [];
list.push({
text: topText,
item: topItem
});
if (bottomText) {
list.push({
text: bottomText,
item: bottomItem
});
}
return list;
}
export default {
getNowPlayingNames: getNowPlayingNames
};

View file

@ -1907,11 +1907,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
// Setting this to true may cause some incorrect sorting
Recursive: false,
SortBy: options.shuffle ? 'Random' : 'SortName',
MediaTypes: 'Photo,Video',
Limit: 500
MediaTypes: 'Photo,Video'
}).then(function (result) {
var items = result.Items;
var index = items.map(function (i) {

View file

@ -1,57 +1,57 @@
define(['playbackManager', 'layoutManager', 'events'], function (playbackManager, layoutManager, events) {
'use strict';
import playbackManager from 'playbackManager';
import layoutManager from 'layoutManager';
import events from 'events';
var orientationLocked;
var orientationLocked;
function onOrientationChangeSuccess() {
orientationLocked = true;
}
function onOrientationChangeSuccess() {
orientationLocked = true;
}
function onOrientationChangeError(err) {
orientationLocked = false;
console.error('error locking orientation: ' + err);
}
function onOrientationChangeError(err) {
orientationLocked = false;
console.error('error locking orientation: ' + err);
}
events.on(playbackManager, 'playbackstart', function (e, player, state) {
events.on(playbackManager, 'playbackstart', function (e, player, state) {
var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player);
var isLocalVideo = player.isLocalPlayer && !player.isExternalPlayer && playbackManager.isPlayingVideo(player);
if (isLocalVideo && layoutManager.mobile) {
/* eslint-disable-next-line compat/compat */
var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock);
if (isLocalVideo && layoutManager.mobile) {
/* eslint-disable-next-line compat/compat */
var lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || (screen.orientation && screen.orientation.lock);
if (lockOrientation) {
if (lockOrientation) {
try {
var promise = lockOrientation('landscape');
if (promise.then) {
promise.then(onOrientationChangeSuccess, onOrientationChangeError);
} else {
// returns a boolean
orientationLocked = promise;
}
} catch (err) {
onOrientationChangeError(err);
try {
var promise = lockOrientation('landscape');
if (promise.then) {
promise.then(onOrientationChangeSuccess, onOrientationChangeError);
} else {
// returns a boolean
orientationLocked = promise;
}
} catch (err) {
onOrientationChangeError(err);
}
}
});
events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) {
if (orientationLocked && !playbackStopInfo.nextMediaType) {
/* eslint-disable-next-line compat/compat */
var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock);
if (unlockOrientation) {
try {
unlockOrientation();
} catch (err) {
console.error('error unlocking orientation: ' + err);
}
orientationLocked = false;
}
}
});
}
});
events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) {
if (orientationLocked && !playbackStopInfo.nextMediaType) {
/* eslint-disable-next-line compat/compat */
var unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock);
if (unlockOrientation) {
try {
unlockOrientation();
} catch (err) {
console.error('error unlocking orientation: ' + err);
}
orientationLocked = false;
}
}
});

View file

@ -1,320 +1,325 @@
define(['appSettings', 'events', 'browser', 'loading', 'playbackManager', 'appRouter', 'globalize', 'apphost'], function (appSettings, events, browser, loading, playbackManager, appRouter, globalize, appHost) {
'use strict';
import appSettings from 'appSettings';
import events from 'events';
import browser from 'browser';
import loading from 'loading';
import playbackManager from 'playbackManager';
import appRouter from 'appRouter';
import globalize from 'globalize';
import appHost from 'apphost';
function mirrorItem(info, player) {
function mirrorItem(info, player) {
var item = info.item;
var item = info.item;
playbackManager.displayContent({
playbackManager.displayContent({
ItemName: item.Name,
ItemId: item.Id,
ItemType: item.Type,
Context: info.context
}, player);
}
ItemName: item.Name,
ItemId: item.Id,
ItemType: item.Type,
Context: info.context
}, player);
}
function mirrorIfEnabled(info) {
function mirrorIfEnabled(info) {
if (info && playbackManager.enableDisplayMirroring()) {
if (info && playbackManager.enableDisplayMirroring()) {
var getPlayerInfo = playbackManager.getPlayerInfo();
var getPlayerInfo = playbackManager.getPlayerInfo();
if (getPlayerInfo) {
if (!getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
mirrorItem(info, playbackManager.getCurrentPlayer());
}
if (getPlayerInfo) {
if (!getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
mirrorItem(info, playbackManager.getCurrentPlayer());
}
}
}
}
function emptyCallback() {
// avoid console logs about uncaught promises
function emptyCallback() {
// avoid console logs about uncaught promises
}
function getTargetSecondaryText(target) {
if (target.user) {
return target.user.Name;
}
function getTargetSecondaryText(target) {
return null;
}
if (target.user) {
function getIcon(target) {
return target.user.Name;
}
var deviceType = target.deviceType;
return null;
}
function getIcon(target) {
var deviceType = target.deviceType;
if (!deviceType && target.isLocalPlayer) {
if (browser.tv) {
deviceType = 'tv';
} else if (browser.mobile) {
deviceType = 'smartphone';
} else {
deviceType = 'desktop';
}
}
if (!deviceType) {
if (!deviceType && target.isLocalPlayer) {
if (browser.tv) {
deviceType = 'tv';
}
switch (deviceType) {
case 'smartphone':
return 'smartphone';
case 'tablet':
return 'tablet';
case 'tv':
return 'tv';
case 'cast':
return 'cast';
case 'desktop':
return 'computer';
default:
return 'tv';
}
}
function showPlayerSelection(button) {
var currentPlayerInfo = playbackManager.getPlayerInfo();
if (currentPlayerInfo) {
if (!currentPlayerInfo.isLocalPlayer) {
showActivePlayerMenu(currentPlayerInfo);
return;
}
}
var currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null;
loading.show();
playbackManager.getTargets().then(function (targets) {
var menuItems = targets.map(function (t) {
var name = t.name;
if (t.appName && t.appName !== t.name) {
name += ' - ' + t.appName;
}
return {
name: name,
id: t.id,
selected: currentPlayerId === t.id,
secondaryText: getTargetSecondaryText(t),
icon: getIcon(t)
};
});
require(['actionsheet'], function (actionsheet) {
loading.hide();
var menuOptions = {
title: globalize.translate('HeaderPlayOn'),
items: menuItems,
positionTo: button,
resolveOnClick: true,
border: true
};
// Unfortunately we can't allow the url to change or chromecast will throw a security error
// Might be able to solve this in the future by moving the dialogs to hashbangs
if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) {
menuOptions.enableHistory = false;
}
actionsheet.show(menuOptions).then(function (id) {
var target = targets.filter(function (t) {
return t.id === id;
})[0];
playbackManager.trySetActivePlayer(target.playerName, target);
mirrorIfEnabled();
}, emptyCallback);
});
});
}
function showActivePlayerMenu(playerInfo) {
require(['dialogHelper', 'dialog', 'emby-checkbox', 'emby-button'], function (dialogHelper) {
showActivePlayerMenuInternal(dialogHelper, playerInfo);
});
}
function disconnectFromPlayer(currentDeviceName) {
if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) {
require(['dialog'], function (dialog) {
var menuItems = [];
menuItems.push({
name: globalize.translate('Yes'),
id: 'yes'
});
menuItems.push({
name: globalize.translate('No'),
id: 'no'
});
dialog({
buttons: menuItems,
//positionTo: positionTo,
text: globalize.translate('ConfirmEndPlayerSession', currentDeviceName)
}).then(function (id) {
switch (id) {
case 'yes':
playbackManager.getCurrentPlayer().endSession();
playbackManager.setDefaultPlayerActive();
break;
case 'no':
playbackManager.setDefaultPlayerActive();
break;
default:
break;
}
});
});
} else if (browser.mobile) {
deviceType = 'smartphone';
} else {
playbackManager.setDefaultPlayerActive();
deviceType = 'desktop';
}
}
function showActivePlayerMenuInternal(dialogHelper, playerInfo) {
var html = '';
var dialogOptions = {
removeOnClose: true
};
dialogOptions.modal = false;
dialogOptions.entryAnimationDuration = 160;
dialogOptions.exitAnimationDuration = 160;
dialogOptions.autoFocus = false;
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('promptDialog');
var currentDeviceName = (playerInfo.deviceName || playerInfo.name);
html += '<div class="promptDialogContent" style="padding:1.5em;">';
html += '<h2 style="margin-top:.5em;">';
html += currentDeviceName;
html += '</h2>';
html += '<div>';
if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
html += '<label class="checkboxContainer">';
var checkedHtml = playbackManager.enableDisplayMirroring() ? ' checked' : '';
html += '<input type="checkbox" is="emby-checkbox" class="chkMirror"' + checkedHtml + '/>';
html += '<span>' + globalize.translate('EnableDisplayMirroring') + '</span>';
html += '</label>';
}
html += '</div>';
html += '<div style="margin-top:1em;display:flex;justify-content: flex-end;">';
html += '<button is="emby-button" type="button" class="button-flat btnRemoteControl promptDialogButton">' + globalize.translate('HeaderRemoteControl') + '</button>';
html += '<button is="emby-button" type="button" class="button-flat btnDisconnect promptDialogButton ">' + globalize.translate('Disconnect') + '</button>';
html += '<button is="emby-button" type="button" class="button-flat btnCancel promptDialogButton">' + globalize.translate('ButtonCancel') + '</button>';
html += '</div>';
html += '</div>';
dlg.innerHTML = html;
var chkMirror = dlg.querySelector('.chkMirror');
if (chkMirror) {
chkMirror.addEventListener('change', onMirrorChange);
}
var destination = '';
var btnRemoteControl = dlg.querySelector('.btnRemoteControl');
if (btnRemoteControl) {
btnRemoteControl.addEventListener('click', function () {
destination = 'nowplaying';
dialogHelper.close(dlg);
});
}
dlg.querySelector('.btnDisconnect').addEventListener('click', function () {
destination = 'disconnectFromPlayer';
dialogHelper.close(dlg);
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
dialogHelper.open(dlg).then(function () {
if (destination === 'nowplaying') {
appRouter.showNowPlaying();
} else if (destination === 'disconnectFromPlayer') {
disconnectFromPlayer(currentDeviceName);
}
}, emptyCallback);
if (!deviceType) {
deviceType = 'tv';
}
function onMirrorChange() {
playbackManager.enableDisplayMirroring(this.checked);
switch (deviceType) {
case 'smartphone':
return 'smartphone';
case 'tablet':
return 'tablet';
case 'tv':
return 'tv';
case 'cast':
return 'cast';
case 'desktop':
return 'computer';
default:
return 'tv';
}
}
document.addEventListener('viewshow', function (e) {
export function show(button) {
var state = e.detail.state || {};
var item = state.item;
var currentPlayerInfo = playbackManager.getPlayerInfo();
if (item && item.ServerId) {
mirrorIfEnabled({
item: item
});
if (currentPlayerInfo) {
if (!currentPlayerInfo.isLocalPlayer) {
showActivePlayerMenu(currentPlayerInfo);
return;
}
});
}
events.on(appSettings, 'change', function (e, name) {
if (name === 'displaymirror') {
mirrorIfEnabled();
}
});
var currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null;
events.on(playbackManager, 'pairing', function (e) {
loading.show();
});
loading.show();
events.on(playbackManager, 'paired', function (e) {
loading.hide();
});
playbackManager.getTargets().then(function (targets) {
events.on(playbackManager, 'pairerror', function (e) {
loading.hide();
});
var menuItems = targets.map(function (t) {
return {
show: showPlayerSelection
var name = t.name;
if (t.appName && t.appName !== t.name) {
name += ' - ' + t.appName;
}
return {
name: name,
id: t.id,
selected: currentPlayerId === t.id,
secondaryText: getTargetSecondaryText(t),
icon: getIcon(t)
};
});
require(['actionsheet'], function (actionsheet) {
loading.hide();
var menuOptions = {
title: globalize.translate('HeaderPlayOn'),
items: menuItems,
positionTo: button,
resolveOnClick: true,
border: true
};
// Unfortunately we can't allow the url to change or chromecast will throw a security error
// Might be able to solve this in the future by moving the dialogs to hashbangs
if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) {
menuOptions.enableHistory = false;
}
actionsheet.show(menuOptions).then(function (id) {
var target = targets.filter(function (t) {
return t.id === id;
})[0];
playbackManager.trySetActivePlayer(target.playerName, target);
mirrorIfEnabled();
}, emptyCallback);
});
});
}
function showActivePlayerMenu(playerInfo) {
require(['dialogHelper', 'dialog', 'emby-checkbox', 'emby-button'], function (dialogHelper) {
showActivePlayerMenuInternal(dialogHelper, playerInfo);
});
}
function disconnectFromPlayer(currentDeviceName) {
if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) {
require(['dialog'], function (dialog) {
var menuItems = [];
menuItems.push({
name: globalize.translate('Yes'),
id: 'yes'
});
menuItems.push({
name: globalize.translate('No'),
id: 'no'
});
dialog({
buttons: menuItems,
//positionTo: positionTo,
text: globalize.translate('ConfirmEndPlayerSession', currentDeviceName)
}).then(function (id) {
switch (id) {
case 'yes':
playbackManager.getCurrentPlayer().endSession();
playbackManager.setDefaultPlayerActive();
break;
case 'no':
playbackManager.setDefaultPlayerActive();
break;
default:
break;
}
});
});
} else {
playbackManager.setDefaultPlayerActive();
}
}
function showActivePlayerMenuInternal(dialogHelper, playerInfo) {
var html = '';
var dialogOptions = {
removeOnClose: true
};
dialogOptions.modal = false;
dialogOptions.entryAnimationDuration = 160;
dialogOptions.exitAnimationDuration = 160;
dialogOptions.autoFocus = false;
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('promptDialog');
var currentDeviceName = (playerInfo.deviceName || playerInfo.name);
html += '<div class="promptDialogContent" style="padding:1.5em;">';
html += '<h2 style="margin-top:.5em;">';
html += currentDeviceName;
html += '</h2>';
html += '<div>';
if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
html += '<label class="checkboxContainer">';
var checkedHtml = playbackManager.enableDisplayMirroring() ? ' checked' : '';
html += '<input type="checkbox" is="emby-checkbox" class="chkMirror"' + checkedHtml + '/>';
html += '<span>' + globalize.translate('EnableDisplayMirroring') + '</span>';
html += '</label>';
}
html += '</div>';
html += '<div style="margin-top:1em;display:flex;justify-content: flex-end;">';
html += '<button is="emby-button" type="button" class="button-flat btnRemoteControl promptDialogButton">' + globalize.translate('HeaderRemoteControl') + '</button>';
html += '<button is="emby-button" type="button" class="button-flat btnDisconnect promptDialogButton ">' + globalize.translate('Disconnect') + '</button>';
html += '<button is="emby-button" type="button" class="button-flat btnCancel promptDialogButton">' + globalize.translate('ButtonCancel') + '</button>';
html += '</div>';
html += '</div>';
dlg.innerHTML = html;
var chkMirror = dlg.querySelector('.chkMirror');
if (chkMirror) {
chkMirror.addEventListener('change', onMirrorChange);
}
var destination = '';
var btnRemoteControl = dlg.querySelector('.btnRemoteControl');
if (btnRemoteControl) {
btnRemoteControl.addEventListener('click', function () {
destination = 'nowplaying';
dialogHelper.close(dlg);
});
}
dlg.querySelector('.btnDisconnect').addEventListener('click', function () {
destination = 'disconnectFromPlayer';
dialogHelper.close(dlg);
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
dialogHelper.open(dlg).then(function () {
if (destination === 'nowplaying') {
appRouter.showNowPlaying();
} else if (destination === 'disconnectFromPlayer') {
disconnectFromPlayer(currentDeviceName);
}
}, emptyCallback);
}
function onMirrorChange() {
playbackManager.enableDisplayMirroring(this.checked);
}
document.addEventListener('viewshow', function (e) {
var state = e.detail.state || {};
var item = state.item;
if (item && item.ServerId) {
mirrorIfEnabled({
item: item
});
return;
}
});
events.on(appSettings, 'change', function (e, name) {
if (name === 'displaymirror') {
mirrorIfEnabled();
}
});
events.on(playbackManager, 'pairing', function (e) {
loading.show();
});
events.on(playbackManager, 'paired', function (e) {
loading.hide();
});
events.on(playbackManager, 'pairerror', function (e) {
loading.hide();
});
export default {
show: show
};

View file

@ -1,270 +1,272 @@
define(['connectionManager', 'actionsheet', 'datetime', 'playbackManager', 'globalize', 'appSettings', 'qualityoptions'], function (connectionManager, actionsheet, datetime, playbackManager, globalize, appSettings, qualityoptions) {
'use strict';
import connectionManager from 'connectionManager';
import actionsheet from 'actionsheet';
import playbackManager from 'playbackManager';
import globalize from 'globalize';
import qualityoptions from 'qualityoptions';
function showQualityMenu(player, btn) {
function showQualityMenu(player, btn) {
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
return stream.Type === 'Video';
})[0];
var videoWidth = videoStream ? videoStream.Width : null;
var videoHeight = videoStream ? videoStream.Height : null;
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
return stream.Type === 'Video';
})[0];
var videoWidth = videoStream ? videoStream.Width : null;
var videoHeight = videoStream ? videoStream.Height : null;
var options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
videoWidth: videoWidth,
videoHeight: videoHeight,
enableAuto: true
});
var options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
videoWidth: videoWidth,
videoHeight: videoHeight,
enableAuto: true
});
var menuItems = options.map(function (o) {
var opt = {
name: o.name,
id: o.bitrate,
asideText: o.secondaryText
};
var menuItems = options.map(function (o) {
var opt = {
name: o.name,
id: o.bitrate,
asideText: o.secondaryText
};
if (o.selected) {
opt.selected = true;
}
if (o.selected) {
opt.selected = true;
}
return opt;
});
return opt;
});
var selectedId = options.filter(function (o) {
return o.selected;
});
var selectedId = options.filter(function (o) {
return o.selected;
});
selectedId = selectedId.length ? selectedId[0].bitrate : null;
selectedId = selectedId.length ? selectedId[0].bitrate : null;
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (id) {
var bitrate = parseInt(id);
if (bitrate !== selectedId) {
playbackManager.setMaxStreamingBitrate({
enableAutomaticBitrateDetection: bitrate ? false : true,
maxBitrate: bitrate
}, player);
}
});
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (id) {
var bitrate = parseInt(id);
if (bitrate !== selectedId) {
playbackManager.setMaxStreamingBitrate({
enableAutomaticBitrateDetection: bitrate ? false : true,
maxBitrate: bitrate
}, player);
}
});
}
function showRepeatModeMenu(player, btn) {
var menuItems = [];
var currentValue = playbackManager.getRepeatMode(player);
menuItems.push({
name: globalize.translate('RepeatAll'),
id: 'RepeatAll',
selected: currentValue === 'RepeatAll'
});
menuItems.push({
name: globalize.translate('RepeatOne'),
id: 'RepeatOne',
selected: currentValue === 'RepeatOne'
});
menuItems.push({
name: globalize.translate('None'),
id: 'RepeatNone',
selected: currentValue === 'RepeatNone'
});
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (mode) {
if (mode) {
playbackManager.setRepeatMode(mode, player);
}
});
}
function getQualitySecondaryText(player) {
var state = playbackManager.getPlayerState(player);
var isAutoEnabled = playbackManager.enableAutomaticBitrateDetection(player);
var currentMaxBitrate = playbackManager.getMaxStreamingBitrate(player);
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
return stream.Type === 'Video';
})[0];
var videoWidth = videoStream ? videoStream.Width : null;
var videoHeight = videoStream ? videoStream.Height : null;
var options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
videoWidth: videoWidth,
videoHeight: videoHeight,
enableAuto: true
});
var menuItems = options.map(function (o) {
var opt = {
name: o.name,
id: o.bitrate,
asideText: o.secondaryText
};
if (o.selected) {
opt.selected = true;
}
return opt;
});
var selectedOption = options.filter(function (o) {
return o.selected;
});
if (!selectedOption.length) {
return null;
}
function showRepeatModeMenu(player, btn) {
var menuItems = [];
var currentValue = playbackManager.getRepeatMode(player);
selectedOption = selectedOption[0];
var text = selectedOption.name;
menuItems.push({
name: globalize.translate('RepeatAll'),
id: 'RepeatAll',
selected: currentValue === 'RepeatAll'
});
menuItems.push({
name: globalize.translate('RepeatOne'),
id: 'RepeatOne',
selected: currentValue === 'RepeatOne'
});
menuItems.push({
name: globalize.translate('None'),
id: 'RepeatNone',
selected: currentValue === 'RepeatNone'
});
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (mode) {
if (mode) {
playbackManager.setRepeatMode(mode, player);
}
});
if (selectedOption.autoText) {
if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') {
text += ' - Direct';
} else {
text += ' ' + selectedOption.autoText;
}
}
function getQualitySecondaryText(player) {
var state = playbackManager.getPlayerState(player);
var isAutoEnabled = playbackManager.enableAutomaticBitrateDetection(player);
var currentMaxBitrate = playbackManager.getMaxStreamingBitrate(player);
return text;
}
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
return stream.Type === 'Video';
})[0];
function showAspectRatioMenu(player, btn) {
// each has a name and id
var currentId = playbackManager.getAspectRatio(player);
var menuItems = playbackManager.getSupportedAspectRatios(player).map(function (i) {
return {
id: i.id,
name: i.name,
selected: i.id === currentId
};
});
var videoWidth = videoStream ? videoStream.Width : null;
var videoHeight = videoStream ? videoStream.Height : null;
var options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
videoWidth: videoWidth,
videoHeight: videoHeight,
enableAuto: true
});
var menuItems = options.map(function (o) {
var opt = {
name: o.name,
id: o.bitrate,
asideText: o.secondaryText
};
if (o.selected) {
opt.selected = true;
}
return opt;
});
var selectedOption = options.filter(function (o) {
return o.selected;
});
if (!selectedOption.length) {
return null;
}
selectedOption = selectedOption[0];
var text = selectedOption.name;
if (selectedOption.autoText) {
if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') {
text += ' - Direct';
} else {
text += ' ' + selectedOption.autoText;
}
}
return text;
}
function showAspectRatioMenu(player, btn) {
// each has a name and id
var currentId = playbackManager.getAspectRatio(player);
var menuItems = playbackManager.getSupportedAspectRatios(player).map(function (i) {
return {
id: i.id,
name: i.name,
selected: i.id === currentId
};
});
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (id) {
if (id) {
playbackManager.setAspectRatio(id, player);
return Promise.resolve();
}
return Promise.reject();
});
}
function showWithUser(options, player, user) {
var supportedCommands = playbackManager.getSupportedCommands(player);
var mediaType = options.mediaType;
var menuItems = [];
if (supportedCommands.indexOf('SetAspectRatio') !== -1) {
var currentAspectRatioId = playbackManager.getAspectRatio(player);
var currentAspectRatio = playbackManager.getSupportedAspectRatios(player).filter(function (i) {
return i.id === currentAspectRatioId;
})[0];
menuItems.push({
name: globalize.translate('AspectRatio'),
id: 'aspectratio',
asideText: currentAspectRatio ? currentAspectRatio.name : null
});
}
if (user && user.Policy.EnableVideoPlaybackTranscoding) {
var secondaryQualityText = getQualitySecondaryText(player);
menuItems.push({
name: globalize.translate('Quality'),
id: 'quality',
asideText: secondaryQualityText
});
}
var repeatMode = playbackManager.getRepeatMode(player);
if (supportedCommands.indexOf('SetRepeatMode') !== -1 && playbackManager.currentMediaSource(player).RunTimeTicks) {
menuItems.push({
name: globalize.translate('RepeatMode'),
id: 'repeatmode',
asideText: repeatMode === 'RepeatNone' ? globalize.translate('None') : globalize.translate('' + repeatMode)
});
}
if (options.suboffset) {
menuItems.push({
name: globalize.translate('SubtitleOffset'),
id: 'suboffset',
asideText: null
});
}
if (options.stats) {
menuItems.push({
name: globalize.translate('PlaybackData'),
id: 'stats',
asideText: null
});
}
return actionsheet.show({
items: menuItems,
positionTo: options.positionTo
}).then(function (id) {
return handleSelectedOption(id, options, player);
});
}
function show(options) {
var player = options.player;
var currentItem = playbackManager.currentItem(player);
if (!currentItem || !currentItem.ServerId) {
return showWithUser(options, player, null);
}
var apiClient = connectionManager.getApiClient(currentItem.ServerId);
return apiClient.getCurrentUser().then(function (user) {
return showWithUser(options, player, user);
});
}
function handleSelectedOption(id, options, player) {
switch (id) {
case 'quality':
return showQualityMenu(player, options.positionTo);
case 'aspectratio':
return showAspectRatioMenu(player, options.positionTo);
case 'repeatmode':
return showRepeatModeMenu(player, options.positionTo);
case 'stats':
if (options.onOption) {
options.onOption('stats');
}
return Promise.resolve();
case 'suboffset':
if (options.onOption) {
options.onOption('suboffset');
}
return Promise.resolve();
default:
break;
return actionsheet.show({
items: menuItems,
positionTo: btn
}).then(function (id) {
if (id) {
playbackManager.setAspectRatio(id, player);
return Promise.resolve();
}
return Promise.reject();
});
}
function showWithUser(options, player, user) {
var supportedCommands = playbackManager.getSupportedCommands(player);
var mediaType = options.mediaType;
var menuItems = [];
if (supportedCommands.indexOf('SetAspectRatio') !== -1) {
var currentAspectRatioId = playbackManager.getAspectRatio(player);
var currentAspectRatio = playbackManager.getSupportedAspectRatios(player).filter(function (i) {
return i.id === currentAspectRatioId;
})[0];
menuItems.push({
name: globalize.translate('AspectRatio'),
id: 'aspectratio',
asideText: currentAspectRatio ? currentAspectRatio.name : null
});
}
return {
show: show
};
});
if (user && user.Policy.EnableVideoPlaybackTranscoding) {
var secondaryQualityText = getQualitySecondaryText(player);
menuItems.push({
name: globalize.translate('Quality'),
id: 'quality',
asideText: secondaryQualityText
});
}
var repeatMode = playbackManager.getRepeatMode(player);
if (supportedCommands.indexOf('SetRepeatMode') !== -1 && playbackManager.currentMediaSource(player).RunTimeTicks) {
menuItems.push({
name: globalize.translate('RepeatMode'),
id: 'repeatmode',
asideText: repeatMode === 'RepeatNone' ? globalize.translate('None') : globalize.translate('' + repeatMode)
});
}
if (options.suboffset) {
menuItems.push({
name: globalize.translate('SubtitleOffset'),
id: 'suboffset',
asideText: null
});
}
if (options.stats) {
menuItems.push({
name: globalize.translate('PlaybackData'),
id: 'stats',
asideText: null
});
}
return actionsheet.show({
items: menuItems,
positionTo: options.positionTo
}).then(function (id) {
return handleSelectedOption(id, options, player);
});
}
export function show(options) {
var player = options.player;
var currentItem = playbackManager.currentItem(player);
if (!currentItem || !currentItem.ServerId) {
return showWithUser(options, player, null);
}
var apiClient = connectionManager.getApiClient(currentItem.ServerId);
return apiClient.getCurrentUser().then(function (user) {
return showWithUser(options, player, user);
});
}
function handleSelectedOption(id, options, player) {
switch (id) {
case 'quality':
return showQualityMenu(player, options.positionTo);
case 'aspectratio':
return showAspectRatioMenu(player, options.positionTo);
case 'repeatmode':
return showRepeatModeMenu(player, options.positionTo);
case 'stats':
if (options.onOption) {
options.onOption('stats');
}
return Promise.resolve();
case 'suboffset':
if (options.onOption) {
options.onOption('suboffset');
}
return Promise.resolve();
default:
break;
}
return Promise.reject();
}
export default {
show: show
};

View file

@ -1,24 +1,20 @@
define([], function () {
'use strict';
export function getDisplayPlayMethod(session) {
function getDisplayPlayMethod(session) {
if (!session.NowPlayingItem) {
return null;
}
if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect) {
return 'DirectStream';
} else if (session.PlayState.PlayMethod === 'Transcode') {
return 'Transcode';
} else if (session.PlayState.PlayMethod === 'DirectStream') {
return 'DirectPlay';
} else if (session.PlayState.PlayMethod === 'DirectPlay') {
return 'DirectPlay';
}
if (!session.NowPlayingItem) {
return null;
}
return {
getDisplayPlayMethod: getDisplayPlayMethod
};
});
if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect) {
return 'DirectStream';
} else if (session.PlayState.PlayMethod === 'Transcode') {
return 'Transcode';
} else if (session.PlayState.PlayMethod === 'DirectStream') {
return 'DirectPlay';
} else if (session.PlayState.PlayMethod === 'DirectPlay') {
return 'DirectPlay';
}
}
export default {
getDisplayPlayMethod: getDisplayPlayMethod
};

View file

@ -1,159 +1,161 @@
define(['events', 'playbackManager', 'dom', 'browser', 'css!./iconosd', 'material-icons'], function (events, playbackManager, dom, browser) {
'use strict';
import events from 'events';
import playbackManager from 'playbackManager';
import dom from 'dom';
import browser from 'browser';
import 'css!./iconosd';
import 'material-icons';
var currentPlayer;
var osdElement;
var iconElement;
var progressElement;
var currentPlayer;
var osdElement;
var iconElement;
var progressElement;
var enableAnimation;
var enableAnimation;
function getOsdElementHtml() {
var html = '';
function getOsdElementHtml() {
var html = '';
html += '<span class="material-icons iconOsdIcon volume_up"></span>';
html += '<span class="material-icons iconOsdIcon volume_up"></span>';
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner"></div></div>';
html += '<div class="iconOsdProgressOuter"><div class="iconOsdProgressInner"></div></div>';
return html;
return html;
}
function ensureOsdElement() {
var elem = osdElement;
if (!elem) {
enableAnimation = browser.supportsCssAnimation();
elem = document.createElement('div');
elem.classList.add('hide');
elem.classList.add('iconOsd');
elem.classList.add('iconOsd-hidden');
elem.classList.add('volumeOsd');
elem.innerHTML = getOsdElementHtml();
iconElement = elem.querySelector('.material-icons');
progressElement = elem.querySelector('.iconOsdProgressInner');
document.body.appendChild(elem);
osdElement = elem;
}
}
function ensureOsdElement() {
function onHideComplete() {
this.classList.add('hide');
}
var elem = osdElement;
if (!elem) {
var hideTimeout;
function showOsd() {
enableAnimation = browser.supportsCssAnimation();
clearHideTimeout();
elem = document.createElement('div');
elem.classList.add('hide');
elem.classList.add('iconOsd');
elem.classList.add('iconOsd-hidden');
elem.classList.add('volumeOsd');
elem.innerHTML = getOsdElementHtml();
var elem = osdElement;
iconElement = elem.querySelector('.material-icons');
progressElement = elem.querySelector('.iconOsdProgressInner');
document.body.appendChild(elem);
osdElement = elem;
}
}
function onHideComplete() {
this.classList.add('hide');
}
var hideTimeout;
function showOsd() {
clearHideTimeout();
var elem = osdElement;
dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
elem.classList.remove('hide');
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.remove('iconOsd-hidden');
hideTimeout = setTimeout(hideOsd, 3000);
});
}
function clearHideTimeout() {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
}
function hideOsd() {
clearHideTimeout();
var elem = osdElement;
if (elem) {
if (enableAnimation) {
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.add('iconOsd-hidden');
dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
});
} else {
onHideComplete.call(elem);
}
}
}
function updatePlayerVolumeState(isMuted, volume) {
if (iconElement) {
iconElement.classList.remove('volume_off', 'volume_up');
iconElement.classList.add(isMuted ? 'volume_off' : 'volume_up');
}
if (progressElement) {
progressElement.style.width = (volume || 0) + '%';
}
}
function releaseCurrentPlayer() {
var player = currentPlayer;
if (player) {
events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'playbackstop', hideOsd);
currentPlayer = null;
}
}
function onVolumeChanged(e) {
var player = this;
ensureOsdElement();
updatePlayerVolumeState(player.isMuted(), player.getVolume());
showOsd();
}
function bindToPlayer(player) {
if (player === currentPlayer) {
return;
}
releaseCurrentPlayer();
currentPlayer = player;
if (!player) {
return;
}
hideOsd();
events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'playbackstop', hideOsd);
}
events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer());
dom.removeEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
bindToPlayer(playbackManager.getCurrentPlayer());
elem.classList.remove('hide');
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.remove('iconOsd-hidden');
hideTimeout = setTimeout(hideOsd, 3000);
});
}
function clearHideTimeout() {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
}
function hideOsd() {
clearHideTimeout();
var elem = osdElement;
if (elem) {
if (enableAnimation) {
// trigger reflow
void elem.offsetWidth;
requestAnimationFrame(function () {
elem.classList.add('iconOsd-hidden');
dom.addEventListener(elem, dom.whichTransitionEvent(), onHideComplete, {
once: true
});
});
} else {
onHideComplete.call(elem);
}
}
}
function updatePlayerVolumeState(isMuted, volume) {
if (iconElement) {
iconElement.classList.remove('volume_off', 'volume_up');
iconElement.classList.add(isMuted ? 'volume_off' : 'volume_up');
}
if (progressElement) {
progressElement.style.width = (volume || 0) + '%';
}
}
function releaseCurrentPlayer() {
var player = currentPlayer;
if (player) {
events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'playbackstop', hideOsd);
currentPlayer = null;
}
}
function onVolumeChanged(e) {
var player = this;
ensureOsdElement();
updatePlayerVolumeState(player.isMuted(), player.getVolume());
showOsd();
}
function bindToPlayer(player) {
if (player === currentPlayer) {
return;
}
releaseCurrentPlayer();
currentPlayer = player;
if (!player) {
return;
}
hideOsd();
events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'playbackstop', hideOsd);
}
events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer());
});
bindToPlayer(playbackManager.getCurrentPlayer());

View file

@ -2,12 +2,12 @@
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1">
<span class="material-icons arrow_back"></span>
</button>
<h3 class="formDialogHeaderTitle"></h3>
</div>
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">
<form>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtInput" label="" />
@ -19,7 +19,6 @@
<span class="submitText"></span>
</button>
</div>
</form>
</div>
</div>

View file

@ -196,7 +196,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio');
}
} else {
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardContent-button cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album"></span></button></div>';
imgContainer.innerHTML = '<div class="nowPlayingPageImageContainerNoAlbum"><button data-action="link" class="cardImageContainer coveredImage ' + cardBuilder.getDefaultBackgroundClass(item.Name) + ' cardContent cardContent-shadow itemAction"><span class="cardImageIcon material-icons album"></span></button></div>';
}
}

View file

@ -1,158 +0,0 @@
define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
'use strict';
var currentApiClient;
var currentDlg;
var currentInstance;
function reloadPageWhenServerAvailable(retryCount) {
var apiClient = currentApiClient;
if (!apiClient) {
return;
}
// Don't use apiclient method because we don't want it reporting authentication under the old version
apiClient.getJSON(apiClient.getUrl('System/Info')).then(function (info) {
// If this is back to false, the restart completed
if (!info.IsShuttingDown) {
currentInstance.restarted = true;
dialogHelper.close(currentDlg);
} else {
retryReload(retryCount);
}
}, function () {
retryReload(retryCount);
});
}
function retryReload(retryCount) {
setTimeout(function () {
retryCount = retryCount || 0;
retryCount++;
if (retryCount < 150) {
reloadPageWhenServerAvailable(retryCount);
}
}, 500);
}
function startRestart(instance, apiClient, dlg) {
currentApiClient = apiClient;
currentDlg = dlg;
currentInstance = instance;
apiClient.restartServer().then(function () {
setTimeout(reloadPageWhenServerAvailable, 250);
});
}
function showDialog(instance, options, template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
var enableTvLayout = layoutManager.tv;
if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
var dlg = dialogHelper.createDialog(dialogOptions);
var configuredButtons = [];
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-items-center');
var formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.style['flex-grow'] = 'initial';
if (enableTvLayout) {
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = (Math.min((configuredButtons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
dlg.classList.add('dialog-fullscreen-lowres');
}
dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('HeaderRestartingServer');
dlg.querySelector('.text').innerHTML = globalize.translate('RestartPleaseWaitMessage');
var i;
var length;
var html = '';
for (i = 0, length = configuredButtons.length; i < length; i++) {
var item = configuredButtons[i];
var autoFocus = i === 0 ? ' autofocus' : '';
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ' button-' + item.type;
}
html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + '>' + item.name + '</button>';
}
dlg.querySelector('.formDialogFooter').innerHTML = html;
function onButtonClick() {
dialogHelper.close(dlg);
}
var buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
var dlgPromise = dialogHelper.open(dlg);
startRestart(instance, options.apiClient, dlg);
return dlgPromise.then(function () {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
instance.destroy();
loading.hide();
if (instance.restarted) {
events.trigger(instance, 'restarted');
}
});
}
function ServerRestartDialog(options) {
this.options = options;
}
ServerRestartDialog.prototype.show = function () {
var instance = this;
loading.show();
return new Promise(function (resolve, reject) {
require(['text!./../dialog/dialog.template.html'], function (template) {
showDialog(instance, instance.options, template).then(resolve, reject);
});
});
};
ServerRestartDialog.prototype.destroy = function () {
currentApiClient = null;
currentDlg = null;
currentInstance = null;
this.options = null;
};
return ServerRestartDialog;
});

View file

@ -2,9 +2,20 @@
* Image viewer component
* @module components/slideshow/slideshow
*/
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) {
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) {
'use strict';
/**
* Name of transition event.
*/
const transitionEndEventName = dom.whichTransitionEvent();
/**
* Flag to use fake image to fix blurry zoomed image.
* At least WebKit doesn't restore quality for zoomed images.
*/
const useFakeZoomImage = browser.safari;
/**
* Retrieves an item's image URL from the API.
* @param {object|string} item - Item used to generate the image URL.
@ -240,6 +251,41 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}
}
/**
* Handles zoom changes.
*/
function onZoomChange(scale, imageEl, slideEl) {
const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg');
if (zoomImage) {
zoomImage.style.width = zoomImage.style.height = scale * 100 + '%';
if (scale > 1) {
if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) {
// Await for Swiper style changes
setTimeout(() => {
const callback = () => {
imageEl.removeEventListener(transitionEndEventName, callback);
zoomImage.classList.remove('swiper-zoom-fakeimg-hidden');
};
// Swiper set 'transition-duration: 300ms' for auto zoom
// and 'transition-duration: 0s' for touch zoom
const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, ''));
if (transitionDuration > 0) {
imageEl.addEventListener(transitionEndEventName, callback);
} else {
callback();
}
}, 0);
}
} else {
zoomImage.classList.add('swiper-zoom-fakeimg-hidden');
}
}
}
/**
* Initializes the Swiper instance and binds the relevant events.
* @param {HTMLElement} dialog - Element containing the dialog.
@ -260,8 +306,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
loop: false,
zoom: {
minRatio: 1,
toggle: true,
containerClass: 'slider-zoom-container'
toggle: true
},
autoplay: !options.interactive,
keyboard: {
@ -288,6 +333,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
swiperInstance.on('autoplayStart', onAutoplayStart);
swiperInstance.on('autoplayStop', onAutoplayStop);
if (useFakeZoomImage) {
swiperInstance.on('zoomChange', onZoomChange);
}
});
}
@ -328,7 +377,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
function getSwiperSlideHtmlFromSlide(item) {
var html = '';
html += '<div class="swiper-slide" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
html += '<div class="slider-zoom-container">';
html += '<div class="swiper-zoom-container">';
if (useFakeZoomImage) {
html += `<div class="swiper-zoom-fakeimg swiper-zoom-fakeimg-hidden" style="background-image: url('${item.originalImage}')"></div>`;
}
html += '<img src="' + item.originalImage + '" class="swiper-slide-img">';
html += '</div>';
if (item.title || item.subtitle) {

View file

@ -40,16 +40,6 @@
text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
.swiper-slide-img {
max-height: 100%;
max-width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
margin: auto;
}
.slideshowButtonIcon {
color: #fff;
opacity: 0.7;
@ -135,13 +125,18 @@
color: #ccc;
}
.swiper-slide {
display: flex;
flex-direction: column;
.swiper-zoom-fakeimg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
z-index: 1;
pointer-events: none;
}
.slider-zoom-container {
margin: auto;
max-height: 100%;
max-width: 100%;
.swiper-zoom-fakeimg-hidden {
display: none;
}

View file

@ -1,6 +1,6 @@
/**
* Module that manages the SyncPlay feature.
* @module components/syncplay/syncPlayManager
* @module components/syncPlay/syncPlayManager
*/
import events from 'events';

View file

@ -1,6 +1,6 @@
/**
* Module that manages time syncing with server.
* @module components/syncplay/timeSyncManager
* @module components/syncPlay/timeSyncManager
*/
import events from 'events';
@ -65,8 +65,6 @@ class TimeSyncManager {
this.pings = 0; // number of pings
this.measurement = null; // current time sync
this.measurements = [];
this.startPing();
}
/**

View file

@ -43,10 +43,6 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta
var newView = newViewInfo.elem;
var modulesToLoad = [];
if (isPluginpage) {
modulesToLoad.push('legacyDashboard');
}
if (newViewInfo.hasjQuerySelect) {
modulesToLoad.push('legacySelectMenu');
}

View file

@ -21,10 +21,11 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi
if (!newView.initComplete) {
newView.initComplete = true;
var controller;
if (typeof options.controllerFactory === 'function') {
// Use controller method
var controller = new options.controllerFactory(newView, eventDetail.detail.params);
controller = new options.controllerFactory(newView, eventDetail.detail.params);
} else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') {
controller = new options.controllerFactory.default(newView, eventDetail.detail.params);
}
if (!options.controllerFactory || dispatchPageEvents) {

View file

@ -57,7 +57,7 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne
return new Promise(function (resolve, reject) {
require(['text!./viewsettings.template.html'], function (template) {
require(['text!./viewSettings.template.html'], function (template) {
var dialogOptions = {
removeOnClose: true,

1
src/config.json Symbolic link
View file

@ -0,0 +1 @@
config.template.json

View file

@ -1,3 +1,3 @@
{
"multiserver": true
"multiserver": false
}

View file

@ -252,12 +252,6 @@ define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globa
html += '</div>';
html += '</div>';
if (session.TranscodingInfo && session.TranscodingInfo.Framerate) {
html += '<div class="sessionTranscodingFramerate">' + session.TranscodingInfo.Framerate + ' fps</div>';
} else {
html += '<div class="sessionTranscodingFramerate"></div>';
}
html += '<div class="sessionNowPlayingDetails">';
var nowPlayingName = DashboardPage.getNowPlayingName(session);
html += '<div class="sessionNowPlayingInfo" data-imgsrc="' + nowPlayingName.image + '">';
@ -573,7 +567,6 @@ define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globa
row.querySelector('.sessionNowPlayingTime').innerHTML = DashboardPage.getSessionNowPlayingTime(session);
row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session);
row.querySelector('.sessionAppSecondaryText').innerHTML = DashboardPage.getAppSecondaryText(session);
row.querySelector('.sessionTranscodingFramerate').innerHTML = session.TranscodingInfo && session.TranscodingInfo.Framerate ? session.TranscodingInfo.Framerate + ' fps' : '';
var nowPlayingName = DashboardPage.getNowPlayingName(session);
var nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo');

View file

@ -23,8 +23,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
$('.chkMediaType', page).each(function () {
this.checked = -1 != (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value'));
});
$('#chkEnableAlbumArtInDidl', page).checked = profile.EnableAlbumArtInDidl;
$('#chkEnableSingleImageLimit', page).checked = profile.EnableSingleAlbumArtLimit;
$('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl);
$('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit);
renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []);
var idInfo = profile.Identification || {};
renderIdentificationHeaders(page, idInfo.Headers || []);
@ -51,11 +51,11 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
$('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || '');
$('#txtIconMaxWidth', page).val(profile.MaxIconWidth || '');
$('#txtIconMaxHeight', page).val(profile.MaxIconHeight || '');
$('#chkIgnoreTranscodeByteRangeRequests', page).checked = profile.IgnoreTranscodeByteRangeRequests;
$('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests);
$('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || '');
$('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || '');
$('#chkRequiresPlainFolders', page).checked = profile.RequiresPlainFolders;
$('#chkRequiresPlainVideoItems', page).checked = profile.RequiresPlainVideoItems;
$('#chkRequiresPlainFolders', page).prop('checked', profile.RequiresPlainFolders);
$('#chkRequiresPlainVideoItems', page).prop('checked', profile.RequiresPlainVideoItems);
$('#txtProtocolInfo', page).val(profile.ProtocolInfo || '');
$('#txtXDlnaCap', page).val(profile.XDlnaCap || '');
$('#txtXDlnaDoc', page).val(profile.XDlnaDoc || '');
@ -357,9 +357,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
$('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || '');
$('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || '');
$('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http');
$('#chkEnableMpegtsM2TsMode', popup).checked = transcodingProfile.EnableMpegtsM2TsMode || false;
$('#chkEstimateContentLength', popup).checked = transcodingProfile.EstimateContentLength || false;
$('#chkReportByteRangeRequests', popup).checked = 'Bytes' == transcodingProfile.TranscodeSeekInfo;
$('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false);
$('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false);
$('#chkReportByteRangeRequests', popup).prop('checked', 'Bytes' == transcodingProfile.TranscodeSeekInfo);
$('.radioTabButton:first', popup).trigger('click');
openPopup(popup[0]);
}
@ -376,9 +376,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val();
currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val();
currentSubProfile.Context = 'Streaming';
currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).checked;
currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).checked;
currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).checked ? 'Bytes' : 'Auto';
currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).is(':checked');
currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).is(':checked');
currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).is(':checked') ? 'Bytes' : 'Auto';
if (isSubProfileNew) {
currentProfile.TranscodingProfiles.push(currentSubProfile);
@ -647,8 +647,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
function updateProfile(page, profile) {
profile.Name = $('#txtName', page).val();
profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).checked;
profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).checked;
profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).is(':checked');
profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).is(':checked');
profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) {
return c.getAttribute('data-value');
}).join(',');
@ -675,9 +675,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in
profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val();
profile.MaxIconWidth = $('#txtIconMaxWidth', page).val();
profile.MaxIconHeight = $('#txtIconMaxHeight', page).val();
profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).checked;
profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).checked;
profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).checked;
profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).is(':checked');
profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).is(':checked');
profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).is(':checked');
profile.MaxStreamingBitrate = $('#txtMaxAllowedBitrate', page).val();
profile.MusicStreamingTranscodingBitrate = $('#txtMusicStreamingTranscodingBitrate', page).val();
profile.ProtocolInfo = $('#txtProtocolInfo', page).val();

View file

@ -5,8 +5,8 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo;
page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog;
$('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds);
$('#chkEnableServer', page).checked = config.EnableServer;
$('#chkBlastAliveMessages', page).checked = config.BlastAliveMessages;
$('#chkEnableServer', page).prop('checked', config.EnableServer);
$('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages);
$('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds);
var usersHtml = users.map(function (u) {
return '<option value="' + u.Id + '">' + u.Name + '</option>';
@ -22,8 +22,8 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked;
config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked;
config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val();
config.EnableServer = $('#chkEnableServer', form).checked;
config.BlastAliveMessages = $('#chkBlastAliveMessages', form).checked;
config.EnableServer = $('#chkEnableServer', form).is(':checked');
config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked');
config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val();
config.DefaultUserId = $('#selectUser', form).val();
ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult);

View file

@ -5,6 +5,8 @@ define(['jQuery', 'loading', 'globalize', 'dom', 'libraryMenu'], function ($, lo
Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) {
c.checked = -1 !== (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec'));
});
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
$('#selectThreadCount', page).val(config.EncodingThreadCount);
@ -67,6 +69,8 @@ define(['jQuery', 'loading', 'globalize', 'dom', 'libraryMenu'], function ($, lo
}), function (c) {
return c.getAttribute('data-codec');
});
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
updateEncoder(form);

View file

@ -1,6 +1,12 @@
define(['datetime', 'loading', 'apphost', 'listViewStyle', 'emby-button', 'flexStyles'], function(datetime, loading, appHost) {
'use strict';
return function(view, params) {
import datetime from 'datetime';
import loading from 'loading';
import 'emby-button';
import 'listViewStyle';
import 'flexStyles';
/* eslint-disable indent */
export default function(view, params) {
view.addEventListener('viewbeforeshow', function() {
loading.show();
var apiClient = ApiClient;
@ -29,5 +35,6 @@ define(['datetime', 'loading', 'apphost', 'listViewStyle', 'emby-button', 'flexS
loading.hide();
});
});
};
});
}
/* eslint-enable indent */

View file

@ -50,7 +50,7 @@ define(['jQuery', 'emby-checkbox'], function ($) {
fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers);
fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true);
fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices);
$('#chkEnabled', page).checked = notificationConfig.Enabled || false;
$('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false);
$('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change');
});
}
@ -73,7 +73,7 @@ define(['jQuery', 'emby-checkbox'], function ($) {
notificationOptions.Options.push(notificationConfig);
}
notificationConfig.Enabled = $('#chkEnabled', page).checked;
notificationConfig.Enabled = $('#chkEnabled', page).is(':checked');
notificationConfig.SendToUserMode = $('#selectUsers', page).val();
notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) {
return !c.checked;

View file

@ -123,6 +123,9 @@ define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}

View file

@ -37,20 +37,24 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'
})[0];
var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null;
var html = '';
html += "<div data-id='" + plugin.Id + "' data-name='" + plugin.Name + "' class='card backdropCard'>";
html += "<div data-id='" + plugin.Id + "' data-name='" + plugin.Name + "' data-removable='" + plugin.CanUninstall + "' class='card backdropCard'>";
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += configPageUrl ? '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + configPageUrl + '">' : '<div class="cardContent noConfigPluginCard noHoverEffect cardImageContainer">';
html += configPageUrl ? '<a class="cardContent cardImageContainer" is="emby-linkbutton" href="' + configPageUrl + '">' : '<div class="cardContent noConfigPluginCard noHoverEffect cardImageContainer emby-button">';
html += '<span class="cardImageIcon material-icons folder"></span>';
html += configPageUrl ? '</a>' : '</div>';
html += '</div>';
html += '<div class="cardFooter">';
html += '<div style="text-align:right; float:right;padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert"></span></button>';
html += '</div>';
if (configPage || plugin.CanUninstall) {
html += '<div style="text-align:right; float:right;padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert"></span></button>';
html += '</div>';
}
html += "<div class='cardText'>";
html += configPage.DisplayName || plugin.Name;
html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name;
html += '</div>';
html += "<div class='cardText cardText-secondary'>";
html += plugin.Version;
@ -104,6 +108,7 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'
var card = dom.parentWithClass(elem, 'card');
var id = card.getAttribute('data-id');
var name = card.getAttribute('data-name');
var removable = card.getAttribute('data-removable');
var configHref = card.querySelector('.cardContent').getAttribute('href');
var menuItems = [];
@ -115,11 +120,13 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'
});
}
menuItems.push({
name: globalize.translate('ButtonUninstall'),
id: 'delete',
icon: 'delete'
});
if (removable === 'true') {
menuItems.push({
name: globalize.translate('ButtonUninstall'),
id: 'delete',
icon: 'delete'
});
}
require(['actionsheet'], function (actionsheet) {
actionsheet.show({
@ -153,6 +160,9 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}

View file

@ -0,0 +1,153 @@
import loading from 'loading';
import libraryMenu from 'libraryMenu';
import globalize from 'globalize';
import dialogHelper from 'dialogHelper';
import 'emby-button';
import 'emby-checkbox';
import 'emby-select';
import 'formDialogStyle';
import 'listViewStyle';
let repositories = [];
function reloadList(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Repositories')).then(list => {
repositories = list;
populateList({
listElement: page.querySelector('#repositories'),
noneElement: page.querySelector('#none'),
repositories: repositories
});
}).catch(error => {
console.error('error loading repositories');
page.querySelector('#none').classList.remove('hide');
loading.hide();
});
}
function saveList(page) {
loading.show();
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('Repositories'),
data: JSON.stringify(repositories),
contentType: 'application/json'
}).then(response => {
reloadList(page);
}).catch(error => {
console.error('error saving repositories');
loading.hide();
});
}
function populateList(options) {
var html = '';
html += '<div class="paperList">';
for (var i = 0; i < options.repositories.length; i++) {
html += getRepositoryHtml(options.repositories[i]);
}
html += '</div>';
if (!options.repositories.length) {
options.noneElement.classList.remove('hide');
}
options.listElement.innerHTML = html;
loading.hide();
}
function getRepositoryHtml(repository) {
var html = '';
html += '<div class="listItem listItem-border">';
html += `<a is="emby-linkbutton" style="margin:0;padding:0" class="clearLink listItemIconContainer" href="${repository.Url}">`;
html += '<span class="material-icons listItemIcon open_in_new"></span>';
html += '</a>';
html += '<div class="listItemBody two-line">';
html += `<h3 class="listItemBodyText">${repository.Name}</h3>`;
html += `<div class="listItemBodyText secondary">${repository.Url}</div>`;
html += '</div>';
html += `<button type="button" is="paper-icon-button-light" id="${repository.Url}" class="btnDelete" title="${globalize.translate('ButtonDelete')}"><span class="material-icons delete"></span></button>`;
html += '</div>';
return html;
}
function getTabs() {
return [{
href: 'installedplugins.html',
name: globalize.translate('TabMyPlugins')
}, {
href: 'availableplugins.html',
name: globalize.translate('TabCatalog')
}, {
href: 'repositories.html',
name: globalize.translate('TabRepositories')
}];
}
export default function(view, params) {
view.addEventListener('viewshow', function () {
libraryMenu.setTabs('plugins', 2, getTabs);
reloadList(this);
var save = this;
$('#repositories', view).on('click', '.btnDelete', function() {
var button = this;
repositories = repositories.filter(function (r) {
return r.Url !== button.id;
});
saveList(save);
});
});
view.querySelector('.btnNewRepository').addEventListener('click', () => {
let dialog = dialogHelper.createDialog({
scrollY: false,
size: 'large',
modal: false,
removeOnClose: true
});
let html = '';
html += '<div class="formDialogHeader">';
html += '<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += `<h3 class="formDialogHeaderTitle">${globalize.translate('HeaderNewRepository')}</h3>`;
html += '</div>';
html += '<form class="newPluginForm" style="margin:4em">';
html += '<div class="inputContainer">';
html += `<input is="emby-input" id="txtRepositoryName" label="${globalize.translate('LabelRepositoryName')}" type="text" required />`;
html += `<div class="fieldDescription">${globalize.translate('LabelRepositoryNameHelp')}</div>`;
html += '</div>';
html += '<div class="inputContainer">';
html += `<input is="emby-input" id="txtRepositoryUrl" label="${globalize.translate('LabelRepositoryUrl')}" type="url" required />`;
html += `<div class="fieldDescription">${globalize.translate('LabelRepositoryUrlHelp')}</div>`;
html += '</div>';
html += `<button is="emby-button" type="submit" class="raised button-submit block"><span>${globalize.translate('ButtonSave')}</span></button>`;
html += '</div>';
html += '</form>';
dialog.innerHTML = html;
dialog.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dialog);
});
dialog.querySelector('.newPluginForm').addEventListener('submit', () => {
repositories.push({
Name: dialog.querySelector('#txtRepositoryName').value,
Url: dialog.querySelector('#txtRepositoryUrl').value,
Enabled: true
});
saveList(view);
dialogHelper.close(dialog);
return false;
});
dialogHelper.open(dialog);
});
}

View file

@ -27,7 +27,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
}
$('.deleteAccess', page).html(html).trigger('create');
$('#chkEnableDeleteAllFolders', page).checked = user.Policy.EnableContentDeletion;
$('#chkEnableDeleteAllFolders', page).prop('checked', user.Policy.EnableContentDeletion);
});
}
@ -85,23 +85,23 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
libraryMenu.setTitle(user.Name);
page.querySelector('.username').innerHTML = user.Name;
$('#txtUserName', page).val(user.Name);
$('#chkIsAdmin', page).checked = user.Policy.IsAdministrator;
$('#chkDisabled', page).checked = user.Policy.IsDisabled;
$('#chkIsHidden', page).checked = user.Policy.IsHidden;
$('#chkRemoteControlSharedDevices', page).checked = user.Policy.EnableSharedDeviceControl;
$('#chkEnableRemoteControlOtherUsers', page).checked = user.Policy.EnableRemoteControlOfOtherUsers;
$('#chkEnableDownloading', page).checked = user.Policy.EnableContentDownloading;
$('#chkManageLiveTv', page).checked = user.Policy.EnableLiveTvManagement;
$('#chkEnableLiveTvAccess', page).checked = user.Policy.EnableLiveTvAccess;
$('#chkEnableMediaPlayback', page).checked = user.Policy.EnableMediaPlayback;
$('#chkEnableAudioPlaybackTranscoding', page).checked = user.Policy.EnableAudioPlaybackTranscoding;
$('#chkEnableVideoPlaybackTranscoding', page).checked = user.Policy.EnableVideoPlaybackTranscoding;
$('#chkEnableVideoPlaybackRemuxing', page).checked = user.Policy.EnablePlaybackRemuxing;
$('#chkForceRemoteSourceTranscoding', page).checked = user.Policy.ForceRemoteSourceTranscoding;
$('#chkRemoteAccess', page).checked = null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess;
$('#chkEnableSyncTranscoding', page).checked = user.Policy.EnableSyncTranscoding;
$('#chkEnableConversion', page).checked = user.Policy.EnableMediaConversion || false;
$('#chkEnableSharing', page).checked = user.Policy.EnablePublicSharing;
$('#chkIsAdmin', page).prop('checked', user.Policy.IsAdministrator);
$('#chkDisabled', page).prop('checked', user.Policy.IsDisabled);
$('#chkIsHidden', page).prop('checked', user.Policy.IsHidden);
$('#chkRemoteControlSharedDevices', page).prop('checked', user.Policy.EnableSharedDeviceControl);
$('#chkEnableRemoteControlOtherUsers', page).prop('checked', user.Policy.EnableRemoteControlOfOtherUsers);
$('#chkEnableDownloading', page).prop('checked', user.Policy.EnableContentDownloading);
$('#chkManageLiveTv', page).prop('checked', user.Policy.EnableLiveTvManagement);
$('#chkEnableLiveTvAccess', page).prop('checked', user.Policy.EnableLiveTvAccess);
$('#chkEnableMediaPlayback', page).prop('checked', user.Policy.EnableMediaPlayback);
$('#chkEnableAudioPlaybackTranscoding', page).prop('checked', user.Policy.EnableAudioPlaybackTranscoding);
$('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding);
$('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing);
$('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding);
$('#chkRemoteAccess', page).prop('checked', null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess);
$('#chkEnableSyncTranscoding', page).prop('checked', user.Policy.EnableSyncTranscoding);
$('#chkEnableConversion', page).prop('checked', user.Policy.EnableMediaConversion || false);
$('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing);
$('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || '');
$('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0');
$('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess);
@ -119,28 +119,28 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
function saveUser(user, page) {
user.Name = $('#txtUserName', page).val();
user.Policy.IsAdministrator = $('#chkIsAdmin', page).checked;
user.Policy.IsHidden = $('#chkIsHidden', page).checked;
user.Policy.IsDisabled = $('#chkDisabled', page).checked;
user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).checked;
user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).checked;
user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).checked;
user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).checked;
user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).checked;
user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).checked;
user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).checked;
user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).checked;
user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).checked;
user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).checked;
user.Policy.EnableSyncTranscoding = $('#chkEnableSyncTranscoding', page).checked;
user.Policy.EnableMediaConversion = $('#chkEnableConversion', page).checked;
user.Policy.EnablePublicSharing = $('#chkEnableSharing', page).checked;
user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).checked;
user.Policy.IsAdministrator = $('#chkIsAdmin', page).is(':checked');
user.Policy.IsHidden = $('#chkIsHidden', page).is(':checked');
user.Policy.IsDisabled = $('#chkDisabled', page).is(':checked');
user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).is(':checked');
user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).is(':checked');
user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).is(':checked');
user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).is(':checked');
user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).is(':checked');
user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).is(':checked');
user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).is(':checked');
user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).is(':checked');
user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked');
user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked');
user.Policy.EnableSyncTranscoding = $('#chkEnableSyncTranscoding', page).is(':checked');
user.Policy.EnableMediaConversion = $('#chkEnableConversion', page).is(':checked');
user.Policy.EnablePublicSharing = $('#chkEnableSharing', page).is(':checked');
user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked');
user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0'));
user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0');
user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value;
user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value;
user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).checked;
user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).is(':checked');
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : $('.chkFolder', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {

View file

@ -47,7 +47,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
$('.channelAccessContainer', page).hide();
}
$('#chkEnableAllChannels', page).checked = user.Policy.EnableAllChannels;
$('#chkEnableAllChannels', page).prop('checked', user.Policy.EnableAllChannels);
}
function loadDevices(page, user, devices) {
@ -63,7 +63,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
html += '</div>';
$('.deviceAccess', page).show().html(html);
$('#chkEnableAllDevices', page).checked = user.Policy.EnableAllDevices;
$('#chkEnableAllDevices', page).prop('checked', user.Policy.EnableAllDevices);
if (user.Policy.IsAdministrator) {
page.querySelector('.deviceAccessContainer').classList.add('hide');
@ -90,19 +90,19 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading,
}
function saveUser(user, page) {
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).checked;
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked');
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : $('.chkFolder', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {
return c.getAttribute('data-id');
});
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).checked;
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked');
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : $('.chkChannel', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {
return c.getAttribute('data-id');
});
user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).checked;
user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).is(':checked');
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $('.chkDevice', page).get().filter(function (c) {
return c.checked;
}).map(function (c) {

View file

@ -13,7 +13,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading
html += '</div>';
$('.folderAccess', page).html(html).trigger('create');
$('#chkEnableAllFolders', page).checked = false;
$('#chkEnableAllFolders', page).prop('checked', false);
}
function loadChannels(page, channels) {
@ -35,7 +35,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading
$('.channelAccessContainer', page).hide();
}
$('#chkEnableAllChannels', page).checked = false;
$('#chkEnableAllChannels', page).prop('checked', false);
}
function loadUser(page) {
@ -58,7 +58,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading
user.Name = $('#txtUsername', page).val();
user.Password = $('#txtPassword', page).val();
ApiClient.createUser(user).then(function (user) {
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).checked;
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked');
user.Policy.EnabledFolders = [];
if (!user.Policy.EnableAllFolders) {
@ -69,7 +69,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading
});
}
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).checked;
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked');
user.Policy.EnabledChannels = [];
if (!user.Policy.EnableAllChannels) {

View file

@ -141,29 +141,29 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
return;
}
playbackManager.getPlaybackMediaSources(item).then(function (mediaSources) {
instance._currentPlaybackMediaSources = mediaSources;
page.querySelector('.trackSelections').classList.remove('hide');
select.setLabel(globalize.translate('LabelVersion'));
var currentValue = select.value;
var selectedId = mediaSources[0].Id;
select.innerHTML = mediaSources.map(function (v) {
var selected = v.Id === selectedId ? ' selected' : '';
return '<option value="' + v.Id + '"' + selected + '>' + v.Name + '</option>';
}).join('');
var mediaSources = item.MediaSources;
instance._currentPlaybackMediaSources = mediaSources;
page.querySelector('.trackSelections').classList.remove('hide');
select.setLabel(globalize.translate('LabelVersion'));
var currentValue = select.value;
var selectedId = mediaSources[0].Id;
select.innerHTML = mediaSources.map(function (v) {
var selected = v.Id === selectedId ? ' selected' : '';
return '<option value="' + v.Id + '"' + selected + '>' + v.Name + '</option>';
}).join('');
if (mediaSources.length > 1) {
page.querySelector('.selectSourceContainer').classList.remove('hide');
} else {
page.querySelector('.selectSourceContainer').classList.add('hide');
}
if (mediaSources.length > 1) {
page.querySelector('.selectSourceContainer').classList.remove('hide');
} else {
page.querySelector('.selectSourceContainer').classList.add('hide');
}
if (select.value !== currentValue || forceReload) {
renderVideoSelections(page, mediaSources);
renderAudioSelections(page, mediaSources);
renderSubtitleSelections(page, mediaSources);
}
if (select.value !== currentValue || forceReload) {
renderVideoSelections(page, mediaSources);
renderAudioSelections(page, mediaSources);
renderSubtitleSelections(page, mediaSources);
}
});
}
function renderVideoSelections(page, mediaSources) {
@ -400,6 +400,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} else if (item.Album) {
parentNameHtml.push(item.Album);
}
// FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022
var html = '';
var tvShowHtml = parentNameHtml[0];
@ -415,9 +416,9 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
} else {
if (layoutManager.mobile) {
html = '<h1 class="parentName" style="margin: .1em 0 .25em;">' + parentNameHtml.join('</br>') + '</h1>';
html = '<h1 class="parentName" style="margin: 0.2em 0 0">' + parentNameHtml.join('</br>') + '</h1>';
} else {
html = '<h1 class="parentName" style="margin: .1em 0 .25em;">' + tvShowHtml + '</h1>';
html = '<h1 class="parentName" style="margin: 0.2em 0 0">' + tvShowHtml + '</h1>';
}
}
}
@ -425,20 +426,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var name = itemHelper.getDisplayName(item, {
includeParentInfo: false
});
var offset = parentNameLast ? '.25em' : '.5em';
if (html && !parentNameLast) {
if (!layoutManager.mobile && tvSeasonHtml) {
html += '<h3 class="itemName infoText" style="margin: .25em 0 .5em;">' + tvSeasonHtml + ' - ' + name + '</h3>';
html += '<h3 class="itemName infoText" style="margin: 0.2em 0 0">' + tvSeasonHtml + ' - ' + name + '</h3>';
} else {
html += '<h3 class="itemName infoText" style="margin: .25em 0 .5em;">' + name + '</h3>';
html += '<h3 class="itemName infoText" style="margin: 0.2em 0 0">' + name + '</h3>';
}
} else {
html = '<h1 class="itemName infoText" style="margin: .1em 0 ' + offset + ';">' + name + '</h1>' + html;
html = '<h1 class="itemName infoText" style="margin: 0.4em 0 0">' + name + '</h1>' + html;
}
if (item.OriginalTitle && item.OriginalTitle != item.Name) {
html += '<h4 class="itemName infoText" style="margin: -' + offset + ' 0 0;">' + item.OriginalTitle + '</h4>';
html += '<h4 class="itemName infoText" style="margin: 0 0 0;">' + item.OriginalTitle + '</h4>';
}
container.innerHTML = html;
@ -1106,10 +1106,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var externalLinksElem = page.querySelector('.itemExternalLinks');
renderOverview([overview], item);
var i;
var itemMiscInfo;
itemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary');
for (i = 0; i < itemMiscInfo.length; i++) {
mediaInfo.fillPrimaryMediaInfo(itemMiscInfo[i], item, {
interactive: true,
@ -1125,7 +1125,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
itemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary');
for (i = 0; i < itemMiscInfo.length; i++) {
mediaInfo.fillSecondaryMediaInfo(itemMiscInfo[i], item, {
interactive: true
@ -1840,7 +1839,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
chaptercardbuilder.buildChapterCards(item, chapters, {
itemsContainer: scenesContent,
backdropShape: 'overflowBackdrop',
squareShape: 'overflowSquare'
squareShape: 'overflowSquare',
imageBlurhashes: item.ImageBlurHashes
});
});
} else {
@ -1891,7 +1891,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
itemsContainer: castContent,
coverImage: true,
serverId: item.ServerId,
shape: 'overflowPortrait'
shape: 'overflowPortrait',
imageBlurhashes: item.ImageBlurHashes
});
});
}

View file

@ -86,7 +86,7 @@ define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'us
}
function showFilterMenu(context) {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(),
mode: 'livetvchannels',

View file

@ -171,7 +171,12 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
}
if (!result.Items.length) {
html = '<p style="text-align:center;">' + globalize.translate('MessageNoCollectionsAvailable') + '</p>';
html = '';
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate('MessageNoCollectionsAvailable') + '</p>';
html += '</div>';
}
var itemsContainer = tabContent.querySelector('.itemsContainer');

View file

@ -165,6 +165,15 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
html += '</div>';
}
if (!result.Items.length) {
html = '';
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate('MessageNoGenresAvailable') + '</p>';
html += '</div>';
}
elem.innerHTML = html;
lazyLoader.lazyChildren(elem, fillItemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);

View file

@ -270,7 +270,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser',
query = userSettings.loadQuerySettings(savedQueryKey, query);
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: query,
mode: 'movies',

View file

@ -158,7 +158,12 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
}
if (!result.Items.length) {
html = '<p style="text-align:center;">' + globalize.translate('MessageNoTrailersFound') + '</p>';
html = '';
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate('MessageNoTrailersFound') + '</p>';
html += '</div>';
}
var itemsContainer = tabContent.querySelector('.itemsContainer');
@ -180,7 +185,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'movies',

View file

@ -186,7 +186,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(),
mode: 'albums',

View file

@ -170,7 +170,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: self.mode,

View file

@ -124,7 +124,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'songs',

View file

@ -164,7 +164,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'episodes',

View file

@ -161,6 +161,15 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader
html += '</div>';
}
if (!result.Items.length) {
html = '';
html += '<div class="noItemsMessage centerMessage">';
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
html += '<p>' + globalize.translate('MessageNoGenresAvailable') + '</p>';
html += '</div>';
}
elem.innerHTML = html;
lazyLoader.lazyChildren(elem, fillItemsContainer);
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);

View file

@ -197,7 +197,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', '
var isLoading = false;
self.showFilterMenu = function () {
require(['components/filterdialog/filterdialog'], function (filterDialogFactory) {
require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) {
var filterDialog = new filterDialogFactory({
query: getQuery(tabContent),
mode: 'series',

View file

@ -1,6 +1,9 @@
define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySettings, userSettings, autoFocuser) {
'use strict';
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
@ -11,7 +14,7 @@ define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySett
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);

View file

@ -1,6 +1,9 @@
define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) {
'use strict';
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
@ -11,7 +14,7 @@ define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'au
var homescreenSettingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);

View file

@ -43,12 +43,6 @@ define(['apphost', 'connectionManager', 'layoutManager', 'listViewStyle', 'emby-
page.querySelector('.adminSection').classList.add('hide');
}
if (layoutManager.mobile) {
page.querySelector('.headerUsername').classList.add('hide');
page.querySelector('.adminSection').classList.add('hide');
page.querySelector('.userSection').classList.add('hide');
}
ApiClient.getUser(userId).then(function(user) {
page.querySelector('.headerUsername').innerHTML = user.Name;
if (!user.Policy.IsAdministrator) {

View file

@ -1,6 +1,9 @@
define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) {
'use strict';
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
@ -11,7 +14,7 @@ define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'auto
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);

View file

@ -1,6 +1,9 @@
define(['subtitleSettings', 'userSettings', 'autoFocuser'], function (SubtitleSettings, userSettings, autoFocuser) {
'use strict';
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
@ -11,7 +14,7 @@ define(['subtitleSettings', 'userSettings', 'autoFocuser'], function (SubtitleSe
var subtitleSettingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);

View file

@ -109,9 +109,7 @@ define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'registerElement'
}
EmbyInputPrototype.attachedCallback = function () {
this.labelElement.htmlFor = this.id;
onChange.call(this);
};

View file

@ -19,6 +19,7 @@
<option value="nvenc">Nvidia NVENC</option>
<option value="vaapi">Video Acceleration API (VAAPI)</option>
<option value="h264_v4l2m2m">Exynos V4L2 MFC</option>
<option value="videotoolbox">Video ToolBox</option>
</select>
<div class="fieldDescription">
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://docs.jellyfin.org/general/administration/hardware-acceleration.html" target="_blank">${LabelHardwareAccelerationTypeHelp}</a>
@ -35,36 +36,49 @@
<h3 class="checkboxListLabel">${LabelEnableHardwareDecodingFor}</h3>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="h264" data-types="qsv,nvenc,omx,mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="h264" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
<span>H264</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="hevc" data-types="qsv,nvenc,mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="hevc" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>HEVC</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="qsv,nvenc,omx,mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,qsv,nvenc,vaapi,omx,mediacodec,videotoolbox" />
<span>MPEG2</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="nvenc,mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="amf,nvenc,omx,mediacodec,videotoolbox" />
<span>MPEG4</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="qsv,nvenc" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,qsv,nvenc,vaapi,omx,videotoolbox" />
<span>VC1</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp8" data-types="mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp8" data-types="qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>VP8</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp9" data-types="mediacodec" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vp9" data-types="amf,qsv,nvenc,vaapi,mediacodec,videotoolbox" />
<span>VP9</span>
</label>
</div>
</div>
<div class="checkboxListContainer">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Hevc" />
<span>${EnableDecodingColorDepth10Hevc}</span>
</label>
</div>
<div class="checkboxListContainer">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDecodingColorDepth10Vp9" />
<span>${EnableDecodingColorDepth10Vp9}</span>
</label>
</div>
<div class="checkboxListContainer">
<div class="checkboxList">
<label>

View file

@ -10,8 +10,8 @@
<div class="detailPagePrimaryContainer padded-left padded-right">
<div class="infoWrapper infoText">
<div class="nameContainer"></div>
<div class="itemMiscInfo itemMiscInfo-primary"></div>
<div class="itemMiscInfo itemMiscInfo-secondary"></div>
<div class="itemMiscInfo itemMiscInfo-primary" style="margin-bottom: 0.6em"></div>
<div class="itemMiscInfo itemMiscInfo-secondary" style="margin-bottom: 0.6em"></div>
</div>
<div class="mainDetailButtons">

View file

@ -1,24 +0,0 @@
Dashboard.confirm = function(message, title, callback) {
'use strict';
require(['confirm'], function(confirm) {
confirm(message, title).then(function() {
callback(!0);
}).catch(function() {
callback(!1);
});
});
};
Dashboard.showLoadingMsg = function() {
'use strict';
require(['loading'], function(loading) {
loading.show();
});
};
Dashboard.hideLoadingMsg = function() {
'use strict';
require(['loading'], function(loading) {
loading.hide();
});
};

View file

@ -1,19 +1,15 @@
define(['connectionManager'], function (connectionManager) {
/* eslint-disable indent */
import connectionManager from 'connectionManager';
return function () {
var self = this;
self.name = 'Backdrop ScreenSaver';
self.type = 'screensaver';
self.id = 'backdropscreensaver';
self.supportsAnonymous = false;
var currentSlideshow;
self.show = function () {
var query = {
class BackdropScreensaver {
constructor() {
this.name = 'Backdrop ScreenSaver';
this.type = 'screensaver';
this.id = 'backdropscreensaver';
this.supportsAnonymous = false;
}
show() {
const query = {
ImageTypes: 'Backdrop',
EnableImageTypes: 'Backdrop',
IncludeItemTypes: 'Movie,Series,MusicArtist',
@ -25,32 +21,32 @@ define(['connectionManager'], function (connectionManager) {
Limit: 200
};
var apiClient = connectionManager.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) {
const apiClient = connectionManager.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => {
if (result.Items.length) {
require(['slideshow'], function (slideshow) {
var newSlideShow = new slideshow({
import('slideshow').then(({default: Slideshow}) => {
const newSlideShow = new Slideshow({
showTitle: true,
cover: true,
items: result.Items
});
newSlideShow.show();
currentSlideshow = newSlideShow;
});
this.currentSlideshow = newSlideShow;
}).catch(console.error);
}
});
};
}
self.hide = function () {
if (currentSlideshow) {
currentSlideshow.hide();
currentSlideshow = null;
hide() {
if (this.currentSlideshow) {
this.currentSlideshow.hide();
this.currentSlideshow = null;
}
};
};
});
}
}
/* eslint-enable indent */
export default BackdropScreensaver;

View file

@ -7,7 +7,7 @@ import 'css!./style';
import 'material-icons';
import 'paper-icon-button-light';
import TableOfContent from './tableOfContent';
import TableOfContents from './tableOfContents';
export class BookPlayer {
constructor() {
@ -163,7 +163,7 @@ export class BookPlayer {
openTableOfContents() {
if (this._loaded) {
this._tocElement = new TableOfContent(this);
this._tocElement = new TableOfContents(this);
}
}
@ -238,11 +238,17 @@ export class BookPlayer {
this.bindEvents();
return this._rendition.book.locations.generate(1024).then(() => {
return this._rendition.book.locations.generate(1024).then(async () => {
if (cancellationToken.shouldCancel) {
return reject();
}
const percentageTicks = options.startPositionTicks / 10000000;
if (percentageTicks !== 0.0) {
const resumeLocation = book.locations.cfiFromPercentage(percentageTicks);
await rendition.display(resumeLocation);
}
this._loaded = true;
epubElem.style.display = 'block';
rendition.on('relocated', (locations) => {
@ -271,6 +277,7 @@ export class BookPlayer {
if (item.Path && (item.Path.endsWith('epub'))) {
return true;
}
return false;
}
}

View file

@ -1,6 +1,6 @@
import dialogHelper from 'dialogHelper';
export default class TableOfContent {
export default class TableOfContents {
constructor(bookPlayer) {
this._bookPlayer = bookPlayer;
this._rendition = bookPlayer._rendition;
@ -59,6 +59,7 @@ export default class TableOfContent {
autoFocus: false,
removeOnClose: true
});
elem.id = 'dialogToc';
let tocHtml = '<div class="topRightActionButtons">';
@ -72,6 +73,7 @@ export default class TableOfContent {
tocHtml += `<a href="${rendition.book.path.directory + link}">${chapter.label}</a>`;
tocHtml += '</li>';
});
tocHtml += '</ul>';
elem.innerHTML = tocHtml;
@ -84,7 +86,6 @@ export default class TableOfContent {
this._elem = elem;
this.bindEvents();
dialogHelper.open(elem);
}
}

View file

@ -9,17 +9,12 @@ export default class PhotoPlayer {
}
play(options) {
return new Promise(function (resolve, reject) {
import('slideshow').then(({default: slideshow}) => {
var index = options.startIndex || 0;
var apiClient = connectionManager.currentApiClient();
apiClient.getCurrentUser().then(function(result) {
var newSlideShow = new slideshow({
showTitle: false,
cover: false,
@ -31,7 +26,6 @@ export default class PhotoPlayer {
});
newSlideShow.show();
resolve();
});
});
@ -39,7 +33,6 @@ export default class PhotoPlayer {
}
canPlayMediaType(mediaType) {
return (mediaType || '').toLowerCase() === 'photo';
}
}

19
src/repositories.html Normal file
View file

@ -0,0 +1,19 @@
<div id="repositories" data-role="page" class="page type-interior withTabs fullWidthContent">
<div>
<div class="content-primary">
<div class="sectionTitleContainer flex align-items-center">
<h2 class="sectionTitle">${TabRepositories}</h2>
<button is="emby-button" type="button" class="fab btnNewRepository submit" style="margin-left:1em;" title="${ButtonAdd}">
<span class="material-icons add" aria-hidden="true"></span>
</button>
</div>
<div id="repositories"></div>
<div id="none" class="noItemsMessage centerMessage hide">
<h1>${MessageNoRepositories}</h1>
<p>${MessageAddRepository}</p>
</div>
</div>
</div>
</div>

View file

@ -6,18 +6,10 @@ define(['browser'], function (browser) {
}
function canPlayH265(videoTestElement, options) {
if (browser.tizen || browser.orsay || browser.xboxOne || browser.web0s || options.supportsHevc) {
if (browser.tizen || browser.xboxOne || browser.web0s || options.supportsHevc) {
return true;
}
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
if (isChromecastUltra) {
return true;
}
}
if (browser.ps4) {
return false;
}
@ -31,7 +23,7 @@ define(['browser'], function (browser) {
var _supportsTextTracks;
function supportsTextTracks() {
if (browser.tizen || browser.orsay) {
if (browser.tizen) {
return true;
}
@ -53,7 +45,7 @@ define(['browser'], function (browser) {
}
function canPlayNativeHls() {
if (browser.tizen || browser.orsay) {
if (browser.tizen) {
return true;
}
@ -72,7 +64,7 @@ define(['browser'], function (browser) {
}
function supportsAc3(videoTestElement) {
if (browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s) {
if (browser.edgeUwp || browser.tizen || browser.web0s) {
return true;
}
@ -80,7 +72,7 @@ define(['browser'], function (browser) {
}
function supportsEac3(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) {
if (browser.tizen || browser.web0s) {
return true;
}
@ -88,7 +80,7 @@ define(['browser'], function (browser) {
}
function supportsAc3InHls(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) {
if (browser.tizen || browser.web0s) {
return true;
}
@ -104,11 +96,11 @@ define(['browser'], function (browser) {
var typeString;
if (format === 'flac') {
if (browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp) {
if (browser.tizen || browser.web0s || browser.edgeUwp) {
return true;
}
} else if (format === 'wma') {
if (browser.tizen || browser.orsay || browser.edgeUwp) {
if (browser.tizen || browser.edgeUwp) {
return true;
}
} else if (format === 'asf') {
@ -143,7 +135,7 @@ define(['browser'], function (browser) {
}
function testCanPlayMkv(videoTestElement) {
if (browser.tizen || browser.orsay || browser.web0s) {
if (browser.tizen || browser.web0s) {
return true;
}
@ -152,23 +144,6 @@ define(['browser'], function (browser) {
return true;
}
// Unfortunately there's no real way to detect mkv support
if (browser.chrome) {
// Not supported on opera tv
if (browser.operaTv) {
return false;
}
var userAgent = navigator.userAgent.toLowerCase();
// Filter out browsers based on chromium that don't support mkv
if (userAgent.indexOf('vivaldi') !== -1 || userAgent.indexOf('opera') !== -1) {
return false;
}
return true;
}
if (browser.edgeUwp) {
return true;
}
@ -176,16 +151,26 @@ define(['browser'], function (browser) {
return false;
}
function testCanPlayAv1(videoTestElement) {
if (browser.tizenVersion >= 5.5) {
return true;
} else if (browser.web0sVersion >= 5 && window.outerHeight >= 2160) {
return true;
}
return videoTestElement.canPlayType('video/webm; codecs="av01.0.15M.10"').replace(/no/, '');
}
function testCanPlayTs() {
return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
return browser.tizen || browser.web0s || browser.edgeUwp;
}
function supportsMpeg2Video() {
return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
return browser.tizen || browser.web0s || browser.edgeUwp;
}
function supportsVc1() {
return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
function supportsVc1(videoTestElement) {
return browser.tizen || browser.web0s || browser.edgeUwp || videoTestElement.canPlayType('video/mp4; codecs="vc-1"').replace(/no/, '');
}
function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) {
@ -195,11 +180,11 @@ define(['browser'], function (browser) {
switch (container) {
case 'asf':
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.edgeUwp;
videoAudioCodecs = [];
break;
case 'avi':
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.edgeUwp;
// New Samsung TV don't support XviD/DivX
// Explicitly add supported codecs to make other codecs be transcoded
if (browser.tizenVersion >= 4) {
@ -212,26 +197,26 @@ define(['browser'], function (browser) {
break;
case 'mpg':
case 'mpeg':
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.edgeUwp;
break;
case 'flv':
supported = browser.tizen || browser.orsay;
supported = browser.tizen;
break;
case '3gp':
case 'mts':
case 'trp':
case 'vob':
case 'vro':
supported = browser.tizen || browser.orsay;
supported = browser.tizen;
break;
case 'mov':
supported = browser.tizen || browser.orsay || browser.web0s || browser.chrome || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.chrome || browser.edgeUwp;
videoCodecs.push('h264');
break;
case 'm2ts':
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.edgeUwp;
videoCodecs.push('h264');
if (supportsVc1()) {
if (supportsVc1(videoTestElement)) {
videoCodecs.push('vc1');
}
if (supportsMpeg2Video()) {
@ -239,7 +224,7 @@ define(['browser'], function (browser) {
}
break;
case 'wmv':
supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp;
supported = browser.tizen || browser.web0s || browser.edgeUwp;
videoAudioCodecs = [];
break;
case 'ts':
@ -249,7 +234,7 @@ define(['browser'], function (browser) {
videoCodecs.push('h265');
videoCodecs.push('hevc');
}
if (supportsVc1()) {
if (supportsVc1(videoTestElement)) {
videoCodecs.push('vc1');
}
if (supportsMpeg2Video()) {
@ -274,21 +259,6 @@ define(['browser'], function (browser) {
}
function getGlobalMaxVideoBitrate() {
var userAgent = navigator.userAgent.toLowerCase();
if (browser.chromecast) {
var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
if (isChromecastUltra) {
return null;
}
// This is a hack to try and detect chromecast on vizio
if (self.screen && self.screen.width >= 3800) {
return null;
}
return 30000000;
}
var isTizenFhd = false;
if (browser.tizen) {
try {
@ -317,7 +287,6 @@ define(['browser'], function (browser) {
var canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, '');
var canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, '');
var canPlayAv1 = videoTestElement.canPlayType('video/webm; codecs="av1"').replace(/no/, '');
var webmAudioCodecs = ['vorbis'];
var canPlayMkv = testCanPlayMkv(videoTestElement);
@ -333,11 +302,12 @@ define(['browser'], function (browser) {
var videoAudioCodecs = [];
var hlsVideoAudioCodecs = [];
var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '') ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, '');
var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '')
|| videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, '')
|| videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp3"').replace(/no/, '');
// Not sure how to test for this
var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s;
var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.web0s;
var maxVideoWidth = browser.xboxOne ?
(self.screen ? self.screen.width : null) :
@ -349,11 +319,6 @@ define(['browser'], function (browser) {
var canPlayAacVideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"').replace(/no/, '');
if (canPlayAacVideoAudio && browser.chromecast && physicalAudioChannels <= 2) {
// prioritize this first
videoAudioCodecs.push('aac');
}
// Only put mp3 first if mkv support is there
// Otherwise with HLS and mp3 audio we're seeing some browsers
// safari is lying
@ -376,11 +341,6 @@ define(['browser'], function (browser) {
}
}
if (canPlayAacVideoAudio && browser.chromecast && videoAudioCodecs.indexOf('aac') === -1) {
// prioritize this first
videoAudioCodecs.push('aac');
}
if (supportsMp3VideoAudio) {
videoAudioCodecs.push('mp3');
@ -415,7 +375,7 @@ define(['browser'], function (browser) {
videoAudioCodecs.push('mp2');
}
var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts;
var supportsDts = browser.tizen || browser.web0s || options.supportsDts || videoTestElement.canPlayType('video/mp4; codecs="dts-"').replace(/no/, '') || videoTestElement.canPlayType('video/mp4; codecs="dts+"').replace(/no/, '');
// DTS audio not supported in 2018 models (Tizen 4.0)
if (browser.tizenVersion >= 4) {
@ -427,7 +387,7 @@ define(['browser'], function (browser) {
videoAudioCodecs.push('dts');
}
if (browser.tizen || browser.orsay || browser.web0s) {
if (browser.tizen || browser.web0s) {
videoAudioCodecs.push('pcm_s16le');
videoAudioCodecs.push('pcm_s24le');
}
@ -436,7 +396,7 @@ define(['browser'], function (browser) {
videoAudioCodecs.push('truehd');
}
if (browser.tizen || browser.orsay) {
if (browser.tizen) {
videoAudioCodecs.push('aac_latm');
}
@ -459,6 +419,7 @@ define(['browser'], function (browser) {
});
var mp4VideoCodecs = [];
var webmVideoCodecs = [];
var hlsVideoCodecs = [];
if (canPlayH264(videoTestElement)) {
@ -480,26 +441,42 @@ define(['browser'], function (browser) {
mp4VideoCodecs.push('mpeg2video');
}
if (supportsVc1()) {
if (supportsVc1(videoTestElement)) {
mp4VideoCodecs.push('vc1');
}
if (browser.tizen || browser.orsay) {
if (browser.tizen) {
mp4VideoCodecs.push('msmpeg4v2');
}
if (canPlayVp8) {
mp4VideoCodecs.push('vp8');
webmVideoCodecs.push('vp8');
}
if (canPlayVp9) {
mp4VideoCodecs.push('vp9');
webmVideoCodecs.push('vp9');
}
if (canPlayVp8 || browser.tizen || browser.orsay) {
if (testCanPlayAv1(videoTestElement)) {
mp4VideoCodecs.push('av1');
webmVideoCodecs.push('av1');
}
if (canPlayVp8 || browser.tizen) {
videoAudioCodecs.push('vorbis');
}
if (webmVideoCodecs.length) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
VideoCodec: webmVideoCodecs.join(','),
AudioCodec: webmAudioCodecs.join(',')
});
}
if (mp4VideoCodecs.length) {
profile.DirectPlayProfiles.push({
Container: 'mp4,m4v',
@ -558,33 +535,6 @@ define(['browser'], function (browser) {
}
});
if (canPlayVp8) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
VideoCodec: 'VP8'
});
}
if (canPlayVp9) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
VideoCodec: 'VP9'
});
}
if (canPlayAv1) {
profile.DirectPlayProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
VideoCodec: 'AV1'
});
}
profile.TranscodingProfiles = [];
var hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls() ? true : false;
@ -628,7 +578,7 @@ define(['browser'], function (browser) {
});
});
if (canPlayMkv && !browser.tizen && !browser.orsay && options.enableMkvProgressive !== false) {
if (canPlayMkv && !browser.tizen && options.enableMkvProgressive !== false) {
profile.TranscodingProfiles.push({
Container: 'mkv',
Type: 'Video',
@ -693,7 +643,7 @@ define(['browser'], function (browser) {
profile.CodecProfiles = [];
var supportsSecondaryAudio = browser.tizen || browser.orsay || videoTestElement.audioTracks;
var supportsSecondaryAudio = browser.tizen || videoTestElement.audioTracks;
var aacCodecProfileConditions = [];
@ -716,15 +666,6 @@ define(['browser'], function (browser) {
});
}
if (browser.chromecast) {
aacCodecProfileConditions.push({
Condition: 'LessThanEqual',
Property: 'AudioChannels',
Value: '2',
IsRequired: true
});
}
if (aacCodecProfileConditions.length) {
profile.CodecProfiles.push({
Type: 'VideoAudio',
@ -750,7 +691,7 @@ define(['browser'], function (browser) {
var maxH264Level = 42;
var h264Profiles = 'high|main|baseline|constrained baseline';
if (browser.tizen || browser.orsay || browser.web0s ||
if (browser.tizen || browser.web0s ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.640833"').replace(/no/, '')) {
maxH264Level = 51;
}
@ -760,7 +701,7 @@ define(['browser'], function (browser) {
maxH264Level = 52;
}
if (browser.tizen || browser.orsay ||
if (browser.tizen ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {
// These tests are passing in safari, but playback is failing
@ -794,20 +735,13 @@ define(['browser'], function (browser) {
]
});
if (!browser.edgeUwp && !browser.tizen && !browser.orsay && !browser.web0s) {
//profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
// Condition: 'NotEquals',
// Property: 'IsAVC',
// Value: 'false',
// IsRequired: false
//});
//profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
// Condition: 'NotEquals',
// Property: 'IsInterlaced',
// Value: 'true',
// IsRequired: false
//});
if (!browser.edgeUwp && !browser.tizen && !browser.web0s) {
profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({
Condition: 'NotEquals',
Property: 'IsInterlaced',
Value: 'true',
IsRequired: false
});
}
if (maxVideoWidth) {
@ -858,19 +792,6 @@ define(['browser'], function (browser) {
});
}
if (browser.chromecast) {
profile.CodecProfiles.push({
Type: 'Audio',
Codec: 'flac',
Conditions: [
{
Condition: 'LessThanEqual',
Property: 'AudioSampleRate',
Value: '96000'
}]
});
}
// Subtitle profiles
// External vtt or burn in
profile.SubtitleProfiles = [];

View file

@ -12,10 +12,10 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
html += '</div>';
html += '<div class="headerRight">';
html += '<span class="headerSelectedPlayer"></span>';
html += '<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide"><span class="material-icons sync_disabled"></span></button>';
html += `<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide" title="${globalize.translate('ButtonSyncPlay')}"><span class="material-icons sync_disabled"></span></button>`;
html += '<button is="paper-icon-button-light" class="headerAudioPlayerButton audioPlayerButton headerButton headerButtonRight hide"><span class="material-icons music_note"></span></button>';
html += '<button is="paper-icon-button-light" class="headerCastButton castButton headerButton headerButtonRight hide"><span class="material-icons cast"></span></button>';
html += '<button type="button" is="paper-icon-button-light" class="headerButton headerButtonRight headerSearchButton hide"><span class="material-icons search"></span></button>';
html += `<button is="paper-icon-button-light" class="headerCastButton castButton headerButton headerButtonRight hide" title="${globalize.translate('ButtonCast')}"><span class="material-icons cast"></span></button>`;
html += `<button type="button" is="paper-icon-button-light" class="headerButton headerButtonRight headerSearchButton hide" title="${globalize.translate('ButtonSearch')}"><span class="material-icons search"></span></button>`;
html += '<button is="paper-icon-button-light" class="headerButton headerButtonRight headerUserButton hide"><span class="material-icons person"></span></button>';
html += '</div>';
html += '</div>';
@ -64,7 +64,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
updateHeaderUserButton(url);
hasImage = true;
}
headerUserButton.title = user.name;
headerUserButton.classList.remove('hide');
} else {
headerUserButton.classList.add('hide');
@ -834,6 +834,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
var headerAudioPlayerButton;
var headerSyncButton;
var enableLibraryNavDrawer = layoutManager.desktop;
var enableLibraryNavDrawerHome = !layoutManager.tv;
var skinHeader = document.querySelector('.skinHeader');
var requiresUserRefresh = true;
var lastOpenTime = new Date().getTime();
@ -920,7 +921,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
refreshDashboardInfoInDrawer(apiClient);
} else {
if (mainDrawerButton) {
if (enableLibraryNavDrawer || isHomePage) {
if (enableLibraryNavDrawer || (isHomePage && enableLibraryNavDrawerHome)) {
mainDrawerButton.classList.remove('hide');
} else {
mainDrawerButton.classList.add('hide');

View file

@ -215,6 +215,12 @@ define([
roles: 'admin',
controller: 'dashboard/plugins/available'
});
defineRoute({
path: '/repositories.html',
autoFocus: false,
roles: 'admin',
controller: 'dashboard/plugins/repositories'
});
defineRoute({
path: '/home.html',

View file

@ -1,23 +1,30 @@
/* eslint-disable indent */
import appSettings from 'appSettings';
import events from 'events';
function onSaveTimeout() {
var self = this;
self.saveTimeout = null;
self.currentApiClient.updateDisplayPreferences('usersettings', self.displayPrefs, self.currentUserId, 'emby');
function onSaveTimeout() {
var self = this;
self.saveTimeout = null;
self.currentApiClient.updateDisplayPreferences('usersettings', self.displayPrefs, self.currentUserId, 'emby');
}
function saveServerPreferences(instance) {
if (instance.saveTimeout) {
clearTimeout(instance.saveTimeout);
}
function saveServerPreferences(instance) {
if (instance.saveTimeout) {
clearTimeout(instance.saveTimeout);
}
instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50);
}
instance.saveTimeout = setTimeout(onSaveTimeout.bind(instance), 50);
export class UserSettings {
constructor() {
}
export function setUserInfo(userId, apiClient) {
/**
* Bind UserSettings instance to user.
* @param {string} - User identifier.
* @param {Object} - ApiClient instance.
*/
setUserInfo(userId, apiClient) {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
@ -38,15 +45,24 @@ import events from 'events';
});
}
export function getData() {
// FIXME: Seems unused
getData() {
return this.displayPrefs;
}
export function importFrom(instance) {
// FIXME: Seems unused
importFrom(instance) {
this.displayPrefs = instance.getData();
}
export function set(name, value, enableOnServer) {
// FIXME: 'appSettings.set' doesn't return any value
/**
* Set value of setting.
* @param {string} name - Name of setting.
* @param {mixed} value - Value of setting.
* @param {boolean} enableOnServer - Flag to save preferences on server.
*/
set(name, value, enableOnServer) {
var userId = this.currentUserId;
var currentValue = this.get(name, enableOnServer);
var result = appSettings.set(name, value, userId);
@ -63,7 +79,13 @@ import events from 'events';
return result;
}
export function get(name, enableOnServer) {
/**
* Get value of setting.
* @param {string} name - Name of setting.
* @param {boolean} enableOnServer - Flag to return preferences from server (cached).
* @return {string} Value of setting.
*/
get(name, enableOnServer) {
var userId = this.currentUserId;
if (enableOnServer !== false && this.displayPrefs) {
return this.displayPrefs.CustomPrefs[name];
@ -72,7 +94,12 @@ import events from 'events';
return appSettings.get(name, userId);
}
export function serverConfig(config) {
/**
* Get or set user config.
* @param {Object|undefined} config - Configuration or undefined.
* @return {Object|Promise} Configuration or Promise.
*/
serverConfig(config) {
var apiClient = this.currentApiClient;
if (config) {
return apiClient.updateUserConfiguration(this.currentUserId, config);
@ -83,7 +110,12 @@ import events from 'events';
});
}
export function enableCinemaMode(val) {
/**
* Get or set 'Cinema Mode' state.
* @param {boolean|undefined} val - Flag to enable 'Cinema Mode' or undefined.
* @return {boolean} 'Cinema Mode' state.
*/
enableCinemaMode(val) {
if (val !== undefined) {
return this.set('enableCinemaMode', val.toString(), false);
}
@ -92,7 +124,12 @@ import events from 'events';
return val !== 'false';
}
export function enableNextVideoInfoOverlay(val) {
/**
* Get or set 'Next Video Info Overlay' state.
* @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined.
* @return {boolean} 'Next Video Info Overlay' state.
*/
enableNextVideoInfoOverlay(val) {
if (val !== undefined) {
return this.set('enableNextVideoInfoOverlay', val.toString());
}
@ -101,7 +138,12 @@ import events from 'events';
return val !== 'false';
}
export function enableThemeSongs(val) {
/**
* Get or set 'Theme Songs' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
* @return {boolean} 'Theme Songs' state.
*/
enableThemeSongs(val) {
if (val !== undefined) {
return this.set('enableThemeSongs', val.toString(), false);
}
@ -110,7 +152,12 @@ import events from 'events';
return val === 'true';
}
export function enableThemeVideos(val) {
/**
* Get or set 'Theme Videos' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Videos' or undefined.
* @return {boolean} 'Theme Videos' state.
*/
enableThemeVideos(val) {
if (val !== undefined) {
return this.set('enableThemeVideos', val.toString(), false);
}
@ -119,7 +166,12 @@ import events from 'events';
return val === 'true';
}
export function enableFastFadein(val) {
/**
* Get or set 'Fast Fade-in' state.
* @param {boolean|undefined} val - Flag to enable 'Fast Fade-in' or undefined.
* @return {boolean} 'Fast Fade-in' state.
*/
enableFastFadein(val) {
if (val !== undefined) {
return this.set('fastFadein', val.toString(), false);
}
@ -128,7 +180,26 @@ import events from 'events';
return val !== 'false';
}
export function enableBackdrops(val) {
/**
* Get or set 'Blurhash' state.
* @param {boolean|undefined} val - Flag to enable 'Blurhash' or undefined.
* @return {boolean} 'Blurhash' state.
*/
enableBlurhash(val) {
if (val !== undefined) {
return this.set('blurhash', val.toString(), false);
}
val = this.get('blurhash', false);
return val !== 'false';
}
/**
* Get or set 'Backdrops' state.
* @param {boolean|undefined} val - Flag to enable 'Backdrops' or undefined.
* @return {boolean} 'Backdrops' state.
*/
enableBackdrops(val) {
if (val !== undefined) {
return this.set('enableBackdrops', val.toString(), false);
}
@ -137,7 +208,12 @@ import events from 'events';
return val !== 'false';
}
export function detailsBanner(val) {
/**
* Get or set 'Details Banner' state.
* @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined.
* @return {boolean} 'Details Banner' state.
*/
detailsBanner(val) {
if (val !== undefined) {
return this.set('detailsBanner', val.toString(), false);
}
@ -146,7 +222,12 @@ import events from 'events';
return val !== 'false';
}
export function language(val) {
/**
* Get or set language.
* @param {string|undefined} val - Language.
* @return {string} Language.
*/
language(val) {
if (val !== undefined) {
return this.set('language', val.toString(), false);
}
@ -154,7 +235,12 @@ import events from 'events';
return this.get('language', false);
}
export function dateTimeLocale(val) {
/**
* Get or set datetime locale.
* @param {string|undefined} val - Datetime locale.
* @return {string} Datetime locale.
*/
dateTimeLocale(val) {
if (val !== undefined) {
return this.set('datetimelocale', val.toString(), false);
}
@ -162,7 +248,12 @@ import events from 'events';
return this.get('datetimelocale', false);
}
export function chromecastVersion(val) {
/**
* Get or set Chromecast version.
* @param {string|undefined} val - Chromecast version.
* @return {string} Chromecast version.
*/
chromecastVersion(val) {
if (val !== undefined) {
return this.set('chromecastVersion', val.toString());
}
@ -170,7 +261,12 @@ import events from 'events';
return this.get('chromecastVersion') || 'stable';
}
export function skipBackLength(val) {
/**
* Get or set amount of rewind.
* @param {number|undefined} val - Amount of rewind.
* @return {number} Amount of rewind.
*/
skipBackLength(val) {
if (val !== undefined) {
return this.set('skipBackLength', val.toString());
}
@ -178,7 +274,12 @@ import events from 'events';
return parseInt(this.get('skipBackLength') || '10000');
}
export function skipForwardLength(val) {
/**
* Get or set amount of fast forward.
* @param {number|undefined} val - Amount of fast forward.
* @return {number} Amount of fast forward.
*/
skipForwardLength(val) {
if (val !== undefined) {
return this.set('skipForwardLength', val.toString());
}
@ -186,7 +287,12 @@ import events from 'events';
return parseInt(this.get('skipForwardLength') || '30000');
}
export function dashboardTheme(val) {
/**
* Get or set theme for Dashboard.
* @param {string|undefined} val - Theme for Dashboard.
* @return {string} Theme for Dashboard.
*/
dashboardTheme(val) {
if (val !== undefined) {
return this.set('dashboardTheme', val);
}
@ -194,7 +300,12 @@ import events from 'events';
return this.get('dashboardTheme');
}
export function skin(val) {
/**
* Get or set skin.
* @param {string|undefined} val - Skin.
* @return {string} Skin.
*/
skin(val) {
if (val !== undefined) {
return this.set('skin', val, false);
}
@ -202,7 +313,12 @@ import events from 'events';
return this.get('skin', false);
}
export function theme(val) {
/**
* Get or set main theme.
* @param {string|undefined} val - Main theme.
* @return {string} Main theme.
*/
theme(val) {
if (val !== undefined) {
return this.set('appTheme', val, false);
}
@ -210,7 +326,12 @@ import events from 'events';
return this.get('appTheme', false);
}
export function screensaver(val) {
/**
* Get or set screensaver.
* @param {string|undefined} val - Screensaver.
* @return {string} Screensaver.
*/
screensaver(val) {
if (val !== undefined) {
return this.set('screensaver', val, false);
}
@ -218,7 +339,12 @@ import events from 'events';
return this.get('screensaver', false);
}
export function libraryPageSize(val) {
/**
* Get or set library page size.
* @param {number|undefined} val - Library page size.
* @return {number} Library page size.
*/
libraryPageSize(val) {
if (val !== undefined) {
return this.set('libraryPageSize', parseInt(val, 10), false);
}
@ -232,7 +358,12 @@ import events from 'events';
}
}
export function soundEffects(val) {
/**
* Get or set sound effects.
* @param {string|undefined} val - Sound effects.
* @return {string} Sound effects.
*/
soundEffects(val) {
if (val !== undefined) {
return this.set('soundeffects', val, false);
}
@ -240,7 +371,13 @@ import events from 'events';
return this.get('soundeffects', false);
}
export function loadQuerySettings(key, query) {
/**
* Load query settings.
* @param {string} key - Query key.
* @param {Object} query - Query base.
* @return {Object} Query.
*/
loadQuerySettings(key, query) {
var values = this.get(key);
if (values) {
values = JSON.parse(values);
@ -250,7 +387,12 @@ import events from 'events';
return query;
}
export function saveQuerySettings(key, query) {
/**
* Save query settings.
* @param {string} key - Query key.
* @param {Object} query - Query.
*/
saveQuerySettings(key, query) {
var values = {};
if (query.SortBy) {
values.SortBy = query.SortBy;
@ -263,52 +405,76 @@ import events from 'events';
return this.set(key, JSON.stringify(values));
}
export function getSubtitleAppearanceSettings(key) {
/**
* Get subtitle appearance settings.
* @param {string|undefined} key - Settings key.
* @return {Object} Subtitle appearance settings.
*/
getSubtitleAppearanceSettings(key) {
key = key || 'localplayersubtitleappearance3';
return JSON.parse(this.get(key, false) || '{}');
}
export function setSubtitleAppearanceSettings(value, key) {
/**
* Set subtitle appearance settings.
* @param {Object} value - Subtitle appearance settings.
* @param {string|undefined} key - Settings key.
*/
setSubtitleAppearanceSettings(value, key) {
key = key || 'localplayersubtitleappearance3';
return this.set(key, JSON.stringify(value), false);
}
export function setFilter(key, value) {
/**
* Set filter.
* @param {string} key - Filter key.
* @param {string} value - Filter value.
*/
setFilter(key, value) {
return this.set(key, value, true);
}
export function getFilter(key) {
/**
* Get filter.
* @param {string} key - Filter key.
* @return {string} Filter value.
*/
getFilter(key) {
return this.get(key, true);
}
}
/* eslint-enable indent */
export default {
setUserInfo: setUserInfo,
getData: getData,
importFrom: importFrom,
set: set,
get: get,
serverConfig: serverConfig,
enableCinemaMode: enableCinemaMode,
enableNextVideoInfoOverlay: enableNextVideoInfoOverlay,
enableThemeSongs: enableThemeSongs,
enableThemeVideos: enableThemeVideos,
enableFastFadein: enableFastFadein,
enableBackdrops: enableBackdrops,
language: language,
dateTimeLocale: dateTimeLocale,
skipBackLength: skipBackLength,
skipForwardLength: skipForwardLength,
dashboardTheme: dashboardTheme,
skin: skin,
theme: theme,
screensaver: screensaver,
libraryPageSize: libraryPageSize,
soundEffects: soundEffects,
loadQuerySettings: loadQuerySettings,
saveQuerySettings: saveQuerySettings,
getSubtitleAppearanceSettings: getSubtitleAppearanceSettings,
setSubtitleAppearanceSettings: setSubtitleAppearanceSettings,
setFilter: setFilter,
getFilter: getFilter
};
export const currentSettings = new UserSettings;
// Wrappers for non-ES6 modules and backward compatibility
export const setUserInfo = currentSettings.setUserInfo.bind(currentSettings);
export const getData = currentSettings.getData.bind(currentSettings);
export const importFrom = currentSettings.importFrom.bind(currentSettings);
export const set = currentSettings.set.bind(currentSettings);
export const get = currentSettings.get.bind(currentSettings);
export const serverConfig = currentSettings.serverConfig.bind(currentSettings);
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
export const enableBlurhash = currentSettings.enableBlurhash.bind(currentSettings);
export const enableBackdrops = currentSettings.enableBackdrops.bind(currentSettings);
export const detailsBanner = currentSettings.detailsBanner.bind(currentSettings);
export const language = currentSettings.language.bind(currentSettings);
export const dateTimeLocale = currentSettings.dateTimeLocale.bind(currentSettings);
export const chromecastVersion = currentSettings.chromecastVersion.bind(currentSettings);
export const skipBackLength = currentSettings.skipBackLength.bind(currentSettings);
export const skipForwardLength = currentSettings.skipForwardLength.bind(currentSettings);
export const dashboardTheme = currentSettings.dashboardTheme.bind(currentSettings);
export const skin = currentSettings.skin.bind(currentSettings);
export const theme = currentSettings.theme.bind(currentSettings);
export const screensaver = currentSettings.screensaver.bind(currentSettings);
export const libraryPageSize = currentSettings.libraryPageSize.bind(currentSettings);
export const soundEffects = currentSettings.soundEffects.bind(currentSettings);
export const loadQuerySettings = currentSettings.loadQuerySettings.bind(currentSettings);
export const saveQuerySettings = currentSettings.saveQuerySettings.bind(currentSettings);
export const getSubtitleAppearanceSettings = currentSettings.getSubtitleAppearanceSettings.bind(currentSettings);
export const setSubtitleAppearanceSettings = currentSettings.setSubtitleAppearanceSettings.bind(currentSettings);
export const setFilter = currentSettings.setFilter.bind(currentSettings);
export const getFilter = currentSettings.getFilter.bind(currentSettings);

View file

@ -2,7 +2,7 @@ let data;
function getConfig() {
if (data) return Promise.resolve(data);
return fetch('/config.json?nocache=' + new Date().getUTCMilliseconds()).then(response => {
return fetch('config.json?nocache=' + new Date().getUTCMilliseconds()).then(response => {
data = response.json();
return data;
}).catch(error => {
@ -12,7 +12,7 @@ function getConfig() {
}
function getDefaultConfig() {
return fetch('/config.template.json').then(function (response) {
return fetch('config.template.json').then(function (response) {
data = response.json();
return data;
});

View file

@ -193,25 +193,6 @@ var Dashboard = {
}).then(options.callback || function () {});
});
},
restartServer: function () {
var apiClient = window.ApiClient;
if (apiClient) {
require(['serverRestartDialog', 'events'], function (ServerRestartDialog, events) {
var dialog = new ServerRestartDialog({
apiClient: apiClient
});
events.on(dialog, 'restarted', function () {
if (AppInfo.isNativeApp) {
apiClient.ensureWebSocket();
} else {
window.location.reload(true);
}
});
dialog.show();
});
}
},
capabilities: function (appHost) {
var capabilities = {
PlayableMediaTypes: ['Audio', 'Video'],
@ -228,6 +209,28 @@ var Dashboard = {
} else {
Dashboard.navigate('selectserver.html');
}
},
hideLoadingMsg: function() {
'use strict';
require(['loading'], function(loading) {
loading.hide();
});
},
showLoadingMsg: function() {
'use strict';
require(['loading'], function(loading) {
loading.show();
});
},
confirm: function(message, title, callback) {
'use strict';
require(['confirm'], function(confirm) {
confirm(message, title).then(function() {
callback(!0);
}).catch(function() {
callback(!1);
});
});
}
};
@ -653,7 +656,7 @@ var AppInfo = {};
playQueueManager: componentsPath + '/playback/playqueuemanager',
nowPlayingHelper: componentsPath + '/playback/nowplayinghelper',
pluginManager: componentsPath + '/pluginManager',
packageManager: componentsPath + '/packagemanager',
packageManager: componentsPath + '/packageManager',
screensaverManager: componentsPath + '/screensavermanager',
chromecastHelper: 'plugins/chromecastPlayer/chromecastHelpers'
};
@ -737,7 +740,6 @@ var AppInfo = {};
// define legacy features
// TODO delete the rest of these
define('fnchecked', ['legacy/fnchecked'], returnFirstDependency);
define('legacyDashboard', ['legacy/dashboard'], returnFirstDependency);
define('legacySelectMenu', ['legacy/selectmenu'], returnFirstDependency);
// there are several objects that need to be instantiated
@ -793,7 +795,6 @@ var AppInfo = {};
define('tabbedView', [componentsPath + '/tabbedview/tabbedview'], returnFirstDependency);
define('itemsTab', [componentsPath + '/tabbedview/itemstab'], returnFirstDependency);
define('collectionEditor', [componentsPath + '/collectionEditor/collectionEditor'], returnFirstDependency);
define('serverRestartDialog', [componentsPath + '/serverRestartDialog'], returnFirstDependency);
define('playlistEditor', [componentsPath + '/playlisteditor/playlisteditor'], returnFirstDependency);
define('recordingCreator', [componentsPath + '/recordingcreator/recordingcreator'], returnFirstDependency);
define('recordingEditor', [componentsPath + '/recordingcreator/recordingeditor'], returnFirstDependency);
@ -820,10 +821,10 @@ var AppInfo = {};
define('playbackSettings', [componentsPath + '/playbackSettings/playbackSettings'], returnFirstDependency);
define('homescreenSettings', [componentsPath + '/homeScreenSettings/homeScreenSettings'], returnFirstDependency);
define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager);
define('timeSyncManager', [componentsPath + '/syncplay/timeSyncManager'], returnDefault);
define('groupSelectionMenu', [componentsPath + '/syncplay/groupSelectionMenu'], returnFirstDependency);
define('syncPlayManager', [componentsPath + '/syncplay/syncPlayManager'], returnDefault);
define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault);
define('timeSyncManager', [componentsPath + '/syncPlay/timeSyncManager'], returnDefault);
define('groupSelectionMenu', [componentsPath + '/syncPlay/groupSelectionMenu'], returnFirstDependency);
define('syncPlayManager', [componentsPath + '/syncPlay/syncPlayManager'], returnDefault);
define('playbackPermissionManager', [componentsPath + '/syncPlay/playbackPermissionManager'], returnDefault);
define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager);
define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency);
define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency);
@ -836,7 +837,6 @@ var AppInfo = {};
define('deleteHelper', [scriptsPath + '/deleteHelper'], returnFirstDependency);
define('tvguide', [componentsPath + '/guide/guide'], returnFirstDependency);
define('guide-settings-dialog', [componentsPath + '/guide/guide-settings'], returnFirstDependency);
define('loadingDialog', [componentsPath + '/loadingDialog/loadingDialog'], returnFirstDependency);
define('viewManager', [componentsPath + '/viewManager/viewManager'], function (viewManager) {
window.ViewManager = viewManager;
viewManager.dispatchPageEvents(true);
@ -847,7 +847,7 @@ var AppInfo = {};
define('userdataButtons', [componentsPath + '/userdatabuttons/userdatabuttons'], returnFirstDependency);
define('listView', [componentsPath + '/listview/listview'], returnFirstDependency);
define('indicators', [componentsPath + '/indicators/indicators'], returnFirstDependency);
define('viewSettings', [componentsPath + '/viewsettings/viewsettings'], returnFirstDependency);
define('viewSettings', [componentsPath + '/viewSettings/viewSettings'], returnFirstDependency);
define('filterMenu', [componentsPath + '/filtermenu/filtermenu'], returnFirstDependency);
define('sortMenu', [componentsPath + '/sortmenu/sortmenu'], returnFirstDependency);
define('sanitizefilename', [componentsPath + '/sanitizeFilename'], returnFirstDependency);

View file

@ -1018,7 +1018,6 @@
"HeaderSeriesStatus": "حالة المسلسل",
"HeaderSeriesOptions": "اعدادات المسلسل",
"HeaderSecondsValue": "{0} ثوانى",
"HeaderRestartingServer": "اعادة تشغيل الخادم",
"HeaderRecordingOptions": "اعدادات التسجيل",
"HeaderPlaybackError": "خطأ فى التشغيل",
"HeaderPlayOn": "شغل على",

View file

@ -277,7 +277,7 @@
"Identify": "Разпознаване",
"Images": "Изображения",
"ImportMissingEpisodesHelp": "Ако е активирано, информация за липсващи епизоди ще бъде добавена в базата данни на Jellyfin и ще бъде показвана заедно със сезони и серии. Това може да доведе до значително по-дълго сканиране на библиотеката.",
"InstallingPackage": "Инсталиране на {0}",
"InstallingPackage": "Инсталиране на {0} на версия {1})",
"InstantMix": "Пускане на подобни",
"Label3DFormat": "Триизмерен формат:",
"LabelAirDays": "Дни на излъчване:",
@ -302,7 +302,7 @@
"LabelCountry": "Държава:",
"LabelCriticRating": "Оценка на критиците:",
"LabelCurrentPassword": "Текуща парола:",
"LabelCustomCertificatePath": "Път към потребителския сертификат:",
"LabelCustomCertificatePath": "Ръчно задаване на пътя към SSL сертификата:",
"LabelCustomCertificatePathHelp": "Път до файл с шифровъчен стандарт №12 (PKCS #12), съдържащ сертификат и частен ключ за поддръжка на протокол TLS на собствен домейн.",
"LabelCustomCss": "CSS по избор:",
"LabelCustomCssHelp": "Добавете собствен стил към уеб-интерфейса.",
@ -333,7 +333,7 @@
"LabelEnableDlnaPlayTo": "Включване на функцията \"възпроизвеждане с ДЛНА\"",
"LabelEnableDlnaPlayToHelp": "Засичане на устройства в мрежата ви и предлагане на възможност за дистанционно управление.",
"LabelEnableDlnaServer": "Включване на ДЛНА-сървър",
"LabelEnableDlnaServerHelp": "Разрешава на UPnP устройства в мрежата да разглеждат и пускат Jellyfin съдържание.",
"LabelEnableDlnaServerHelp": "Разрешава на UPnP устройства в мрежата да разглеждат и пускат Джелифин съдържание.",
"LabelEnableRealtimeMonitor": "Активиране на наблюдение в реално време",
"LabelEnableRealtimeMonitorHelp": "Промените ще бъдат обработени веднага, на поддържани файлови системи.",
"LabelEpisodeNumber": "Номер на епизода:",
@ -348,20 +348,20 @@
"LabelHomeNetworkQuality": "Качество на домашната мрежа:",
"LabelHomeScreenSectionValue": "Раздел {0} на началния екран:",
"LabelHttpsPort": "Локален HTTPS порт:",
"LabelHttpsPortHelp": "TCP портът на който HTTPS сървърът на Jellyfin трябва да се закачи.",
"LabelHttpsPortHelp": "TCP портът на който HTTPS сървърът на Джелифин трябва да се закачи.",
"LabelImageType": "Вид изображение:",
"LabelInternetQuality": "Качество на интернетната връзка:",
"LabelKodiMetadataDateFormat": "Формат на датата на издаване:",
"LabelKodiMetadataUserHelp": "Разрешете това, за да запазите данните за гледанията във файлове Nfo за употреба от други програми.",
"LabelLanguage": "Език:",
"LabelLocalHttpServerPortNumber": "Локален HTTP порт:",
"LabelLocalHttpServerPortNumberHelp": "TCP портът на който HTTP сървърът на Jellyfin трябва да се закачи.",
"LabelLocalHttpServerPortNumberHelp": "TCP портът на който HTTP сървърът на Джелифин трябва да се закачи.",
"LabelLogs": "Журнали:",
"LabelManufacturer": "Производител",
"LabelManufacturer": "Производител:",
"LabelManufacturerUrl": "Адрес на производителя",
"LabelMaxBackdropsPerItem": "Максимален брой фонове на медия:",
"LabelMaxParentalRating": "Максимално допустима родителска оценка:",
"LabelMaxResumePercentage": "Макс процент за продължение:",
"LabelMaxResumePercentage": "Максимален процент за продължение:",
"LabelMaxScreenshotsPerItem": "Максимален брой снимки на екрана на медия:",
"LabelMaxStreamingBitrate": "Максимално качество на излъчване:",
"LabelMetadata": "Метаданни:",
@ -369,9 +369,9 @@
"LabelMetadataPath": "Път към метаданните:",
"LabelMetadataPathHelp": "Задайте място по избор за свалени картини и метаданни.",
"LabelMinBackdropDownloadWidth": "Минимална широчина на сваления фон:",
"LabelMinResumeDuration": "Мин време за продължение (секунди):",
"LabelMinResumePercentage": "Мин процент за продължение:",
"LabelMinResumePercentageHelp": "Заглавията се считат за непускани ако бъдат спрени преди това време.",
"LabelMinResumeDuration": "Минимално време за продължение:",
"LabelMinResumePercentage": "Минимален процент за продължение:",
"LabelMinResumePercentageHelp": "Заглавията се считат ,че не са възпроизведени ако бъдат спрени преди този момент.",
"LabelMinScreenshotDownloadWidth": "Минимална широчина на свалената снимка на екрана:",
"LabelModelDescription": "Описание на модела",
"LabelModelName": "Модел",
@ -443,7 +443,7 @@
"LabelTimeLimitHours": "Времево ограничение (часове):",
"LabelTitle": "Заглавие:",
"LabelTranscodingAudioCodec": "Звуков кодек:",
"LabelTranscodingTempPathHelp": "Тази папка съдържа работни файлове използвани от транскодера. Задайте място по избор или оставете празно за мястото по подразбиране.",
"LabelTranscodingTempPathHelp": "Посочете персонализиран път за файлове,които е необходимо да бъдат транскодирани и доставени на клиентите. Оставете празно ,за да се използва мястото по подразбиране.",
"LabelTranscodingVideoCodec": "Видеокодек:",
"LabelTriggerType": "Тип на спусъка:",
"LabelType": "Вид:",
@ -512,7 +512,7 @@
"News": "Новини",
"NoNextUpItemsMessage": "Нищо не е намерено. Започнете да гледате вашите предавания!",
"NoSubtitleSearchResultsFound": "Няма намерени резултати.",
"NoSubtitles": "Без субтитри",
"NoSubtitles": "Без",
"None": "Нищо",
"Normal": "Нормален",
"NumLocationsValue": "{0} папки",
@ -583,7 +583,7 @@
"OptionMissingEpisode": "Липсващи епизоди",
"OptionMonday": "Понеделник",
"OptionNameSort": "Име",
"OptionNew": "Нов...",
"OptionNew": "Нов",
"OptionNone": "Нищо",
"OptionOnAppStartup": "Като се стартира приложението",
"OptionOnInterval": "През интервал",
@ -640,7 +640,7 @@
"Refresh": "Опресняване",
"RefreshMetadata": "Опресняване на метаданните",
"ReleaseDate": "Дата на издаване",
"RememberMe": "Запомняне на данните",
"RememberMe": "Запомни ме",
"RemoveFromCollection": "Премахване от колекцията",
"RemoveFromPlaylist": "Премахване от списъка",
"Repeat": "Повтаряне",
@ -711,7 +711,7 @@
"TabMusicVideos": "Музикални клипове",
"TabMyPlugins": "Моите приставки",
"TabNetworks": "Мрежи",
"TabNfoSettings": "Формат за метаданни .nfo",
"TabNfoSettings": "Формат за метаданни NFO",
"TabNotifications": "Известия",
"TabOther": "Други",
"TabParentalControl": "Родителски контрол",
@ -724,7 +724,7 @@
"TabProfiles": "Профили",
"TabRecordings": "Записи",
"TabResponses": "Отговори",
"TabResumeSettings": "Настройки за продължаване",
"TabResumeSettings": "Възобнови",
"TabScheduledTasks": "Планирани задачи",
"TabSeries": "Сериали",
"TabServer": "Сървър",
@ -847,7 +847,7 @@
"BrowsePluginCatalogMessage": "За да видите наличните добавки, прегледайте каталога с добавките.",
"Box": "Кутия",
"AlwaysPlaySubtitlesHelp": "Поднадписите, съвпадащи с езика от настройките, ще се зареждат, независимо от езика на аудио то.",
"BookLibraryHelp": "Поддържат се звукови и текстови книги. Преглед на инструкция за наименоване {1} на книга {0}.",
"BookLibraryHelp": "Поддържат се аудио книги такива съдържащи текст. Проверете ръководството за наименуване {1} на книги {0}.",
"Blacklist": "Списък с блокирани",
"BirthLocation": "Месторождение",
"Banner": "Банер",
@ -856,7 +856,7 @@
"Ascending": "Възходящо",
"AsManyAsPossible": "Колкото е възможно повече",
"Artist": "Артист",
"AroundTime": "Към {0}",
"AroundTime": "Около {0}",
"Anytime": "По всяко време",
"AnyLanguage": "Който и да е език",
"AlwaysPlaySubtitles": "Постоянно изпълнение",
@ -879,9 +879,9 @@
"BoxSet": "Комплект",
"AuthProviderHelp": "Избор на доставчик на услуга за Автентификация, която ще се използва за автентификация на потребителската парола.",
"AllowedRemoteAddressesHelp": "Списък с IP адреси или IP/маска записи, разделени със запетая, които ще имат отдалечен достъп. Ако полето не е попълнено всички адреси ще имат отдалечен достъп.",
"BurnSubtitlesHelp": "Определя дали сървърът трябва да записва субтитри във видеоклиповете припрекодиране. Избягването на това значително ще подобри производителността. Изберете Auto, за да запишете формати, базирани на изображения (VOBSUB, PGS, SUB, IDX) и някои ASS или SSA субтитри.",
"BurnSubtitlesHelp": "Определя дали сървърът трябва да записва субтитри във видеофайлове при прекодиране. Избягването на това значително ще подобри производителността. Изберете Auto, за да запишете формати, базирани на изображения (VOBSUB, PGS, SUB, IDX) и някои ASS или SSA субтитри.",
"AllowFfmpegThrottlingHelp": "Когато прекодирането или запазването на видео стигне достатъчно далеч напред от текущата позиция за възпроизвеждане, поставете на пауза процеса, така ще се изразходват по-малко ресурси. Това е най-полезно, когато се гледа, без да се търси често из видеото. Изключете това, ако имате проблеми с възпроизвеждането.",
"AllowOnTheFlySubtitleExtractionHelp": "Вградените субтитри могат да бъдат извлечени от видеоклиповете и прехвърлени към клиентите като обикновен текст, за да се предотврати транскодирането на видеото. В някои системи това може да отнеме много време и да спре възпроизвеждането на видео по време на процеса на извличане. Деактивирайте това, за да има вградени субтитри, записани с видео кодиране, когато те не се поддържат от клиентското устройство.",
"AllowOnTheFlySubtitleExtractionHelp": "Вградените субтитри могат да бъдат извлечени от видеофайловете и прехвърлени към клиентите като обикновен текст, за да се предотврати транскодирането на видеото. В някои системи това може да отнеме много време и да спре възпроизвеждането на видео по време на процеса на извличане. Деактивирайте това, за да има вградени субтитри, записани с видео кодиране, когато те не се поддържат от клиентското устройство.",
"CinemaModeConfigurationHelp": "Режимът на кино носи театрално изживяване направо във вашата всекидневна с възможност за пускане на трейлъри и персонализирани интродукции преди основния филм.",
"ChangingMetadataImageSettingsNewContent": "Промените в настройките за изтегляне на метаданни или изображения ще се прилагат само за ново съдържание, добавено към вашата библиотека. За да приложите промените към съществуващите заглавия, ще трябва да обновите метаданните им ръчно.",
"DefaultMetadataLangaugeDescription": "Това са настройки по подразбиране и могат да се променят на база библиотека.",
@ -903,5 +903,653 @@
"DeleteImageConfirmation": "Сигурнили сте че искате да премахнете това Изображение?",
"DeleteImage": "Премахване на Исображение",
"ButtonTogglePlaylist": "Списък с изпълнения",
"ButtonToggleContextMenu": "Повече"
"ButtonToggleContextMenu": "Повече",
"ErrorSavingTvProvider": "Има проблем със запазването на ТВ доставчика.Убедете се ,че е достъпен и опитайте отново.",
"ErrorPleaseSelectLineup": "Моля изберете списък и опитайте отново.Ако няма налични списъци се убедете ,че името,паролата и пощенския код са точни.",
"ErrorMessageStartHourGreaterThanEnd": "Времето за край трябва да бъде по-голямо от началното време.",
"ErrorGettingTvLineups": "Има проблем при опита да бъдат свалени списъци с ТВ.Убедете се ,че информацията е правилна и опитайте отново.",
"ErrorDeletingItem": "Има проблем при опита да бъде изтрит файла от сървъра.Убедете се ,че сървъра има право да трие папки и опитайте отново.",
"ErrorAddingXmlTvFile": "Има проблем при достъпа на XMLTV файла.Уверете се ,че е наличен и пробвайте отново.",
"ErrorAddingTunerDevice": "Възникна проблем при добавянето на тунера. Уверете се ,че устройството е достъпно и пробвайте отново.",
"ErrorAddingMediaPathToVirtualFolder": "Има проблем при добавянето на пътя.Моля уверете се ,че е валиден и сървъра има достъп до папката.",
"Episode": "Епизод",
"EnableDetailsBannerHelp": "Покажи картинка с банер в горната част на страницата с детайли.",
"EnableDetailsBanner": "Банер с подробности",
"EnableThemeVideosHelp": "Пускай тематични видеа на заден план ,докато се разглежда библиотеката.",
"EnableThemeVideos": "Тематични видеа",
"EnableThemeSongsHelp": "Пускай тематична музика ,докато се разглежда библиотеката.",
"EnableStreamLoopingHelp": "Включи това ,ако поточното видео се предава на кратки интервали и е необходимо да се изпращат заявки постоянно.Включването на тази опция без нужда може да породи проблеми.",
"EnableStreamLooping": "Автоматично повторение на поточни видеа",
"EnablePhotosHelp": "Снимките ще бъдат разпознати и показани заедно с другите медийни файлове.",
"EnablePhotos": "Покажи снимки",
"EnableNextVideoInfoOverlayHelp": "В края на видеото покажи информация за следващия файл от текущия списък.",
"EnableNextVideoInfoOverlay": "Покажи информация за следващото видео по време на възпроизвеждане",
"EnableHardwareEncoding": "Включи хардуерно енкодиране",
"EnableExternalVideoPlayersHelp": "Меню от външен плеър ще бъде показано при стартиране на възпроизвеждането.",
"EnableDisplayMirroring": "Дублиране на дисплей",
"EnableColorCodedBackgrounds": "Цветни \"декори\"",
"EnableBackdropsHelp": "Показвай \"декори\" на заден план за някои страници ,докато се разглежда библиотеката.",
"DrmChannelsNotImported": "Канали със DRM няма да бъдат импортирани.",
"Down": "Долу",
"DoNotRecord": "Не записвай",
"DisplayModeHelp": "Изберете какво оформление желаете за интерфейса.",
"DisplayMissingEpisodesWithinSeasonsHelp": "Това трябва да бъде включено и за ТВ библиотеки в настройките на сървъра.",
"DisplayMissingEpisodesWithinSeasons": "Показвай липсващите епизоди от сезона",
"DisplayInOtherHomeScreenSections": "Покажи на главната страница (като \"последно добавени\" и \"продължи да гледаш\")",
"DisplayInMyMedia": "Покажи на главната страница",
"Disconnect": "Прекъсване",
"Disabled": "Изключено",
"DirectStreaming": "Директно възпроизвеждане",
"DirectStreamHelp2": "Директното възпроизвеждане на файла използва минимална процесорна мощност без загуба на качество.",
"DirectStreamHelp1": "Файлът е съвместим с устройството по отношение на резолюция и тип (H.264, AC3 и т.н.), но контейнера е несъвместим (mkv, avi, wmv, т.н.).Файлът ще бъде \"препакетиран\" преди да се възпроизведе от устройството.",
"DirectPlaying": "Директен метод на възпроизвеждане",
"DetectingDevices": "Откриване на устройства",
"Descending": "Намаляващ",
"Depressed": "Понижен",
"DeleteDeviceConfirmation": "Сигурни ли сте ,че искате да изтриете устройството? Ще се появи отново ,когато потребителят се впише с него.",
"DeinterlaceMethodHelp": "Избери типа деинтерлейсинг ,когато е необходимо транскодиране на подобно съдържание.",
"DefaultSubtitlesHelp": "Субтитрите са заредени според настройките зададени в метадатата на видеофайла.Когато има повече от едни субтитри се зарежда първо зададените в настройките.",
"HeaderFavoriteVideos": "Любими видеа",
"HeaderFavoritePeople": "Любими хора",
"HeaderFavoriteMovies": "Любими филми",
"HeaderFavoriteBooks": "Любими книги",
"HeaderExternalIds": "Външни идентификатори:",
"HeaderEpisodes": "Епизоди",
"HeaderEnabledFieldsHelp": "Махни отметката ,за да го заключиш и да предотвратиш неговата промяна.",
"HeaderDVR": "DVR (Цифрово записващо устройство)",
"HeaderDirectPlayProfileHelp": "Добави профили за директно възпроизвеждане ,за да се укаже кои формати може да възпроизвежда устройството.",
"HeaderDeleteTaskTrigger": "Изтрий \"пусковият механизъм\" за задачи",
"HeaderDeleteProvider": "Изтрий доставчик",
"HeaderDeleteItems": "Изтрий елемент",
"HeaderDeleteItem": "Изтрий елемент",
"HeaderDeleteDevice": "Изтрий устройство",
"HeaderDefaultRecordingSettings": "Стандартни настройки за запис",
"HeaderDateIssued": "Дата на издаване",
"HeaderContainerProfileHelp": "Профилите на различните контейнери показват ограниченията на устройството ,когато се възпроизвеждат определени формати.Ако ограничението е валидно - медията ще бъде транскодирана дори дадения формат да е конфигуриран за директно възпроизвеждане.",
"HeaderConnectionFailure": "Проблем при свързване",
"HeaderConnectToServer": "Свържи се със сървър",
"HeaderConfirmRevokeApiKey": "Отмяна на API ключа",
"HeaderConfirmProfileDeletion": "Потвърди изтриването на профила",
"HeaderConfirmPluginInstallation": "Потвърди инсталирането на плъгина",
"HeaderConfigureRemoteAccess": "Настройка на отдалечения достъп",
"HeaderCodecProfileHelp": "Профилите на различните кодеци показват ограниченията на устройството при тяхното възпроизвеждане.Ако ограничението е валидно - медията ще бъде транскодирана дори дадения кодек да е конфигуриран за директно възпроизвеждане.",
"HeaderChapterImages": "Картинки към раздела",
"HeaderChannelAccess": "Достъп до канали",
"HeaderCancelSeries": "Отмени сериал",
"HeaderCancelRecording": "Отмени запис",
"HeaderBranding": "Оформление",
"HeaderBlockItemsWithNoRating": "Забрани достъпа на елементи без информация за рейтинг:",
"HeaderAudioBooks": "Аудио книги",
"HeaderAppearsOn": "Фигурира в",
"ApiKeysCaption": "Списък с работещите в момента API ключове",
"HeaderApiKeysHelp": "Външните програми се налага да имат API ключ ,за да комуникират правилно със сървъра.Такива се издават при вписването в сървъра или чрез ръчно предоставяне.",
"HeaderAllowMediaDeletionFrom": "Позволи изтриването на медия от",
"HeaderAlert": "Предупреждение",
"HeaderAccessScheduleHelp": "Създай разписание за достъп ,за да го ограничиш до определени часове.",
"HeaderAccessSchedule": "Разписание за достъп",
"HardwareAccelerationWarning": "Включването на хардуерното ускорение може да предизвика нестабилност в някои случаи.Уверете се ,че операционната ви система и видео драйверите са актуални.Ако имате проблеми с възпроизвеждането при включена настройка е необходимо да я изключите.",
"EncoderPresetHelp": "Избери по-бърза стойност ,за да се подобри производителността или по-бавна стойност ,за да се подобри качеството.",
"H264CrfHelp": "Факторът \"постоянна скорост на предаване\" (CRF) е настройката по подразбиране на x264 енкодера.Може да определите стойност между 0 и 51, където най-ниската стойност ще даде по-добро качество (като се вземе предвид,че крайния размер на файла ще се увеличи).Препоръчват се стойности между 18 и 28.Стойността по подразбиране за x264 е 23 ,така че може да я имате за някакъв ориентир.",
"GuideProviderSelectListings": "Избери списъци",
"GroupBySeries": "Групирай по сериали",
"General": "Общи",
"FileReadError": "Има проблем при четенето на файла.",
"FileReadCancelled": "Четенето на файла е преустановено.",
"FileNotFound": "Файлът не е намерен.",
"FetchingData": "Извличане на допълнителни данни",
"Features": "Характеристики",
"FFmpegSavePathNotFound": "Не е намерен FFmpeg в зададения път.FFprobe е също задължителен и трябва да се намира в същата папка.Двете програми обикновено вървят заедно ,когато ги теглите.Уверете се ,че пътя е правилен и опитайте отново.",
"ExtractChapterImagesHelp": "Извличането на снимки от раздел ще позволи на устройствата да възпроизвеждат графични кадри при избора на меню.Този процес може бавен,ресурсно натоварващ и може да е необходимо да се заемат няколко гигабайта дисково пространство.Процесът се извършва при откриването на нови файлове или може да бъде зададен , като планирана задача при слаба активност на сървъра.Това може да бъде конфигурирано в раздела с планирани задачи.Не е препоръчително да се задава изпълнението на такава задача в натоварените часове на сървъра.",
"HttpsRequiresCert": "За да разрешите \"сигурни връзки\" е необходимо да притежавате доверен издател на сертификати ,като Let's Encrypt.Моля въведете ,ако притежавате такъв или изключете \"сигурни връзки\".",
"HeaderXmlSettings": "Настройки на XML",
"HeaderXmlDocumentAttributes": "Атрибути на XML документа",
"HeaderXmlDocumentAttribute": "Атрибут на XML документа",
"HeaderUpcomingOnTV": "Скоро по ТВ",
"HeaderTypeText": "Въведи текст",
"HeaderTypeImageFetchers": "{0} Извличане на картини",
"HeaderTuners": "Тунери",
"HeaderTranscodingProfileHelp": "Добави профили за транскодиране ,за да се види кои формати ще се използват ,когато е необходимо транскодиране.",
"HeaderThisUserIsCurrentlyDisabled": "Този потребител в момента е блокиран",
"HeaderSyncPlayEnabled": "Включване на \"синхронизирано възпроизвеждане\"",
"HeaderSyncPlaySelectGroup": "Присъединяване към група",
"HeaderSubtitleProfilesHelp": "Профилите за субтитри показват форматите поддържани от устройството.",
"HeaderSubtitleProfiles": "Профили за субтитри",
"HeaderSubtitleProfile": "Профил за субтитри",
"HeaderSubtitleDownloads": "Сваляне на субтитри",
"HeaderStopRecording": "Спри запис",
"HeaderSpecialEpisodeInfo": "Информация за специалните епизоди",
"HeaderShutdown": "Изключване",
"HeaderServerAddressSettings": "Настройки за адреса на сървъра",
"HeaderSeriesStatus": "Състояние на сериала",
"HeaderSeriesOptions": "Настройки на сериала",
"HeaderSelectTranscodingPathHelp": "Търси или въведи ръчно пътя ,където искаш да се съхраняват временните файлове при транскодиране.Папката трябва да има права за запис.",
"HeaderSelectTranscodingPath": "Избери папка за временното транскодиране на файлове",
"HeaderSelectServerCachePathHelp": "Търси или въведи ръчно пътя до временните файлове на сървъра.Папката трябва да има права за запис.",
"HeaderSelectServerCachePath": "Избери папка за временните файлове на сървъра",
"HeaderSelectServer": "Избери сървър",
"HeaderSelectMetadataPathHelp": "Търси или въведи ръчно пътя ,където искаш да се съхраняват метаданните.Папката трябва да има права за запис.",
"HeaderSelectMetadataPath": "Избери папка със метаданни",
"HeaderSelectCertificatePath": "Избери папка със сертификат",
"HeaderRestartingServer": "Рестартиране на сървъра",
"HeaderResponseProfileHelp": "Профилите за комуникация дават възможност да се настрои типът информация ,която се изпраща към устройството при възпроизвеждането на определени типове медия.",
"HeaderRemoveMediaLocation": "Премахни папката с медия",
"HeaderRemoteAccessSettings": "Настройки за отдалечен достъп",
"HeaderRecordingPostProcessing": "Последваща обработка на записи",
"HeaderRecordingOptions": "Настройки за запис",
"HeaderPluginInstallation": "Инсталиране на добавка",
"HeaderPlaybackError": "Грешка при възпроизвеждане",
"HeaderPlayback": "Възпроизвеждане на медия",
"HeaderPinCodeReset": "Зануляване на пин код",
"HeaderPhotoAlbums": "Фото албум",
"HeaderPasswordReset": "Зануляване на парола",
"HeaderOtherItems": "Други елементи",
"HeaderNextVideoPlayingInValue": "Следващото видео ще се възпроизведе след {0}",
"HeaderNavigation": "Навигация",
"HeaderLiveTvTunerSetup": "Настройка на тв тунера",
"HeaderKodiMetadataHelp": "За да включиш или изключиш използването на NFO метаданните влез в настройките за библиотеки на сървъра и намери секцията за съхранение.",
"HeaderKeepSeries": "Запази сериалите",
"HeaderKeepRecording": "Запази записите",
"HeaderItems": "Елементи",
"HeaderImageOptions": "Настройки на картини",
"HeaderIdentifyItemHelp": "Въведете един или повече критерии за търсене.Премахнете ,ако искате да увеличите резултатите при търсенето.",
"HeaderIdentificationHeader": "Идентификационен хедър",
"HeaderIdentificationCriteriaHelp": "Въведете пони един критерии.",
"HeaderHttpsSettings": "HTTPS настройки",
"HeaderHttpHeaders": "HTTP Хедъри",
"HeaderHome": "Главна",
"HeaderFetcherSettings": "Настройки на програмата за изтегляне",
"HeaderFavoritePlaylists": "Любими списъци",
"LabelDeathDate": "Дата на смърт:",
"LabelDateAddedBehaviorHelp": "Ако е взета стойност от метаданните, тя винаги ще бъде използвана преди някоя от тези опции.",
"LabelDateAddedBehavior": "за ново съдържание се приема дата на добавяне:",
"LabelCustomDeviceDisplayNameHelp": "Добави ръчно име ,с което ще се показва устройството или остави това ,което е по подразбиране.",
"LabelCorruptedFrames": "Повредени кадри:",
"LabelCancelled": "Отменено",
"LabelCache": "Временни файлове:",
"LabelBurnSubtitles": "Вграждане на субтитри:",
"LabelBlockContentWithTags": "Блокирай елементи с етикети:",
"LabelBlastMessageIntervalHelp": "Определя продължителността в секунди при \"бомбардирането\" с активни съобщения.",
"LabelBlastMessageInterval": "Интервал на активните съобщения (в секунди)",
"LabelBitrate": "Битрейт:",
"LabelBirthYear": "Година на раждане:",
"LabelBirthDate": "Дата на раждане:",
"LabelBindToLocalNetworkAddressHelp": "Не задължително.Замени локалния IP адрес за \"закачане\" към http сървъра.Ако полето е празно сървъра ще \"закачи\" всички налични адреси.Промяната на тази стойност изисква рестарт на сървъра.",
"LabelBindToLocalNetworkAddress": "\"Закачи\" към локален мрежов адрес:",
"LabelAutomaticallyRefreshInternetMetadataEvery": "Автоматично обновявай метаданните от Интернет:",
"LabelAuthProvider": "Доставчик за идентификация:",
"LabelAudioSampleRate": "Честотна дискретизация на аудиото:",
"LabelAudioCodec": "Аудио кодек:",
"LabelAudioChannels": "Аудио канали:",
"LabelAudioBitrate": "Скорост на предаване на аудиото:",
"LabelAudioBitDepth": "Битова дълбочина на аудиото:",
"LabelAudio": "Аудио",
"LabelAppNameExample": "Примерно: Sickbeard, Sonarr",
"LabelAllowedRemoteAddressesMode": "Режим на филтъра за външни ИП адреси:",
"LabelAllowedRemoteAddresses": "Филтър за външни ИП адреси:",
"LabelAllowHWTranscoding": "Разреши хардуерно транскодиране",
"LabelAll": "Всички",
"LabelAlbumArtMaxWidthHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.",
"LabelAlbumArtMaxHeightHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.",
"LabelAlbumArtMaxHeight": "Максимална височина на обложките за албуми:",
"LabelAlbumArtHelp": "PN се използва за обложки на албуми dlna:profileID attribute on upnp:albumArtURI.Някои устройства се нуждаят от определена стойност ,независимо от размера на картината.",
"LabelAirsBeforeSeason": "Ще бъде излъчен преди сезон:",
"LabelAirsBeforeEpisode": "Ще бъде излъчен преди епизод:",
"LabelAirsAfterSeason": "Ще бъде излъчен след сезон:",
"LabelAccessStart": "Начално време:",
"LabelAccessEnd": "Време на приключване:",
"LabelAccessDay": "Ден от седмицата:",
"LabelAbortedByServerShutdown": "(Прекъснато поради изключването на сървъра)",
"Kids": "Деца",
"Items": "Елементи",
"ItemCount": "{0} Елементи",
"ImportFavoriteChannelsHelp": "Ако е включено само канали ,които са отбелязани ,като любими на устройството ще бъдат добавени.",
"LabelFriendlyName": "Лесно име:",
"LabelFormat": "Формат:",
"LabelFolder": "Папка:",
"LabelFileOrUrl": "Файл или URL:",
"LabelExtractChaptersDuringLibraryScanHelp": "Генерирай изображения от различните раздели при импортиране на видеофайлове (по време на сканиране на библиотеката).В противен случай те ще бъдат извлечени при планиране на такава задача,което ще позволи сканирането на библиотеката да се извърши по-бързо.",
"LabelExtractChaptersDuringLibraryScan": "Извлечи изображения от разделите при сканиране на библиотеката",
"LabelBaseUrlHelp": "Добавя потребителска папка към адреса на съръвра.Например: <code>http://example.com/<b>&lt;baseurl&gt;</b></code>",
"LabelBaseUrl": "Основен URL:",
"LabelEndDate": "Крайна дата:",
"LabelEnableSingleImageInDidlLimitHelp": "Някои устройства няма да го покажат правилно ,ако множество изображения са вградени в Didl.",
"LabelEnableSingleImageInDidlLimit": "Ограничи до едно вградено изображение",
"LabelEnableHttpsHelp": "Позволява на сървъра да \"слуша\" на предварително зададен HTTPS порт.Необходимо е да има настроен валиден сертификат ,за да работи правилно настройката.",
"LabelEnableHttps": "Включи HTTPS",
"LabelEnableHardwareDecodingFor": "Включи хардуерно декодиране за:",
"LabelEnableDlnaDebugLoggingHelp": "Създава големи журнали файлове и е редно да се използва само с цел отстраняване на проблеми.",
"LabelEnableBlastAliveMessagesHelp": "Включи ,ако сървъра не се открива правилно от други UPnP устройства в мрежата.",
"LabelEnableBlastAliveMessages": "\"Бомбардира\" активните съобщения",
"LabelEnableAutomaticPortMapHelp": "Автоматично пренасочва външните портове на рутера към локалните портове на сървъра чрез UPnP.Тази опция може да на работи при някои модели рутери или мрежови конфигурации.Промените влизат в сила само след рестарт на сървъра.",
"LabelEmbedAlbumArtDidlHelp": "Някои устройства предпочитат този метод за извличане на обложки за албуми.При други може да има проблем при възпроизвеждането ,ако опцията е включена.",
"LabelEasyPinCode": "Лесен пин код:",
"LabelDynamicExternalId": "{0} Ид:",
"LabelDroppedFrames": "Пропуснати кадри:",
"LabelDiscNumber": "Диск номер:",
"LabelDidlMode": "DIDL режим:",
"LabelDeinterlaceMethod": "Метод на деинтерлейсинг:",
"LabelDefaultUserHelp": "Определя коя потребителска библиотека ще се показва на свързаните устройства.Това може да бъде отменено за всяко отделно устройство чрез използване на профилите.",
"LabelDefaultUser": "Потребител по подразбиране:",
"LabelDefaultScreen": "Дисплей по подразбиране:",
"LabelPostProcessor": "Приложение за последваща обработка:",
"LabelPleaseRestart": "Промените ще влязат в сила след ръчно презареждане на уеб клиента.",
"LabelPlayMethod": "Метод на възпроизвеждане:",
"LabelPlayerDimensions": "Размери на плеъра:",
"LabelPlayer": "Плеър:",
"LabelPersonRoleHelp": "Пример: Шофьор на камион за сладолед",
"LabelPasswordRecoveryPinCode": "Пин код:",
"LabelPasswordResetProvider": "Услуга за нулиране на парола:",
"LabelParentNumber": "Родителски номер:",
"LabelOptionalNetworkPathHelp": "Ако папката е споделена във вашата мрежа,предоставянето на споделения път ще позволи приложения инсталирани на други устройства да имат достъп до медията директно.Например, {0} or {1}.",
"LabelNumber": "Номер:",
"LabelNotificationEnabled": "Включване на известие",
"LabelNewsCategories": "Категории новини:",
"LabelNightly": "Тестов",
"LabelStable": "Стабилна",
"LabelChromecastVersion": "Версия на Chromecast",
"LabelMusicStreamingTranscodingBitrateHelp": "Посочете максимален битрейт при поточно предаване на музика.",
"LabelMusicStreamingTranscodingBitrate": "Битрейт при транскодиране на музика:",
"LabelMoviePrefixHelp": "Ако има добавен префикс към филмовите заглавия го въведете тук ,за да може сървъра да го обработи правилно.",
"LabelMoviePrefix": "Префикс на филма:",
"LabelMovieCategories": "Филмови категории:",
"LabelMonitorUsers": "Следете активността от:",
"LabelMinResumeDurationHelp": "Най-краткото време в секунди позволяващо ви да запазите текущата позиция на възпроизвеждане и впоследствие да я възобновите.",
"LabelMethod": "Метод:",
"LabelMetadataSaversHelp": "Изберете форматите на файловете, в които да запишете метаданните си.",
"LabelMetadataSavers": "Хранилища на метаданни:",
"LabelMetadataReadersHelp": "Подреди по важност любимите си източници на локални метаданни.Първият намерен файл ще бъде прочетен.",
"LabelMetadataReaders": "Четци на метаданни:",
"LabelMetadataDownloadersHelp": "Включи и подреди по важност любимите си програми за изтегляне на метаданни.Тези с по-нисък ранг ще се използват само за попълване на липсваща информация.",
"LabelMessageTitle": "Заглавие на съобщението:",
"LabelMessageText": "Текст на съобщението:",
"LabelMaxStreamingBitrateHelp": "Определете максималния битрейт при излъчване.",
"LabelMaxResumePercentageHelp": "Приема се ,че файловете се възпроизведени до края ,ако се спре след този момент.",
"LabelMaxChromecastBitrate": "Качество на излъчване чрез Chromecast:",
"LabelMatchType": "Тип съвпадение:",
"LabelLoginDisclaimerHelp": "Съобщението ще се показва в долната част на страницата за вход.",
"LabelLoginDisclaimer": "Предупреждение при вход:",
"LabelLockItemToPreventChanges": "Заключи дадения елемент ,за да забраниш бъдещи промени",
"LabelLineup": "Редица:",
"LabelLibraryPageSizeHelp": "Настройва броя елементи ,които ще се показват в страницата на библиотеката.Въведете 0 ,за да забраните номерирането.",
"LabelLibraryPageSize": "Размер на страницата на библиотеката:",
"LabelKodiMetadataUser": "Запази данните за активността на потребителя в файл тип NFO за:",
"LabelKodiMetadataEnablePathSubstitutionHelp": "Активирай подмяната на пътя за изображения ,като се използват настройките зададени от сървъра.",
"LabelKodiMetadataEnablePathSubstitution": "Активирай подмяната на пътя",
"LabelKodiMetadataEnableExtraThumbsHelp": "Когато се свалят изображения те могат да бъдат запазени и в двете полета extrathumbs и extrafanart за по-добра съвместимост с облиците на Коди.",
"LabelKodiMetadataEnableExtraThumbs": "Копирай extrafanart в полето extrathumbs",
"LabelKodiMetadataDateFormatHelp": "Всички дати в NFO файловете ще бъдат анализирани в този формат.",
"LabelKidsCategories": "Детски категории:",
"LabelKeepUpTo": "Пази до:",
"LabelInNetworkSignInWithEasyPasswordHelp": "Използвай лесен пин код за вписване от потребителите в домашната мрежа.Основната ви парола ще се използва ,само когато сте вписвате отдалечено.Ако полето за пин код е празно няма да имате нужда да се вписвате с парола в домашната мрежа.",
"LabelInNetworkSignInWithEasyPassword": "Включи вписване в мрежата с лесен пин код",
"LabelImportOnlyFavoriteChannels": "Ограничи до канали обозначени ,като любими",
"LabelImageFetchersHelp": "Включете и подредете любимите ви услуги за извличане на изображения по важност.",
"LabelIconMaxWidthHelp": "Максимална резолюция на иконите показани чрез upnp:icon.",
"LabelIconMaxWidth": "Максимална ширина на икона:",
"LabelIconMaxHeightHelp": "Максимална резолюция на иконите показани чрез upnp:icon.",
"LabelIconMaxHeight": "Максимална височина на икона:",
"LabelHardwareAccelerationTypeHelp": "Хардуерното ускорение се нуждае от допълнителни настройки.",
"LabelEncoderPreset": "Шаблон за енкодиране чрез H264 и H265 :",
"LabelH264Crf": "Значение на CRF H264-кодиране:",
"LabelGroupMoviesIntoCollectionsHelp": "Когато се показват списъци с филми, филмите, принадлежащи към колекция, ще бъдат показани като един общ елемент.",
"LabelServerNameHelp": "Това е името ,което ще се ползва ,за да се разпознава сървъра и по подразбиране съвпада с името на компютъра.",
"LabelRefreshMode": "Режим на опресняване:",
"LabelRecordingPathHelp": "Посочете местоположението по подразбиране, за запазване на записи.Ако полето е празно ще се използва папката на сървъра.",
"LabelRecord": "Запис:",
"LabelReasonForTranscoding": "Причина за транскодиране:",
"LabelProtocolInfoHelp": "Стойността, която ще бъде използвана при отговор към GetProtocolInfo заявките от устройството.",
"LabelProtocolInfo": "Информация за протокола:",
"LabelPostProcessorArgumentsHelp": "Използвай {path},като път за записване на файла.",
"LabelPostProcessorArguments": "Аргументи на командния ред след обработка:",
"EnableBlurhashHelp": "Изображенията, които все още се зареждат, ще се показват чрез функцията\"размито запълване\"",
"EnableBlurhash": "Активиране на функцията \"размито запълване\" за изображения",
"UnsupportedPlayback": "Джелифин не може да дешифрира съдържание, защитено с DRM, но въпреки това цялото съдържание ще бъде обработено, включително защитените заглавия. Някои файлове могат да изглеждат напълно черни поради криптиране или други неподдържани функции, например интерактивни заглавия.",
"OnApplicationStartup": "При стартиране на приложението",
"EveryXHours": "На всеки {0} часа",
"EveryHour": "Всеки час",
"EveryXMinutes": "Всеки {0} минути",
"OnWakeFromSleep": "При \"събуждане от сън\"",
"WeeklyAt": "{0}сек в {1}",
"DailyAt": "Ежедневно в {0}",
"LastSeen": "Последно видян {0}",
"PersonRole": "като {0}",
"ListPaging": "{0}-{1} от {2}",
"WriteAccessRequired": "Джелифин сървъра изисква достъп с права за запис до тази папка. Моля, осигурете достъп с права за запис и опитайте отново.",
"PathNotFound": "Пътят не можа да бъде намерен. Моля, уверете се, че пътят е валиден и опитайте отново.",
"Yesterday": "Вчера",
"YadifBob": "YADIF х2",
"Yadif": "YADIF (Yet Another DeInterlacing Filter)",
"XmlTvSportsCategoriesHelp": "Програмите от тези категории ще се показват като спортни програми.За разделяне на множество използвайте '|'.",
"XmlTvPathHelp": "Път до XMLTV файла.Джелифин ще чете от този файл и периодично ще го проверява за актуализации.Ваша грижа е да го създадете и редовно да го обновявате.",
"XmlTvKidsCategoriesHelp": "Програмите с тези категории ще се показват като програми за деца.За разделяне на множество използвайте '|'.",
"XmlTvNewsCategoriesHelp": "Програмите от тези категории ще се показват като новинарски програми.За разделяне на множество използвайте '|'.",
"XmlTvMovieCategoriesHelp": "Програмите от тези категории ще се показват като филми.За разделяне на множество използвайте '|'.",
"XmlDocumentAttributeListHelp": "Тези атрибути се прилагат към коренния елемент на всеки XML отговор.",
"Whitelist": "Бял списък",
"ViewPlaybackInfo": "Вижте информация за възпроизвеждането",
"VideoRange": "Диапазон на видео",
"ValueTimeLimitSingleHour": "Времеви лимит: 1 час",
"ValueTimeLimitMultiHour": "Времеви лимит {0} часове",
"ValueContainer": "Контейнер: {0}",
"UserAgentHelp": "Предоставяне на персонализиран ползавтел-агент HTTP хедър.",
"Up": "Нагоре",
"Unrated": "Без категории",
"UninstallPluginConfirmation": "Сигурни ли сте ,че искате да премахнете {0}?",
"Uniform": "Еднороден",
"TvLibraryHelp": "Прегледайте {0}ръководството за именуване на ТВ{1}.",
"Transcoding": "Транскодиране",
"Track": "Пътека",
"TitleHostingSettings": "Настройки за хостинг",
"TitleHardwareAcceleration": "Хардуерно ускорение",
"TabNetworking": "Работа в мрежа",
"TabDVR": "ДВР",
"SystemDlnaProfilesHelp": "Системните профили са с достъп \"само за четене\".При промяна в системния профил ще бъде създаден нов персонализиран профил.",
"SyncPlayAccessHelp": "Избери нивото на достъп ,който този потребител ще има за услугата \"Синхронизирано възпроизвеждане\".С нейна помощ може да синхронизирате възпроизвеждането с други устройства.",
"SubtitleOffset": "Изместване на субтитрите",
"SubtitleDownloadersHelp": "Включи и подреди по собствени предпочитания услугите за сваляне на субтитри.",
"SubtitleAppearanceSettingsDisclaimer": "Настройките не могат да се приложат за графични субтитри (PGS, DVD, т.н.) или ASS/SSA ,защото те зареждат свои стилове.",
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Настройките важат за всеки Chromecast поток стартиран от това устройство.",
"StopRecording": "Спри запис",
"SortName": "Подреди по име",
"SortChannelsBy": "Подреди канали по:",
"SmartSubtitlesHelp": "Когато аудиото е чуждоезично ще бъдат заредени субтитри според зададените настройки за език.",
"ErrorAddingListingsToSchedulesDirect": "Възникна грешка при добавянето на списъка към вашия \"Schedules Direct\" акаунт.Услугата позволява определен брой списъци към един акаунт.Ще е необходимо да влезете в акаунта си (през сайта на услугата) и да отстраните другите списъци преди да продължите с добавянето на нови.",
"LabelIdentificationFieldHelp": "Подниз или регулярен израз с различаване на главни букви.",
"EnableFastImageFadeInHelp": "Използвай бързи анимации преходи",
"EnableFastImageFadeIn": "Бързи анимации",
"LabelScreensaver": "Скрийнсейвър:",
"LabelScheduledTaskLastRan": "Последно пускан {0}, заел {1}.",
"LabelRuntimeMinutes": "Продължителност (в минути):",
"LabelRequireHttpsHelp": "Ако е отметнато сървъра ще пренасочва автоматично всички заявка от HTTP към HTTPS.Няма никакъв ефект ,ако сървъра не \"слуша\" по HTTPS.",
"LabelRequireHttps": "Изисква HTTPS",
"LabelRemoteClientBitrateLimitHelp": "Допълнително ограничаване битрейта на поточното предаване за всички мрежови устройства.Това е необходимо ,за да не допуснете устройствата да изискват по-висок битрейт, отколкото вашата интернет връзка позволява.Това може да доведе до повишено натоварване на процесора на вашия сървър, за щото видеата ще се прекодират \"в движение\" до по-нисък битрейт.",
"LabelTranscodingThreadCount": "Количество транскодирани потоци :",
"LabelTranscodingProgress": "Прогрес на транскодирането:",
"LabelTranscodingFramerate": "Честота на кадрите при транскодиране:",
"LabelTranscodes": "Транскодирания:",
"LabelTranscodePath": "Път за транскодиране:",
"LabelTranscodingContainer": "Контейнер:",
"LabelTrackNumber": "Номер на песен:",
"LabelTextBackgroundColor": "Цвят на фона на текста:",
"LabelTagline": "Ключова фраза:",
"LabelTVHomeScreen": "Главна страница в режим ТВ:",
"LabelSyncPlayAccess": "Достъп до \"синхронизирано възпроизвеждане\"",
"LabelSyncPlayAccessNone": "Изключено за този потребител",
"LabelSyncPlayAccessJoinGroups": "Разрешаване на потребителя да се присъединява към групи",
"LabelSyncPlayAccessCreateAndJoinGroups": "Разрешаване на потребителя да създава и да се присъединява към групи",
"LabelSyncPlayLeaveGroupDescription": "Деактивирай \"синхронизирано възпроизвеждане\"",
"LabelSyncPlayLeaveGroup": "Напусни групата",
"LabelSyncPlayNewGroupDescription": "Създай нова група",
"LabelSyncPlayNewGroup": "Нова група",
"LabelSyncPlaySyncMethod": "Метод за синхронизиране:",
"LabelSyncPlayPlaybackDiff": "Разлика във времето на възпроизвеждане:",
"MillisecondsUnit": "мс",
"LabelSyncPlayTimeOffset": "Компенсиране на разликата във времето със сървъра:",
"LabelSubtitleFormatHelp": "Пример: srt",
"LabelSubtitleDownloaders": "Услуги за сваляне на субтитри:",
"LabelStreamType": "Вид на потока:",
"LabelStopping": "Спиране",
"LabelSportsCategories": "Спортни категории:",
"LabelSoundEffects": "Звукови ефекти:",
"LabelSortTitle": "Подреди по заглавие:",
"LabelSonyAggregationFlags": "\"Флагове\" за статистическа обработка на Сони:",
"LabelSkipIfGraphicalSubsPresentHelp": "Наличието на текстови версии на субтитрите ще доведе до по-ефективна доставка и намаляване на вероятността от транскодиране на видеото.",
"LabelSkipIfAudioTrackPresentHelp": "Махнете отметката ,за да се гарантира ,че всички видеофайлове имат субтитри,независимо от езика на аудиото им.",
"LabelSkin": "Облик:",
"LabelSize": "Размер:",
"LabelSimultaneousConnectionLimit": "Ограничение на броя едновременни потоци:",
"LabelServerName": "Име на сървъра:",
"LabelServerHostHelp": "192.168.1.100:8096 или https://myserver.com",
"LabelServerHost": "Хост:",
"LabelSendNotificationToUsers": "Изпрати уведомление до:",
"LabelSelectFolderGroupsHelp": "Папките ,които не са отметнати ще се показват самостоятелно с техен изглед.",
"LabelSelectFolderGroups": "Автоматично групира съдържанието от следните папки в режим \"изглед\" като Филми, Музика и ТВ:",
"MessageConfirmRemoveMediaLocation": "Сигурни ли сте ,че искате да премахнете мястото?",
"MessageConfirmRecordingCancellation": "Отмяна на записа?",
"MessageConfirmProfileDeletion": "Сигурни ли сте ,че искате да изтриете този профил?",
"MessageConfirmDeleteTunerDevice": "Сигурни ли сте ,че искате да изтриете това устройство?",
"MessageConfirmDeleteGuideProvider": "Сигурни ли се ,че искате да изтриете доставчика на справочника?",
"MessageConfirmAppExit": "Искате ли да излезете?",
"MessageAreYouSureDeleteSubtitles": "Сигурни ли се ,че искате да изтриете файла със субтитри?",
"MediaIsBeingConverted": "Медията е конвертирана във формат ,който е съвместим с устройството ,което ще я възпроизведе.",
"MediaInfoSoftware": "Софтуер",
"MediaInfoTimestamp": "Времеви отпечатък",
"MediaInfoSampleRate": "Кадрова честота",
"MediaInfoRefFrames": "Ref кадри",
"MediaInfoPixelFormat": "Пикселен формат",
"MediaInfoCodecTag": "Етикет на кодека",
"MediaInfoBitDepth": "Дълбочина на цвета",
"MediaInfoAnamorphic": "Анаморфно",
"MapChannels": "Разпредели канали",
"ManageRecording": "Управление на записа",
"ManageLibrary": "Управление на библиотеката",
"LiveBroadcasts": "Предавания на живо",
"LeaveBlankToNotSetAPassword": "Можете да оставите това поле празно и да не задавате парола.",
"LearnHowYouCanContribute": "Научете как можете да допринесете.",
"LaunchWebAppOnStartupHelp": "Отвори уеб клиента във браузъра по подразбиране при първото стартиране на сървъра.Това няма да се случи при използване на функцията на сървъра за рестартиране.",
"LaunchWebAppOnStartup": "Стартирай уеб интерфейса ,когато се стартира сървъра",
"LanNetworksHelp": "Списък разделен със запетая съдържащ ИП адреси или записи за ИП/мрежова маски отнасящи се за мрежи ,които ще се считат за локални ,когато се налагат ограничения в честотната лента.Ако е зададено всички други ИП адреси ще се считат за принадлежащи към външни мрежи и за тях ще важат правилата за ограничения на външни ИП -та.Ако полето е празно ще се счита ,че само подмрежата на сървъра е част от локалната мрежа.",
"LabelffmpegPathHelp": "Пътят към файла на приложението ffmpeg или папката, съдържаща ffmpeg.",
"LabelffmpegPath": "Път към FFmpeg:",
"LabelZipCode": "Пощенски код:",
"LabelXDlnaDoc": "Схема X-DLNA:",
"LabelXDlnaCapHelp": "Определя съдържанието на елемента X_DLNACAP във urn:schemas-dlna-org:device-1-0 в пространството от имена.",
"LabelXDlnaCap": "Свойства на X-Dlna:",
"LabelWeb": "Уеб:",
"LabelVideoResolution": "Видео резолюция:",
"LabelVideoCodec": "Видео кодек:",
"LabelVideoBitrate": "Видео битрейт:",
"DashboardArchitecture": "Архитектура: {0}",
"DashboardOperatingSystem": "Операционна система: {0}",
"DashboardServerName": "Сървър: {0}",
"DashboardVersionNumber": "Версия: {0}",
"LabelVersionInstalled": "{0} Инсталирано",
"LabelValue": "Стойност:",
"LabelVaapiDeviceHelp": "Това е възелът на визуализация, който се използва при хардуерно ускорение.",
"LabelVaapiDevice": "Устройство VA-API:",
"LabelUserRemoteClientBitrateLimitHelp": "Замени глобалната стойност по подразбиране, зададена в настройките за възпроизвеждане на сървъра.",
"LabelUserLoginAttemptsBeforeLockout": "Неуспешни опити за влизане, преди потребителският акаунт да бъде заключен:",
"LabelUserLibraryHelp": "Изберете коя библиотека да се показва на устройството.Оставете полето празно ,за да работят настройките по подразбиране.",
"LabelUserAgent": "Потребителски агент:",
"LabelUseNotificationServices": "Използвайте следните услуги:",
"LabelTypeMetadataDownloaders": "{0} услуги за сваляне на метаданни:",
"LabelTunerType": "Тип на тунера:",
"LabelTunerIpAddress": "ИП адрес на тунера:",
"LabelTranscodingThreadCountHelp": "Изберете максималния брой процесорни нишки ,които ще се използват при транскодиране.Намаляването на броя на нишките ще намали използването на процесора, но може да не преобразува достатъчно бързо за да осигури гладко възпроизвеждане.",
"MessageForgotPasswordFileCreated": "Следния файл беше създаден на вашия сървър и съдържа инструкции как да постъпите:",
"MessageFileReadError": "Възникна грешка при четенето на файла.Моля опитайте отново.",
"MessageEnablingOptionLongerScans": "Включването на тази опция значително ще удължи сканирането на библиотеките.",
"MessageDownloadQueued": "Свалянето е на опашката.",
"MessageDirectoryPickerLinuxInstruction": "За потребителите на следните Линукс дистрибуции: Arch Linux, CentOS, Debian, Fedora, openSUSE, или Ubuntu е необходимо да се предостави на сервизния потребител поне достъп с права за четене на вашите хранилища.",
"MessageDirectoryPickerBSDInstruction": "За BSD ще е нужно да настроите хранилище във вашия FreeNAS Jail ,за да може сървъра да има достъп до него.",
"MessageDeleteTaskTrigger": "Сигурни ли сте ,че искате да изтриете \"пусковият механизъм\" за задачи?",
"MessageCreateAccountAt": "Създай акаунт в {0}",
"MessageContactAdminToResetPassword": "Моля, свържете се със системния си администратор, за да нулирате паролата си.",
"MessageConfirmRevokeApiKey": "Сигурни ли сте ,че искате да оттеглите api ключа?Връзката на приложението със сървъра ще бъде прекратена незабавно.",
"MessageSyncPlayGroupDoesNotExist": "Неуспешно присъединяване към групата, защото не съществува.",
"MessageSyncPlayPlaybackPermissionRequired": "Необходимо е разрешение за възпроизвеждане.",
"MessageSyncPlayNoGroupsAvailable": "Няма налични групи.Пуснете нещо да се възпроизвежда.",
"MessageSyncPlayGroupWait": "<b>{0}</b> буферира се...",
"MessageSyncPlayUserLeft": "<b>{0}</b> е напуснал групата.",
"MessageSyncPlayUserJoined": "<b>{0}</b> се присъедини към групата.",
"MessageSyncPlayDisabled": "Услугата \"синхронизирано възпроизвеждане\" е изключена.",
"MessageSyncPlayEnabled": "Услугата \"синхронизирано възпроизвеждане\" е включена.",
"MessageUnsetContentHelp": "Съдържанието ще се показва като обикновени папки. За най-добри резултати използвайте мениджъра на метаданни, за да зададете типовете съдържание на подпапките.",
"MessageUnableToConnectToServer": "В момента не можем да се свържем с избрания сървър. Моля, уверете се, че работи и опитайте отново.",
"MessageReenableUser": "Вижте по-долу, за да активирате отново",
"MessagePluginInstallDisclaimer": "Приставките, създадени от членове на общността, са чудесен начин да подобрите изживяването с Джелифин чрез допълнителните функции и предимства.Преди да инсталирате, имайте предвид ефектите, които те могат да имат върху вашия Джелифин сървър, като по-дълго време за сканиране на библиотеки, допълнителна обработка на заден фон и намалена стабилност на системата.",
"MessagePluginConfigurationRequiresLocalAccess": "За да конфигурирате тази приставка, моля, впишете се директно в локалния си сървър.",
"MessagePleaseWait": "Моля,изчакайте. Това може да отнеме минута.",
"MessagePlayAccessRestricted": "Възпроизвеждането на това съдържание в момента е ограничено.Моля, свържете се с администратора на вашия сървър за повече информация.",
"MessagePasswordResetForUsers": "Следните потребители са занулили паролите си.Те вече могат да влязат с пин кодовете, използвани за извършване на нулирането.",
"MessageNoTrailersFound": "Не са намерени трейлъри.За да подобрите филмовото изживяване инсталирайте канал за трейлъри,може да подредите няколко канала в библиотека.",
"MessageNoServersAvailable": "Не са намерени сървъри, използващи функцията за автоматично откриване на сървър.",
"MessageNoMovieSuggestionsAvailable": "Понастоящем няма предложени филми. Започнете да гледате и оценявате филмите си, а след това се върнете, за да видите препоръките си.",
"MessageNoCollectionsAvailable": "Колекциите ви позволяват да се наслаждавате на персонализирани групи от филми, сериали и албуми. Кликнете върху бутона +, за да започнете да създавате колекции.",
"MessageLeaveEmptyToInherit": "Оставете празни, за да наследите настройки от родителски елемент или глобалната стойност по подразбиране.",
"MessageItemsAdded": "Добавени са елементи.",
"MessageItemSaved": "Елементът е запазен.",
"MessageUnauthorizedUser": "Понастоящем нямате право да получите достъп до сървъра. Моля, свържете се с администратора на вашия сървър за повече информация.",
"MessageInvalidUser": "Невалидно потребителско име или парола. Моля, опитайте отново.",
"MessageInvalidForgotPasswordPin": "Въведен е невалиден или изтекъл пин код. Моля, опитайте отново.",
"MessageInstallPluginFromApp": "Този плъгин трябва да бъде инсталиран от приложението, в което възнамерявате да го използвате.",
"MessageImageTypeNotSelected": "Изберете типът изображение от падащото меню.",
"MessageImageFileTypeAllowed": "Поддържат се само файлове с разширение JPEG и PNG.",
"MessageForgotPasswordInNetworkRequired": "Опитайте пак в домашната мрежа да повторите процеса по нулиране на паролата.",
"Smaller": "По-малък",
"SmallCaps": "Малки букви",
"SkipEpisodesAlreadyInMyLibraryHelp": "Епизодите ще се сравняват, като се използват номера на сезона и епизода, когато са налични.",
"SkipEpisodesAlreadyInMyLibrary": "Не записвай епизоди, които вече са в моята библиотека",
"SimultaneousConnectionLimitHelp": "Максималният брой разрешени едновременни потоци. Въвеждането на 0 означава ,че няма ограничение.",
"Filter": "Филтър",
"New": "Нов",
"ShowMore": "Покажи повече",
"ShowLess": "Покажи по-малко",
"ShowIndicatorsFor": "Показване на индикатори за:",
"SettingsWarning": "Промяната на тези стойности може да доведе до нестабилност или проблеми в свързаността. Ако срещнете някакви проблеми, препоръчваме ви да ги върнете по подразбиране.",
"SeriesSettings": "Настройки на сериала",
"SeriesRecordingScheduled": "Назначено е запис на сериал.",
"SeriesDisplayOrderHelp": "Подредете епизодите по дата на излъчване, излизане на DVD или абсолютна номерация.",
"SeriesCancelled": "Сериала е отменен.",
"Series": "Сериал",
"SelectAdminUsername": "Моля, изберете потребителско име за администраторския акаунт.",
"Season": "Сезон",
"SearchResults": "Резултати от търсенето",
"ScanForNewAndUpdatedFiles": "Сканирай за нови и актуализирани файлове",
"SaveSubtitlesIntoMediaFoldersHelp": "Съхраняването на субтитрите при видео файлове ще позволи по-лесното им управление.",
"SaveSubtitlesIntoMediaFolders": "Запазване на субтитрите в папките с медията",
"SaveChanges": "Запазете промените",
"RunAtStartup": "Пускай при стартиране",
"RestartPleaseWaitMessage": "Моля, изчакайте, докато сървъра се изключи и рестартира. Това може да отнеме минута или две.",
"RepeatOne": "Повтори един път",
"RepeatMode": "Режим на повторение",
"RepeatEpisodes": "Повтори епизодите",
"RepeatAll": "Повтори всички",
"ReleaseGroup": "Издаден от група",
"RefreshQueued": "Назначено е обновяване.",
"RefreshDialogHelp": "Метаданните се обновяват въз основа на настройките и интернет услугите, които са активирани от таблото за управление на сървъра.",
"Recordings": "Записи",
"RecordingScheduled": "Записът е насрочен.",
"RecordingPathChangeMessage": "Промяната на вашата папка за запис няма да мигрира съществуващите записи от старото местоположение към новото.Необходимо е да направите това ръчно.",
"RecordSeries": "Запиши сериал",
"RecommendationStarring": "В главните роли {0}",
"RecommendationDirectedBy": "Режисьор {0}",
"Rate": "Оценка",
"QueueAllFromHere": "Поред всичко от тук",
"ProductionLocations": "Места на заснемане",
"Previous": "Предишен",
"PreferEmbeddedEpisodeInfosOverFileNames": "Предпочитай \"вградената\" информация за епизода вместо името на файла",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Това използва информацията за епизода от вградените метаданни, ако е налична.",
"PreferEmbeddedTitlesOverFileNamesHelp": "Това определя заглавието по подразбиране, когато няма интернет метаданни или локални метаданни.",
"PluginInstalledMessage": "Добавката е успешно инсталирана. Джелифин ще трябва да бъде рестартиран, за да влязат в сила промените.",
"PleaseSelectTwoItems": "Моля, изберете поне два елемента.",
"PleaseEnterNameOrId": "Моля, въведете име или външен идентификатор.",
"PleaseConfirmPluginInstallation": "Моля, щракнете върху OK, за да потвърдите, че сте прочели горното и искате да продължите с инсталирането на добавката.",
"PleaseAddAtLeastOneFolder": "Моля, добавете поне една папка към тази библиотека, като щракнете върху бутона \"Добавяне\".",
"PlaybackErrorNoCompatibleStream": "Този клиент не е съвместим с медията и сървърът не изпраща съвместим формат за медията.",
"PlayNext": "Възпроизведи следващ",
"PlayFromBeginning": "Възпроизведи от началото",
"PlayCount": "Брой възпроизвеждания",
"PlaybackData": "Данни за възпроизвеждане",
"PlaceFavoriteChannelsAtBeginning": "Постави любимите канали в началото",
"PinCodeResetConfirmation": "Сигурни ли сте, че искате да нулирате пин кода?",
"PinCodeResetComplete": "Пин кодът е нулиран.",
"Person": "Личност",
"PerfectMatch": "Перфектно съвпадение",
"PasswordSaved": "Паролата е запазена.",
"PasswordResetProviderHelp": "Изберете доставчик за нулиране на пароли, който да се използва, когато този потребител поиска нулиране на паролата",
"PasswordResetConfirmation": "Сигурни ли сте, че искате да нулирате паролата?",
"PasswordResetComplete": "Паролата е нулирана.",
"PasswordMatchError": "Паролата и потвърждението на паролата трябва да съвпадат.",
"PackageInstallFailed": "Инсталирането на {0} версия {1}) е неуспешно.",
"PackageInstallCompleted": "Инсталирането на {0} версия {1}) е завършено.",
"PackageInstallCancelled": "Инсталирането на {0} версия {1}) е отменено.",
"OtherArtist": "Друг изпълнител",
"OptionWeekends": "Почивни дни",
"OptionWeekdays": "Делници",
"OptionTvdbRating": "Рейтинг според ТВДБ",
"OptionThumbCard": "Икона карта",
"OptionThumb": "Икона",
"OptionSubstring": "Подниз",
"OptionSaveMetadataAsHiddenHelp": "Промяната на това ще се прилага за нови метаданни, запазени занапред. Съществуващите файлове с метаданни ще бъдат актуализирани следващия път, когато бъдат запазени на сървъра.",
"OptionSaveMetadataAsHidden": "Запишете метаданните и изображенията като скрити файлове",
"OptionRequirePerfectSubtitleMatchHelp": "Изискването за перфектно съвпадение ще филтрира субтитрите, за да включва само тези, които са тествани и проверени с точния видео файл.Ако премахнете отметката, това ще увеличи броя изтеглени субтитри, но ще увеличи шансовете за изтегляне на субтитри с неправилна синхронизация или несъвпадащи с видеофайла.",
"OptionReportByteRangeSeekingWhenTranscodingHelp": "Това е необходимо за някои устройства, които не търсят \"по време\" правилно.",
"OptionReportByteRangeSeekingWhenTranscoding": "Подава информация, че сървърът поддържа търсене на байтове при прекодиране",
"OptionRegex": "Регуларен",
"OptionRandom": "Случаен",
"OptionProtocolHttp": "HTTP",
"OptionProtocolHls": "Директно предаване по HTTP",
"OptionPosterCard": "Плакат карта",
"OptionPoster": "Плакат",
"OptionPlainVideoItemsHelp": "Ако е активирано, всички видеофайлове са представени в DIDL като \"object.item.videoItem\" вместо по-конкретен тип, като например \"object.item.videoItem.movie\".",
"OptionPlainStorageFoldersHelp": "Ако е активирано, всички папки са представени в DIDL като \"object.container.storageFolder\" вместо по-конкретен тип, като например \"object.container.person.musicArtist\".",
"OptionMax": "Максимално",
"OptionLoginAttemptsBeforeLockoutHelp": "Стойност нула означава наследяване по подразбиране на три опита за нормални потребители и пет за администратори. Задаването на това на -1 ще деактивира функцията.",
"OptionLoginAttemptsBeforeLockout": "Определя колко неправилни опита за влизане могат да бъдат направени, преди да бъде блокиран.",
"OptionList": "Списък",
"OptionIgnoreTranscodeByteRangeRequestsHelp": "Ако са активирани, тези заявки ще бъдат удовлетворени, но ще се пренебрегне заглавната част от обхвата на байтовете.",
"OptionIgnoreTranscodeByteRangeRequests": "Игнорирайте заявките за обхват на байтове при прекодиране",
"OptionHlsSegmentedSubtitles": "HLS сегментирани субтитри",
"OptionExtractChapterImage": "Включи извличането на изображения от разделите",
"OptionEstimateContentLength": "Приблизителна дължина на съдържанието при транскодиране",
"OptionEquals": "Се равнява",
"OptionEnableM2tsModeHelp": "Включи режим m2ts ,когато се декодира до mpegts.",
"OptionEnableM2tsMode": "Включи режим M2ts",
"OptionEnableForAllTuners": "Активиране за всички тунер устройства",
"OptionEnableExternalContentInSuggestionsHelp": "Разрешете интернет трейлърите и телевизионните програми на живо да бъдат включени в предложеното съдържание.",
"OptionEnableExternalContentInSuggestions": "Активиране на външно съдържание в предложенията",
"OptionEmbedSubtitles": "Внедрено вътре в контейнера",
"OptionDownloadImagesInAdvanceHelp": "По подразбиране повечето изображения се изтеглят само когато са поискани от приложенията свързани с Джелифин.Активирайте тази опция, за да изтеглите всички изображения предварително, при добавянето на нова медия. Това може да доведе до значително по-дълго сканиране на библиотеката.",
"OptionDisplayFolderViewHelp": "Показвай папките заедно с тези от медийната библиотека. Това може да бъде полезно, ако искате да имате по разбираем изглед за папките.",
"OptionDateAddedImportTime": "Използвай датата на сканиране в библиотеката",
"OptionDateAddedFileTime": "Използвай датата на създаване на файла",
"OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)",
"OptionBluray": "Блу-рей",
"OptionBlockTrailers": "Трейлъри",
"OptionBlockMusic": "Музика",
"OptionBlockLiveTvChannels": "Телевизионни канали на живо",
"OptionBlockChannelContent": "Съдържание на интернет канала",
"OptionBanner": "Банер",
"OptionAutomaticallyGroupSeriesHelp": "Ако е активирано, сезоните, които се намират в различни папки, ще бъдат автоматично обединени в един сериал.",
"OptionAutomaticallyGroupSeries": "Автоматично обединява сезони, които са разпределени в множество папки",
"OptionAllowVideoPlaybackTranscoding": "Разреши възпроизвеждане на видео, което изисква транскодиране",
"OptionAllowVideoPlaybackRemuxing": "Разреши възпроизвеждане на видео, което изисква преобразуване без повторно кодиране",
"OptionAllowSyncTranscoding": "Разреши изтегляне и синхронизиране на медия, която изисква транскодиране",
"OptionAllowMediaPlaybackTranscodingHelp": "Ограничаването на достъпа до транскодирането може да доведе до проблеми при възпроизвеждането в приложенията на Джелифин поради неподдържани медийни формати.",
"OptionAllowLinkSharingHelp": "Споделят се само уеб страници, съдържащи медийна информация.Медийните файлове никога не се споделят публично. Споделянията са ограничени във времето и изтичат след {0} дни.",
"OptionAllowContentDownloading": "Разрешаване на изтегляне и синхронизиране на медия",
"OptionForceRemoteSourceTranscoding": "Принудително транскодиране на отдалечени медийни източници (като поточна ТВ)",
"OptionAllowAudioPlaybackTranscoding": "Разреши възпроизвеждане на аудио, което изисква транскодиране",
"OnlyImageFormats": "Само графични формати (VOBSUB, PGS, SUB)",
"OnlyForcedSubtitlesHelp": "Ще бъдат показани само субтитри, маркирани за \"принудително\" зареждане.",
"OnlyForcedSubtitles": "Само принудително",
"OneChannel": "Един канал",
"NoSubtitlesHelp": "Субтитрите няма да бъдат заредени по подразбиране. Те все още могат да бъдат включени ръчно по време на възпроизвеждане.",
"NoPluginConfigurationMessage": "Тази добавка няма настройки за конфигуриране.",
"NoNewDevicesFound": "Не са намерени нови устройства. За да добавите нов тунер, затворете този диалогов прозорец и въведете ръчно информацията за устройството.",
"NoCreatedLibraries": "Изглежда, че все още не сте създали библиотеки. {0} Искате ли да ги създадете сега? {1}",
"NextUp": "Следващ по ред",
"Next": "Следващ",
"NewCollectionHelp": "Колекциите ви позволяват да създавате персонализирани групи от филми и друго библиотечно съдържание.",
"MusicLibraryHelp": "Прегледайте {0} ръководството за именуване на музика {1}.",
"MusicArtist": "Музикален изпълнител",
"MusicAlbum": "Музикален албум",
"Movie": "Филм",
"MovieLibraryHelp": "Прегледайте {0} ръководството за именуване на филми {1}.",
"MoveRight": "Премести надясно",
"MoveLeft": "Премести на ляво",
"MoreMediaInfo": "Информация за медията",
"MetadataSettingChangeHelp": "Промяната на настройките на метаданните ще повлияе на новото съдържание, което ще се добавя занапред.За да обновите съществуващото съдържание, отворете панела с подробности и щракнете върху бутона за опресняване или извършете групово обновяване с помощта на мениджъра на метаданни.",
"MessageSyncPlayErrorMedia": "Неуспешно активиране на услугата \"синхронизирано възпроизвеждане\"!Грешка в медията.",
"MessageSyncPlayErrorMissingSession": "Неуспешно активиране на услугата \"синхронизирано възпроизвеждане\"! Липсва сесия.",
"MessageSyncPlayErrorNoActivePlayer": "Не е намерен активен плеър.Функцията \"синхронизирано възпроизвеждане\" е изключена.",
"MessageSyncPlayErrorAccessingGroups": "Възникна грешка при достъп до списъка с групи.",
"MessageSyncPlayLibraryAccessDenied": "Достъпът до това съдържание е ограничен.",
"MessageSyncPlayJoinGroupDenied": "Необходимо е разрешени за използване на функцията \"синхронизирано възпроизвеждане\".",
"MessageSyncPlayCreateGroupDenied": "Необходимо е разрешени за създаване на група.",
"LabelSonyAggregationFlagsHelp": "Определя съдържанието на aggregationFlags елемента във urn:schemas-sonycom:av пространство от имена.",
"LabelXDlnaDocHelp": "Определя съдържанието на X_DLNADOC елемента в urn:schemas-dlna-org:device-1-0 пространство от имена.",
"LabelSkipForwardLength": "Време за придвижване напред:",
"LabelSkipBackLength": "Време за придвижване назад:"
}

View file

@ -812,7 +812,7 @@
"MessageNoAvailablePlugins": "Nejsou dostupné žádné zásuvné moduly.",
"MessageNoMovieSuggestionsAvailable": "Žádné návrhy nejsou v současnosti k dispozici. Začněte sledovat a hodnotit filmy, a pak se vám doporučení zobrazí.",
"MessageNoPluginsInstalled": "Nemáte instalovány žádné zásuvné moduly.",
"MessageNoTrailersFound": "Nebyly nalezeny žádné upoutávky k filmu. Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.",
"MessageNoTrailersFound": "Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.",
"MessageNothingHere": "Tady nic není.",
"MessagePasswordResetForUsers": "Obnovení hesla bylo provedeno následujícími uživateli. Nyní se mohou přihlásit pomocí kódů PIN, které byly použity k provedení resetu.",
"MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.",
@ -1059,7 +1059,6 @@
"RepeatOne": "Opakovat jeden",
"ReplaceAllMetadata": "Přepsat všechna metadata",
"ReplaceExistingImages": "Nahradit existující obrázky",
"RestartPleaseWaitMessage": "Počkejte prosím, než se server Jellyfin vypne a restartuje. Může to trvat několik minut.",
"ResumeAt": "Obnovit přehrávání od {0}",
"Rewind": "Přetočit zpět",
"RunAtStartup": "Spustit po startu",
@ -1395,7 +1394,6 @@
"HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti",
"HeaderFavoriteSongs": "Oblíbená hudba",
"HeaderRestartingServer": "Restartování serveru",
"LabelAuthProvider": "Poskytovatel ověření:",
"LabelServerNameHelp": "Tento název bude použit k identifikaci serveru a bude výchozí pro název počítače serveru.",
"LabelPasswordResetProvider": "Poskytovatel obnovy hesla:",
@ -1540,8 +1538,6 @@
"CopyStreamURLError": "Při kopírování URL došlo k chybě.",
"LabelVideoResolution": "Rozlišení videa:",
"LabelStreamType": "Typ streamu:",
"EnableFastImageFadeInHelp": "Zobrazí plakáty a další obrázky s rychlejší animací přechodu po dokončení načítání.",
"EnableFastImageFadeIn": "Rychlé animace přechodů obrazu",
"LabelPlayerDimensions": "Zobrazené rozlišení:",
"LabelDroppedFrames": "Vynechané snímky:",
"LabelCorruptedFrames": "Poškozené snímky:",
@ -1635,5 +1631,22 @@
"EnableDetailsBannerHelp": "Zobrazí obrázek ve vrchní části detailu položky.",
"EnableDetailsBanner": "Obrázek detailu",
"ShowMore": "Zobrazit více",
"ShowLess": "Zobrazit méně"
"ShowLess": "Zobrazit méně",
"EnableBlurhashHelp": "Nenačtené obrázky budou zobrazeny pomocí neurčitých zástupných obrázků",
"EnableBlurhash": "Povolit zástupné obrázky",
"ButtonCast": "Přehrát v zařízení",
"ButtonSyncPlay": "Synchronizace přehrávání",
"MessageNoGenresAvailable": "Povolit některým poskytovatelům metadat stahovat informace o žánrech z Internetu.",
"EnableFasterAnimationsHelp": "Použít rychlejší animace a přechody",
"EnableFasterAnimations": "Rychlejší animace",
"EnableDecodingColorDepth10Vp9": "Povolit 10bitové hardwarové dekódování formátu VP9",
"EnableDecodingColorDepth10Hevc": "Povolit 10bitové hardwarové dekódování formátu HEVC",
"TabRepositories": "Repozitáře",
"MessageAddRepository": "Pokud chcete přidat repozitář, klikněte na tlačítko vedle záhlaví a vyplňte požadované informace.",
"LabelRepositoryNameHelp": "Vlastní pojmenování, které slouží k odlišení tohoto repozitáře od ostatních repozitářů přidaných na vašem serveru.",
"LabelRepositoryName": "Název repozitáře",
"LabelRepositoryUrlHelp": "Umístění manifestu repozitáře, který chcete zahrnout.",
"LabelRepositoryUrl": "URL adresa repozitáře",
"HeaderNewRepository": "Nový repozitář",
"MessageNoRepositories": "Neexistují žádné repozitáře."
}

View file

@ -1414,7 +1414,6 @@
"RepeatAll": "Gentag alle",
"RepeatMode": "Gentagelses tilstand",
"RepeatOne": "Gentag én",
"RestartPleaseWaitMessage": "Vent venligst mens Jellyfin Server lukker og genstarter. Dette kan tage et minut eller to.",
"RunAtStartup": "Kør ved opstart",
"ScanForNewAndUpdatedFiles": "Skan for nye og opdaterede filer",
"Schedule": "Tidsplan",
@ -1500,7 +1499,6 @@
"HeaderHome": "Hjem",
"LabelServerName": "Server navn:",
"LabelUserLoginAttemptsBeforeLockout": "Fejlede loginforsøg før bruger lukkes ude:",
"HeaderRestartingServer": "Genstarter Server",
"ButtonAddImage": "Tilføj billede",
"AllowFfmpegThrottlingHelp": "Når en omkodning eller remux kommer langt nok foran den nuværende afspildings position, pauses processen så der bruges færre resurser. Dette er mest brugbart når man ikke springer i filmen. Slå dette fra hvis du har problemer med playback.",
"AllowFfmpegThrottling": "Begræns Omkodning",
@ -1569,8 +1567,6 @@
"LabelStreamType": "Stream type:",
"LabelSonyAggregationFlags": "Sony aggregering flag:",
"LabelSize": "Størrelse:",
"EnableFastImageFadeInHelp": "Aktivér hurtigere fade-in-animation til indlæste billeder",
"EnableFastImageFadeIn": "Hurtigt billede indtoning",
"LabelPleaseRestart": "Ændringer vil træde i kraft efter web klienten er blevet genindlæst manuelt.",
"LabelPlayMethod": "Afspilnings metode:",
"LabelPlayerDimensions": "Afspillerdimensioner:",

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