diff --git a/package.json b/package.json index ca4ba6bd13..9c19f8f0d1 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "src/components/favoriteitems.js", "src/components/fetchhelper.js", "src/components/filterdialog/filterdialog.js", + "src/components/filtermenu/filtermenu.js", "src/components/focusManager.js", "src/components/groupedcards.js", "src/components/guide/guide.js", @@ -142,6 +143,7 @@ "src/components/metadataEditor/metadataEditor.js", "src/components/metadataEditor/personEditor.js", "src/components/multiSelect/multiSelect.js", + "src/components/notifications/notifications.js", "src/components/nowPlayingBar/nowPlayingBar.js", "src/components/playback/brightnessosd.js", "src/components/playback/mediasession.js", @@ -187,13 +189,17 @@ "src/components/syncPlay/playbackPermissionManager.js", "src/components/syncPlay/syncPlayManager.js", "src/components/syncPlay/timeSyncManager.js", + "src/components/themeMediaPlayer.js", "src/components/tabbedview/tabbedview.js", "src/components/viewManager/viewManager.js", "src/components/tvproviders/schedulesdirect.js", "src/components/tvproviders/xmltv.js", "src/components/toast/toast.js", + "src/components/tunerPicker.js", "src/components/upnextdialog/upnextdialog.js", + "src/components/userdatabuttons/userdatabuttons.js", "src/components/viewContainer.js", + "src/components/viewSettings/viewSettings.js", "src/components/castSenderApi.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", @@ -221,8 +227,8 @@ "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", "src/controllers/dashboard/networking.js", - "src/controllers/dashboard/notifications/notification.js", - "src/controllers/dashboard/notifications/notifications.js", + "src/controllers/dashboard/notifications/notification/index.js", + "src/controllers/dashboard/notifications/notifications/index.js", "src/controllers/dashboard/playback.js", "src/controllers/dashboard/plugins/add/index.js", "src/controllers/dashboard/plugins/installed/index.js", @@ -256,6 +262,7 @@ "src/controllers/searchpage.js", "src/controllers/livetv/livetvguide.js", "src/controllers/livetvtuner.js", + "src/controllers/livetv/livetvsuggested.js", "src/controllers/livetvstatus.js", "src/controllers/livetvguideprovider.js", "src/controllers/livetvsettings.js", diff --git a/src/assets/css/videoosd.css b/src/assets/css/videoosd.css index 50cb41021b..59a485468d 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -7,7 +7,6 @@ } .osdPoster img, -.pageContainer, .videoOsdBottom { bottom: 0; left: 0; @@ -248,11 +247,6 @@ animation: spin 4s linear infinite; } -.pageContainer { - top: 0; - position: fixed; -} - @media all and (max-width: 30em) { .btnFastForward, .btnRewind, diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 06332949b8..637214a878 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -1,226 +1,220 @@ -define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', 'inputManager', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) { - 'use strict'; +import dom from 'dom'; +import focusManager from 'focusManager'; +import dialogHelper from 'dialogHelper'; +import inputManager from 'inputManager'; +import layoutManager from 'layoutManager'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import 'emby-checkbox'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; +import 'flexStyles'; - focusManager = focusManager.default || focusManager; - appRouter = appRouter.default || appRouter; - layoutManager = layoutManager.default || layoutManager; +function onSubmit(e) { + e.preventDefault(); + return false; +} +function renderOptions(context, selector, cssClass, items, isCheckedFn) { + var elem = context.querySelector(selector); - function onSubmit(e) { - e.preventDefault(); - return false; + if (items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } - function renderOptions(context, selector, cssClass, items, isCheckedFn) { - var elem = context.querySelector(selector); + var html = ''; - if (items.length) { - elem.classList.remove('hide'); + html += items.map(function (filter) { + var itemHtml = ''; + + var checkedHtml = isCheckedFn(filter) ? ' checked' : ''; + itemHtml += ''; + + return itemHtml; + }).join(''); + + elem.querySelector('.filterOptions').innerHTML = html; +} + +function renderDynamicFilters(context, result, options) { + renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { + // Switching from | to , + var delimeter = (options.settings.GenreIds || '').indexOf('|') === -1 ? ',' : '|'; + return (delimeter + (options.settings.GenreIds || '') + delimeter).indexOf(delimeter + i.Id + delimeter) !== -1; + }); +} + +function setBasicFilter(context, key, elem) { + var value = elem.checked; + value = value ? value : null; + userSettings.setFilter(key, value); +} +function moveCheckboxFocus(elem, offset) { + var parent = dom.parentWithClass(elem, 'checkboxList-verticalwrap'); + var elems = focusManager.getFocusableElements(parent); + + var index = -1; + for (let i = 0, length = elems.length; i < length; i++) { + if (elems[i] === elem) { + index = i; + break; + } + } + + index += offset; + + index = Math.min(elems.length - 1, index); + index = Math.max(0, index); + + var newElem = elems[index]; + if (newElem) { + focusManager.focus(newElem); + } +} +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({ default: scrollHelper }) => { + var fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} +function onInputCommand(e) { + switch (e.detail.command) { + case 'left': + moveCheckboxFocus(e.target, -1); + e.preventDefault(); + break; + case 'right': + moveCheckboxFocus(e.target, 1); + e.preventDefault(); + break; + default: + break; + } +} +function saveValues(context, settings, settingsKey) { + var elems = context.querySelectorAll('.simpleFilter'); + for (let i = 0, length = elems.length; i < length; i++) { + if (elems[i].tagName === 'INPUT') { + setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i]); } else { - elem.classList.add('hide'); + setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input')); } - - var html = ''; - - html += items.map(function (filter) { - var itemHtml = ''; - - var checkedHtml = isCheckedFn(filter) ? ' checked' : ''; - itemHtml += ''; - - return itemHtml; - }).join(''); - - elem.querySelector('.filterOptions').innerHTML = html; } - function renderDynamicFilters(context, result, options) { - renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { - // Switching from | to , - var delimeter = (options.settings.GenreIds || '').indexOf('|') === -1 ? ',' : '|'; - return (delimeter + (options.settings.GenreIds || '') + delimeter).indexOf(delimeter + i.Id + delimeter) !== -1; - }); + // Video type + var videoTypes = []; + elems = context.querySelectorAll('.chkVideoTypeFilter'); + + for (let i = 0, length = elems.length; i < length; i++) { + if (elems[i].checked) { + videoTypes.push(elems[i].getAttribute('data-filter')); + } + } + userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(',')); + + // Series status + var seriesStatuses = []; + elems = context.querySelectorAll('.chkSeriesStatus'); + + for (let i = 0, length = elems.length; i < length; i++) { + if (elems[i].checked) { + seriesStatuses.push(elems[i].getAttribute('data-filter')); + } } - function loadDynamicFilters(context, options) { - var apiClient = connectionManager.getApiClient(options.serverId); + // Genres + var genres = []; + elems = context.querySelectorAll('.chkGenreFilter'); - var filterMenuOptions = Object.assign(options.filterMenuOptions, { - - UserId: apiClient.getCurrentUserId(), - ParentId: options.parentId, - IncludeItemTypes: options.itemTypes.join(',') - }); - - apiClient.getFilters(filterMenuOptions).then(function (result) { - renderDynamicFilters(context, result, options); - }); + for (let i = 0, length = elems.length; i < length; i++) { + if (elems[i].checked) { + genres.push(elems[i].getAttribute('data-filter')); + } } - - function initEditor(context, settings) { - context.querySelector('form').addEventListener('submit', onSubmit); - - var elems = context.querySelectorAll('.simpleFilter'); - var i; - var length; - - for (i = 0, length = elems.length; i < length; i++) { - if (elems[i].tagName === 'INPUT') { - elems[i].checked = settings[elems[i].getAttribute('data-settingname')] || false; - } else { - elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false; - } - } - - var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(',') : []; - elems = context.querySelectorAll('.chkVideoTypeFilter'); - - for (i = 0, length = elems.length; i < length; i++) { - elems[i].checked = videoTypes.indexOf(elems[i].getAttribute('data-filter')) !== -1; - } - - var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(',') : []; - elems = context.querySelectorAll('.chkSeriesStatus'); - - for (i = 0, length = elems.length; i < length; i++) { - elems[i].checked = seriesStatuses.indexOf(elems[i].getAttribute('data-filter')) !== -1; - } - - if (context.querySelector('.basicFilterSection .viewSetting:not(.hide)')) { - context.querySelector('.basicFilterSection').classList.remove('hide'); + userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(',')); +} +function bindCheckboxInput(context, on) { + var elems = context.querySelectorAll('.checkboxList-verticalwrap'); + for (let i = 0, length = elems.length; i < length; i++) { + if (on) { + inputManager.on(elems[i], onInputCommand); } else { - context.querySelector('.basicFilterSection').classList.add('hide'); + inputManager.off(elems[i], onInputCommand); } + } +} +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); - if (context.querySelector('.featureSection .viewSetting:not(.hide)')) { - context.querySelector('.featureSection').classList.remove('hide'); + var elems = context.querySelectorAll('.simpleFilter'); + var i; + var length; + + for (i = 0, length = elems.length; i < length; i++) { + if (elems[i].tagName === 'INPUT') { + elems[i].checked = settings[elems[i].getAttribute('data-settingname')] || false; } else { - context.querySelector('.featureSection').classList.add('hide'); + elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false; } } - function saveValues(context, settings, settingsKey) { - var elems = context.querySelectorAll('.simpleFilter'); - var i; - var length; - for (i = 0, length = elems.length; i < length; i++) { - if (elems[i].tagName === 'INPUT') { - setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i]); - } else { - setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input')); - } - } + var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(',') : []; + elems = context.querySelectorAll('.chkVideoTypeFilter'); - // Video type - var videoTypes = []; - elems = context.querySelectorAll('.chkVideoTypeFilter'); - - for (i = 0, length = elems.length; i < length; i++) { - if (elems[i].checked) { - videoTypes.push(elems[i].getAttribute('data-filter')); - } - } - userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(',')); - - // Series status - var seriesStatuses = []; - elems = context.querySelectorAll('.chkSeriesStatus'); - - for (i = 0, length = elems.length; i < length; i++) { - if (elems[i].checked) { - seriesStatuses.push(elems[i].getAttribute('data-filter')); - } - } - - // Genres - var genres = []; - elems = context.querySelectorAll('.chkGenreFilter'); - - for (i = 0, length = elems.length; i < length; i++) { - if (elems[i].checked) { - genres.push(elems[i].getAttribute('data-filter')); - } - } - userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(',')); + for (i = 0, length = elems.length; i < length; i++) { + elems[i].checked = videoTypes.indexOf(elems[i].getAttribute('data-filter')) !== -1; } - function setBasicFilter(context, key, elem) { - var value = elem.checked; - value = value ? value : null; - userSettings.setFilter(key, value); + var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(',') : []; + elems = context.querySelectorAll('.chkSeriesStatus'); + + for (i = 0, length = elems.length; i < length; i++) { + elems[i].checked = seriesStatuses.indexOf(elems[i].getAttribute('data-filter')) !== -1; } - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - scrollHelper = scrollHelper.default || scrollHelper; - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); + if (context.querySelector('.basicFilterSection .viewSetting:not(.hide)')) { + context.querySelector('.basicFilterSection').classList.remove('hide'); + } else { + context.querySelector('.basicFilterSection').classList.add('hide'); } - function moveCheckboxFocus(elem, offset) { - var parent = dom.parentWithClass(elem, 'checkboxList-verticalwrap'); - var elems = focusManager.getFocusableElements(parent); - - var index = -1; - for (var i = 0, length = elems.length; i < length; i++) { - if (elems[i] === elem) { - index = i; - break; - } - } - - index += offset; - - index = Math.min(elems.length - 1, index); - index = Math.max(0, index); - - var newElem = elems[index]; - if (newElem) { - focusManager.focus(newElem); - } + if (context.querySelector('.featureSection .viewSetting:not(.hide)')) { + context.querySelector('.featureSection').classList.remove('hide'); + } else { + context.querySelector('.featureSection').classList.add('hide'); } +} +function loadDynamicFilters(context, options) { + var apiClient = connectionManager.getApiClient(options.serverId); - function onInputCommand(e) { - switch (e.detail.command) { - case 'left': - moveCheckboxFocus(e.target, -1); - e.preventDefault(); - break; - case 'right': - moveCheckboxFocus(e.target, 1); - e.preventDefault(); - break; - default: - break; - } - } + var filterMenuOptions = Object.assign(options.filterMenuOptions, { - function FilterMenu() { + UserId: apiClient.getCurrentUserId(), + ParentId: options.parentId, + IncludeItemTypes: options.itemTypes.join(',') + }); - } - - function bindCheckboxInput(context, on) { - var elems = context.querySelectorAll('.checkboxList-verticalwrap'); - for (var i = 0, length = elems.length; i < length; i++) { - if (on) { - inputManager.on(elems[i], onInputCommand); - } else { - inputManager.off(elems[i], onInputCommand); - } - } - } - - FilterMenu.prototype.show = function (options) { - return new Promise(function (resolve, reject) { - require(['text!./filtermenu.template.html'], function (template) { + apiClient.getFilters(filterMenuOptions).then((result) => { + renderDynamicFilters(context, result, options); + }); +} +class FilterMenu { + show(options) { + return new Promise( (resolve, reject) => { + import('text!./filtermenu.template.html').then(({ default: template }) => { var dialogOptions = { removeOnClose: true, scrollY: false }; - if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; } else { @@ -244,7 +238,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', dlg.innerHTML = globalize.translateHtml(html, 'core'); var settingElements = dlg.querySelectorAll('.viewSetting'); - for (var i = 0, length = settingElements.length; i < length; i++) { + for (let i = 0, length = settingElements.length; i < length; i++) { if (options.visibleSettings.indexOf(settingElements[i].getAttribute('data-settingname')) === -1) { settingElements[i].classList.add('hide'); } else { @@ -256,7 +250,6 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', loadDynamicFilters(dlg, options); bindCheckboxInput(dlg, true); - dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); @@ -271,7 +264,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', submitted = true; }, true); - dialogHelper.open(dlg).then(function () { + dialogHelper.open(dlg).then( function() { bindCheckboxInput(dlg, false); if (layoutManager.tv) { @@ -281,16 +274,14 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', if (submitted) { //if (!options.onChange) { saveValues(dlg, options.settings, options.settingsKey); - resolve(); + return resolve(); //} - return; } - - reject(); + return resolve(); }); }); }); - }; + } +} - return FilterMenu; -}); +export default FilterMenu; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 289c784bd9..020d9953ef 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,182 +1,181 @@ -define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'require'], function (serverNotifications, playbackManager, events, globalize, require) { - 'use strict'; +import serverNotifications from 'serverNotifications'; +import playbackManager from 'playbackManager'; +import events from 'events'; +import globalize from 'globalize'; - playbackManager = playbackManager.default || playbackManager; - serverNotifications = serverNotifications.default || serverNotifications; +function onOneDocumentClick() { + document.removeEventListener('click', onOneDocumentClick); + document.removeEventListener('keydown', onOneDocumentClick); - function onOneDocumentClick() { - document.removeEventListener('click', onOneDocumentClick); - document.removeEventListener('keydown', onOneDocumentClick); - - // don't request notification permissions if they're already granted or denied - if (window.Notification && window.Notification.permission === 'default') { - /* eslint-disable-next-line compat/compat */ - Notification.requestPermission(); - } - } - - document.addEventListener('click', onOneDocumentClick); - document.addEventListener('keydown', onOneDocumentClick); - - var serviceWorkerRegistration; - - function closeAfter(notification, timeoutMs) { - setTimeout(function () { - if (notification.close) { - notification.close(); - } else if (notification.cancel) { - notification.cancel(); - } - }, timeoutMs); - } - - function resetRegistration() { + // don't request notification permissions if they're already granted or denied + if (window.Notification && window.Notification.permission === 'default') { /* eslint-disable-next-line compat/compat */ - var serviceWorker = navigator.serviceWorker; - if (serviceWorker) { - serviceWorker.ready.then(function (registration) { - serviceWorkerRegistration = registration; - }); + Notification.requestPermission(); + } +} + +document.addEventListener('click', onOneDocumentClick); +document.addEventListener('keydown', onOneDocumentClick); + +let serviceWorkerRegistration; + +function closeAfter(notification, timeoutMs) { + setTimeout(function () { + if (notification.close) { + notification.close(); + } else if (notification.cancel) { + notification.cancel(); + } + }, timeoutMs); +} + +function resetRegistration() { + /* eslint-disable-next-line compat/compat */ + let serviceWorker = navigator.serviceWorker; + if (serviceWorker) { + serviceWorker.ready.then(function (registration) { + serviceWorkerRegistration = registration; + }); + } +} + +resetRegistration(); + +function showPersistentNotification(title, options, timeoutMs) { + serviceWorkerRegistration.showNotification(title, options); +} + +function showNonPersistentNotification(title, options, timeoutMs) { + try { + let notif = new Notification(title, options); /* eslint-disable-line compat/compat */ + + if (notif.show) { + notif.show(); + } + + if (timeoutMs) { + closeAfter(notif, timeoutMs); + } + } catch (err) { + if (options.actions) { + options.actions = []; + showNonPersistentNotification(title, options, timeoutMs); + } else { + throw err; } } +} + +function showNotification(options, timeoutMs, apiClient) { + let title = options.title; + + options.data = options.data || {}; + options.data.serverId = apiClient.serverInfo().Id; + options.icon = options.icon || getIconUrl(); + options.badge = options.badge || getIconUrl('badge.png'); resetRegistration(); - function showPersistentNotification(title, options, timeoutMs) { - serviceWorkerRegistration.showNotification(title, options); + if (serviceWorkerRegistration) { + showPersistentNotification(title, options, timeoutMs); + return; } - function showNonPersistentNotification(title, options, timeoutMs) { - try { - var notif = new Notification(title, options); /* eslint-disable-line compat/compat */ + showNonPersistentNotification(title, options, timeoutMs); +} - if (notif.show) { - notif.show(); - } - - if (timeoutMs) { - closeAfter(notif, timeoutMs); - } - } catch (err) { - if (options.actions) { - options.actions = []; - showNonPersistentNotification(title, options, timeoutMs); - } else { - throw err; - } - } +function showNewItemNotification(item, apiClient) { + if (playbackManager.isPlayingLocally(['Video'])) { + return; } - function showNotification(options, timeoutMs, apiClient) { - var title = options.title; + let body = item.Name; - options.data = options.data || {}; - options.data.serverId = apiClient.serverInfo().Id; - options.icon = options.icon || getIconUrl(); - options.badge = options.badge || getIconUrl('badge.png'); - - resetRegistration(); - - if (serviceWorkerRegistration) { - showPersistentNotification(title, options, timeoutMs); - return; - } - - showNonPersistentNotification(title, options, timeoutMs); + if (item.SeriesName) { + body = item.SeriesName + ' - ' + body; } - function showNewItemNotification(item, apiClient) { - if (playbackManager.isPlayingLocally(['Video'])) { - return; - } + let notification = { + title: 'New ' + item.Type, + body: body, + vibrate: true, + tag: 'newItem' + item.Id, + data: {} + }; - var body = item.Name; + let imageTags = item.ImageTags || {}; - if (item.SeriesName) { - body = item.SeriesName + ' - ' + body; - } - - var notification = { - title: 'New ' + item.Type, - body: body, - vibrate: true, - tag: 'newItem' + item.Id, - data: {} - }; - - var imageTags = item.ImageTags || {}; - - if (imageTags.Primary) { - notification.icon = apiClient.getScaledImageUrl(item.Id, { - width: 80, - tag: imageTags.Primary, - type: 'Primary' - }); - } - - showNotification(notification, 15000, apiClient); - } - - function onLibraryChanged(data, apiClient) { - var newItems = data.ItemsAdded; - - if (!newItems.length) { - return; - } - - // Don't put a massive number of Id's onto the query string - if (newItems.length > 12) { - newItems.length = 12; - } - - apiClient.getItems(apiClient.getCurrentUserId(), { - - Recursive: true, - Limit: 3, - Filters: 'IsNotFolder', - SortBy: 'DateCreated', - SortOrder: 'Descending', - Ids: newItems.join(','), - MediaTypes: 'Audio,Video', - EnableTotalRecordCount: false - - }).then(function (result) { - var items = result.Items; - - for (var i = 0, length = items.length ; i < length; i++) { - showNewItemNotification(items[i], apiClient); - } + if (imageTags.Primary) { + notification.icon = apiClient.getScaledImageUrl(item.Id, { + width: 80, + tag: imageTags.Primary, + type: 'Primary' }); } - function getIconUrl(name) { - name = name || 'notificationicon.png'; - return require.toUrl('.').split('?')[0] + '/' + name; + showNotification(notification, 15000, apiClient); +} + +function onLibraryChanged(data, apiClient) { + let newItems = data.ItemsAdded; + + if (!newItems.length) { + return; } - function showPackageInstallNotification(apiClient, installation, status) { - apiClient.getCurrentUser().then(function (user) { - if (!user.Policy.IsAdministrator) { - return; - } + // Don't put a massive number of Id's onto the query string + if (newItems.length > 12) { + newItems.length = 12; + } - var notification = { - tag: 'install' + installation.Id, - data: {} - }; + apiClient.getItems(apiClient.getCurrentUserId(), { - if (status === 'completed') { - notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); - notification.vibrate = true; - } else if (status === 'cancelled') { - notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); - } else if (status === 'failed') { - notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); - notification.vibrate = true; - } else if (status === 'progress') { - notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); + Recursive: true, + Limit: 3, + Filters: 'IsNotFolder', + SortBy: 'DateCreated', + SortOrder: 'Descending', + Ids: newItems.join(','), + MediaTypes: 'Audio,Video', + EnableTotalRecordCount: false - notification.actions = + }).then(function (result) { + let items = result.Items; + + for (const item of items) { + showNewItemNotification(item, apiClient); + } + }); +} + +function getIconUrl(name) { + name = name || 'notificationicon.png'; + return './components/notifications/' + name; +} + +function showPackageInstallNotification(apiClient, installation, status) { + apiClient.getCurrentUser().then(function (user) { + if (!user.Policy.IsAdministrator) { + return; + } + + let notification = { + tag: 'install' + installation.Id, + data: {} + }; + + if (status === 'completed') { + notification.title = globalize.translate('PackageInstallCompleted', installation.Name, installation.Version); + notification.vibrate = true; + } else if (status === 'cancelled') { + notification.title = globalize.translate('PackageInstallCancelled', installation.Name, installation.Version); + } else if (status === 'failed') { + notification.title = globalize.translate('PackageInstallFailed', installation.Name, installation.Version); + notification.vibrate = true; + } else if (status === 'progress') { + notification.title = globalize.translate('InstallingPackage', installation.Name, installation.Version); + + notification.actions = [ { action: 'cancel-install', @@ -185,67 +184,67 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } ]; - notification.data.id = installation.id; - } + notification.data.id = installation.id; + } - if (status === 'progress') { - var percentComplete = Math.round(installation.PercentComplete || 0); + if (status === 'progress') { + let percentComplete = Math.round(installation.PercentComplete || 0); - notification.body = percentComplete + '% complete.'; - } + notification.body = percentComplete + '% complete.'; + } - var timeout = status === 'cancelled' ? 5000 : 0; + let timeout = status === 'cancelled' ? 5000 : 0; - showNotification(notification, timeout, apiClient); - }); - } - - events.on(serverNotifications, 'LibraryChanged', function (e, apiClient, data) { - onLibraryChanged(data, apiClient); + showNotification(notification, timeout, apiClient); }); +} - events.on(serverNotifications, 'PackageInstallationCompleted', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'completed'); - }); +events.on(serverNotifications, 'LibraryChanged', function (e, apiClient, data) { + onLibraryChanged(data, apiClient); +}); - events.on(serverNotifications, 'PackageInstallationFailed', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'failed'); - }); +events.on(serverNotifications, 'PackageInstallationCompleted', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'completed'); +}); - events.on(serverNotifications, 'PackageInstallationCancelled', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'cancelled'); - }); +events.on(serverNotifications, 'PackageInstallationFailed', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'failed'); +}); - events.on(serverNotifications, 'PackageInstalling', function (e, apiClient, data) { - showPackageInstallNotification(apiClient, data, 'progress'); - }); +events.on(serverNotifications, 'PackageInstallationCancelled', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'cancelled'); +}); - events.on(serverNotifications, 'ServerShuttingDown', function (e, apiClient, data) { - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('ServerNameIsShuttingDown', apiClient.serverInfo().Name) - }; - showNotification(notification, 0, apiClient); - }); +events.on(serverNotifications, 'PackageInstalling', function (e, apiClient, data) { + showPackageInstallNotification(apiClient, data, 'progress'); +}); - events.on(serverNotifications, 'ServerRestarting', function (e, apiClient, data) { - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('ServerNameIsRestarting', apiClient.serverInfo().Name) - }; - showNotification(notification, 0, apiClient); - }); +events.on(serverNotifications, 'ServerShuttingDown', function (e, apiClient, data) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('ServerNameIsShuttingDown', apiClient.serverInfo().Name) + }; + showNotification(notification, 0, apiClient); +}); - events.on(serverNotifications, 'RestartRequired', function (e, apiClient) { - var serverId = apiClient.serverInfo().Id; - var notification = { - tag: 'restart' + serverId, - title: globalize.translate('PleaseRestartServerName', apiClient.serverInfo().Name) - }; +events.on(serverNotifications, 'ServerRestarting', function (e, apiClient, data) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('ServerNameIsRestarting', apiClient.serverInfo().Name) + }; + showNotification(notification, 0, apiClient); +}); - notification.actions = +events.on(serverNotifications, 'RestartRequired', function (e, apiClient) { + let serverId = apiClient.serverInfo().Id; + let notification = { + tag: 'restart' + serverId, + title: globalize.translate('PleaseRestartServerName', apiClient.serverInfo().Name) + }; + + notification.actions = [ { action: 'restart', @@ -254,6 +253,6 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir } ]; - showNotification(notification, 0, apiClient); - }); + showNotification(notification, 0, apiClient); }); + diff --git a/src/components/themeMediaPlayer.js b/src/components/themeMediaPlayer.js index 60f0986884..8225156bf0 100644 --- a/src/components/themeMediaPlayer.js +++ b/src/components/themeMediaPlayer.js @@ -1,103 +1,101 @@ -define(['playbackManager', 'userSettings', 'connectionManager'], function (playbackManager, userSettings, connectionManager) { - 'use strict'; +import playbackManager from 'playbackManager'; +import * as userSettings from 'userSettings'; +import connectionManager from 'connectionManager'; - playbackManager = playbackManager.default || playbackManager; +let currentOwnerId; +let currentThemeIds = []; - var currentOwnerId; - var currentThemeIds = []; +function playThemeMedia(items, ownerId) { + const currentThemeItems = items.filter(function (i) { + return enabled(i.MediaType); + }); - function playThemeMedia(items, ownerId) { - var currentThemeItems = items.filter(function (i) { - return enabled(i.MediaType); + if (currentThemeItems.length) { + // Stop if a theme song from another ownerId + // Leave it alone if anything else (e.g user playing a movie) + if (!currentOwnerId && playbackManager.isPlaying()) { + return; + } + + currentThemeIds = currentThemeItems.map(function (i) { + return i.Id; }); - if (currentThemeItems.length) { - // Stop if a theme song from another ownerId - // Leave it alone if anything else (e.g user playing a movie) - if (!currentOwnerId && playbackManager.isPlaying()) { - return; - } - - currentThemeIds = currentThemeItems.map(function (i) { - return i.Id; - }); - - playbackManager.play({ - items: currentThemeItems, - fullscreen: false, - enableRemotePlayers: false - }).then(function () { - currentOwnerId = ownerId; - }); - } else { - stopIfPlaying(); - } - } - - function stopIfPlaying() { - if (currentOwnerId) { - playbackManager.stop(); - } - - currentOwnerId = null; - } - - function enabled(mediaType) { - if (mediaType === 'Video') { - return userSettings.enableThemeVideos(); - } - - return userSettings.enableThemeSongs(); - } - - var excludeTypes = ['CollectionFolder', 'UserView', 'Program', 'SeriesTimer', 'Person', 'TvChannel', 'Channel']; - - function loadThemeMedia(item) { - if (item.CollectionType) { - stopIfPlaying(); - return; - } - - if (excludeTypes.indexOf(item.Type) !== -1) { - stopIfPlaying(); - return; - } - - var apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getThemeMedia(apiClient.getCurrentUserId(), item.Id, true).then(function (themeMediaResult) { - var ownerId = themeMediaResult.ThemeVideosResult.Items.length ? themeMediaResult.ThemeVideosResult.OwnerId : themeMediaResult.ThemeSongsResult.OwnerId; - - if (ownerId !== currentOwnerId) { - var items = themeMediaResult.ThemeVideosResult.Items.length ? themeMediaResult.ThemeVideosResult.Items : themeMediaResult.ThemeSongsResult.Items; - - playThemeMedia(items, ownerId); - } + playbackManager.play({ + items: currentThemeItems, + fullscreen: false, + enableRemotePlayers: false + }).then(function () { + currentOwnerId = ownerId; }); + } else { + stopIfPlaying(); + } +} + +function stopIfPlaying() { + if (currentOwnerId) { + playbackManager.stop(); } - document.addEventListener('viewshow', function (e) { - var state = e.detail.state || {}; - var item = state.item; + currentOwnerId = null; +} - if (item && item.ServerId) { - loadThemeMedia(item); - return; - } +function enabled(mediaType) { + if (mediaType === 'Video') { + return userSettings.enableThemeVideos(); + } - var viewOptions = e.detail.options || {}; + return userSettings.enableThemeSongs(); +} - if (viewOptions.supportsThemeMedia) { - // Do nothing here, allow it to keep playing - } else { - playThemeMedia([], null); - } - }, true); +const excludeTypes = ['CollectionFolder', 'UserView', 'Program', 'SeriesTimer', 'Person', 'TvChannel', 'Channel']; - Events.on(playbackManager, 'playbackstart', function (e, player) { - var item = playbackManager.currentItem(player); - // User played something manually - if (currentThemeIds.indexOf(item.Id) == -1) { - currentOwnerId = null; +function loadThemeMedia(item) { + if (item.CollectionType) { + stopIfPlaying(); + return; + } + + if (excludeTypes.indexOf(item.Type) !== -1) { + stopIfPlaying(); + return; + } + + const apiClient = connectionManager.getApiClient(item.ServerId); + apiClient.getThemeMedia(apiClient.getCurrentUserId(), item.Id, true).then(function (themeMediaResult) { + const ownerId = themeMediaResult.ThemeVideosResult.Items.length ? themeMediaResult.ThemeVideosResult.OwnerId : themeMediaResult.ThemeSongsResult.OwnerId; + + if (ownerId !== currentOwnerId) { + const items = themeMediaResult.ThemeVideosResult.Items.length ? themeMediaResult.ThemeVideosResult.Items : themeMediaResult.ThemeSongsResult.Items; + + playThemeMedia(items, ownerId); } }); +} + +document.addEventListener('viewshow', function (e) { + const state = e.detail.state || {}; + const item = state.item; + + if (item && item.ServerId) { + loadThemeMedia(item); + return; + } + + const viewOptions = e.detail.options || {}; + + if (viewOptions.supportsThemeMedia) { + // Do nothing here, allow it to keep playing + } else { + playThemeMedia([], null); + } +}, true); + +Events.on(playbackManager, 'playbackstart', function (e, player) { + const item = playbackManager.currentItem(player); + // User played something manually + if (currentThemeIds.indexOf(item.Id) == -1) { + currentOwnerId = null; + } }); diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index 6b2b1c00e6..13625ddb67 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -1,183 +1,185 @@ -define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize', 'loading', 'browser', 'focusManager', 'scrollHelper', 'material-icons', 'formDialogStyle', 'emby-button', 'emby-itemscontainer', 'cardStyle'], function (dialogHelper, dom, layoutManager, connectionManager, globalize, loading, browser, focusManager, scrollHelper) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import dom from 'dom'; +import layoutManager from 'layoutManager'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; +import loading from 'loading'; +import browser from 'browser'; +import focusManager from 'focusManager'; +import scrollHelper from 'scrollHelper'; +import 'material-icons'; +import 'formDialogStyle'; +import 'emby-button'; +import 'emby-itemscontainer'; +import 'cardStyle'; - browser = browser.default || browser; - loading = loading.default || loading; - layoutManager = layoutManager.default || layoutManager; - focusManager = focusManager.default || focusManager; - scrollHelper = scrollHelper.default || scrollHelper; +const enableFocusTransform = !browser.slow && !browser.edge; - var enableFocusTransform = !browser.slow && !browser.edge; +function getEditorHtml() { + let html = ''; + html += '
' + globalize.translate('NoNewDevicesFound') + '
' + globalize.translate('NoNewDevicesFound') + '
http://example.com/<baseurl>
",
"LabelExtractChaptersDuringLibraryScan": "於媒體庫掃描時擷取章節圖片",
- "LabelHttpsPort": "本地 HTTPS 端口:",
+ "LabelHttpsPort": "本地 HTTPS 埠:",
"LabelFailed": "失敗",
"LabelSubtitles": "字幕",
"LabelSupportedMediaTypes": "支援的媒體類型:",
@@ -864,7 +864,7 @@
"ManageLibrary": "管理媒體庫",
"MarkPlayed": "標記為已播放",
"MarkUnplayed": "標記為未播放",
- "MediaInfoBitrate": "比特率",
+ "MediaInfoBitrate": "位元率",
"MediaInfoChannels": "聲道",
"MediaInfoCodec": "編碼",
"MediaInfoContainer": "影片容器",
@@ -946,7 +946,7 @@
"LabelDownloadLanguages": "下載語言:",
"LabelDynamicExternalId": "{0} Id:",
"LabelEasyPinCode": "簡易代碼:",
- "LabelEnableAutomaticPortMap": "啟用自動端口映射",
+ "LabelEnableAutomaticPortMap": "啟用自動埠映射",
"LabelEnableSingleImageInDidlLimit": "限制單個嵌入式圖片",
"LabelEndDate": "結束日期:",
"LabelLockItemToPreventChanges": "鎖定此項目來避免被更改",
@@ -959,7 +959,7 @@
"LabelSelectVersionToInstall": "選擇要安裝的版本:",
"LabelSendNotificationToUsers": "傳送通知給:",
"LabelSortBy": "排序按照:",
- "LabelVideoBitrate": "影片比特率:",
+ "LabelVideoBitrate": "影片位元率:",
"MediaInfoSize": "大小",
"MediaInfoTimestamp": "時間戳",
"MediaInfoStreamTypeData": "檔案",
@@ -990,12 +990,12 @@
"LabelGroupMoviesIntoCollectionsHelp": "顯示電影清單時,屬於相同集合的電影將作為分組項目顯示。",
"LabelEncoderPreset": "H264 解碼品質:",
"LabelHardwareAccelerationType": "硬體加速:",
- "LabelIconMaxWidthHelp": "通過 upnp:icon 的圖標最大解析度。",
+ "LabelIconMaxWidthHelp": "通過 upnp:icon 的圖示最大解析度。",
"LabelImportOnlyFavoriteChannels": "僅限收藏的頻道",
"LabelInNetworkSignInWithEasyPassword": "啟用以簡易密碼進行區域網路登入",
"LabelH264Crf": "H264 編碼 CRF:",
"LabelMaxStreamingBitrate": "最大串流畫質:",
- "LabelMaxStreamingBitrateHelp": "指定最大串流比特率。",
+ "LabelMaxStreamingBitrateHelp": "指定最大串流位元率。",
"LabelMessageText": "訊息文字:",
"LabelMessageTitle": "訊息標題:",
"LabelMetadataDownloadLanguage": "偏好下載語言:",
@@ -1086,10 +1086,10 @@
"Writer": "編劇",
"XmlTvMovieCategoriesHelp": "有這些類別的節目會被當作電影,以「|」來分隔多個項目。",
"ValueSeriesCount": "{0} 劇集",
- "LabelHardwareAccelerationTypeHelp": "硬件加速需要額外的配置。",
+ "LabelHardwareAccelerationTypeHelp": "硬體加速需要額外的配置。",
"LabelHomeNetworkQuality": "區域網路畫質:",
- "LabelHomeScreenSectionValue": "主畫面模塊 {0}:",
- "LabelMetadataDownloadersHelp": "啟用中繼資料下載器的優先次序,愈下次序只會用來填補缺少的信息。",
+ "LabelHomeScreenSectionValue": "主畫面模組 {0}:",
+ "LabelMetadataDownloadersHelp": "啟用中繼資料下載器的優先次序,愈下次序只會用來填補缺少的訊息。",
"LabelMetadataReaders": "中繼資料閱讀器:",
"LabelMetadataSaversHelp": "選取儲存中繼資料的檔案格式。",
"LabelModelNumber": "型號",
@@ -1099,7 +1099,7 @@
"OptionDownloadBannerImage": "橫幅",
"OptionEnableAccessToAllChannels": "允許存取所有頻道",
"OptionEnableAccessToAllLibraries": "允許存取所有媒體庫",
- "OptionEnableForAllTuners": "开启所有调谐器",
+ "OptionEnableForAllTuners": "開啟所有調諧器",
"OptionExtractChapterImage": "開啟章節圖片擷取",
"OptionEnableM2tsModeHelp": "當編碼為 MPEGTS 時啟用 M2TS 模式。",
"OptionEquals": "等於",
@@ -1139,13 +1139,13 @@
"LabelSimultaneousConnectionLimit": "同時串流限制:",
"LabelSize": "大小:",
"LabelSkipBackLength": "跳過長度:",
- "LabelSkipIfGraphicalSubsPresentHelp": "保留文字版本的字幕會更有效率傳遞,減低影片轉碼的機會。",
+ "LabelSkipIfGraphicalSubsPresentHelp": "保留文字版本的字幕會更有效率傳遞,減低影片轉檔的機會。",
"LabelStopWhenPossible": "當可能時自動停止:",
"LabelStopping": "停止",
"LabelTagline": "個性宣言:",
"LabelSubtitleDownloaders": "字幕下載器:",
- "LabelSubtitleFormatHelp": "例如:SRT",
- "LabelSubtitlePlaybackMode": "字幕模式:",
+ "LabelSubtitleFormatHelp": "如:SRT",
+ "LabelSubtitlePlaybackMode": "字幕載入:",
"LabelTranscodingThreadCount": "轉檔執行緒數:",
"LabelTunerIpAddress": "調諧器 IP 位址:",
"LabelTunerType": "調諧器類型:",
@@ -1184,7 +1184,7 @@
"MessageDirectoryPickerLinuxInstruction": "使用 Linux on Arch Linux、CentOS、Debian、Fedora、openSUSE 或 Ubuntu 作業系統,您必須授權使用者至少讀取權限來存取您的儲存路徑。",
"MessageEnablingOptionLongerScans": "啟用這個選項可能會延長媒體庫的掃描時間。",
"MessageFileReadError": "讀取檔案時發生錯誤。",
- "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再開始密碼重置流程。",
+ "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再開始密碼重設流程。",
"MessageForgotPasswordFileCreated": "已在伺服器上建立了以下檔案,並包含有關後續步驟說明:",
"MessageNoAvailablePlugins": "沒有可用的附加元件。",
"MessageNoServersAvailable": "無法自動偵測伺服器。",
@@ -1192,7 +1192,7 @@
"MessageNoCollectionsAvailable": "分組能夠讓您享受個性化的影片、劇集和專輯。點擊+按鈕開始建立分組。",
"MessagePlayAccessRestricted": "此內容的播放受到限制,聯繫您的伺服器管理員以獲取更多訊息。",
"MessagePluginConfigurationRequiresLocalAccess": "請直接登入你的本地伺服器以設定這個附加元件。",
- "MessagePluginInstallDisclaimer": "安裝 Jellyfin 社區成員建立的附加元件來讓您的 Jellyfin 獲取額外的功能優化您的使用體驗。但可能會對你的 Jellyfin 伺服器效能造成影響,如更長的媒體庫掃描時間、額外的背景資料處理與系統穩定性降低等。",
+ "MessagePluginInstallDisclaimer": "安裝 Jellyfin 社區成員建立的附加元件來讓您的 Jellyfin 獲取額外的功能最佳化您的使用體驗。但可能會對你的 Jellyfin 伺服器效能造成影響,如更長的媒體庫掃描時間、額外的背景資料處理與系統穩定性降低等。",
"MessageReenableUser": "請參閱以下以重新啟用",
"MessageUnableToConnectToServer": "無法連上所選的伺服器,請確保該伺服器正在運作中。",
"MessageYouHaveVersionInstalled": "你目前安裝了 {0} 版本。",
@@ -1201,7 +1201,7 @@
"NoNewDevicesFound": "找不到裝置,要添加新調諧器,請關閉此對話框並手動輸入裝置訊息。",
"OnlyForcedSubtitles": "僅顯示強制字幕",
"OnlyImageFormats": "圖片格式(VOBSUB、PGS、SUB)",
- "OptionAllowLinkSharingHelp": "只有網頁包含的媒體訊息會被共享,媒體檔案本身不會被公開共享,共享的內容會在 {0} 天後到期。",
+ "OptionAllowLinkSharingHelp": "只有網頁包含的媒體訊息會被共享,媒體檔案本身不會被公開共享,共享的內容會在 {0} 天后到期。",
"OptionAllowRemoteSharedDevices": "允許遠端控制共享裝置",
"OptionAllowSyncTranscoding": "允許需要轉檔的媒體下載和同步",
"OptionAllowVideoPlaybackRemuxing": "允許播放需轉換但無需重新編碼的影片",
@@ -1245,7 +1245,7 @@
"Programs": "節目",
"Quality": "品質",
"PackageInstallFailed": "{0} (版本 {1}) 安裝失敗。",
- "Raised": "提高",
+ "Raised": "浮凹",
"Rate": "評等",
"Recordings": "錄影",
"ServerRestartNeededAfterPluginInstall": "安裝附加元件後,Jellyfin 伺服器需要重啟使其生效。",
@@ -1255,7 +1255,7 @@
"TheseSettingsAffectSubtitlesOnThisDevice": "這些設定僅影響該裝置的字幕顯示",
"TitleHardwareAcceleration": "硬體加速",
"TitleHostingSettings": "主機設定",
- "Uniform": "輪廓",
+ "Uniform": "邊框",
"Unmute": "取消靜音",
"Unplayed": "尚未播放",
"TvLibraryHelp": "查看 {0} 電視命名指南 {1} 。",
@@ -1266,7 +1266,7 @@
"LabelInNetworkSignInWithEasyPasswordHelp": "你可以在你的家庭網路中使用你的簡易 PIN 碼登錄 Jellyfin 應用程式,僅在你使用外部網路時才需要輸入密碼,如果 PIN 碼留空,那麼在你的區域網路中便不需輸入密碼。",
"LabelReleaseDate": "釋出日期:",
"LabelRemoteClientBitrateLimit": "網際網路串流傳輸位元率限制(Mbps):",
- "LanNetworksHelp": "在強制頻寬限制時,認作本地網路上的 IP 位址或 IP/子網域遮罩項目的逗號分隔清單。若設置此項,所有其它 IP 位址將被視作在外部網路上,并且將受到外部頻寬限制。如果保留爲空,則只將伺服器的子網域遮罩作本地網路。",
+ "LanNetworksHelp": "在強制頻寬限制時,認作本地網路上的 IP 位址或 IP/子網域遮罩項目的逗號分隔清單。若設置此項,所有其它 IP 位址將被視作在外部網路上,並且將受到外部頻寬限制。如果保留為空,則只將伺服器的子網域遮罩作本地網路。",
"OptionIgnoreTranscodeByteRangeRequests": "忽略轉檔位元組範圍請求",
"OptionIgnoreTranscodeByteRangeRequestsHelp": "這些請求會被接受,但會忽略的位元組範圍標頭。",
"OptionLoginAttemptsBeforeLockoutHelp": "若值為 0,則表示將允許普通使用者嘗試三次、管理員嘗試五次的預設值,設定為 -1 來停用此功能。",
@@ -1280,9 +1280,9 @@
"LabelEnableSingleImageInDidlLimitHelp": "若在 Didl 中嵌入多個圖片,某些裝置可能無法正常顯示。",
"SortByValue": "排序方式:{0}",
"LabelLineup": "排隊:",
- "LabelLocalHttpServerPortNumber": "本地 HTTP 端口:",
- "LabelLocalHttpServerPortNumberHelp": "HTTP 伺服器的 TCP 端口。",
- "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "這些設定也會被套用在任何透過此裝置發起的 Chromecast 播放。",
+ "LabelLocalHttpServerPortNumber": "本地 HTTP 埠:",
+ "LabelLocalHttpServerPortNumberHelp": "HTTP 伺服器的 TCP 埠。",
+ "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "此設定也會影響透過此裝置投放的 Chromecast。",
"LabelLoginDisclaimer": "登入字句:",
"LabelLogs": "日誌:",
"SubtitleDownloadersHelp": "按優先順序啟用並排列您的首選字幕下載程式。",
@@ -1290,7 +1290,7 @@
"SystemDlnaProfilesHelp": "系統設定檔案是唯讀的,更改系統設定檔案將被儲存到自訂的新設定檔案。",
"LabelNumber": "編號:",
"LabelNumberOfGuideDays": "下載電視指南日數:",
- "OnlyForcedSubtitlesHelp": "僅被標記為「強制」的字幕會被載入。",
+ "OnlyForcedSubtitlesHelp": "僅標記為「強制」的字幕會被載入。",
"PackageInstallCompleted": "{0} (版本 {1}) 安裝完成。",
"OptionDisplayFolderViewHelp": "在其他媒體庫旁邊顯示資料夾,對想要一個普通的資料夾檢視很有用。",
"LabelReasonForTranscoding": "轉檔原因:",
@@ -1318,7 +1318,7 @@
"LabelVaapiDevice": "VA API 裝置:",
"DashboardArchitecture": "架構:{0}",
"MediaInfoSampleRate": "採樣率",
- "MessageContactAdminToResetPassword": "請聯繫您的管理員來重置密碼。",
+ "MessageContactAdminToResetPassword": "請聯絡您的管理員來重設密碼。",
"MessageUnsetContentHelp": "內容將顯示為純資料夾,建議使用中繼資料管理器設置子資料夾的內容類型。",
"OptionAllowAudioPlaybackTranscoding": "允許播放需要轉檔的音訊",
"OptionCustomUsers": "自訂",
@@ -1336,8 +1336,8 @@
"LabelModelDescription": "型號描述",
"LabelModelName": "型號名稱",
"LabelModelUrl": "型號網址",
- "LabelMusicStreamingTranscodingBitrate": "音樂轉檔比特率:",
- "LabelMusicStreamingTranscodingBitrateHelp": "指定音樂串流時的最大比特率。",
+ "LabelMusicStreamingTranscodingBitrate": "音樂轉檔位元率:",
+ "LabelMusicStreamingTranscodingBitrateHelp": "指定音樂串流時的最大位元率。",
"LabelOptionalNetworkPathHelp": "如果這個資料夾在網路上分享,提供網路分享路徑可以供其他應用程式直接存取媒體檔案,例如 {0} 或者 {1}。",
"LabelOriginalAspectRatio": "原始長寬比:",
"LabelOverview": "內容概述:",
@@ -1361,8 +1361,8 @@
"LabelPreferredSubtitleLanguage": "字幕語言偏好:",
"LabelProtocol": "協議:",
"LabelProtocolInfo": "協議資訊:",
- "LabelPublicHttpPort": "公開 HTTP 端口:",
- "LabelPublicHttpsPort": "公開 HTTPS 端口:",
+ "LabelPublicHttpPort": "公開 HTTP 埠:",
+ "LabelPublicHttpsPort": "公開 HTTPS 埠:",
"LabelProtocolInfoHelp": "當響應來自裝置的 GetProtocolInfo(獲取協議訊息)請求時,該值將被使用。",
"LabelPublicHttpPortHelp": "公開連接埠應映射到本地 HTTP 連接埠。",
"LabelPublicHttpsPortHelp": "公開連接埠應映射到本地 HTTPS 連接埠。",
@@ -1404,20 +1404,20 @@
"HeaderFavoritePeople": "最愛人物",
"XmlDocumentAttributeListHelp": "這些屬性會在每一個 XML 回應的根元素上套用。",
"SkipEpisodesAlreadyInMyLibraryHelp": "劇集將使用季和劇集編號進行比較。",
- "SelectAdminUsername": "請為管理員賬戶選擇一個用戶名。",
+ "SelectAdminUsername": "請為管理員帳戶選擇一個使用者名稱。",
"OptionSaveMetadataAsHiddenHelp": "更改此項將套用至未來保存的中繼資料。現有中繼資料檔案將在下一次伺服器儲存它們時被更新。",
"OptionAllowRemoteSharedDevicesHelp": "DLNA裝置將被視為共享中,直至有使用者控制。",
- "OptionForceRemoteSourceTranscoding": "强制遠端轉碼(像電視直播一樣)",
+ "OptionForceRemoteSourceTranscoding": "強制遠端轉檔(像電視直播一樣)",
"MessageConfirmAppExit": "您要退出嗎?",
- "LabelVideoResolution": "視頻解析度:",
+ "LabelVideoResolution": "影片解析度:",
"LabelStreamType": "串流類型:",
"LabelPlayerDimensions": "播放器尺寸:",
"LabelDroppedFrames": "丟棄的幀:",
"LabelCorruptedFrames": "損壞的幀:",
"ButtonSplit": "分割",
- "AskAdminToCreateLibrary": "如要建立資料庫,請聯繫管理員。",
+ "AskAdminToCreateLibrary": "如要建立資料庫,請聯絡管理員。",
"NoCreatedLibraries": "看來您還未創任何媒體庫。{0}立刻創一個新的嗎?{1}",
- "ClientSettings": "客戶端設定",
+ "ClientSettings": "用戶端設定",
"AllowFfmpegThrottlingHelp": "當轉檔或重組進度遠超於目前播放進度時,將暫停轉檔節省消耗的資源。在不常跳播的時候最有效。如果遇到播放問題,請關閉此功能。",
"AllowFfmpegThrottling": "限制轉檔",
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "這將會使用內建中繼資料。",
@@ -1448,7 +1448,7 @@
"LabelLibraryPageSize": "媒體庫分頁大小:",
"LabelDeinterlaceMethod": "反交錯方法:",
"Episode": "劇集",
- "DeinterlaceMethodHelp": "選擇對隔行掃描內容進行轉碼時所用的反交錯方法。",
+ "DeinterlaceMethodHelp": "選擇對隔行掃描內容進行轉檔時所用的反交錯方法。",
"BoxSet": "套裝",
"UnsupportedPlayback": "Jellyfin 無法解密受 DRM 保護的內容,但仍然會嘗試播放所有內容。某些檔案由於被加密或包含如互動標題等不受支援的內容,在播放時可能會沒有畫面。",
"Filter": "篩選器",
@@ -1462,7 +1462,7 @@
"LabelRequireHttps": "強制 HTTPS",
"LabelStable": "穩定版",
"LabelChromecastVersion": "Chromecast 版本",
- "LabelEnableHttpsHelp": "監聽指定的 HTTPS 端口。須設定有效的證書使其生效。",
+ "LabelEnableHttpsHelp": "監聽指定的 HTTPS 埠。須設定有效的證書使其生效。",
"LabelEnableHttps": "啟用HTTPS",
"HeaderServerAddressSettings": "伺服器位置設定",
"HeaderRemoteAccessSettings": "遠端存取設定",