diff --git a/.eslintrc.js b/.eslintrc.js index ff12e198c3..a4e972c83e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,6 +44,7 @@ module.exports = { 'no-unused-vars': ['error', { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': true }], 'one-var': ['error', 'never'], 'padded-blocks': ['error', 'never'], + //'prefer-const': ['error', {'destructuring': 'all'}], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], 'semi': ['error'], 'space-before-blocks': ['error'], diff --git a/package.json b/package.json index 0a65bead49..6496239967 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "license": "GPL-2.0-or-later", "devDependencies": { "@babel/core": "^7.11.1", - "@babel/eslint-parser": "^7.11.0", - "@babel/eslint-plugin": "^7.11.0", + "@babel/eslint-parser": "^7.11.3", + "@babel/eslint-plugin": "^7.11.3", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.10.5", @@ -63,7 +63,7 @@ "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", - "hls.js": "^0.14.7", + "hls.js": "^0.14.8", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", @@ -80,7 +80,7 @@ "sortablejs": "^1.10.2", "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", - "whatwg-fetch": "^3.3.1" + "whatwg-fetch": "^3.4.0" }, "babel": { "presets": [ @@ -95,6 +95,7 @@ "src/components/alert.js", "src/components/alphaPicker/alphaPicker.js", "src/components/appFooter/appFooter.js", + "src/components/apphost.js", "src/components/autoFocuser.js", "src/components/backdrop/backdrop.js", "src/components/cardbuilder/cardBuilder.js", @@ -110,8 +111,11 @@ "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", + "src/components/guide/guide-settings.js", "src/components/homeScreenSettings/homeScreenSettings.js", "src/components/homesections/homesections.js", "src/components/htmlMediaHelper.js", @@ -125,6 +129,8 @@ "src/components/itemHelper.js", "src/components/itemidentifier/itemidentifier.js", "src/components/itemMediaInfo/itemMediaInfo.js", + "src/components/itemsrefresher.js", + "src/components/layoutManager.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/libraryoptionseditor/libraryoptionseditor.js", "src/components/listview/listview.js", @@ -136,6 +142,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", @@ -153,30 +160,45 @@ "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", "src/components/prompt/prompt.js", + "src/components/recordingcreator/recordingbutton.js", + "src/components/recordingcreator/recordingcreator.js", "src/components/recordingcreator/seriesrecordingeditor.js", "src/components/recordingcreator/recordinghelper.js", "src/components/refreshdialog/refreshdialog.js", "src/components/recordingcreator/recordingeditor.js", "src/components/recordingcreator/recordingfields.js", + "src/components/qualityOptions.js", + "src/components/remotecontrol/remotecontrol.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/plugins/htmlAudioPlayer/plugin.js", + "src/plugins/chromecastPlayer/plugin.js", + "src/components/slideshow/slideshow.js", + "src/components/sortmenu/sortmenu.js", "src/plugins/htmlVideoPlayer/plugin.js", + "src/plugins/logoScreensaver/plugin.js", + "src/plugins/playAccessValidation/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", "src/components/settingshelper.js", "src/components/shortcuts.js", + "src/components/subtitleeditor/subtitleeditor.js", + "src/components/subtitlesync/subtitlesync.js", "src/components/subtitlesettings/subtitleappearancehelper.js", "src/components/subtitlesettings/subtitlesettings.js", "src/components/syncPlay/groupSelectionMenu.js", "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/upnextdialog/upnextdialog.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", @@ -200,13 +222,16 @@ "src/controllers/music/musicplaylists.js", "src/controllers/music/musicrecommended.js", "src/controllers/music/songs.js", - "src/controllers/dashboard/mediaLibrary.js", + "src/controllers/dashboard/library.js", "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", + "src/controllers/dashboard/plugins/available/index.js", "src/controllers/dashboard/plugins/repositories/index.js", "src/controllers/dashboard/scheduledtasks/scheduledtask.js", "src/controllers/dashboard/scheduledtasks/scheduledtasks.js", @@ -218,6 +243,8 @@ "src/controllers/dashboard/users/userparentalcontrol.js", "src/controllers/dashboard/users/userpasswordpage.js", "src/controllers/dashboard/users/userprofilespage.js", + "src/controllers/home.js", + "src/controllers/list.js", "src/controllers/edititemmetadata.js", "src/controllers/favorites.js", "src/controllers/hometab.js", @@ -232,7 +259,9 @@ "src/controllers/playback/queue/index.js", "src/controllers/playback/video/index.js", "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", @@ -278,6 +307,7 @@ "src/elements/emby-tabs/emby-tabs.js", "src/elements/emby-textarea/emby-textarea.js", "src/elements/emby-toggle/emby-toggle.js", + "src/libraries/screensavermanager.js", "src/libraries/navdrawer/navdrawer.js", "src/libraries/scroller.js", "src/plugins/backdropScreensaver/plugin.js", @@ -297,11 +327,14 @@ "src/scripts/filesystem.js", "src/scripts/globalize.js", "src/scripts/imagehelper.js", + "src/scripts/itembynamedetailpage.js", "src/scripts/inputManager.js", "src/scripts/autoThemes.js", "src/scripts/themeManager.js", "src/scripts/keyboardNavigation.js", + "src/scripts/libraryMenu.js", "src/scripts/libraryBrowser.js", + "src/scripts/livetvcomponents.js", "src/scripts/mouseManager.js", "src/scripts/multiDownload.js", "src/scripts/playlists.js", diff --git a/src/bundle.js b/src/bundle.js index ae2a59f0d5..5a7ffed075 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -2,159 +2,159 @@ * require.js module definitions bundled by webpack */ // Use define from require.js not webpack's define -var _define = window.define; +const _define = window.define; // fetch -var fetch = require('whatwg-fetch'); +const fetch = require('whatwg-fetch'); _define('fetch', function() { return fetch; }); // Blurhash -var blurhash = require('blurhash'); +const blurhash = require('blurhash'); _define('blurhash', function() { return blurhash; }); // query-string -var query = require('query-string'); +const query = require('query-string'); _define('queryString', function() { return query; }); // flvjs -var flvjs = require('flv.js/dist/flv').default; +const flvjs = require('flv.js/dist/flv').default; _define('flvjs', function() { return flvjs; }); // jstree -var jstree = require('jstree'); +const jstree = require('jstree'); require('jstree/dist/themes/default/style.css'); _define('jstree', function() { return jstree; }); // jquery -var jquery = require('jquery'); +const jquery = require('jquery'); _define('jQuery', function() { return jquery; }); // hlsjs -var hlsjs = require('hls.js'); +const hlsjs = require('hls.js'); _define('hlsjs', function() { return hlsjs; }); // howler -var howler = require('howler'); +const howler = require('howler'); _define('howler', function() { return howler; }); // resize-observer-polyfill -var resize = require('resize-observer-polyfill').default; +const resize = require('resize-observer-polyfill').default; _define('resize-observer-polyfill', function() { return resize; }); // swiper -var swiper = require('swiper/js/swiper'); +const swiper = require('swiper/js/swiper'); require('swiper/css/swiper.min.css'); _define('swiper', function() { return swiper; }); // sortable -var sortable = require('sortablejs').default; +const sortable = require('sortablejs').default; _define('sortable', function() { return sortable; }); // webcomponents -var webcomponents = require('webcomponents.js/webcomponents-lite'); +const webcomponents = require('webcomponents.js/webcomponents-lite'); _define('webcomponents', function() { return webcomponents; }); // libass-wasm -var libassWasm = require('libass-wasm'); +const libassWasm = require('libass-wasm'); _define('JavascriptSubtitlesOctopus', function() { return libassWasm; }); // material-icons -var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); +const materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); _define('material-icons', function() { return materialIcons; }); // noto font -var noto = require('jellyfin-noto'); +const noto = require('jellyfin-noto'); _define('jellyfin-noto', function () { return noto; }); -var epubjs = require('epubjs'); +const epubjs = require('epubjs'); _define('epubjs', function () { return epubjs; }); // page.js -var page = require('page'); +const page = require('page'); _define('page', function() { return page; }); // core-js -var polyfill = require('@babel/polyfill/dist/polyfill'); +const polyfill = require('@babel/polyfill/dist/polyfill'); _define('polyfill', function () { return polyfill; }); // domtokenlist-shim -var classlist = require('classlist.js'); +const classlist = require('classlist.js'); _define('classlist-polyfill', function () { return classlist; }); // Date-FNS -var dateFns = require('date-fns'); +const dateFns = require('date-fns'); _define('date-fns', function () { return dateFns; }); -var dateFnsLocale = require('date-fns/locale'); +const dateFnsLocale = require('date-fns/locale'); _define('date-fns/locale', function () { return dateFnsLocale; }); -var fast_text_encoding = require('fast-text-encoding'); +const fast_text_encoding = require('fast-text-encoding'); _define('fast-text-encoding', function () { return fast_text_encoding; }); // intersection-observer -var intersection_observer = require('intersection-observer'); +const intersection_observer = require('intersection-observer'); _define('intersection-observer', function () { return intersection_observer; }); // screenfull -var screenfull = require('screenfull'); +const screenfull = require('screenfull'); _define('screenfull', function () { return screenfull; }); // headroom.js -var headroom = require('headroom.js/dist/headroom'); +const headroom = require('headroom.js/dist/headroom'); _define('headroom', function () { return headroom; }); // apiclient -var apiclient = require('jellyfin-apiclient'); +const apiclient = require('jellyfin-apiclient'); _define('apiclient', function () { return apiclient.ApiClient; diff --git a/src/components/accessSchedule/accessSchedule.template.html b/src/components/accessSchedule/accessSchedule.template.html index 493150ae5e..d89b69d9bd 100644 --- a/src/components/accessSchedule/accessSchedule.template.html +++ b/src/components/accessSchedule/accessSchedule.template.html @@ -33,7 +33,7 @@
diff --git a/src/components/actionSheet/actionSheet.js b/src/components/actionSheet/actionSheet.js index 937cd2afe5..be84cf0a06 100644 --- a/src/components/actionSheet/actionSheet.js +++ b/src/components/actionSheet/actionSheet.js @@ -9,14 +9,14 @@ import 'scrollStyles'; import 'listViewStyle'; function getOffsets(elems) { - let results = []; + const results = []; if (!document) { return results; } for (const elem of elems) { - let box = elem.getBoundingClientRect(); + const box = elem.getBoundingClientRect(); results.push({ top: box.top, @@ -34,7 +34,7 @@ function getPosition(options, dlg) { const windowHeight = windowSize.innerHeight; const windowWidth = windowSize.innerWidth; - let pos = getOffsets([options.positionTo])[0]; + const pos = getOffsets([options.positionTo])[0]; if (options.positionY !== 'top') { pos.top += (pos.height || 0) / 2; @@ -82,7 +82,7 @@ export function show(options) { // positionTo // showCancel // title - let dialogOptions = { + const dialogOptions = { removeOnClose: true, enableHistory: options.enableHistory, scrollY: false @@ -103,7 +103,7 @@ export function show(options) { dialogOptions.autoFocus = false; } - let dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); if (isFullscreen) { dlg.classList.add('actionsheet-fullscreen'); @@ -129,7 +129,7 @@ export function show(options) { } let renderIcon = false; - let icons = []; + const icons = []; let itemIcon; for (const item of options.items) { itemIcon = item.icon || (item.selected ? 'check' : null); @@ -241,7 +241,7 @@ export function show(options) { centerFocus(dlg.querySelector('.actionSheetScroller'), false, true); } - let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); + const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); if (btnCloseActionSheet) { btnCloseActionSheet.addEventListener('click', function () { dialogHelper.close(dlg); diff --git a/src/components/appRouter.js b/src/components/appRouter.js index e7b697daf4..e6bd86336b 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -1,6 +1,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdrop', 'browser', 'page', 'appSettings', 'apphost', 'connectionManager'], function (loading, globalize, events, viewManager, skinManager, backdrop, browser, page, appSettings, appHost, connectionManager) { 'use strict'; + appHost = appHost.default || appHost; viewManager = viewManager.default || viewManager; browser = browser.default || browser; loading = loading.default || loading; diff --git a/src/components/apphost.js b/src/components/apphost.js index 3ed590b546..c3e9342827 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -1,447 +1,412 @@ -define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) { - 'use strict'; +import appSettings from 'appSettings'; +import browser from 'browser'; +import events from 'events'; +import * as htmlMediaHelper from 'htmlMediaHelper'; +import * as webSettings from 'webSettings'; +import globalize from 'globalize'; - browser = browser.default || browser; +function getBaseProfileOptions(item) { + const disableHlsVideoAudioCodecs = []; - function getBaseProfileOptions(item) { - var disableHlsVideoAudioCodecs = []; + if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { + if (browser.edge) { + disableHlsVideoAudioCodecs.push('mp3'); + } - if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { - if (browser.edge) { - disableHlsVideoAudioCodecs.push('mp3'); + disableHlsVideoAudioCodecs.push('ac3'); + disableHlsVideoAudioCodecs.push('eac3'); + disableHlsVideoAudioCodecs.push('opus'); + } + + return { + enableMkvProgressive: false, + disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs + }; +} + +function getDeviceProfile(item, options = {}) { + return new Promise(function (resolve) { + import('browserdeviceprofile').then(({default: profileBuilder}) => { + let profile; + + if (window.NativeShell) { + profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); + } else { + const builderOpts = getBaseProfileOptions(item); + builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats'); + profile = profileBuilder(builderOpts); } - disableHlsVideoAudioCodecs.push('ac3'); - disableHlsVideoAudioCodecs.push('eac3'); - disableHlsVideoAudioCodecs.push('opus'); - } - - return { - enableMkvProgressive: false, - disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs - }; - } - - function getDeviceProfileForWindowsUwp(item) { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) { - var profileOptions = getBaseProfileOptions(item); - profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); - profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(); - profileOptions.audioChannels = uwpMediaCaps.getAudioChannels(); - resolve(profileBuilder(profileOptions)); - }); + resolve(profile); }); + }); +} + +function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'); +} + +function replaceAll(originalString, strReplace, strWith) { + const strReplace2 = escapeRegExp(strReplace); + const reg = new RegExp(strReplace2, 'ig'); + return originalString.replace(reg, strWith); +} + +function generateDeviceId() { + const keys = []; + + if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { + const result = replaceAll(btoa(keys.join('|')), '=', '1'); + return Promise.resolve(result); } - function getDeviceProfile(item, options) { - options = options || {}; + return Promise.resolve(new Date().getTime()); +} - if (self.Windows) { - return getDeviceProfileForWindowsUwp(item); - } +function getDeviceId() { + const key = '_deviceId2'; + const deviceId = appSettings.get(key); - return new Promise(function (resolve) { - require(['browserdeviceprofile'], function (profileBuilder) { - var profile; - - if (window.NativeShell) { - profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); - } else { - var builderOpts = getBaseProfileOptions(item); - builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats'); - profile = profileBuilder(builderOpts); - } - - resolve(profile); - }); - }); + if (deviceId) { + return Promise.resolve(deviceId); } - function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + return generateDeviceId().then(function (deviceId) { + appSettings.set(key, deviceId); + return deviceId; + }); +} + +function getDeviceName() { + var deviceName; + if (browser.tizen) { + deviceName = 'Samsung Smart TV'; + } else if (browser.web0s) { + deviceName = 'LG Smart TV'; + } else if (browser.operaTv) { + deviceName = 'Opera TV'; + } else if (browser.xboxOne) { + deviceName = 'Xbox One'; + } else if (browser.ps4) { + deviceName = 'Sony PS4'; + } else if (browser.chrome) { + deviceName = 'Chrome'; + } else if (browser.edgeChromium) { + deviceName = 'Edge Chromium'; + } else if (browser.edge) { + deviceName = 'Edge'; + } else if (browser.firefox) { + deviceName = 'Firefox'; + } else if (browser.opera) { + deviceName = 'Opera'; + } else if (browser.safari) { + deviceName = 'Safari'; + } else { + deviceName = 'Web Browser'; } - function replaceAll(originalString, strReplace, strWith) { - var strReplace2 = escapeRegExp(strReplace); - var reg = new RegExp(strReplace2, 'ig'); - return originalString.replace(reg, strWith); + if (browser.ipad) { + deviceName += ' iPad'; + } else if (browser.iphone) { + deviceName += ' iPhone'; + } else if (browser.android) { + deviceName += ' Android'; } - function generateDeviceId() { - var keys = []; + return deviceName; +} - if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { - var result = replaceAll(btoa(keys.join('|')), '=', '1'); - return Promise.resolve(result); - } - - return Promise.resolve(new Date().getTime()); +function supportsVoiceInput() { + if (!browser.tv) { + return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition; } - function getDeviceId() { - var key = '_deviceId2'; - var deviceId = appSettings.get(key); - - if (deviceId) { - return Promise.resolve(deviceId); - } - - return generateDeviceId().then(function (deviceId) { - appSettings.set(key, deviceId); - return deviceId; - }); - } - - function getDeviceName() { - var deviceName; - if (browser.tizen) { - deviceName = 'Samsung Smart TV'; - } else if (browser.web0s) { - deviceName = 'LG Smart TV'; - } else if (browser.operaTv) { - deviceName = 'Opera TV'; - } else if (browser.xboxOne) { - deviceName = 'Xbox One'; - } else if (browser.ps4) { - deviceName = 'Sony PS4'; - } else if (browser.chrome) { - deviceName = 'Chrome'; - } else if (browser.edgeChromium) { - deviceName = 'Edge Chromium'; - } else if (browser.edge) { - deviceName = 'Edge'; - } else if (browser.firefox) { - deviceName = 'Firefox'; - } else if (browser.opera) { - deviceName = 'Opera'; - } else if (browser.safari) { - deviceName = 'Safari'; - } else { - deviceName = 'Web Browser'; - } - - if (browser.ipad) { - deviceName += ' iPad'; - } else if (browser.iphone) { - deviceName += ' iPhone'; - } else if (browser.android) { - deviceName += ' Android'; - } - - return deviceName; - } - - function supportsVoiceInput() { - if (!browser.tv) { - return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition; - } + return false; +} +function supportsFullscreen() { + if (browser.tv) { return false; } - function supportsFullscreen() { - if (browser.tv) { - return false; - } + const element = document.documentElement; + return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; +} - var element = document.documentElement; - return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; - } - - function getSyncProfile() { - return new Promise(function (resolve) { - require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) { - var profile; - - if (window.NativeShell) { - profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings); - } else { - profile = profileBuilder(); - profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate(); - } - - resolve(profile); - }); - }); - } - - function getDefaultLayout() { - return 'desktop'; - } - - function supportsHtmlMediaAutoplay() { - if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) { - return true; - } - - if (browser.mobile) { - return false; - } +function getDefaultLayout() { + return 'desktop'; +} +function supportsHtmlMediaAutoplay() { + if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) { return true; } - function supportsCue() { - try { - var video = document.createElement('video'); - var style = document.createElement('style'); - - style.textContent = 'video::cue {background: inherit}'; - document.body.appendChild(style); - document.body.appendChild(video); - - var cue = window.getComputedStyle(video, '::cue').background; - document.body.removeChild(style); - document.body.removeChild(video); - - return !!cue.length; - } catch (err) { - console.error('error detecting cue support: ' + err); - return false; - } + if (browser.mobile) { + return false; } - function onAppVisible() { - if (isHidden) { - isHidden = false; - console.debug('triggering app resume event'); - events.trigger(appHost, 'resume'); - } + return true; +} + +function supportsCue() { + try { + const video = document.createElement('video'); + const style = document.createElement('style'); + + style.textContent = 'video::cue {background: inherit}'; + document.body.appendChild(style); + document.body.appendChild(video); + + const cue = window.getComputedStyle(video, '::cue').background; + document.body.removeChild(style); + document.body.removeChild(video); + + return !!cue.length; + } catch (err) { + console.error('error detecting cue support: ' + err); + return false; + } +} + +function onAppVisible() { + if (isHidden) { + isHidden = false; + console.debug('triggering app resume event'); + events.trigger(appHost, 'resume'); + } +} + +function onAppHidden() { + if (!isHidden) { + isHidden = true; + console.debug('app is hidden'); + } +} + +const supportedFeatures = function () { + const features = []; + + if (navigator.share) { + features.push('sharing'); } - function onAppHidden() { - if (!isHidden) { - isHidden = true; - console.debug('app is hidden'); - } + if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { + features.push('filedownload'); } - var supportedFeatures = function () { - var features = []; + if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { + features.push('exit'); + } else { + features.push('exitmenu'); + features.push('plugins'); + } - if (navigator.share) { - features.push('sharing'); - } + if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { + features.push('externallinks'); + features.push('externalpremium'); + } - if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { - features.push('filedownload'); - } + if (!browser.operaTv) { + features.push('externallinkdisplay'); + } - if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { - features.push('exit'); + if (supportsVoiceInput()) { + features.push('voiceinput'); + } + + if (supportsHtmlMediaAutoplay()) { + features.push('htmlaudioautoplay'); + features.push('htmlvideoautoplay'); + } + + if (browser.edgeUwp) { + features.push('sync'); + } + + if (supportsFullscreen()) { + features.push('fullscreenchange'); + } + + if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { + features.push('physicalvolumecontrol'); + } + + if (!browser.tv && !browser.xboxOne && !browser.ps4) { + features.push('remotecontrol'); + } + + if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { + features.push('remotevideo'); + } + + features.push('displaylanguage'); + features.push('otherapppromotions'); + features.push('displaymode'); + features.push('targetblank'); + features.push('screensaver'); + + webSettings.getMultiServer().then(enabled => { + if (enabled) features.push('multiserver'); + }); + + if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { + features.push('subtitleappearancesettings'); + } + + if (!browser.orsay) { + features.push('subtitleburnsettings'); + } + + if (!browser.tv && !browser.ps4 && !browser.xboxOne) { + features.push('fileinput'); + } + + if (browser.chrome || browser.edgeChromium) { + features.push('chromecast'); + } + + return features; +}(); + +/** + * Do exit according to platform + */ +function doExit() { + try { + if (window.NativeShell) { + window.NativeShell.AppHost.exit(); + } else if (browser.tizen) { + tizen.application.getCurrentApplication().exit(); + } else if (browser.web0s) { + webOS.platformBack(); } else { - features.push('exitmenu'); - features.push('plugins'); + window.close(); } + } catch (err) { + console.error('error closing application: ' + err); + } +} - if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { - features.push('externallinks'); - features.push('externalpremium'); - } +let exitPromise; - if (!browser.operaTv) { - features.push('externallinkdisplay'); - } - - if (supportsVoiceInput()) { - features.push('voiceinput'); - } - - if (supportsHtmlMediaAutoplay()) { - features.push('htmlaudioautoplay'); - features.push('htmlvideoautoplay'); - } - - if (browser.edgeUwp) { - features.push('sync'); - } - - if (supportsFullscreen()) { - features.push('fullscreenchange'); - } - - if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { - features.push('physicalvolumecontrol'); - } - - if (!browser.tv && !browser.xboxOne && !browser.ps4) { - features.push('remotecontrol'); - } - - if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { - features.push('remotevideo'); - } - - features.push('displaylanguage'); - features.push('otherapppromotions'); - features.push('displaymode'); - features.push('targetblank'); - features.push('screensaver'); - - webSettings.getMultiServer().then(enabled => { - if (enabled) features.push('multiserver'); - }); - - if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { - features.push('subtitleappearancesettings'); - } - - if (!browser.orsay) { - features.push('subtitleburnsettings'); - } - - if (!browser.tv && !browser.ps4 && !browser.xboxOne) { - features.push('fileinput'); - } - - if (browser.chrome || browser.edgeChromium) { - features.push('chromecast'); - } - - return features; - }(); - - /** - * Do exit according to platform - */ - function doExit() { - try { - if (window.NativeShell) { - window.NativeShell.AppHost.exit(); - } else if (browser.tizen) { - tizen.application.getCurrentApplication().exit(); - } else if (browser.web0s) { - webOS.platformBack(); - } else { - window.close(); - } - } catch (err) { - console.error('error closing application: ' + err); - } +/** + * Ask user for exit + */ +function askForExit() { + if (exitPromise) { + return; } - var exitPromise; - - /** - * Ask user for exit - */ - function askForExit() { - if (exitPromise) { - return; - } - - require(['actionsheet'], function (actionsheet) { - exitPromise = actionsheet.show({ - title: globalize.translate('MessageConfirmAppExit'), - items: [ - {id: 'yes', name: globalize.translate('Yes')}, - {id: 'no', name: globalize.translate('No')} - ] - }).then(function (value) { - if (value === 'yes') { - doExit(); - } - }).finally(function () { - exitPromise = null; - }); - }); - } - - var deviceId; - var deviceName; - var appName = 'Jellyfin Web'; - var appVersion = '10.7.0'; - - var appHost = { - getWindowState: function () { - return document.windowState || 'Normal'; - }, - setWindowState: function (state) { - alert('setWindowState is not supported and should not be called'); - }, - exit: function () { - if (!!window.appMode && browser.tizen) { - askForExit(); - } else { + import('actionsheet').then(({default: actionsheet}) => { + exitPromise = actionsheet.show({ + title: globalize.translate('MessageConfirmAppExit'), + items: [ + {id: 'yes', name: globalize.translate('Yes')}, + {id: 'no', name: globalize.translate('No')} + ] + }).then(function (value) { + if (value === 'yes') { doExit(); } - }, - supports: function (command) { - if (window.NativeShell) { - return window.NativeShell.AppHost.supports(command); - } + }).finally(function () { + exitPromise = null; + }); + }); +} - return supportedFeatures.indexOf(command.toLowerCase()) !== -1; - }, - preferVisualCards: browser.android || browser.chrome, - getSyncProfile: getSyncProfile, - getDefaultLayout: function () { - if (window.NativeShell) { - return window.NativeShell.AppHost.getDefaultLayout(); - } +let deviceId; +let deviceName; +const appName = 'Jellyfin Web'; +const appVersion = '10.7.0'; - return getDefaultLayout(); - }, - getDeviceProfile: getDeviceProfile, - init: function () { - if (window.NativeShell) { - return window.NativeShell.AppHost.init(); - } - - deviceName = getDeviceName(); - getDeviceId().then(function (id) { - deviceId = id; - }); - }, - deviceName: function () { - return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName; - }, - deviceId: function () { - return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId; - }, - appName: function () { - return window.NativeShell ? window.NativeShell.AppHost.appName() : appName; - }, - appVersion: function () { - return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion; - }, - getPushTokenInfo: function () { - return {}; - }, - setUserScalable: function (scalable) { - if (!browser.tv) { - var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; - document.querySelector('meta[name=viewport]').setAttribute('content', att); - } - } - }; - - var isHidden = false; - var hidden; - var visibilityChange; - - if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */ - hidden = 'hidden'; - visibilityChange = 'visibilitychange'; - } else if (typeof document.webkitHidden !== 'undefined') { - hidden = 'webkitHidden'; - visibilityChange = 'webkitvisibilitychange'; - } - - document.addEventListener(visibilityChange, function () { - /* eslint-disable-next-line compat/compat */ - if (document[hidden]) { - onAppHidden(); +const appHost = { + getWindowState: function () { + return document.windowState || 'Normal'; + }, + setWindowState: function () { + alert('setWindowState is not supported and should not be called'); + }, + exit: function () { + if (!!window.appMode && browser.tizen) { + askForExit(); } else { - onAppVisible(); + doExit(); + } + }, + supports: function (command) { + if (window.NativeShell) { + return window.NativeShell.AppHost.supports(command); } - }, false); - if (self.addEventListener) { - self.addEventListener('focus', onAppVisible); - self.addEventListener('blur', onAppHidden); + return supportedFeatures.indexOf(command.toLowerCase()) !== -1; + }, + preferVisualCards: browser.android || browser.chrome, + getDefaultLayout: function () { + if (window.NativeShell) { + return window.NativeShell.AppHost.getDefaultLayout(); + } + + return getDefaultLayout(); + }, + getDeviceProfile: getDeviceProfile, + init: function () { + if (window.NativeShell) { + return window.NativeShell.AppHost.init(); + } + + deviceName = getDeviceName(); + getDeviceId().then(function (id) { + deviceId = id; + }); + }, + deviceName: function () { + return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName; + }, + deviceId: function () { + return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId; + }, + appName: function () { + return window.NativeShell ? window.NativeShell.AppHost.appName() : appName; + }, + appVersion: function () { + return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion; + }, + getPushTokenInfo: function () { + return {}; + }, + setUserScalable: function (scalable) { + if (!browser.tv) { + const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; + document.querySelector('meta[name=viewport]').setAttribute('content', att); + } } +}; - return appHost; -}); +let isHidden = false; +let hidden; +let visibilityChange; + +if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */ + hidden = 'hidden'; + visibilityChange = 'visibilitychange'; +} else if (typeof document.webkitHidden !== 'undefined') { + hidden = 'webkitHidden'; + visibilityChange = 'webkitvisibilitychange'; +} + +document.addEventListener(visibilityChange, function () { + /* eslint-disable-next-line compat/compat */ + if (document[hidden]) { + onAppHidden(); + } else { + onAppVisible(); + } +}, false); + +if (self.addEventListener) { + self.addEventListener('focus', onAppVisible); + self.addEventListener('blur', onAppHidden); +} + +export default appHost; diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 4a37331ef4..e644365906 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -362,12 +362,12 @@ import 'programStyles'; let hasOpenRow; let hasOpenSection; - let sectionTitleTagName = options.sectionTitleTagName || 'div'; + const sectionTitleTagName = options.sectionTitleTagName || 'div'; let apiClient; let lastServerId; for (const [i, item] of items.entries()) { - let serverId = item.ServerId || options.serverId; + const serverId = item.ServerId || options.serverId; if (serverId !== lastServerId) { lastServerId = serverId; @@ -621,7 +621,7 @@ import 'programStyles'; }); } - let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; + const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; return { imgUrl: imgUrl, @@ -656,7 +656,7 @@ import 'programStyles'; for (let i = 0; i < character.length; i++) { sum += parseInt(character.charAt(i)); } - let index = String(sum).substr(-1); + const index = String(sum).substr(-1); return (index % numRandomColors) + 1; } else { @@ -682,7 +682,7 @@ import 'programStyles'; for (let i = 0; i < lines.length; i++) { let currentCssClass = cssClass; - let text = lines[i]; + const text = lines[i]; if (valid > 0 && isOuterFooter) { currentCssClass += ' cardText-secondary'; @@ -707,7 +707,7 @@ import 'programStyles'; } if (forceLines) { - let linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); + const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); while (valid < linesLength) { html += "
 
"; @@ -1036,7 +1036,7 @@ import 'programStyles'; * @returns {string} HTML markup for the item count indicator. */ function getItemCountsHtml(options, item) { - let counts = []; + const counts = []; let childText; if (item.Type === 'Playlist') { @@ -1318,7 +1318,7 @@ import 'programStyles'; let cardBoxClose = ''; let cardScalableClose = ''; - let cardContentClass = 'cardContent'; + const cardContentClass = 'cardContent'; let blurhashAttrib = ''; if (blurhash && blurhash.length > 0) { @@ -1337,7 +1337,7 @@ import 'programStyles'; cardImageContainerClose = ''; } - let cardScalableClass = 'cardScalable'; + const cardScalableClass = 'cardScalable'; cardImageContainerOpen = '
' + cardImageContainerOpen; cardBoxClose = '
'; @@ -1681,7 +1681,7 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); for (let i = 0, length = cells.length; i < length; i++) { - let cell = cells[i]; + const cell = cells[i]; const icon = cell.querySelector('.timerIndicator'); if (!icon) { const indicatorsElem = ensureIndicators(cell); @@ -1700,8 +1700,8 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); for (let i = 0; i < cells.length; i++) { - let cell = cells[i]; - let icon = cell.querySelector('.timerIndicator'); + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); if (icon) { icon.parentNode.removeChild(icon); } @@ -1718,8 +1718,8 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); for (let i = 0; i < cells.length; i++) { - let cell = cells[i]; - let icon = cell.querySelector('.timerIndicator'); + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); if (icon) { icon.parentNode.removeChild(icon); } diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index 77643791ad..a6f664149c 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -391,11 +391,8 @@ import 'scrollStyles'; dlg.setAttribute('data-autofocus', 'true'); } - let defaultEntryAnimation; - let defaultExitAnimation; - - defaultEntryAnimation = 'scaleup'; - defaultExitAnimation = 'scaledown'; + const defaultEntryAnimation = 'scaleup'; + const defaultExitAnimation = 'scaledown'; const entryAnimation = options.entryAnimation || defaultEntryAnimation; const exitAnimation = options.exitAnimation || defaultExitAnimation; diff --git a/src/components/filterdialog/filterdialog.template.html b/src/components/filterdialog/filterdialog.template.html index 20f0f2fc91..f4bbfe7395 100644 --- a/src/components/filterdialog/filterdialog.template.html +++ b/src/components/filterdialog/filterdialog.template.html @@ -1,6 +1,6 @@
-
+
-
+
-
+
diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 37fb66e0d9..637214a878 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -1,223 +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'; - focusManager = focusManager.default || focusManager; +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'; - function onSubmit(e) { - e.preventDefault(); - return false; +function onSubmit(e) { + e.preventDefault(); + return false; +} +function renderOptions(context, selector, cssClass, items, isCheckedFn) { + var elem = context.querySelector(selector); + + 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 { @@ -241,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 { @@ -253,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); }); @@ -268,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) { @@ -278,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/guide/guide-settings.js b/src/components/guide/guide-settings.js index a644c9c9b0..35f0d3e06e 100644 --- a/src/components/guide/guide-settings.js +++ b/src/components/guide/guide-settings.js @@ -1,149 +1,149 @@ -define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'emby-radio', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import layoutManager from 'layoutManager'; +import scrollHelper from 'scrollHelper'; +import 'emby-checkbox'; +import 'emby-radio'; +import 'css!./../formdialog'; +import 'material-icons'; - scrollHelper = scrollHelper.default || scrollHelper; +function saveCategories(context, options) { + const categories = []; - function saveCategories(context, options) { - var categories = []; + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (const chkCategory of chkCategorys) { + const type = chkCategory.getAttribute('data-type'); - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); - - if (chkCategorys[i].checked) { - categories.push(type); - } - } - - if (categories.length >= 4) { - categories.push('series'); - } - - // differentiate between none and all - categories.push('all'); - options.categories = categories; - } - - function loadCategories(context, options) { - var selectedCategories = options.categories || []; - - var chkCategorys = context.querySelectorAll('.chkCategory'); - for (var i = 0, length = chkCategorys.length; i < length; i++) { - var type = chkCategorys[i].getAttribute('data-type'); - - chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + if (chkCategory.checked) { + categories.push(type); } } - function save(context) { - var i; - var length; + if (categories.length >= 4) { + categories.push('series'); + } - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); - userSettings.set('guide-indicator-' + type, chkIndicators[i].checked); + // differentiate between none and all + categories.push('all'); + options.categories = categories; +} + +function loadCategories(context, options) { + const selectedCategories = options.categories || []; + + const chkCategorys = context.querySelectorAll('.chkCategory'); + for (const chkCategory of chkCategorys) { + const type = chkCategory.getAttribute('data-type'); + + chkCategory.checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1; + } +} + +function save(context) { + const chkIndicators = context.querySelectorAll('.chkIndicator'); + + for (const chkIndicator of chkIndicators) { + const type = chkIndicator.getAttribute('data-type'); + userSettings.set('guide-indicator-' + type, chkIndicator.checked); + } + + userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); + userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); + + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (const sortBy of sortBys) { + if (sortBy.checked) { + userSettings.set('livetv-channelorder', sortBy.value); + break; } + } +} - userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked); - userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked); +function load(context) { + const chkIndicators = context.querySelectorAll('.chkIndicator'); - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - if (sortBys[i].checked) { - userSettings.set('livetv-channelorder', sortBys[i].value); - break; - } + for (const chkIndicator of chkIndicators) { + const type = chkIndicator.getAttribute('data-type'); + + if (chkIndicator.getAttribute('data-default') === 'true') { + chkIndicator.checked = userSettings.get('guide-indicator-' + type) !== 'false'; + } else { + chkIndicator.checked = userSettings.get('guide-indicator-' + type) === 'true'; } } - function load(context) { - var i; - var length; + context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; - var chkIndicators = context.querySelectorAll('.chkIndicator'); - for (i = 0, length = chkIndicators.length; i < length; i++) { - var type = chkIndicators[i].getAttribute('data-type'); + const sortByValue = userSettings.get('livetv-channelorder') || 'Number'; - if (chkIndicators[i].getAttribute('data-default') === 'true') { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false'; + const sortBys = context.querySelectorAll('.chkSortOrder'); + for (const sortBy of sortBys) { + sortBy.checked = sortBy.value === sortByValue; + } +} + +function showEditor(options) { + return new Promise(function (resolve, reject) { + let settingsChanged = false; + + import('text!./guide-settings.template.html').then(({ default: template }) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true'; + dialogOptions.size = 'small'; } - } - context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false'; + const dlg = dialogHelper.createDialog(dialogOptions); - var sortByValue = userSettings.get('livetv-channelorder') || 'Number'; + dlg.classList.add('formDialog'); - var sortBys = context.querySelectorAll('.chkSortOrder'); - for (i = 0, length = sortBys.length; i < length; i++) { - sortBys[i].checked = sortBys[i].value === sortByValue; - } - } + let html = ''; - function showEditor(options) { - return new Promise(function (resolve, reject) { - var settingsChanged = false; + html += globalize.translateHtml(template, 'core'); - require(['text!./guide-settings.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + dlg.innerHTML = html; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - dlg.addEventListener('change', function () { - settingsChanged = true; - }); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - save(dlg); - saveCategories(dlg, options); - - if (settingsChanged) { - resolve(); - } else { - reject(); - } - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - load(dlg); - loadCategories(dlg, options); - dialogHelper.open(dlg); + dlg.addEventListener('change', function () { + settingsChanged = true; }); - }); - } - return { - show: showEditor - }; -}); + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + save(dlg); + saveCategories(dlg, options); + + if (settingsChanged) { + resolve(); + } else { + reject(); + } + }); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } + + load(dlg); + loadCategories(dlg, options); + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 05fa2b608d..a7b32d887d 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1,1181 +1,1198 @@ -define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { - 'use strict'; +import inputManager from 'inputManager'; +import browser from 'browser'; +import globalize from 'globalize'; +import connectionManager from 'connectionManager'; +import scrollHelper from 'scrollHelper'; +import serverNotifications from 'serverNotifications'; +import loading from 'loading'; +import datetime from 'datetime'; +import focusManager from 'focusManager'; +import playbackManager from 'playbackManager'; +import * as userSettings from 'userSettings'; +import imageLoader from 'imageLoader'; +import events from 'events'; +import layoutManager from 'layoutManager'; +import itemShortcuts from 'itemShortcuts'; +import dom from 'dom'; +import 'css!./guide.css'; +import 'programStyles'; +import 'material-icons'; +import 'scrollStyles'; +import 'emby-programcell'; +import 'emby-button'; +import 'paper-icon-button-light'; +import 'emby-tabs'; +import 'emby-scroller'; +import 'flexStyles'; +import 'webcomponents'; - playbackManager = playbackManager.default || playbackManager; - browser = browser.default || browser; - loading = loading.default || loading; - focusManager = focusManager.default || focusManager; - scrollHelper = scrollHelper.default || scrollHelper; - serverNotifications = serverNotifications.default || serverNotifications; - - function showViewSettings(instance) { - require(['guide-settings-dialog'], function (guideSettingsDialog) { - guideSettingsDialog.show(instance.categoryOptions).then(function () { - instance.refresh(); - }); +function showViewSettings(instance) { + import('guide-settings-dialog').then(({default: guideSettingsDialog}) => { + guideSettingsDialog.show(instance.categoryOptions).then(function () { + instance.refresh(); }); + }); +} + +function updateProgramCellOnScroll(cell, scrollPct) { + let left = cell.posLeft; + if (!left) { + left = parseFloat(cell.style.left.replace('%', '')); + cell.posLeft = left; + } + let width = cell.posWidth; + if (!width) { + width = parseFloat(cell.style.width.replace('%', '')); + cell.posWidth = width; } - function updateProgramCellOnScroll(cell, scrollPct) { - var left = cell.posLeft; - if (!left) { - left = parseFloat(cell.style.left.replace('%', '')); - cell.posLeft = left; - } - var width = cell.posWidth; - if (!width) { - width = parseFloat(cell.style.width.replace('%', '')); - cell.posWidth = width; - } + const right = left + width; + const newPct = Math.max(Math.min(scrollPct, right), left); - var right = left + width; - var newPct = Math.max(Math.min(scrollPct, right), left); + const offset = newPct - left; + const pctOfWidth = (offset / width) * 100; - var offset = newPct - left; - var pctOfWidth = (offset / width) * 100; - - var guideProgramName = cell.guideProgramName; - if (!guideProgramName) { - guideProgramName = cell.querySelector('.guideProgramName'); - cell.guideProgramName = guideProgramName; - } - - var caret = cell.caret; - if (!caret) { - caret = cell.querySelector('.guide-programNameCaret'); - cell.caret = caret; - } - - if (guideProgramName) { - if (pctOfWidth > 0 && pctOfWidth <= 100) { - guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; - caret.classList.remove('hide'); - } else { - guideProgramName.style.transform = 'none'; - caret.classList.add('hide'); - } - } + let guideProgramName = cell.guideProgramName; + if (!guideProgramName) { + guideProgramName = cell.querySelector('.guideProgramName'); + cell.guideProgramName = guideProgramName; } - var isUpdatingProgramCellScroll = false; - function updateProgramCellsOnScroll(programGrid, programCells) { - if (isUpdatingProgramCellScroll) { - return; - } - - isUpdatingProgramCellScroll = true; - - requestAnimationFrame(function () { - var scrollLeft = programGrid.scrollLeft; - - var scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - - for (var i = 0, length = programCells.length; i < length; i++) { - updateProgramCellOnScroll(programCells[i], scrollPct); - } - - isUpdatingProgramCellScroll = false; - }); + let caret = cell.caret; + if (!caret) { + caret = cell.querySelector('.guide-programNameCaret'); + cell.caret = caret; } - function onProgramGridClick(e) { - if (!layoutManager.tv) { - return; - } - - var programCell = dom.parentWithClass(e.target, 'programCell'); - if (programCell) { - var startDate = programCell.getAttribute('data-startdate'); - var endDate = programCell.getAttribute('data-enddate'); - startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); - endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); - - var now = new Date().getTime(); - if (now >= startDate && now < endDate) { - var channelId = programCell.getAttribute('data-channelid'); - var serverId = programCell.getAttribute('data-serverid'); - - e.preventDefault(); - e.stopPropagation(); - - playbackManager.play({ - ids: [channelId], - serverId: serverId - }); - } + if (guideProgramName) { + if (pctOfWidth > 0 && pctOfWidth <= 100) { + guideProgramName.style.transform = 'translateX(' + pctOfWidth + '%)'; + caret.classList.remove('hide'); + } else { + guideProgramName.style.transform = 'none'; + caret.classList.add('hide'); } } +} - function Guide(options) { - var self = this; - var items = {}; +let isUpdatingProgramCellScroll = false; +function updateProgramCellsOnScroll(programGrid, programCells) { + if (isUpdatingProgramCellScroll) { + return; + } - self.options = options; - self.categoryOptions = { categories: [] }; + isUpdatingProgramCellScroll = true; - // 30 mins - var cellCurationMinutes = 30; - var cellDurationMs = cellCurationMinutes * 60 * 1000; - var msPerDay = 86400000; + requestAnimationFrame(function () { + const scrollLeft = programGrid.scrollLeft; - var currentDate; - var currentStartIndex = 0; - var currentChannelLimit = 0; - var autoRefreshInterval; - var programCells; - var lastFocusDirection; - var programGrid; + const scrollPct = scrollLeft ? (scrollLeft / programGrid.scrollWidth) * 100 : 0; - self.refresh = function () { - currentDate = null; - reloadPage(options.element); - restartAutoRefresh(); - }; - - self.pause = function () { - stopAutoRefresh(); - }; - - self.resume = function (refreshData) { - if (refreshData) { - self.refresh(); - } else { - restartAutoRefresh(); - } - }; - - self.destroy = function () { - stopAutoRefresh(); - - events.off(serverNotifications, 'TimerCreated', onTimerCreated); - events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); - - setScrollEvents(options.element, false); - itemShortcuts.off(options.element); - items = {}; - }; - - function restartAutoRefresh() { - stopAutoRefresh(); - - var intervalMs = 60000 * 15; // (minutes) - - autoRefreshInterval = setInterval(function () { - self.refresh(); - }, intervalMs); + for (const programCell of programCells) { + updateProgramCellOnScroll(programCell, scrollPct); } - function stopAutoRefresh() { - if (autoRefreshInterval) { - clearInterval(autoRefreshInterval); - autoRefreshInterval = null; - } - } - - function normalizeDateToTimeslot(date) { - var minutesOffset = date.getMinutes() - cellCurationMinutes; - - if (minutesOffset >= 0) { - date.setHours(date.getHours(), cellCurationMinutes, 0, 0); - } else { - date.setHours(date.getHours(), 0, 0, 0); - } - - return date; - } - - function showLoading() { - loading.show(); - } - - function hideLoading() { - loading.hide(); - } - - function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var apiClient = connectionManager.getApiClient(options.serverId); - - var channelQuery = { - - StartIndex: 0, - EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' - }; - - channelQuery.UserId = apiClient.getCurrentUserId(); - - var channelLimit = 500; - currentChannelLimit = channelLimit; - - showLoading(); - - channelQuery.StartIndex = currentStartIndex; - channelQuery.Limit = channelLimit; - channelQuery.AddCurrentProgram = false; - channelQuery.EnableUserData = false; - channelQuery.EnableImageTypes = 'Primary'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - - if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { - channelQuery.IsMovie = null; - channelQuery.IsSports = null; - channelQuery.IsKids = null; - channelQuery.IsNews = null; - channelQuery.IsSeries = null; - } else { - if (displayNewsContent) { - channelQuery.IsNews = true; - } - if (displaySportsContent) { - channelQuery.IsSports = true; - } - if (displayKidsContent) { - channelQuery.IsKids = true; - } - if (displayMovieContent) { - channelQuery.IsMovie = true; - } - if (displaySeriesContent) { - channelQuery.IsSeries = true; - } - } - - if (userSettings.get('livetv-channelorder') === 'DatePlayed') { - channelQuery.SortBy = 'DatePlayed'; - channelQuery.SortOrder = 'Descending'; - } else { - channelQuery.SortBy = null; - channelQuery.SortOrder = null; - } - - var date = newStartDate; - // Add one second to avoid getting programs that are just ending - date = new Date(date.getTime() + 1000); - - // Subtract to avoid getting programs that are starting when the grid ends - var nextDay = new Date(date.getTime() + msPerDay - 2000); - - // Normally we'd want to just let responsive css handle this, - // but since mobile browsers are often underpowered, - // it can help performance to get them out of the markup - var allowIndicators = dom.getWindowSize().innerWidth >= 600; - - var renderOptions = { - showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', - showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', - showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', - showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', - showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', - showEpisodeTitle: layoutManager.tv ? false : true - }; - - apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { - var btnPreviousPage = context.querySelector('.btnPreviousPage'); - var btnNextPage = context.querySelector('.btnNextPage'); - - if (channelsResult.TotalRecordCount > channelLimit) { - context.querySelector('.guideOptions').classList.remove('hide'); - - btnPreviousPage.classList.remove('hide'); - btnNextPage.classList.remove('hide'); - - if (channelQuery.StartIndex) { - context.querySelector('.btnPreviousPage').disabled = false; - } else { - context.querySelector('.btnPreviousPage').disabled = true; - } - - if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { - btnNextPage.disabled = false; - } else { - btnNextPage.disabled = true; - } - } else { - context.querySelector('.guideOptions').classList.add('hide'); - } - - var programFields = []; - - var programQuery = { - UserId: apiClient.getCurrentUserId(), - MaxStartDate: nextDay.toISOString(), - MinEndDate: date.toISOString(), - channelIds: channelsResult.Items.map(function (c) { - return c.Id; - }).join(','), - ImageTypeLimit: 1, - EnableImages: false, - //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", - SortBy: 'StartDate', - EnableTotalRecordCount: false, - EnableUserData: false - }; - - if (renderOptions.showHdIcon) { - programFields.push('IsHD'); - } - - if (programFields.length) { - programQuery.Fields = programFields.join(''); - } - - apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { - renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - - hideLoading(); - }); - }); - } - - function getDisplayTime(date) { - if ((typeof date).toString().toLowerCase() === 'string') { - try { - date = datetime.parseISO8601Date(date, { toLocal: true }); - } catch (err) { - return date; - } - } - - return datetime.getDisplayTime(date).toLowerCase(); - } - - function getTimeslotHeadersHtml(startDate, endDateTime) { - var html = ''; - - // clone - startDate = new Date(startDate.getTime()); - - html += '
'; - - while (startDate.getTime() < endDateTime) { - html += '
'; - - html += getDisplayTime(startDate); - html += '
'; - - // Add 30 mins - startDate.setTime(startDate.getTime() + cellDurationMs); - } - - return html; - } - - function parseDates(program) { - if (!program.StartDateLocal) { - try { - program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for start date'); - } - } - - if (!program.EndDateLocal) { - try { - program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); - } catch (err) { - console.error('error parsing timestamp for end date'); - } - } - - return null; - } - - function getTimerIndicator(item) { - var status; - - if (item.Type === 'SeriesTimer') { - return ''; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; - } else { - return ''; - } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return ''; - } - - return ''; - } - - return ''; - } - - function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { - var html = ''; - - var startMs = date.getTime(); - var endMs = startMs + msPerDay - 1; - - var outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; - - html += '
'; - - var clickAction = layoutManager.tv ? 'link' : 'programdialog'; - - var categories = self.categoryOptions.categories || []; - var displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; - var displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; - var displayNewsContent = !categories.length || categories.indexOf('news') !== -1; - var displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; - var displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; - var enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; - - var programsFound; - var now = new Date().getTime(); - - for (var i = listInfo.startIndex, length = programs.length; i < length; i++) { - var program = programs[i]; - - if (program.ChannelId !== channel.Id) { - if (programsFound) { - break; - } - - continue; - } - - programsFound = true; - listInfo.startIndex++; - - parseDates(program); - - var startDateLocalMs = program.StartDateLocal.getTime(); - var endDateLocalMs = program.EndDateLocal.getTime(); - - if (endDateLocalMs < startMs) { - continue; - } - - if (startDateLocalMs > endMs) { - break; - } - - items[program.Id] = program; - - var renderStartMs = Math.max(startDateLocalMs, startMs); - var startPercent = (startDateLocalMs - startMs) / msPerDay; - startPercent *= 100; - startPercent = Math.max(startPercent, 0); - - var renderEndMs = Math.min(endDateLocalMs, endMs); - var endPercent = (renderEndMs - renderStartMs) / msPerDay; - endPercent *= 100; - - var cssClass = 'programCell itemAction'; - var accentCssClass = null; - var displayInnerContent = true; - - if (program.IsKids) { - displayInnerContent = displayKidsContent; - accentCssClass = 'kids'; - } else if (program.IsSports) { - displayInnerContent = displaySportsContent; - accentCssClass = 'sports'; - } else if (program.IsNews) { - displayInnerContent = displayNewsContent; - accentCssClass = 'news'; - } else if (program.IsMovie) { - displayInnerContent = displayMovieContent; - accentCssClass = 'movie'; - } else if (program.IsSeries) { - displayInnerContent = displaySeriesContent; - } else { - displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; - } - - if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { - cssClass += ' programCell-' + accentCssClass; - } - - if (now >= startDateLocalMs && now < endDateLocalMs) { - cssClass += ' programCell-active'; - } - - var timerAttributes = ''; - if (program.TimerId) { - timerAttributes += ' data-timerid="' + program.TimerId + '"'; - } - if (program.SeriesTimerId) { - timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; - } - - var isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; - - html += ''; - - if (displayInnerContent) { - var guideProgramNameClass = 'guideProgramName'; - - html += '
'; - - html += '
'; - - html += '
' + program.Name; - - var indicatorHtml = null; - if (program.IsLive && options.showLiveIndicator) { - indicatorHtml = '' + globalize.translate('Live') + ''; - } else if (program.IsPremiere && options.showPremiereIndicator) { - indicatorHtml = '' + globalize.translate('Premiere') + ''; - } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { - indicatorHtml = '' + globalize.translate('AttributeNew') + ''; - } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { - indicatorHtml = '' + globalize.translate('Repeat') + ''; - } - html += indicatorHtml || ''; - - if ((program.EpisodeTitle && options.showEpisodeTitle)) { - html += '
'; - - if (program.EpisodeTitle && options.showEpisodeTitle) { - html += '' + program.EpisodeTitle + ''; - } - html += '
'; - } - - html += '
'; - - if (program.IsHD && options.showHdIcon) { - if (layoutManager.tv) { - html += '
HD
'; - } else { - html += '
HD
'; - } - } - - html += getTimerIndicator(program); - - html += '
'; - } - - html += ''; - } - - html += '
'; - - return html; - } - - function renderChannelHeaders(context, channels, apiClient) { - var html = ''; - - for (var i = 0, length = channels.length; i < length; i++) { - var channel = channels[i]; - var hasChannelImage = channel.ImageTags.Primary; - - var cssClass = 'guide-channelHeaderCell itemAction'; - - if (layoutManager.tv) { - cssClass += ' guide-channelHeaderCell-tv'; - } - - var title = []; - if (channel.ChannelNumber) { - title.push(channel.ChannelNumber); - } - if (channel.Name) { - title.push(channel.Name); - } - - html += ''; - } - - var channelList = context.querySelector('.channelsContainer'); - channelList.innerHTML = html; - imageLoader.lazyChildren(channelList); - } - - function renderPrograms(context, date, channels, programs, options) { - var listInfo = { - startIndex: 0 - }; - - var html = []; - - for (var i = 0, length = channels.length; i < length; i++) { - html.push(getChannelProgramsHtml(context, date, channels[i], programs, options, listInfo)); - } - - programGrid.innerHTML = html.join(''); - - programCells = programGrid.querySelectorAll('[is=emby-programcell]'); - - updateProgramCellsOnScroll(programGrid, programCells); - } - - function getProgramSortOrder(program, channels) { - var channelId = program.ChannelId; - var channelIndex = -1; - - for (var i = 0, length = channels.length; i < length; i++) { - if (channelId === channels[i].Id) { - channelIndex = i; - break; - } - } - - var start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); - - return (channelIndex * 10000000) + (start.getTime() / 60000); - } - - function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - programs.sort(function (a, b) { - return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); - }); - - var activeElement = document.activeElement; - var itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; - var channelRowId = null; - - if (activeElement) { - channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); - channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; - } - - renderChannelHeaders(context, channels, apiClient); - - var startDate = date; - var endDate = new Date(startDate.getTime() + msPerDay); - context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); - items = {}; - renderPrograms(context, date, channels, programs, renderOptions); - - if (focusProgramOnRender) { - focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); - } - - scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); - } - - function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { - scrollToTimeMs -= startTimeOfDayMs; - - var pct = scrollToTimeMs / msPerDay; - - programGrid.scrollTop = 0; - - var scrollPos = pct * programGrid.scrollWidth; - - nativeScrollTo(programGrid, scrollPos, true); - } - - function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { - var focusElem; - if (itemId) { - focusElem = context.querySelector('[data-id="' + itemId + '"]'); - } - - if (focusElem) { - focusManager.focus(focusElem); - } else { - var autoFocusParent; - - if (channelRowId) { - autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); - } - - if (!autoFocusParent) { - autoFocusParent = programGrid; - } - - focusToTimeMs -= startTimeOfDayMs; - - var pct = (focusToTimeMs / msPerDay) * 100; - - var programCell = autoFocusParent.querySelector('.programCell'); - - while (programCell) { - var left = (programCell.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (programCell.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if (left >= pct || (left + width) >= pct) { - break; - } - programCell = programCell.nextSibling; - } - - if (programCell) { - focusManager.focus(programCell); - } else { - focusManager.autoFocus(autoFocusParent, true); - } - } - } - - function nativeScrollTo(container, pos, horizontal) { - if (container.scrollTo) { - if (horizontal) { - container.scrollTo(pos, 0); - } else { - container.scrollTo(0, pos); - } - } else { - if (horizontal) { - container.scrollLeft = Math.round(pos); - } else { - container.scrollTop = Math.round(pos); - } - } - } - - var lastGridScroll = 0; - var lastHeaderScroll = 0; - var scrollXPct = 0; - function onProgramGridScroll(context, elem, timeslotHeaders) { - if ((new Date().getTime() - lastHeaderScroll) >= 1000) { - lastGridScroll = new Date().getTime(); - - var scrollLeft = elem.scrollLeft; - scrollXPct = (scrollLeft * 100) / elem.scrollWidth; - nativeScrollTo(timeslotHeaders, scrollLeft, true); - } - - updateProgramCellsOnScroll(elem, programCells); - } - - function onTimeslotHeadersScroll(context, elem) { - if ((new Date().getTime() - lastGridScroll) >= 1000) { - lastHeaderScroll = new Date().getTime(); - nativeScrollTo(programGrid, elem.scrollLeft, true); - } - } - - function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { - var newStartDate = normalizeDateToTimeslot(date); - currentDate = newStartDate; - - reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); - } - - function getDateTabText(date, isActive, tabIndex) { - var cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; - - var html = ''; - - return html; - } - - function setDateRange(page, guideInfo) { - var today = new Date(); - var nowHours = today.getHours(); - today.setHours(nowHours, 0, 0, 0); - - var start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); - var end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); - - start.setHours(nowHours, 0, 0, 0); - end.setHours(0, 0, 0, 0); - - if (start.getTime() >= end.getTime()) { - end.setDate(start.getDate() + 1); - } - - start = new Date(Math.max(today, start)); - - var dateTabsHtml = ''; - var tabIndex = 0; - - // TODO: Use date-fns - var date = new Date(); - - if (currentDate) { - date.setTime(currentDate.getTime()); - } - - date.setHours(nowHours, 0, 0, 0); - - var startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += start.getMinutes() * 60 * 1000; - - while (start <= end) { - var isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); - - dateTabsHtml += getDateTabText(start, isActive, tabIndex); - - start.setDate(start.getDate() + 1); - start.setHours(0, 0, 0, 0); - tabIndex++; - } - - page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; - page.querySelector('.guideDateTabs').refresh(); - - var newDate = new Date(); - var newDateHours = newDate.getHours(); - var scrollToTimeMs = newDateHours * 60 * 60 * 1000; - - var minutes = newDate.getMinutes(); - if (minutes >= 30) { - scrollToTimeMs += 30 * 60 * 1000; - } - - var focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; - changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); - } - - function reloadPage(page) { - showLoading(); - - var apiClient = connectionManager.getApiClient(options.serverId); - - apiClient.getLiveTvGuideInfo().then(function (guideInfo) { - setDateRange(page, guideInfo); - }); - } - - function getChannelProgramsFocusableElements(container) { - var elements = container.querySelectorAll('.programCell'); - - var list = []; - // add 1 to avoid programs that are out of view to the left - var currentScrollXPct = scrollXPct + 1; - - for (var i = 0, length = elements.length; i < length; i++) { - var elem = elements[i]; - - var left = (elem.style.left || '').replace('%', ''); - left = left ? parseFloat(left) : 0; - var width = (elem.style.width || '').replace('%', ''); - width = width ? parseFloat(width) : 0; - - if ((left + width) >= currentScrollXPct) { - list.push(elem); - } - } - - return list; - } - - function onInputCommand(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - var container; - var channelPrograms; - var focusableElements; - var newRow; - - switch (e.detail.command) { - case 'up': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.previousSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveUp(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'down': - if (programCell) { - container = programGrid; - channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); - - newRow = channelPrograms.nextSibling; - if (newRow) { - focusableElements = getChannelProgramsFocusableElements(newRow); - if (focusableElements.length) { - container = newRow; - } else { - focusableElements = null; - } - } else { - container = null; - } - } else { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveDown(target, { - container: container, - focusableElements: focusableElements - }); - break; - case 'left': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - // allow left outside the channelProgramsContainer when the first child is currently focused - if (container && !programCell.previousSibling) { - container = null; - } - lastFocusDirection = e.detail.command; - - focusManager.moveLeft(target, { - container: container - }); - break; - case 'right': - container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; - lastFocusDirection = e.detail.command; - - focusManager.moveRight(target, { - container: container - }); - break; - default: - return; - } + isUpdatingProgramCellScroll = false; + }); +} + +function onProgramGridClick(e) { + if (!layoutManager.tv) { + return; + } + + const programCell = dom.parentWithClass(e.target, 'programCell'); + if (programCell) { + let startDate = programCell.getAttribute('data-startdate'); + let endDate = programCell.getAttribute('data-enddate'); + startDate = datetime.parseISO8601Date(startDate, { toLocal: true }).getTime(); + endDate = datetime.parseISO8601Date(endDate, { toLocal: true }).getTime(); + + const now = new Date().getTime(); + if (now >= startDate && now < endDate) { + const channelId = programCell.getAttribute('data-channelid'); + const serverId = programCell.getAttribute('data-serverid'); e.preventDefault(); e.stopPropagation(); - } - function onScrollerFocus(e) { - var target = e.target; - var programCell = dom.parentWithClass(target, 'programCell'); - - if (programCell) { - var focused = target; - - var id = focused.getAttribute('data-id'); - var item = items[id]; - - if (item) { - events.trigger(self, 'focus', [ - { - item: item - }]); - } - } - - if (lastFocusDirection === 'left') { - if (programCell) { - scrollHelper.toStart(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'right') { - if (programCell) { - scrollHelper.toCenter(programGrid, programCell, true, true); - } - } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { - var verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); - if (verticalScroller) { - var focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); - verticalScroller.toCenter(focusedElement, true); - } - } - } - - function setScrollEvents(view, enabled) { - if (layoutManager.tv) { - var guideVerticalScroller = view.querySelector('.guideVerticalScroller'); - - if (enabled) { - inputManager.on(guideVerticalScroller, onInputCommand); - } else { - inputManager.off(guideVerticalScroller, onInputCommand); - } - } - } - - function onTimerCreated(e, apiClient, data) { - var programId = data.ProgramId; - // This could be null, not supported by all tv providers - var newTimerId = data.Id; - - // find guide cells by program id, ensure timer icon - var cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - - var icon = cell.querySelector('.timerIcon'); - if (!icon) { - cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); - } - - if (newTimerId) { - cell.setAttribute('data-timerid', newTimerId); - } - } - } - - function onSeriesTimerCreated(e, apiClient, data) { - } - - function onTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.timerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-timerid'); - } - } - - function onSeriesTimerCancelled(e, apiClient, data) { - var id = data.Id; - // find guide cells by timer id, remove timer icon - var cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); - for (var i = 0, length = cells.length; i < length; i++) { - var cell = cells[i]; - var icon = cell.querySelector('.seriesTimerIcon'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-seriestimerid'); - } - } - - require(['text!./tvguide.template.html'], function (template) { - var context = options.element; - - context.classList.add('tvguide'); - - context.innerHTML = globalize.translateHtml(template, 'core'); - - programGrid = context.querySelector('.programGrid'); - var timeslotHeaders = context.querySelector('.timeslotHeaders'); - - if (layoutManager.tv) { - dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { - capture: true, - passive: true - }); - } else if (layoutManager.desktop) { - timeslotHeaders.classList.add('timeslotHeaders-desktop'); - } - - if (browser.iOS || browser.osx) { - context.querySelector('.channelsContainer').classList.add('noRubberBanding'); - - programGrid.classList.add('noRubberBanding'); - } - - dom.addEventListener(programGrid, 'scroll', function (e) { - onProgramGridScroll(context, this, timeslotHeaders); - }, { - passive: true + playbackManager.play({ + ids: [channelId], + serverId: serverId }); + } + } +} - dom.addEventListener(timeslotHeaders, 'scroll', function () { - onTimeslotHeadersScroll(context, this); - }, { - passive: true - }); +function Guide(options) { + const self = this; + let items = {}; - programGrid.addEventListener('click', onProgramGridClick); + self.options = options; + self.categoryOptions = { categories: [] }; - context.querySelector('.btnNextPage').addEventListener('click', function () { - currentStartIndex += currentChannelLimit; - reloadPage(context); - restartAutoRefresh(); - }); + // 30 mins + const cellCurationMinutes = 30; + const cellDurationMs = cellCurationMinutes * 60 * 1000; + const msPerDay = 86400000; - context.querySelector('.btnPreviousPage').addEventListener('click', function () { - currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); - reloadPage(context); - restartAutoRefresh(); - }); + let currentDate; + let currentStartIndex = 0; + let currentChannelLimit = 0; + let autoRefreshInterval; + let programCells; + let lastFocusDirection; + let programGrid; - context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { - showViewSettings(self); - restartAutoRefresh(); - }); + self.refresh = function () { + currentDate = null; + reloadPage(options.element); + restartAutoRefresh(); + }; - context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { - var allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); - - var tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; - if (tabButton) { - var previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; - - var date = new Date(); - date.setTime(parseInt(tabButton.getAttribute('data-date'))); - - var scrollWidth = programGrid.scrollWidth; - var scrollToTimeMs; - if (scrollWidth) { - scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; - } else { - scrollToTimeMs = 0; - } - - if (previousButton) { - var previousDate = new Date(); - previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); - - scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); - scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); - } - - var startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); - startTimeOfDayMs += (date.getMinutes() * 60 * 1000); - - changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); - } - }); - - setScrollEvents(context, true); - itemShortcuts.on(context); - - events.trigger(self, 'load'); - - events.on(serverNotifications, 'TimerCreated', onTimerCreated); - events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); - events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); - events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + self.pause = function () { + stopAutoRefresh(); + }; + self.resume = function (refreshData) { + if (refreshData) { self.refresh(); + } else { + restartAutoRefresh(); + } + }; + + self.destroy = function () { + stopAutoRefresh(); + + events.off(serverNotifications, 'TimerCreated', onTimerCreated); + events.off(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.off(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.off(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + setScrollEvents(options.element, false); + itemShortcuts.off(options.element); + items = {}; + }; + + function restartAutoRefresh() { + stopAutoRefresh(); + + const intervalMs = 60000 * 15; // (minutes) + + autoRefreshInterval = setInterval(function () { + self.refresh(); + }, intervalMs); + } + + function stopAutoRefresh() { + if (autoRefreshInterval) { + clearInterval(autoRefreshInterval); + autoRefreshInterval = null; + } + } + + function normalizeDateToTimeslot(date) { + const minutesOffset = date.getMinutes() - cellCurationMinutes; + + if (minutesOffset >= 0) { + date.setHours(date.getHours(), cellCurationMinutes, 0, 0); + } else { + date.setHours(date.getHours(), 0, 0, 0); + } + + return date; + } + + function showLoading() { + loading.show(); + } + + function hideLoading() { + loading.hide(); + } + + function reloadGuide(context, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const apiClient = connectionManager.getApiClient(options.serverId); + + const channelQuery = { + + StartIndex: 0, + EnableFavoriteSorting: userSettings.get('livetv-favoritechannelsattop') !== 'false' + }; + + channelQuery.UserId = apiClient.getCurrentUserId(); + + const channelLimit = 500; + currentChannelLimit = channelLimit; + + showLoading(); + + channelQuery.StartIndex = currentStartIndex; + channelQuery.Limit = channelLimit; + channelQuery.AddCurrentProgram = false; + channelQuery.EnableUserData = false; + channelQuery.EnableImageTypes = 'Primary'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + + if (displayMovieContent && displaySportsContent && displayNewsContent && displayKidsContent) { + channelQuery.IsMovie = null; + channelQuery.IsSports = null; + channelQuery.IsKids = null; + channelQuery.IsNews = null; + channelQuery.IsSeries = null; + } else { + if (displayNewsContent) { + channelQuery.IsNews = true; + } + if (displaySportsContent) { + channelQuery.IsSports = true; + } + if (displayKidsContent) { + channelQuery.IsKids = true; + } + if (displayMovieContent) { + channelQuery.IsMovie = true; + } + if (displaySeriesContent) { + channelQuery.IsSeries = true; + } + } + + if (userSettings.get('livetv-channelorder') === 'DatePlayed') { + channelQuery.SortBy = 'DatePlayed'; + channelQuery.SortOrder = 'Descending'; + } else { + channelQuery.SortBy = null; + channelQuery.SortOrder = null; + } + + let date = newStartDate; + // Add one second to avoid getting programs that are just ending + date = new Date(date.getTime() + 1000); + + // Subtract to avoid getting programs that are starting when the grid ends + const nextDay = new Date(date.getTime() + msPerDay - 2000); + + // Normally we'd want to just let responsive css handle this, + // but since mobile browsers are often underpowered, + // it can help performance to get them out of the markup + const allowIndicators = dom.getWindowSize().innerWidth >= 600; + + const renderOptions = { + showHdIcon: allowIndicators && userSettings.get('guide-indicator-hd') === 'true', + showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', + showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', + showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') !== 'false', + showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', + showEpisodeTitle: layoutManager.tv ? false : true + }; + + apiClient.getLiveTvChannels(channelQuery).then(function (channelsResult) { + const btnPreviousPage = context.querySelector('.btnPreviousPage'); + const btnNextPage = context.querySelector('.btnNextPage'); + + if (channelsResult.TotalRecordCount > channelLimit) { + context.querySelector('.guideOptions').classList.remove('hide'); + + btnPreviousPage.classList.remove('hide'); + btnNextPage.classList.remove('hide'); + + if (channelQuery.StartIndex) { + context.querySelector('.btnPreviousPage').disabled = false; + } else { + context.querySelector('.btnPreviousPage').disabled = true; + } + + if ((channelQuery.StartIndex + channelLimit) < channelsResult.TotalRecordCount) { + btnNextPage.disabled = false; + } else { + btnNextPage.disabled = true; + } + } else { + context.querySelector('.guideOptions').classList.add('hide'); + } + + const programFields = []; + + const programQuery = { + UserId: apiClient.getCurrentUserId(), + MaxStartDate: nextDay.toISOString(), + MinEndDate: date.toISOString(), + channelIds: channelsResult.Items.map(function (c) { + return c.Id; + }).join(','), + ImageTypeLimit: 1, + EnableImages: false, + //EnableImageTypes: layoutManager.tv ? "Primary,Backdrop" : "Primary", + SortBy: 'StartDate', + EnableTotalRecordCount: false, + EnableUserData: false + }; + + if (renderOptions.showHdIcon) { + programFields.push('IsHD'); + } + + if (programFields.length) { + programQuery.Fields = programFields.join(''); + } + + apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { + renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + + hideLoading(); + }); }); } - return Guide; -}); + function getDisplayTime(date) { + if ((typeof date).toString().toLowerCase() === 'string') { + try { + date = datetime.parseISO8601Date(date, { toLocal: true }); + } catch (err) { + return date; + } + } + + return datetime.getDisplayTime(date).toLowerCase(); + } + + function getTimeslotHeadersHtml(startDate, endDateTime) { + let html = ''; + + // clone + startDate = new Date(startDate.getTime()); + + html += '
'; + + while (startDate.getTime() < endDateTime) { + html += '
'; + + html += getDisplayTime(startDate); + html += '
'; + + // Add 30 mins + startDate.setTime(startDate.getTime() + cellDurationMs); + } + + return html; + } + + function parseDates(program) { + if (!program.StartDateLocal) { + try { + program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for start date'); + } + } + + if (!program.EndDateLocal) { + try { + program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true }); + } catch (err) { + console.error('error parsing timestamp for end date'); + } + } + + return null; + } + + function getTimerIndicator(item) { + let status; + + if (item.Type === 'SeriesTimer') { + return ''; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return ''; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return ''; + } + + return ''; + } + + return ''; + } + + function getChannelProgramsHtml(context, date, channel, programs, options, listInfo) { + let html = ''; + + const startMs = date.getTime(); + const endMs = startMs + msPerDay - 1; + + const outerCssClass = layoutManager.tv ? 'channelPrograms channelPrograms-tv' : 'channelPrograms'; + + html += '
'; + + const clickAction = layoutManager.tv ? 'link' : 'programdialog'; + + const categories = self.categoryOptions.categories || []; + const displayMovieContent = !categories.length || categories.indexOf('movies') !== -1; + const displaySportsContent = !categories.length || categories.indexOf('sports') !== -1; + const displayNewsContent = !categories.length || categories.indexOf('news') !== -1; + const displayKidsContent = !categories.length || categories.indexOf('kids') !== -1; + const displaySeriesContent = !categories.length || categories.indexOf('series') !== -1; + const enableColorCodedBackgrounds = userSettings.get('guide-colorcodedbackgrounds') === 'true'; + + let programsFound; + const now = new Date().getTime(); + + for (let i = listInfo.startIndex, length = programs.length; i < length; i++) { + const program = programs[i]; + + if (program.ChannelId !== channel.Id) { + if (programsFound) { + break; + } + + continue; + } + + programsFound = true; + listInfo.startIndex++; + + parseDates(program); + + const startDateLocalMs = program.StartDateLocal.getTime(); + const endDateLocalMs = program.EndDateLocal.getTime(); + + if (endDateLocalMs < startMs) { + continue; + } + + if (startDateLocalMs > endMs) { + break; + } + + items[program.Id] = program; + + const renderStartMs = Math.max(startDateLocalMs, startMs); + let startPercent = (startDateLocalMs - startMs) / msPerDay; + startPercent *= 100; + startPercent = Math.max(startPercent, 0); + + const renderEndMs = Math.min(endDateLocalMs, endMs); + let endPercent = (renderEndMs - renderStartMs) / msPerDay; + endPercent *= 100; + + let cssClass = 'programCell itemAction'; + let accentCssClass = null; + let displayInnerContent = true; + + if (program.IsKids) { + displayInnerContent = displayKidsContent; + accentCssClass = 'kids'; + } else if (program.IsSports) { + displayInnerContent = displaySportsContent; + accentCssClass = 'sports'; + } else if (program.IsNews) { + displayInnerContent = displayNewsContent; + accentCssClass = 'news'; + } else if (program.IsMovie) { + displayInnerContent = displayMovieContent; + accentCssClass = 'movie'; + } else if (program.IsSeries) { + displayInnerContent = displaySeriesContent; + } else { + displayInnerContent = displayMovieContent && displayNewsContent && displaySportsContent && displayKidsContent && displaySeriesContent; + } + + if (displayInnerContent && enableColorCodedBackgrounds && accentCssClass) { + cssClass += ' programCell-' + accentCssClass; + } + + if (now >= startDateLocalMs && now < endDateLocalMs) { + cssClass += ' programCell-active'; + } + + let timerAttributes = ''; + if (program.TimerId) { + timerAttributes += ' data-timerid="' + program.TimerId + '"'; + } + if (program.SeriesTimerId) { + timerAttributes += ' data-seriestimerid="' + program.SeriesTimerId + '"'; + } + + const isAttribute = endPercent >= 2 ? ' is="emby-programcell"' : ''; + + html += ''; + + if (displayInnerContent) { + const guideProgramNameClass = 'guideProgramName'; + + html += '
'; + + html += '
'; + + html += '
' + program.Name; + + let indicatorHtml = null; + if (program.IsLive && options.showLiveIndicator) { + indicatorHtml = '' + globalize.translate('Live') + ''; + } else if (program.IsPremiere && options.showPremiereIndicator) { + indicatorHtml = '' + globalize.translate('Premiere') + ''; + } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { + indicatorHtml = '' + globalize.translate('AttributeNew') + ''; + } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { + indicatorHtml = '' + globalize.translate('Repeat') + ''; + } + html += indicatorHtml || ''; + + if ((program.EpisodeTitle && options.showEpisodeTitle)) { + html += '
'; + + if (program.EpisodeTitle && options.showEpisodeTitle) { + html += '' + program.EpisodeTitle + ''; + } + html += '
'; + } + + html += '
'; + + if (program.IsHD && options.showHdIcon) { + if (layoutManager.tv) { + html += '
HD
'; + } else { + html += '
HD
'; + } + } + + html += getTimerIndicator(program); + + html += '
'; + } + + html += ''; + } + + html += '
'; + + return html; + } + + function renderChannelHeaders(context, channels, apiClient) { + let html = ''; + + for (const channel of channels) { + const hasChannelImage = channel.ImageTags.Primary; + + let cssClass = 'guide-channelHeaderCell itemAction'; + + if (layoutManager.tv) { + cssClass += ' guide-channelHeaderCell-tv'; + } + + const title = []; + if (channel.ChannelNumber) { + title.push(channel.ChannelNumber); + } + if (channel.Name) { + title.push(channel.Name); + } + + html += ''; + } + + const channelList = context.querySelector('.channelsContainer'); + channelList.innerHTML = html; + imageLoader.lazyChildren(channelList); + } + + function renderPrograms(context, date, channels, programs, options) { + const listInfo = { + startIndex: 0 + }; + + const html = []; + + for (const channel of channels) { + html.push(getChannelProgramsHtml(context, date, channel, programs, options, listInfo)); + } + + programGrid.innerHTML = html.join(''); + + programCells = programGrid.querySelectorAll('[is=emby-programcell]'); + + updateProgramCellsOnScroll(programGrid, programCells); + } + + function getProgramSortOrder(program, channels) { + const channelId = program.ChannelId; + let channelIndex = -1; + + for (let i = 0, length = channels.length; i < length; i++) { + if (channelId === channels[i].Id) { + channelIndex = i; + break; + } + } + + const start = datetime.parseISO8601Date(program.StartDate, { toLocal: true }); + + return (channelIndex * 10000000) + (start.getTime() / 60000); + } + + function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + programs.sort(function (a, b) { + return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); + }); + + const activeElement = document.activeElement; + const itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; + let channelRowId = null; + + if (activeElement) { + channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); + channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; + } + + renderChannelHeaders(context, channels, apiClient); + + const startDate = date; + const endDate = new Date(startDate.getTime() + msPerDay); + context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate); + items = {}; + renderPrograms(context, date, channels, programs, renderOptions); + + if (focusProgramOnRender) { + focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); + } + + scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); + } + + function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { + scrollToTimeMs -= startTimeOfDayMs; + + const pct = scrollToTimeMs / msPerDay; + + programGrid.scrollTop = 0; + + const scrollPos = pct * programGrid.scrollWidth; + + nativeScrollTo(programGrid, scrollPos, true); + } + + function focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs) { + let focusElem; + if (itemId) { + focusElem = context.querySelector('[data-id="' + itemId + '"]'); + } + + if (focusElem) { + focusManager.focus(focusElem); + } else { + let autoFocusParent; + + if (channelRowId) { + autoFocusParent = context.querySelector('[data-channelid="' + channelRowId + '"]'); + } + + if (!autoFocusParent) { + autoFocusParent = programGrid; + } + + focusToTimeMs -= startTimeOfDayMs; + + const pct = (focusToTimeMs / msPerDay) * 100; + + let programCell = autoFocusParent.querySelector('.programCell'); + + while (programCell) { + let left = (programCell.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + let width = (programCell.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if (left >= pct || (left + width) >= pct) { + break; + } + programCell = programCell.nextSibling; + } + + if (programCell) { + focusManager.focus(programCell); + } else { + focusManager.autoFocus(autoFocusParent, true); + } + } + } + + function nativeScrollTo(container, pos, horizontal) { + if (container.scrollTo) { + if (horizontal) { + container.scrollTo(pos, 0); + } else { + container.scrollTo(0, pos); + } + } else { + if (horizontal) { + container.scrollLeft = Math.round(pos); + } else { + container.scrollTop = Math.round(pos); + } + } + } + + let lastGridScroll = 0; + let lastHeaderScroll = 0; + let scrollXPct = 0; + function onProgramGridScroll(context, elem, timeslotHeaders) { + if ((new Date().getTime() - lastHeaderScroll) >= 1000) { + lastGridScroll = new Date().getTime(); + + const scrollLeft = elem.scrollLeft; + scrollXPct = (scrollLeft * 100) / elem.scrollWidth; + nativeScrollTo(timeslotHeaders, scrollLeft, true); + } + + updateProgramCellsOnScroll(elem, programCells); + } + + function onTimeslotHeadersScroll(context, elem) { + if ((new Date().getTime() - lastGridScroll) >= 1000) { + lastHeaderScroll = new Date().getTime(); + nativeScrollTo(programGrid, elem.scrollLeft, true); + } + } + + function changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { + const newStartDate = normalizeDateToTimeslot(date); + currentDate = newStartDate; + + reloadGuide(page, newStartDate, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); + } + + function getDateTabText(date, isActive, tabIndex) { + const cssClass = isActive ? 'emby-tab-button guide-date-tab-button emby-tab-button-active' : 'emby-tab-button guide-date-tab-button'; + + let html = ''; + + return html; + } + + function setDateRange(page, guideInfo) { + const today = new Date(); + const nowHours = today.getHours(); + today.setHours(nowHours, 0, 0, 0); + + let start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true }); + const end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true }); + + start.setHours(nowHours, 0, 0, 0); + end.setHours(0, 0, 0, 0); + + if (start.getTime() >= end.getTime()) { + end.setDate(start.getDate() + 1); + } + + start = new Date(Math.max(today, start)); + + let dateTabsHtml = ''; + let tabIndex = 0; + + // TODO: Use date-fns + const date = new Date(); + + if (currentDate) { + date.setTime(currentDate.getTime()); + } + + date.setHours(nowHours, 0, 0, 0); + + let startTimeOfDayMs = (start.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += start.getMinutes() * 60 * 1000; + + while (start <= end) { + const isActive = date.getDate() === start.getDate() && date.getMonth() === start.getMonth() && date.getFullYear() === start.getFullYear(); + + dateTabsHtml += getDateTabText(start, isActive, tabIndex); + + start.setDate(start.getDate() + 1); + start.setHours(0, 0, 0, 0); + tabIndex++; + } + + page.querySelector('.emby-tabs-slider').innerHTML = dateTabsHtml; + page.querySelector('.guideDateTabs').refresh(); + + const newDate = new Date(); + const newDateHours = newDate.getHours(); + let scrollToTimeMs = newDateHours * 60 * 60 * 1000; + + const minutes = newDate.getMinutes(); + if (minutes >= 30) { + scrollToTimeMs += 30 * 60 * 1000; + } + + const focusToTimeMs = ((newDateHours * 60) + minutes) * 60 * 1000; + changeDate(page, date, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, layoutManager.tv); + } + + function reloadPage(page) { + showLoading(); + + const apiClient = connectionManager.getApiClient(options.serverId); + + apiClient.getLiveTvGuideInfo().then(function (guideInfo) { + setDateRange(page, guideInfo); + }); + } + + function getChannelProgramsFocusableElements(container) { + const elements = container.querySelectorAll('.programCell'); + + const list = []; + // add 1 to avoid programs that are out of view to the left + const currentScrollXPct = scrollXPct + 1; + + for (const elem of elements) { + let left = (elem.style.left || '').replace('%', ''); + left = left ? parseFloat(left) : 0; + + let width = (elem.style.width || '').replace('%', ''); + width = width ? parseFloat(width) : 0; + + if ((left + width) >= currentScrollXPct) { + list.push(elem); + } + } + + return list; + } + + function onInputCommand(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + let container; + let channelPrograms; + let focusableElements; + let newRow; + + switch (e.detail.command) { + case 'up': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.previousSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveUp(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'down': + if (programCell) { + container = programGrid; + channelPrograms = dom.parentWithClass(programCell, 'channelPrograms'); + + newRow = channelPrograms.nextSibling; + if (newRow) { + focusableElements = getChannelProgramsFocusableElements(newRow); + if (focusableElements.length) { + container = newRow; + } else { + focusableElements = null; + } + } else { + container = null; + } + } else { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveDown(target, { + container: container, + focusableElements: focusableElements + }); + break; + case 'left': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + // allow left outside the channelProgramsContainer when the first child is currently focused + if (container && !programCell.previousSibling) { + container = null; + } + lastFocusDirection = e.detail.command; + + focusManager.moveLeft(target, { + container: container + }); + break; + case 'right': + container = programCell ? dom.parentWithClass(programCell, 'channelPrograms') : null; + lastFocusDirection = e.detail.command; + + focusManager.moveRight(target, { + container: container + }); + break; + default: + return; + } + + e.preventDefault(); + e.stopPropagation(); + } + + function onScrollerFocus(e) { + const target = e.target; + const programCell = dom.parentWithClass(target, 'programCell'); + + if (programCell) { + const focused = target; + + const id = focused.getAttribute('data-id'); + const item = items[id]; + + if (item) { + events.trigger(self, 'focus', [ + { + item: item + }]); + } + } + + if (lastFocusDirection === 'left') { + if (programCell) { + scrollHelper.toStart(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'right') { + if (programCell) { + scrollHelper.toCenter(programGrid, programCell, true, true); + } + } else if (lastFocusDirection === 'up' || lastFocusDirection === 'down') { + const verticalScroller = dom.parentWithClass(target, 'guideVerticalScroller'); + if (verticalScroller) { + const focusedElement = programCell || dom.parentWithTag(target, 'BUTTON'); + verticalScroller.toCenter(focusedElement, true); + } + } + } + + function setScrollEvents(view, enabled) { + if (layoutManager.tv) { + const guideVerticalScroller = view.querySelector('.guideVerticalScroller'); + + if (enabled) { + inputManager.on(guideVerticalScroller, onInputCommand); + } else { + inputManager.off(guideVerticalScroller, onInputCommand); + } + } + } + + function onTimerCreated(e, apiClient, data) { + const programId = data.ProgramId; + // This could be null, not supported by all tv providers + const newTimerId = data.Id; + + // find guide cells by program id, ensure timer icon + const cells = options.element.querySelectorAll('.programCell[data-id="' + programId + '"]'); + for (const cell of cells) { + const icon = cell.querySelector('.timerIcon'); + if (!icon) { + cell.querySelector('.guideProgramName').insertAdjacentHTML('beforeend', ''); + } + + if (newTimerId) { + cell.setAttribute('data-timerid', newTimerId); + } + } + } + + function onSeriesTimerCreated(e, apiClient, data) { + } + + function onTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-timerid="' + id + '"]'); + + for (const cell of cells) { + const icon = cell.querySelector('.timerIcon'); + + if (icon) { + icon.parentNode.removeChild(icon); + } + + cell.removeAttribute('data-timerid'); + } + } + + function onSeriesTimerCancelled(e, apiClient, data) { + const id = data.Id; + // find guide cells by timer id, remove timer icon + const cells = options.element.querySelectorAll('.programCell[data-seriestimerid="' + id + '"]'); + + for (const cell of cells) { + const icon = cell.querySelector('.seriesTimerIcon'); + + if (icon) { + icon.parentNode.removeChild(icon); + } + + cell.removeAttribute('data-seriestimerid'); + } + } + + import('text!./tvguide.template.html').then(({default: template}) => { + const context = options.element; + + context.classList.add('tvguide'); + + context.innerHTML = globalize.translateHtml(template, 'core'); + + programGrid = context.querySelector('.programGrid'); + const timeslotHeaders = context.querySelector('.timeslotHeaders'); + + if (layoutManager.tv) { + dom.addEventListener(context.querySelector('.guideVerticalScroller'), 'focus', onScrollerFocus, { + capture: true, + passive: true + }); + } else if (layoutManager.desktop) { + timeslotHeaders.classList.add('timeslotHeaders-desktop'); + } + + if (browser.iOS || browser.osx) { + context.querySelector('.channelsContainer').classList.add('noRubberBanding'); + + programGrid.classList.add('noRubberBanding'); + } + + dom.addEventListener(programGrid, 'scroll', function (e) { + onProgramGridScroll(context, this, timeslotHeaders); + }, { + passive: true + }); + + dom.addEventListener(timeslotHeaders, 'scroll', function () { + onTimeslotHeadersScroll(context, this); + }, { + passive: true + }); + + programGrid.addEventListener('click', onProgramGridClick); + + context.querySelector('.btnNextPage').addEventListener('click', function () { + currentStartIndex += currentChannelLimit; + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnPreviousPage').addEventListener('click', function () { + currentStartIndex = Math.max(currentStartIndex - currentChannelLimit, 0); + reloadPage(context); + restartAutoRefresh(); + }); + + context.querySelector('.btnGuideViewSettings').addEventListener('click', function () { + showViewSettings(self); + restartAutoRefresh(); + }); + + context.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { + const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); + + const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; + if (tabButton) { + const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; + + const date = new Date(); + date.setTime(parseInt(tabButton.getAttribute('data-date'))); + + const scrollWidth = programGrid.scrollWidth; + let scrollToTimeMs; + if (scrollWidth) { + scrollToTimeMs = (programGrid.scrollLeft / scrollWidth) * msPerDay; + } else { + scrollToTimeMs = 0; + } + + if (previousButton) { + const previousDate = new Date(); + previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); + + scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); + scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); + } + + let startTimeOfDayMs = (date.getHours() * 60 * 60 * 1000); + startTimeOfDayMs += (date.getMinutes() * 60 * 1000); + + changeDate(context, date, scrollToTimeMs, scrollToTimeMs, startTimeOfDayMs, false); + } + }); + + setScrollEvents(context, true); + itemShortcuts.on(context); + + events.trigger(self, 'load'); + + events.on(serverNotifications, 'TimerCreated', onTimerCreated); + events.on(serverNotifications, 'SeriesTimerCreated', onSeriesTimerCreated); + events.on(serverNotifications, 'TimerCancelled', onTimerCancelled); + events.on(serverNotifications, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + self.refresh(); + }); +} + +export default Guide; diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 2be2ef09b2..3f78a5ecbb 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -8,7 +8,6 @@ import browser from 'browser'; import layoutManager from 'layoutManager'; import scrollHelper from 'scrollHelper'; import globalize from 'globalize'; -import require from 'require'; import 'emby-checkbox'; import 'paper-icon-button-light'; import 'emby-button'; @@ -317,7 +316,7 @@ import 'cardStyle'; function showEditor(itemId, serverId, itemType) { loading.show(); - require(['text!./imageDownloader.template.html'], function (template) { + import('text!./imageDownloader.template.html').then(({default: template}) => { const apiClient = connectionManager.getApiClient(serverId); currentItemId = itemId; diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index 0effcc7a57..39dc6c2622 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -17,8 +17,8 @@ import 'css!./style'; // 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; + const width = 18; + const height = 18; let pixels; try { pixels = blurhash.decode(blurhashstr, width, height); @@ -27,11 +27,11 @@ import 'css!./style'; target.classList.add('non-blurhashable'); return; } - let canvas = document.createElement('canvas'); + const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; - let ctx = canvas.getContext('2d'); - let imgData = ctx.createImageData(width, height); + const ctx = canvas.getContext('2d'); + const imgData = ctx.createImageData(width, height); imgData.data.set(pixels); ctx.putImageData(imgData, 0, 0); @@ -55,7 +55,7 @@ import 'css!./style'; if (!entry) { throw new Error('entry cannot be null'); } - let target = entry.target; + const target = entry.target; var source = undefined; if (target) { @@ -78,7 +78,7 @@ import 'css!./style'; throw new TypeError('url cannot be undefined'); } - let preloaderImg = new Image(); + const preloaderImg = new Image(); preloaderImg.src = url; elem.classList.add('lazy-hidden'); diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index 604f480f1d..bbd672ef72 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -82,7 +82,7 @@ export function enablePlayedIndicator(item) { export function getPlayedIndicatorHtml(item) { if (enablePlayedIndicator(item)) { - let userData = item.UserData || {}; + const userData = item.UserData || {}; if (userData.UnplayedItemCount) { return '
' + userData.UnplayedItemCount + '
'; } diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 173383d064..c1cac11d87 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -16,7 +16,7 @@ import actionsheet from 'actionsheet'; const canPlay = playbackManager.canPlay(item); const restrictOptions = (browser.operaTv || browser.web0s) && !user.Policy.IsAdministrator; - let commands = []; + const commands = []; if (canPlay && item.MediaType !== 'Photo') { if (options.play !== false) { @@ -367,7 +367,7 @@ import actionsheet from 'actionsheet'; case 'copy-stream': { const downloadHref = apiClient.getItemDownloadUrl(itemId); const textAreaCopy = function () { - let textArea = document.createElement('textarea'); + const textArea = document.createElement('textarea'); textArea.value = downloadHref; document.body.appendChild(textArea); textArea.focus(); diff --git a/src/components/itemsrefresher.js b/src/components/itemsrefresher.js index 5ce9a3b6e4..3883e6e490 100644 --- a/src/components/itemsrefresher.js +++ b/src/components/itemsrefresher.js @@ -1,131 +1,130 @@ -define(['playbackManager', 'serverNotifications', 'events'], function (playbackManager, serverNotifications, events) { - 'use strict'; +import playbackManager from 'playbackManager'; +import serverNotifications from 'serverNotifications'; +import events from 'events'; - serverNotifications = serverNotifications.default || serverNotifications; - playbackManager = playbackManager.default || playbackManager; +function onUserDataChanged(e, apiClient, userData) { + const instance = this; - function onUserDataChanged(e, apiClient, userData) { - var instance = this; - - var eventsToMonitor = getEventsToMonitor(instance); - - // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1) { - instance.notifyRefreshNeeded(); - } else if (eventsToMonitor.indexOf('markplayed') !== -1) { - instance.notifyRefreshNeeded(); - } - } - - function getEventsToMonitor(instance) { - var options = instance.options; - var monitor = options ? options.monitorEvents : null; - if (monitor) { - return monitor.split(','); - } - - return []; - } - - function onTimerCreated(e, apiClient, data) { - var instance = this; - - if (getEventsToMonitor(instance).indexOf('timers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onSeriesTimerCreated(e, apiClient, data) { - var instance = this; - if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onTimerCancelled(e, apiClient, data) { - var instance = this; - - if (getEventsToMonitor(instance).indexOf('timers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onSeriesTimerCancelled(e, apiClient, data) { - var instance = this; - if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } - } - - function onLibraryChanged(e, apiClient, data) { - var instance = this; - var eventsToMonitor = getEventsToMonitor(instance); - if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { - // yes this is an assumption - return; - } - - var itemsAdded = data.ItemsAdded || []; - var itemsRemoved = data.ItemsRemoved || []; - if (!itemsAdded.length && !itemsRemoved.length) { - return; - } - - var options = instance.options || {}; - var parentId = options.parentId; - if (parentId) { - var foldersAddedTo = data.FoldersAddedTo || []; - var foldersRemovedFrom = data.FoldersRemovedFrom || []; - var collectionFolders = data.CollectionFolders || []; - - if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { - return; - } - } + const eventsToMonitor = getEventsToMonitor(instance); + // TODO: Check user data change reason? + if (eventsToMonitor.indexOf('markfavorite') !== -1) { + instance.notifyRefreshNeeded(); + } else if (eventsToMonitor.indexOf('markplayed') !== -1) { instance.notifyRefreshNeeded(); } +} - function onPlaybackStopped(e, stopInfo) { - var instance = this; +function getEventsToMonitor(instance) { + const options = instance.options; + const monitor = options ? options.monitorEvents : null; + if (monitor) { + return monitor.split(','); + } - var state = stopInfo.state; + return []; +} - var eventsToMonitor = getEventsToMonitor(instance); - if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { - if (eventsToMonitor.indexOf('videoplayback') !== -1) { - instance.notifyRefreshNeeded(true); - return; - } - } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { - if (eventsToMonitor.indexOf('audioplayback') !== -1) { - instance.notifyRefreshNeeded(true); - return; - } +function onTimerCreated(e, apiClient, data) { + const instance = this; + + if (getEventsToMonitor(instance).indexOf('timers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onSeriesTimerCreated(e, apiClient, data) { + const instance = this; + if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onTimerCancelled(e, apiClient, data) { + const instance = this; + + if (getEventsToMonitor(instance).indexOf('timers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onSeriesTimerCancelled(e, apiClient, data) { + const instance = this; + if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { + instance.notifyRefreshNeeded(); + return; + } +} + +function onLibraryChanged(e, apiClient, data) { + const instance = this; + const eventsToMonitor = getEventsToMonitor(instance); + if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { + // yes this is an assumption + return; + } + + const itemsAdded = data.ItemsAdded || []; + const itemsRemoved = data.ItemsRemoved || []; + if (!itemsAdded.length && !itemsRemoved.length) { + return; + } + + const options = instance.options || {}; + const parentId = options.parentId; + if (parentId) { + const foldersAddedTo = data.FoldersAddedTo || []; + const foldersRemovedFrom = data.FoldersRemovedFrom || []; + const collectionFolders = data.CollectionFolders || []; + + if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { + return; } } - function addNotificationEvent(instance, name, handler, owner) { - var localHandler = handler.bind(instance); + instance.notifyRefreshNeeded(); +} + +function onPlaybackStopped(e, stopInfo) { + const instance = this; + + const state = stopInfo.state; + + const eventsToMonitor = getEventsToMonitor(instance); + if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + if (eventsToMonitor.indexOf('videoplayback') !== -1) { + instance.notifyRefreshNeeded(true); + return; + } + } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { + if (eventsToMonitor.indexOf('audioplayback') !== -1) { + instance.notifyRefreshNeeded(true); + return; + } + } +} + +function addNotificationEvent(instance, name, handler, owner) { + const localHandler = handler.bind(instance); + owner = owner || serverNotifications; + events.on(owner, name, localHandler); + instance['event_' + name] = localHandler; +} + +function removeNotificationEvent(instance, name, owner) { + const handler = instance['event_' + name]; + if (handler) { owner = owner || serverNotifications; - events.on(owner, name, localHandler); - instance['event_' + name] = localHandler; + events.off(owner, name, handler); + instance['event_' + name] = null; } +} - function removeNotificationEvent(instance, name, owner) { - var handler = instance['event_' + name]; - if (handler) { - owner = owner || serverNotifications; - events.off(owner, name, handler); - instance['event_' + name] = null; - } - } - - function ItemsRefresher(options) { +class ItemsRefresher { + constructor(options) { this.options = options || {}; addNotificationEvent(this, 'UserDataChanged', onUserDataChanged); @@ -137,18 +136,18 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager); } - ItemsRefresher.prototype.pause = function () { + pause() { clearRefreshInterval(this, true); this.paused = true; - }; + } - ItemsRefresher.prototype.resume = function (options) { + resume(options) { this.paused = false; - var refreshIntervalEndTime = this.refreshIntervalEndTime; + const refreshIntervalEndTime = this.refreshIntervalEndTime; if (refreshIntervalEndTime) { - var remainingMs = refreshIntervalEndTime - new Date().getTime(); + const remainingMs = refreshIntervalEndTime - new Date().getTime(); if (remainingMs > 0 && !this.needsRefresh) { resetRefreshInterval(this, remainingMs); } else { @@ -162,9 +161,9 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM } return Promise.resolve(); - }; + } - ItemsRefresher.prototype.refreshItems = function () { + refreshItems() { if (!this.fetchData) { return Promise.resolve(); } @@ -177,15 +176,15 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM this.needsRefresh = false; return this.fetchData().then(onDataFetched.bind(this)); - }; + } - ItemsRefresher.prototype.notifyRefreshNeeded = function (isInForeground) { + notifyRefreshNeeded(isInForeground) { if (this.paused) { this.needsRefresh = true; return; } - var timeout = this.refreshTimeout; + const timeout = this.refreshTimeout; if (timeout) { clearTimeout(timeout); } @@ -195,44 +194,9 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM } else { this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000); } - }; - - function clearRefreshInterval(instance, isPausing) { - if (instance.refreshInterval) { - clearInterval(instance.refreshInterval); - instance.refreshInterval = null; - - if (!isPausing) { - instance.refreshIntervalEndTime = null; - } - } } - function resetRefreshInterval(instance, intervalMs) { - clearRefreshInterval(instance); - - if (!intervalMs) { - var options = instance.options; - if (options) { - intervalMs = options.refreshIntervalMs; - } - } - - if (intervalMs) { - instance.refreshInterval = setInterval(instance.notifyRefreshNeeded.bind(instance), intervalMs); - instance.refreshIntervalEndTime = new Date().getTime() + intervalMs; - } - } - - function onDataFetched(result) { - resetRefreshInterval(this); - - if (this.afterRefresh) { - this.afterRefresh(result); - } - } - - ItemsRefresher.prototype.destroy = function () { + destroy() { clearRefreshInterval(this); removeNotificationEvent(this, 'UserDataChanged'); @@ -245,7 +209,42 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackM this.fetchData = null; this.options = null; - }; + } +} - return ItemsRefresher; -}); +function clearRefreshInterval(instance, isPausing) { + if (instance.refreshInterval) { + clearInterval(instance.refreshInterval); + instance.refreshInterval = null; + + if (!isPausing) { + instance.refreshIntervalEndTime = null; + } + } +} + +function resetRefreshInterval(instance, intervalMs) { + clearRefreshInterval(instance); + + if (!intervalMs) { + const options = instance.options; + if (options) { + intervalMs = options.refreshIntervalMs; + } + } + + if (intervalMs) { + instance.refreshInterval = setInterval(instance.notifyRefreshNeeded.bind(instance), intervalMs); + instance.refreshIntervalEndTime = new Date().getTime() + intervalMs; + } +} + +function onDataFetched(result) { + resetRefreshInterval(this); + + if (this.afterRefresh) { + this.afterRefresh(result); + } +} + +export default ItemsRefresher; diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index 85d78f8ff4..88a7265f8b 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -1,23 +1,19 @@ -define(['browser', 'appSettings', 'events'], function (browser, appSettings, events) { - 'use strict'; +import browser from 'browser'; +import appSettings from 'appSettings'; +import events from 'events'; - browser = browser.default || browser; - - function setLayout(instance, layout, selectedLayout) { - if (layout === selectedLayout) { - instance[layout] = true; - document.documentElement.classList.add('layout-' + layout); - } else { - instance[layout] = false; - document.documentElement.classList.remove('layout-' + layout); - } +function setLayout(instance, layout, selectedLayout) { + if (layout === selectedLayout) { + instance[layout] = true; + document.documentElement.classList.add('layout-' + layout); + } else { + instance[layout] = false; + document.documentElement.classList.remove('layout-' + layout); } +} - function LayoutManager() { - - } - - LayoutManager.prototype.setLayout = function (layout, save) { +class LayoutManager { + setLayout(layout, save) { if (!layout || layout === 'auto') { this.autoLayout(); @@ -35,13 +31,13 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve } events.trigger(this, 'modechange'); - }; + } - LayoutManager.prototype.getSavedLayout = function (layout) { + getSavedLayout(layout) { return appSettings.get('layout'); - }; + } - LayoutManager.prototype.autoLayout = function () { + autoLayout() { // Take a guess at initial layout. The consuming app can override if (browser.mobile) { this.setLayout('mobile', false); @@ -50,16 +46,16 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, eve } else { this.setLayout(this.defaultLayout || 'tv', false); } - }; + } - LayoutManager.prototype.init = function () { - var saved = this.getSavedLayout(); + init() { + const saved = this.getSavedLayout(); if (saved) { this.setLayout(saved, false); } else { this.autoLayout(); } - }; + } +} - return new LayoutManager(); -}); +export default new LayoutManager(); diff --git a/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html index f92a63e403..2884ce256c 100644 --- a/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.template.html @@ -25,7 +25,7 @@

${HeadersFolders}

-
diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html index 6c814cf2dd..ae7d65e1e5 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.template.html @@ -19,7 +19,7 @@

${HeadersFolders}

-
diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index be44e86b44..82b3c66f53 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -6,7 +6,6 @@ import loading from 'loading'; import focusManager from 'focusManager'; import connectionManager from 'connectionManager'; import globalize from 'globalize'; -import require from 'require'; import shell from 'shell'; import 'emby-checkbox'; import 'emby-input'; @@ -37,7 +36,7 @@ import 'flexStyles'; function submitUpdatedItem(form, item) { function afterContentTypeUpdated() { - require(['toast'], function (toast) { + import('toast').then(({default: toast}) => { toast(globalize.translate('MessageItemSaved')); }); @@ -227,7 +226,7 @@ import 'flexStyles'; } function editPerson(context, person, index) { - require(['personEditor'], function (personEditor) { + import('personEditor').then(({default: personEditor}) => { personEditor.show(person).then(function (updatedPerson) { const isNew = index === -1; @@ -246,14 +245,14 @@ import 'flexStyles'; if (parentId) { reload(context, parentId, item.ServerId); } else { - require(['appRouter'], function (appRouter) { + import('appRouter').then(({default: appRouter}) => { appRouter.goHome(); }); } } function showMoreMenu(context, button, user) { - require(['itemContextMenu'], function (itemContextMenu) { + import('itemContextMenu').then(({default: itemContextMenu}) => { var item = currentItem; itemContextMenu.show({ @@ -907,7 +906,7 @@ import 'flexStyles'; } function populatePeople(context, people) { - let lastType = ''; + const lastType = ''; let html = ''; const elem = context.querySelector('#peopleList'); 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/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index d411dcc620..7aa8c623b3 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -500,20 +500,20 @@ import 'emby-ratingbutton'; const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; nowPlayingTextElement.innerHTML = ''; if (textLines) { - let itemText = document.createElement('div'); - let secondaryText = document.createElement('div'); + const itemText = document.createElement('div'); + const secondaryText = document.createElement('div'); secondaryText.classList.add('nowPlayingBarSecondaryText'); if (textLines.length > 1) { textLines[1].secondary = true; if (textLines[1].text) { - let text = document.createElement('a'); + const text = document.createElement('a'); text.innerHTML = textLines[1].text; secondaryText.appendChild(text); } } if (textLines[0].text) { - let text = document.createElement('a'); + const text = document.createElement('a'); text.innerHTML = textLines[0].text; itemText.appendChild(text); } @@ -555,10 +555,10 @@ import 'emby-ratingbutton'; if (!layoutManager.mobile) { let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); // We remove the previous event listener by replacing the item in each update event - let contextButtonClone = contextButton.cloneNode(true); + const contextButtonClone = contextButton.cloneNode(true); contextButton.parentNode.replaceChild(contextButtonClone, contextButton); contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - let options = { + const options = { play: false, queue: false, clearQueue: true, @@ -600,10 +600,10 @@ import 'emby-ratingbutton'; return; } - let shuffleMode = playbackManager.getQueueShuffleMode(); - let context = nowPlayingBarElement; + const shuffleMode = playbackManager.getQueueShuffleMode(); + const context = nowPlayingBarElement; const cssClass = 'buttonActive'; - let toggleShuffleButton = context.querySelector('.btnShuffleQueue'); + const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); switch (shuffleMode) { case 'Shuffle': toggleShuffleButton.classList.add(cssClass); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 0f275c88f7..5c7ddf45dd 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -127,7 +127,7 @@ import connectionManager from 'connectionManager'; artwork: getImageUrls(item) }); } else { - let itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); + const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); window.NativeShell.updateMediaSession({ action: eventName, @@ -244,10 +244,10 @@ import connectionManager from 'connectionManager'; /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('seekto', function (object) { - let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; // Convert to ms - let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); - let wantedTime = object.seekTime * 1000; + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + const wantedTime = object.seekTime * 1000; playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); }); } diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 437127be18..8502b551af 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -8,7 +8,7 @@ import * as userSettings from 'userSettings'; import globalize from 'globalize'; import connectionManager from 'connectionManager'; import loading from 'loading'; -import apphost from 'apphost'; +import appHost from 'apphost'; import screenfull from 'screenfull'; function enableLocalPlaylistManagement(player) { @@ -322,7 +322,7 @@ function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBi PlaySessionId: startingPlaySession, StartTimeTicks: startPosition || 0, EnableRedirection: true, - EnableRemoteMedia: apphost.supports('remoteaudio') + EnableRemoteMedia: appHost.supports('remoteaudio') }); } @@ -606,7 +606,7 @@ function supportsDirectPlay(apiClient, item, mediaSource) { const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; if (mediaSource.SupportsDirectPlay || isFolderRip) { - if (mediaSource.IsRemote && !apphost.supports('remotevideo')) { + if (mediaSource.IsRemote && !appHost.supports('remotevideo')) { return Promise.resolve(false); } @@ -3156,7 +3156,7 @@ class PlaybackManager { return streamInfo ? streamInfo.playbackStartTimeTicks : null; }; - if (apphost.supports('remotecontrol')) { + if (appHost.supports('remotecontrol')) { import('serverNotifications').then(({ default: serverNotifications }) => { events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self)); events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self)); @@ -3520,7 +3520,7 @@ class PlaybackManager { 'PlayTrailers' ]; - if (apphost.supports('fullscreenchange')) { + if (appHost.supports('fullscreenchange')) { list.push('ToggleFullscreen'); } diff --git a/src/components/playbackSettings/playbackSettings.template.html b/src/components/playbackSettings/playbackSettings.template.html index 700215223d..d10b069bb2 100644 --- a/src/components/playbackSettings/playbackSettings.template.html +++ b/src/components/playbackSettings/playbackSettings.template.html @@ -99,6 +99,6 @@
diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 10a5df39ba..63d9557c7b 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,160 +1,158 @@ -define(['globalize'], function (globalize) { - 'use strict'; +import globalize from 'globalize'; - function getVideoQualityOptions(options) { - var maxStreamingBitrate = options.currentMaxBitrate; - var videoWidth = options.videoWidth; - var videoHeight = options.videoHeight; +export function getVideoQualityOptions(options) { + var maxStreamingBitrate = options.currentMaxBitrate; + var videoWidth = options.videoWidth; + var videoHeight = options.videoHeight; - // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. - // 4:3 1440x1080 -> 1920x1080 - if (videoWidth / videoHeight < 16 / 9) { - videoWidth = videoHeight * (16 / 9); - } - - var maxAllowedWidth = videoWidth || 4096; - - var qualityOptions = []; - - if (maxAllowedWidth >= 3800) { - qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); - qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); - qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); - } - - // Some 1080- videos are reported as 1912? - if (maxAllowedWidth >= 1900) { - qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); - qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); - qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); - qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); - qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); - qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); - qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); - qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); - qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); - qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); - qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); - qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); - } else if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); - qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); - qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); - qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); - } else if (maxAllowedWidth >= 620) { - qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); - qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); - qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); - qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); - qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); - } - - if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); - qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); - qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); - - // The extra 1 is because they're keyed off the bitrate value - qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); - qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); - } - - qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); - qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); - qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); - qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); - qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); - qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. + // 4:3 1440x1080 -> 1920x1080 + if (videoWidth / videoHeight < 16 / 9) { + videoWidth = videoHeight * (16 / 9); } - function getAudioQualityOptions(options) { - var maxStreamingBitrate = options.currentMaxBitrate; + var maxAllowedWidth = videoWidth || 4096; - var qualityOptions = []; + var qualityOptions = []; - qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); - qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); - qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); - qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); - qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); - qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); - qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); - qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); - qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + if (maxAllowedWidth >= 3800) { + qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); + qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); + qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); } - return { - getVideoQualityOptions: getVideoQualityOptions, - getAudioQualityOptions: getAudioQualityOptions + // Some 1080- videos are reported as 1912? + if (maxAllowedWidth >= 1900) { + qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); + qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); + qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); + qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); + qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); + qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); + qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); + qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); + qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); + qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); + qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); + qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); + } else if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); + qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); + qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); + qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); + } else if (maxAllowedWidth >= 620) { + qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); + qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); + qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); + qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); + qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); + } + + if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); + qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); + qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); + + // The extra 1 is because they're keyed off the bitrate value + qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); + qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); + } + + qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); + qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); + qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); + qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); + qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); + qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled }; -}); + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export function getAudioQualityOptions(options) { + var maxStreamingBitrate = options.currentMaxBitrate; + + var qualityOptions = []; + + qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); + qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); + qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); + qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); + qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); + qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); + qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); + qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); + qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled + }; + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export default { + getVideoQualityOptions, + getAudioQualityOptions +}; diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js index 1207208e90..9f102e2f9c 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -1,37 +1,40 @@ -define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom', 'recordingHelper', 'events', 'paper-icon-button-light', 'emby-button', 'css!./recordingfields'], function (globalize, connectionManager, require, loading, appHost, dom, recordingHelper, events) { - 'use strict'; +import connectionManager from 'connectionManager'; +import dom from 'dom'; +import recordingHelper from 'recordingHelper'; +import 'paper-icon-button-light'; +import 'emby-button'; +import 'css!./recordingfields'; - recordingHelper = recordingHelper.default || recordingHelper; +function onRecordingButtonClick(e) { + const item = this.item; - function onRecordingButtonClick(e) { - var item = this.item; + if (item) { + const serverId = item.ServerId; + const programId = item.Id; + const timerId = item.TimerId; + const timerStatus = item.Status; + const seriesTimerId = item.SeriesTimerId; - if (item) { - var serverId = item.ServerId; - var programId = item.Id; - var timerId = item.TimerId; - var timerStatus = item.Status; - var seriesTimerId = item.SeriesTimerId; + const instance = this; - var instance = this; - - recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { - instance.refresh(serverId, programId); - }); - } + recordingHelper.toggleRecording(serverId, programId, timerId, timerStatus, seriesTimerId).then(function () { + instance.refresh(serverId, programId); + }); } +} - function setButtonIcon(button, icon) { - var inner = button.querySelector('.material-icons'); - inner.classList.remove('fiber_smart_record'); - inner.classList.remove('fiber_manual_record'); - inner.classList.add(icon); - } +function setButtonIcon(button, icon) { + const inner = button.querySelector('.material-icons'); + inner.classList.remove('fiber_smart_record'); + inner.classList.remove('fiber_manual_record'); + inner.classList.add(icon); +} - function RecordingButton(options) { +class RecordingButton { + constructor(options) { this.options = options; - var button = options.button; + const button = options.button; setButtonIcon(button, 'fiber_manual_record'); @@ -41,7 +44,7 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.refresh(options.itemId, options.serverId); } - var clickFn = onRecordingButtonClick.bind(this); + const clickFn = onRecordingButtonClick.bind(this); this.clickFn = clickFn; dom.addEventListener(button, 'click', clickFn, { @@ -49,39 +52,17 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' }); } - function getIndicatorIcon(item) { - var status; - - if (item.Type === 'SeriesTimer') { - return 'fiber_smart_record'; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; - } else { - return 'fiber_manual_record'; - } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return 'fiber_smart_record'; - } - } - - return 'fiber_manual_record'; - } - - RecordingButton.prototype.refresh = function (serverId, itemId) { - var apiClient = connectionManager.getApiClient(serverId); - var self = this; + refresh(serverId, itemId) { + const apiClient = connectionManager.getApiClient(serverId); + const self = this; apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { self.refreshItem(item); }); - }; + } - RecordingButton.prototype.refreshItem = function (item) { - var options = this.options; - var button = options.button; + refreshItem(item) { + const options = this.options; + const button = options.button; this.item = item; setButtonIcon(button, getIndicatorIcon(item)); @@ -90,15 +71,15 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' } else { button.classList.remove('recordingIcon-active'); } - }; + } - RecordingButton.prototype.destroy = function () { - var options = this.options; + destroy() { + const options = this.options; if (options) { - var button = options.button; + const button = options.button; - var clickFn = this.clickFn; + const clickFn = this.clickFn; if (clickFn) { dom.removeEventListener(button, 'click', clickFn, { @@ -109,7 +90,29 @@ define(['globalize', 'connectionManager', 'require', 'loading', 'apphost', 'dom' this.options = null; this.item = null; - }; + } +} - return RecordingButton; -}); +function getIndicatorIcon(item) { + let status; + + if (item.Type === 'SeriesTimer') { + return 'fiber_smart_record'; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return 'fiber_manual_record'; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { + return 'fiber_smart_record'; + } + } + + return 'fiber_manual_record'; +} + +export default RecordingButton; diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index 20dee6e372..412ec6e274 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -1,174 +1,188 @@ -define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'connectionManager', 'require', 'loading', 'scrollHelper', 'datetime', 'imageLoader', 'recordingFields', 'events', 'emby-checkbox', 'emby-button', 'emby-collapse', 'emby-input', 'paper-icon-button-light', 'css!./../formdialog', 'css!./recordingcreator', 'material-icons'], function (dialogHelper, globalize, layoutManager, mediaInfo, appHost, connectionManager, require, loading, scrollHelper, datetime, imageLoader, recordingFields, events) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import mediaInfo from 'mediaInfo'; +import connectionManager from 'connectionManager'; +import require from 'require'; +import loading from 'loading'; +import scrollHelper from 'scrollHelper'; +import datetime from 'datetime'; +import imageLoader from 'imageLoader'; +import recordingFields from 'recordingFields'; +import events from 'events'; +import 'emby-checkbox'; +import 'emby-button'; +import 'emby-collapse'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'css!./recordingcreator'; +import 'material-icons'; - scrollHelper = scrollHelper.default || scrollHelper; +let currentDialog; +let closeAction; +let currentRecordingFields; - var currentDialog; - var closeAction; - var currentRecordingFields; +function closeDialog() { + dialogHelper.close(currentDialog); +} - function closeDialog() { - dialogHelper.close(currentDialog); +function init(context) { + context.querySelector('.btnPlay').addEventListener('click', function () { + closeAction = 'play'; + closeDialog(); + }); + + context.querySelector('.btnCancel').addEventListener('click', function () { + closeAction = null; + closeDialog(); + }); +} + +function getImageUrl(item, apiClient, imageHeight) { + const imageTags = item.ImageTags || {}; + + if (item.PrimaryImageTag) { + imageTags.Primary = item.PrimaryImageTag; } - function init(context) { - context.querySelector('.btnPlay').addEventListener('click', function () { - closeAction = 'play'; - closeDialog(); + if (imageTags.Primary) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Primary', + maxHeight: imageHeight, + tag: item.ImageTags.Primary }); - - context.querySelector('.btnCancel').addEventListener('click', function () { - closeAction = null; - closeDialog(); + } else if (imageTags.Thumb) { + return apiClient.getScaledImageUrl(item.Id, { + type: 'Thumb', + maxHeight: imageHeight, + tag: item.ImageTags.Thumb }); } - function getImageUrl(item, apiClient, imageHeight) { - var imageTags = item.ImageTags || {}; + return null; +} - if (item.PrimaryImageTag) { - imageTags.Primary = item.PrimaryImageTag; +function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { + if (!refreshRecordingStateOnly) { + const imgUrl = getImageUrl(program, apiClient, 200); + const imageContainer = context.querySelector('.recordingDialog-imageContainer'); + + if (imgUrl) { + imageContainer.innerHTML = ''; + imageContainer.classList.remove('hide'); + + imageLoader.lazyChildren(imageContainer); + } else { + imageContainer.innerHTML = ''; + imageContainer.classList.add('hide'); } - if (imageTags.Primary) { - return apiClient.getScaledImageUrl(item.Id, { - type: 'Primary', - maxHeight: imageHeight, - tag: item.ImageTags.Primary + context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; + context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; + context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); + context.querySelector('.itemOverview').innerHTML = program.Overview || ''; + + const formDialogFooter = context.querySelector('.formDialogFooter'); + const now = new Date(); + if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { + formDialogFooter.classList.remove('hide'); + } else { + formDialogFooter.classList.add('hide'); + } + + context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); + } + + context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { + }); + + loading.hide(); +} + +function reload(context, programId, serverId, refreshRecordingStateOnly) { + loading.show(); + + const apiClient = connectionManager.getApiClient(serverId); + + const promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); + const promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); + + Promise.all([promise1, promise2]).then(function (responses) { + const defaults = responses[0]; + const program = responses[1]; + + renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); + }); +} + +function executeCloseAction(action, programId, serverId) { + if (action === 'play') { + import('playbackManager').then(({default: playbackManager}) => { + const apiClient = connectionManager.getApiClient(serverId); + + apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { + playbackManager.play({ + ids: [item.ChannelId], + serverId: serverId + }); }); - } else if (imageTags.Thumb) { - return apiClient.getScaledImageUrl(item.Id, { - type: 'Thumb', - maxHeight: imageHeight, - tag: item.ImageTags.Thumb - }); - } - - return null; - } - - function renderRecording(context, defaultTimer, program, apiClient, refreshRecordingStateOnly) { - if (!refreshRecordingStateOnly) { - var imgUrl = getImageUrl(program, apiClient, 200); - var imageContainer = context.querySelector('.recordingDialog-imageContainer'); - - if (imgUrl) { - imageContainer.innerHTML = ''; - imageContainer.classList.remove('hide'); - - imageLoader.lazyChildren(imageContainer); - } else { - imageContainer.innerHTML = ''; - imageContainer.classList.add('hide'); - } - - context.querySelector('.recordingDialog-itemName').innerHTML = program.Name; - context.querySelector('.formDialogHeaderTitle').innerHTML = program.Name; - context.querySelector('.itemGenres').innerHTML = (program.Genres || []).join(' / '); - context.querySelector('.itemOverview').innerHTML = program.Overview || ''; - - var formDialogFooter = context.querySelector('.formDialogFooter'); - var now = new Date(); - if (now >= datetime.parseISO8601Date(program.StartDate, true) && now < datetime.parseISO8601Date(program.EndDate, true)) { - formDialogFooter.classList.remove('hide'); - } else { - formDialogFooter.classList.add('hide'); - } - - context.querySelector('.itemMiscInfoPrimary').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(program); - } - - context.querySelector('.itemMiscInfoSecondary').innerHTML = mediaInfo.getSecondaryMediaInfoHtml(program, { }); - - loading.hide(); + return; } +} + +function showEditor(itemId, serverId) { + return new Promise(function (resolve, reject) { + closeAction = null; - function reload(context, programId, serverId, refreshRecordingStateOnly) { loading.show(); - var apiClient = connectionManager.getApiClient(serverId); + import('text!./recordingcreator.template.html').then(({default: template}) => { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; - var promise1 = apiClient.getNewLiveTvTimerDefaults({ programId: programId }); - var promise2 = apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()); + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } - Promise.all([promise1, promise2]).then(function (responses) { - var defaults = responses[0]; - var program = responses[1]; + const dlg = dialogHelper.createDialog(dialogOptions); - renderRecording(context, defaults, program, apiClient, refreshRecordingStateOnly); - }); - } + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - function executeCloseAction(action, programId, serverId) { - if (action === 'play') { - require(['playbackManager'], function (playbackManager) { - var apiClient = connectionManager.getApiClient(serverId); + let html = ''; - apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { - playbackManager.default.play({ - ids: [item.ChannelId], - serverId: serverId - }); - }); - }); - return; - } - } + html += globalize.translateHtml(template, 'core'); - function showEditor(itemId, serverId) { - return new Promise(function (resolve, reject) { - closeAction = null; + dlg.innerHTML = html; - loading.show(); + currentDialog = dlg; - require(['text!./recordingcreator.template.html'], function (template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; + function onRecordingChanged() { + reload(dlg, itemId, serverId, true); + } - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + dlg.addEventListener('close', function () { + events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); + executeCloseAction(closeAction, itemId, serverId); + + if (currentRecordingFields && currentRecordingFields.hasChanged()) { + resolve(); } else { - dialogOptions.size = 'small'; + reject(); } + }); - var dlg = dialogHelper.createDialog(dialogOptions); + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); + } - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - var html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - currentDialog = dlg; - - function onRecordingChanged() { - reload(dlg, itemId, serverId, true); - } - - dlg.addEventListener('close', function () { - events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); - executeCloseAction(closeAction, itemId, serverId); - - if (currentRecordingFields && currentRecordingFields.hasChanged()) { - resolve(); - } else { - reject(); - } - }); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - init(dlg); - - reload(dlg, itemId, serverId); + init(dlg); currentRecordingFields = new recordingFields.default({ parent: dlg.querySelector('.recordingFields'), @@ -178,12 +192,19 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); - dialogHelper.open(dlg); + currentRecordingFields = new recordingFields({ + parent: dlg.querySelector('.recordingFields'), + programId: itemId, + serverId: serverId }); - }); - } - return { - show: showEditor - }; -}); + events.on(currentRecordingFields, 'recordingchanged', onRecordingChanged); + + dialogHelper.open(dlg); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index b5ac4c9a8b..6048c918c7 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,939 +1,954 @@ -define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) { - 'use strict'; +import datetime from 'datetime'; +import backdrop from 'backdrop'; +import listView from 'listView'; +import imageLoader from 'imageLoader'; +import playbackManager from 'playbackManager'; +import nowPlayingHelper from 'nowPlayingHelper'; +import events from 'events'; +import connectionManager from 'connectionManager'; +import appHost from 'apphost'; +import globalize from 'globalize'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import cardBuilder from 'cardBuilder'; +import itemContextMenu from 'itemContextMenu'; +import 'cardStyle'; +import 'emby-itemscontainer'; +import 'css!./remotecontrol.css'; +import 'emby-ratingbutton'; - playbackManager = playbackManager.default || playbackManager; +/*eslint prefer-const: "error"*/ - var showMuteButton = true; - var showVolumeSlider = true; +let showMuteButton = true; +let showVolumeSlider = true; - function showAudioMenu(context, player, button, item) { - var currentIndex = playbackManager.getAudioStreamIndex(player); - var streams = playbackManager.audioTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; +function showAudioMenu(context, player, button, item) { + const currentIndex = playbackManager.getAudioStreamIndex(player); + const streams = playbackManager.audioTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; - if (s.Index == currentIndex) { - menuItem.selected = true; - } - - return menuItem; - }); - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setAudioStreamIndex(parseInt(id), player); - } - }); - }); - } - - function showSubtitleMenu(context, player, button, item) { - var currentIndex = playbackManager.getSubtitleStreamIndex(player); - var streams = playbackManager.subtitleTracks(player); - var menuItems = streams.map(function (s) { - var menuItem = { - name: s.DisplayTitle, - id: s.Index - }; - - if (s.Index == currentIndex) { - menuItem.selected = true; - } - - return menuItem; - }); - menuItems.unshift({ - id: -1, - name: globalize.translate('ButtonOff'), - selected: currentIndex == null - }); - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: button, - callback: function (id) { - playbackManager.setSubtitleStreamIndex(parseInt(id), player); - } - }); - }); - } - - function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { - return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { - return i.text; - }).join('
'); - } - - function seriesImageUrl(item, options) { - if (item.Type !== 'Episode') { - return null; + if (s.Index == currentIndex) { + menuItem.selected = true; } - options = options || {}; - options.type = options.type || 'Primary'; - if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + return menuItem; + }); + + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setAudioStreamIndex(parseInt(id), player); + } + }); + }); +} + +function showSubtitleMenu(context, player, button, item) { + const currentIndex = playbackManager.getSubtitleStreamIndex(player); + const streams = playbackManager.subtitleTracks(player); + const menuItems = streams.map(function (s) { + const menuItem = { + name: s.DisplayTitle, + id: s.Index + }; + + if (s.Index == currentIndex) { + menuItem.selected = true; + } + + return menuItem; + }); + menuItems.unshift({ + id: -1, + name: globalize.translate('ButtonOff'), + selected: currentIndex == null + }); + + import('actionsheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: button, + callback: function (id) { + playbackManager.setSubtitleStreamIndex(parseInt(id), player); + } + }); + }); +} + +function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) { + return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) { + return i.text; + }).join('
'); +} + +function seriesImageUrl(item, options) { + if (item.Type !== 'Episode') { + return null; + } + + options = options || {}; + options.type = options.type || 'Primary'; + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } + + if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } - if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - - if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } - - return null; } - function imageUrl(item, options) { - options = options || {}; - options.type = options.type || 'Primary'; + return null; +} - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); - } +function imageUrl(item, options) { + options = options || {}; + options.type = options.type || 'Primary'; - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); - } - - return null; + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } - function updateNowPlayingInfo(context, state, serverId) { - var item = state.NowPlayingItem; - var displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; - if (typeof item !== 'undefined') { - var nowPlayingServerId = (item.ServerId || serverId); - if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { - var songName = item.Name; - var artistsSeries = ''; - var albumName = ''; - if (item.Artists != null) { - if (item.ArtistItems != null) { - for (const artist of item.ArtistItems) { - let artistName = artist.Name; - let artistId = artist.Id; - artistsSeries += `${artistName}`; - if (artist !== item.ArtistItems.slice(-1)[0]) { - artistsSeries += ', '; - } + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return connectionManager.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function updateNowPlayingInfo(context, state, serverId) { + const item = state.NowPlayingItem; + const displayName = item ? getNowPlayingNameHtml(item).replace('
', ' - ') : ''; + if (typeof item !== 'undefined') { + const nowPlayingServerId = (item.ServerId || serverId); + if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') { + const songName = item.Name; + let artistsSeries = ''; + let albumName = ''; + if (item.Artists != null) { + if (item.ArtistItems != null) { + for (const artist of item.ArtistItems) { + const artistName = artist.Name; + const artistId = artist.Id; + artistsSeries += `${artistName}`; + if (artist !== item.ArtistItems.slice(-1)[0]) { + artistsSeries += ', '; } - } else if (item.Artists) { - // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback - // to normal item.Artists item. - // TODO: Normalise fields returned by all the players - for (const artist of item.Artists) { - artistsSeries += `${artist}`; - if (artist !== item.Artists.slice(-1)[0]) { - artistsSeries += ', '; - } + } + } else if (item.Artists) { + // For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback + // to normal item.Artists item. + // TODO: Normalise fields returned by all the players + for (const artist of item.Artists) { + artistsSeries += `${artist}`; + if (artist !== item.Artists.slice(-1)[0]) { + artistsSeries += ', '; } } } - if (item.Album != null) { - albumName = '` + item.Album + ''; - } - context.querySelector('.nowPlayingAlbum').innerHTML = albumName; - context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; - context.querySelector('.nowPlayingSongName').innerHTML = songName; - } else if (item.Type == 'Episode') { - if (item.SeasonName != null) { - var seasonName = item.SeasonName; - context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; - } - if (item.SeriesName != null) { - var seriesName = item.SeriesName; - if (item.SeriesId != null) { - context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; - } else { - context.querySelector('.nowPlayingSerie').innerHTML = seriesName; - } - } - context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; - } else { - context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; } - - if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { - context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); - } else { - context.querySelector('.nowPlayingPageTitle').classList.add('hide'); + if (item.Album != null) { + albumName = '` + item.Album + ''; } - - var url = item ? seriesImageUrl(item, { - maxHeight: 300 - }) || imageUrl(item, { - maxHeight: 300 - }) : null; - - let contextButton = context.querySelector('.btnToggleContextMenu'); - // We remove the previous event listener by replacing the item in each update event - const autoFocusContextButton = document.activeElement === contextButton; - let contextButtonClone = contextButton.cloneNode(true); - contextButton.parentNode.replaceChild(contextButtonClone, contextButton); - contextButton = context.querySelector('.btnToggleContextMenu'); - if (autoFocusContextButton) { - contextButton.focus(); + context.querySelector('.nowPlayingAlbum').innerHTML = albumName; + context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries; + context.querySelector('.nowPlayingSongName').innerHTML = songName; + } else if (item.Type == 'Episode') { + if (item.SeasonName != null) { + const seasonName = item.SeasonName; + context.querySelector('.nowPlayingSeason').innerHTML = '${seasonName}`; } - const stopPlayback = !!layoutManager.mobile; - var options = { - play: false, - queue: false, - stopPlayback: stopPlayback, - clearQueue: true, - openAlbum: false, - positionTo: contextButton - }; - var apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - apiClient.getCurrentUser().then(function (user) { - contextButton.addEventListener('click', function () { - itemContextMenu.show(Object.assign({ - item: fullItem, - user: user - }, options)); - }); - }); - }); - setImageUrl(context, state, url); - if (item) { - backdrop.setBackdrops([item]); - apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { - var userData = fullItem.UserData || {}; - var likes = userData.Likes == null ? '' : userData.Likes; - context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - }); - } else { - backdrop.clearBackdrop(); - context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; - } - } - } - - function setImageUrl(context, state, url) { - var item = state.NowPlayingItem; - var imgContainer = context.querySelector('.nowPlayingPageImageContainer'); - - if (url) { - imgContainer.innerHTML = ''; - if (item.Type == 'Audio') { - context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); - context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); - } else { - context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); - context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); - } - } else { - imgContainer.innerHTML = '
'; - } - } - - function buttonVisible(btn, enabled) { - if (enabled) { - btn.classList.remove('hide'); - } else { - btn.classList.add('hide'); - } - } - - function updateSupportedCommands(context, commands) { - var all = context.querySelectorAll('.btnCommand'); - - for (var i = 0, length = all.length; i < length; i++) { - var enableButton = commands.indexOf(all[i].getAttribute('data-command')) !== -1; - all[i].disabled = !enableButton; - } - } - - return function () { - function toggleRepeat() { - switch (playbackManager.getRepeatMode()) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne'); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone'); - break; - case 'RepeatNone': - playbackManager.setRepeatMode('RepeatAll'); - } - } - - function updatePlayerState(player, context, state) { - lastPlayerState = state; - var item = state.NowPlayingItem; - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - var playState = state.PlayState || {}; - var isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); - buttonVisible(context.querySelector('.btnToggleFullscreen'), item && item.MediaType == 'Video' && supportedCommands.includes('ToggleFullscreen')); - updateAudioTracksDisplay(player, context); - updateSubtitleTracksDisplay(player, context); - - if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendMessageSection').classList.remove('hide'); - } else { - context.querySelector('.sendMessageSection').classList.add('hide'); - } - - if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { - context.querySelector('.sendTextSection').classList.remove('hide'); - } else { - context.querySelector('.sendTextSection').classList.add('hide'); - } - - if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { - context.querySelector('.navigationSection').classList.remove('hide'); - } else { - context.querySelector('.navigationSection').classList.add('hide'); - } - - if (isSupportedCommands && !currentPlayer.isLocalPlayer) { - context.querySelector('.remoteControlSection').classList.remove('hide'); - } else { - context.querySelector('.remoteControlSection').classList.add('hide'); - } - - buttonVisible(context.querySelector('.btnStop'), item != null); - buttonVisible(context.querySelector('.btnNextTrack'), item != null); - buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); - if (layoutManager.mobile) { - buttonVisible(context.querySelector('.btnRewind'), false); - buttonVisible(context.querySelector('.btnFastForward'), false); - } else { - buttonVisible(context.querySelector('.btnRewind'), item != null); - buttonVisible(context.querySelector('.btnFastForward'), item != null); - } - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && item && item.RunTimeTicks) { - positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, - userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); - } - - if (positionSlider && !positionSlider.dragging) { - positionSlider.disabled = !playState.CanSeek; - var isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; - positionSlider.setIsClear(isProgressClear); - } - - updatePlayPauseState(playState.IsPaused, item != null); - updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); - updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); - - if (item && item.MediaType == 'Video') { - context.classList.remove('hideVideoButtons'); - } else { - context.classList.add('hideVideoButtons'); - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - onShuffleQueueModeChange(false); - updateNowPlayingInfo(context, state); - } - - function updateAudioTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && supportedCommands.indexOf('SetAudioStreamIndex') != -1); - } - - function updateSubtitleTracksDisplay(player, context) { - var supportedCommands = currentPlayerSupportedCommands; - buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && supportedCommands.indexOf('SetSubtitleStreamIndex') != -1); - } - - function updateRepeatModeDisplay(repeatMode) { - var context = dlg; - let toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); - const cssClass = 'buttonActive'; - let innHtml = ''; - let repeatOn = true; - - switch (repeatMode) { - case 'RepeatAll': - break; - case 'RepeatOne': - innHtml = ''; - break; - case 'RepeatNone': - default: - repeatOn = false; - break; - } - - for (const toggleRepeatButton of toggleRepeatButtons) { - toggleRepeatButton.classList.toggle(cssClass, repeatOn); - toggleRepeatButton.innerHTML = innHtml; - } - } - - function updatePlayerVolumeState(context, isMuted, volumeLevel) { - var view = context; - var supportedCommands = currentPlayerSupportedCommands; - - if (supportedCommands.indexOf('Mute') === -1) { - showMuteButton = false; - } - - if (supportedCommands.indexOf('SetVolume') === -1) { - showVolumeSlider = false; - } - - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } - - const buttonMute = view.querySelector('.buttonMute'); - const buttonMuteIcon = buttonMute.querySelector('.material-icons'); - - buttonMuteIcon.classList.remove('volume_off', 'volume_up'); - - if (isMuted) { - buttonMute.setAttribute('title', globalize.translate('Unmute')); - buttonMuteIcon.classList.add('volume_off'); - } else { - buttonMute.setAttribute('title', globalize.translate('Mute')); - buttonMuteIcon.classList.add('volume_up'); - } - - if (!showMuteButton && !showVolumeSlider) { - context.querySelector('.volumecontrol').classList.add('hide'); - } else { - buttonMute.classList.toggle('hide', !showMuteButton); - - var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); - var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); - - if (nowPlayingVolumeSlider) { - nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - - if (!nowPlayingVolumeSlider.dragging) { - nowPlayingVolumeSlider.value = volumeLevel || 0; - } - } - } - } - - function updatePlayPauseState(isPaused, isActive) { - var context = dlg; - var btnPlayPause = context.querySelector('.btnPlayPause'); - const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - - btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); - btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - - buttonVisible(btnPlayPause, isActive); - } - - function updateTimeDisplay(positionTicks, runtimeTicks) { - var context = dlg; - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - if (positionSlider && !positionSlider.dragging) { - if (runtimeTicks) { - var pct = positionTicks / runtimeTicks; - pct *= 100; - positionSlider.value = pct; + if (item.SeriesName != null) { + const seriesName = item.SeriesName; + if (item.SeriesId != null) { + context.querySelector('.nowPlayingSerie').innerHTML = '${seriesName}`; } else { - positionSlider.value = 0; + context.querySelector('.nowPlayingSerie').innerHTML = seriesName; } } - - context.querySelector('.positionTime').innerHTML = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - context.querySelector('.runtime').innerHTML = runtimeTicks != null ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + context.querySelector('.nowPlayingEpisode').innerHTML = item.Name; + } else { + context.querySelector('.nowPlayingPageTitle').innerHTML = displayName; } - function getPlaylistItems(player) { - return playbackManager.getPlaylist(player); + if (displayName.length > 0 && item.Type != 'Audio' && item.Type != 'Episode') { + context.querySelector('.nowPlayingPageTitle').classList.remove('hide'); + } else { + context.querySelector('.nowPlayingPageTitle').classList.add('hide'); } - function loadPlaylist(context, player) { - getPlaylistItems(player).then(function (items) { - var html = ''; - let favoritesEnabled = true; - if (layoutManager.mobile) { - if (items.length > 0) { - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - } else { - context.querySelector('.btnTogglePlaylist').classList.add('hide'); - } - favoritesEnabled = false; - } + const url = item ? seriesImageUrl(item, { + maxHeight: 300 + }) || imageUrl(item, { + maxHeight: 300 + }) : null; - html += listView.getListViewHtml({ - items: items, - smallIcon: true, - action: 'setplaylistindex', - enableUserDataButtons: favoritesEnabled, - rightButtons: [{ - icon: 'remove_circle_outline', - title: globalize.translate('ButtonRemove'), - id: 'remove' - }], - dragHandle: true + let contextButton = context.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const autoFocusContextButton = document.activeElement === contextButton; + const contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = context.querySelector('.btnToggleContextMenu'); + if (autoFocusContextButton) { + contextButton.focus(); + } + const stopPlayback = !!layoutManager.mobile; + const options = { + play: false, + queue: false, + stopPlayback: stopPlayback, + clearQueue: true, + openAlbum: false, + positionTo: contextButton + }; + const apiClient = connectionManager.getApiClient(item.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: fullItem, + user: user + }, options)); }); - - var itemsContainer = context.querySelector('.playlist'); - let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); - itemsContainer.innerHTML = html; - if (focusedItemPlaylistId !== null) { - focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); - const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); - if (newFocusedItem !== null) { - newFocusedItem.focus(); - } - } - - var playlistItemId = playbackManager.getCurrentPlaylistItemId(player); - - if (playlistItemId) { - var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); - - if (img) { - img.classList.remove('lazy'); - img.classList.add('playlistIndexIndicatorImage'); - } - } - - imageLoader.lazyChildren(itemsContainer); }); + }); + setImageUrl(context, state, url); + if (item) { + backdrop.setBackdrops([item]); + apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { + const userData = fullItem.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = ''; + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + }); + } else { + backdrop.clearBackdrop(); + context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = ''; + } + } +} + +function setImageUrl(context, state, url) { + const item = state.NowPlayingItem; + const imgContainer = context.querySelector('.nowPlayingPageImageContainer'); + + if (url) { + imgContainer.innerHTML = ''; + if (item.Type == 'Audio') { + context.querySelector('.nowPlayingPageImage').classList.add('nowPlayingPageImageAudio'); + context.querySelector('.nowPlayingPageImageContainer').classList.remove('nowPlayingPageImageAudio'); + } else { + context.querySelector('.nowPlayingPageImageContainer').classList.add('nowPlayingPageImagePoster'); + context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); + } + } else { + imgContainer.innerHTML = '
'; + } +} + +function buttonVisible(btn, enabled) { + if (enabled) { + btn.classList.remove('hide'); + } else { + btn.classList.add('hide'); + } +} + +function updateSupportedCommands(context, commands) { + const all = context.querySelectorAll('.btnCommand'); + + for (let i = 0, length = all.length; i < length; i++) { + const enableButton = commands.indexOf(all[i].getAttribute('data-command')) !== -1; + all[i].disabled = !enableButton; + } +} + +export default function () { + function toggleRepeat() { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); + } + } + + function updatePlayerState(player, context, state) { + lastPlayerState = state; + const item = state.NowPlayingItem; + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + const playState = state.PlayState || {}; + const isSupportedCommands = supportedCommands.includes('DisplayMessage') || supportedCommands.includes('SendString') || supportedCommands.includes('Select'); + buttonVisible(context.querySelector('.btnToggleFullscreen'), item && item.MediaType == 'Video' && supportedCommands.includes('ToggleFullscreen')); + updateAudioTracksDisplay(player, context); + updateSubtitleTracksDisplay(player, context); + + if (supportedCommands.includes('DisplayMessage') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendMessageSection').classList.remove('hide'); + } else { + context.querySelector('.sendMessageSection').classList.add('hide'); } - function onPlaybackStart(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; - onStateChanged.call(player, e, state); + if (supportedCommands.includes('SendString') && !currentPlayer.isLocalPlayer) { + context.querySelector('.sendTextSection').classList.remove('hide'); + } else { + context.querySelector('.sendTextSection').classList.add('hide'); } - function onRepeatModeChange() { - updateRepeatModeDisplay(playbackManager.getRepeatMode()); + if (supportedCommands.includes('Select') && !currentPlayer.isLocalPlayer) { + context.querySelector('.navigationSection').classList.remove('hide'); + } else { + context.querySelector('.navigationSection').classList.add('hide'); } - function onShuffleQueueModeChange(updateView = true) { - let shuffleMode = playbackManager.getQueueShuffleMode(this); - let context = dlg; - const cssClass = 'buttonActive'; - let shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + if (isSupportedCommands && !currentPlayer.isLocalPlayer) { + context.querySelector('.remoteControlSection').classList.remove('hide'); + } else { + context.querySelector('.remoteControlSection').classList.add('hide'); + } - for (let shuffleButton of shuffleButtons) { - switch (shuffleMode) { - case 'Shuffle': - shuffleButton.classList.add(cssClass); - break; - case 'Sorted': - default: - shuffleButton.classList.remove(cssClass); - break; + buttonVisible(context.querySelector('.btnStop'), item != null); + buttonVisible(context.querySelector('.btnNextTrack'), item != null); + buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); + if (layoutManager.mobile) { + buttonVisible(context.querySelector('.btnRewind'), false); + buttonVisible(context.querySelector('.btnFastForward'), false); + } else { + buttonVisible(context.querySelector('.btnRewind'), item != null); + buttonVisible(context.querySelector('.btnFastForward'), item != null); + } + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && item && item.RunTimeTicks) { + positionSlider.setKeyboardSteps(userSettings.skipBackLength() * 1000000 / item.RunTimeTicks, + userSettings.skipForwardLength() * 1000000 / item.RunTimeTicks); + } + + if (positionSlider && !positionSlider.dragging) { + positionSlider.disabled = !playState.CanSeek; + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + positionSlider.setIsClear(isProgressClear); + } + + updatePlayPauseState(playState.IsPaused, item != null); + updateTimeDisplay(playState.PositionTicks, item ? item.RunTimeTicks : null); + updatePlayerVolumeState(context, playState.IsMuted, playState.VolumeLevel); + + if (item && item.MediaType == 'Video') { + context.classList.remove('hideVideoButtons'); + } else { + context.classList.add('hideVideoButtons'); + } + + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onShuffleQueueModeChange(false); + updateNowPlayingInfo(context, state); + } + + function updateAudioTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnAudioTracks'), playbackManager.audioTracks(player).length > 1 && supportedCommands.indexOf('SetAudioStreamIndex') != -1); + } + + function updateSubtitleTracksDisplay(player, context) { + const supportedCommands = currentPlayerSupportedCommands; + buttonVisible(context.querySelector('.btnSubtitles'), playbackManager.subtitleTracks(player).length && supportedCommands.indexOf('SetSubtitleStreamIndex') != -1); + } + + function updateRepeatModeDisplay(repeatMode) { + const context = dlg; + const toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton'); + const cssClass = 'buttonActive'; + let innHtml = ''; + let repeatOn = true; + + switch (repeatMode) { + case 'RepeatAll': + break; + case 'RepeatOne': + innHtml = ''; + break; + case 'RepeatNone': + default: + repeatOn = false; + break; + } + + for (const toggleRepeatButton of toggleRepeatButtons) { + toggleRepeatButton.classList.toggle(cssClass, repeatOn); + toggleRepeatButton.innerHTML = innHtml; + } + } + + function updatePlayerVolumeState(context, isMuted, volumeLevel) { + const view = context; + const supportedCommands = currentPlayerSupportedCommands; + + if (supportedCommands.indexOf('Mute') === -1) { + showMuteButton = false; + } + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + const buttonMute = view.querySelector('.buttonMute'); + const buttonMuteIcon = buttonMute.querySelector('.material-icons'); + + buttonMuteIcon.classList.remove('volume_off', 'volume_up'); + + if (isMuted) { + buttonMute.setAttribute('title', globalize.translate('Unmute')); + buttonMuteIcon.classList.add('volume_off'); + } else { + buttonMute.setAttribute('title', globalize.translate('Mute')); + buttonMuteIcon.classList.add('volume_up'); + } + + if (!showMuteButton && !showVolumeSlider) { + context.querySelector('.volumecontrol').classList.add('hide'); + } else { + buttonMute.classList.toggle('hide', !showMuteButton); + + const nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); + const nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); + + if (nowPlayingVolumeSlider) { + nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider); + + if (!nowPlayingVolumeSlider.dragging) { + nowPlayingVolumeSlider.value = volumeLevel || 0; } } - - if (updateView) { - onPlaylistUpdate(); - } } + } - function onPlaylistUpdate(e) { - loadPlaylist(dlg, this); - } + function updatePlayPauseState(isPaused, isActive) { + const context = dlg; + const btnPlayPause = context.querySelector('.btnPlayPause'); + const btnPlayPauseIcon = btnPlayPause.querySelector('.material-icons'); - function onPlaylistItemRemoved(e, info) { - var context = dlg; - if (info !== undefined) { - var playlistItemIds = info.playlistItemIds; + btnPlayPauseIcon.classList.remove('play_circle_filled', 'pause_circle_filled'); + btnPlayPauseIcon.classList.add(isPaused ? 'play_circle_filled' : 'pause_circle_filled'); - for (var i = 0, length = playlistItemIds.length; i < length; i++) { - var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + buttonVisible(btnPlayPause, isActive); + } - if (listItem) { - listItem.parentNode.removeChild(listItem); - } - } + function updateTimeDisplay(positionTicks, runtimeTicks) { + const context = dlg; + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + if (positionSlider && !positionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + positionSlider.value = pct; } else { - onPlaylistUpdate(); + positionSlider.value = 0; } } - function onPlaybackStopped(e, state) { - console.debug('remotecontrol event: ' + e.type); - var player = this; + context.querySelector('.positionTime').innerHTML = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + context.querySelector('.runtime').innerHTML = runtimeTicks != null ? datetime.getDisplayRunningTime(runtimeTicks) : '--:--'; + } - if (!state.NextMediaType) { - updatePlayerState(player, dlg, {}); - Emby.Page.back(); + function getPlaylistItems(player) { + return playbackManager.getPlaylist(player); + } + + function loadPlaylist(context, player) { + getPlaylistItems(player).then(function (items) { + let html = ''; + let favoritesEnabled = true; + if (layoutManager.mobile) { + if (items.length > 0) { + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + } else { + context.querySelector('.btnTogglePlaylist').classList.add('hide'); + } + favoritesEnabled = false; + } + + html += listView.getListViewHtml({ + items: items, + smallIcon: true, + action: 'setplaylistindex', + enableUserDataButtons: favoritesEnabled, + rightButtons: [{ + icon: 'remove_circle_outline', + title: globalize.translate('ButtonRemove'), + id: 'remove' + }], + dragHandle: true + }); + + const itemsContainer = context.querySelector('.playlist'); + let focusedItemPlaylistId = itemsContainer.querySelector('button:focus'); + itemsContainer.innerHTML = html; + if (focusedItemPlaylistId !== null) { + focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid'); + const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`); + if (newFocusedItem !== null) { + newFocusedItem.focus(); + } + } + + const playlistItemId = playbackManager.getCurrentPlaylistItemId(player); + + if (playlistItemId) { + const img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`); + + if (img) { + img.classList.remove('lazy'); + img.classList.add('playlistIndexIndicatorImage'); + } + } + + imageLoader.lazyChildren(itemsContainer); + }); + } + + function onPlaybackStart(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); + } + + function onRepeatModeChange() { + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + } + + function onShuffleQueueModeChange(updateView = true) { + const shuffleMode = playbackManager.getQueueShuffleMode(this); + const context = dlg; + const cssClass = 'buttonActive'; + const shuffleButtons = context.querySelectorAll('.btnShuffleQueue'); + + for (const shuffleButton of shuffleButtons) { + switch (shuffleMode) { + case 'Shuffle': + shuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + shuffleButton.classList.remove(cssClass); + break; } } - function onPlayPauseStateChanged(e) { - updatePlayPauseState(this.paused(), true); - } - - function onStateChanged(event, state) { - var player = this; - updatePlayerState(player, dlg, state); + if (updateView) { onPlaylistUpdate(); } + } - function onTimeUpdate(e) { - var now = new Date().getTime(); + function onPlaylistUpdate(e) { + loadPlaylist(dlg, this); + } - if (!(now - lastUpdateTime < 700)) { - lastUpdateTime = now; - var player = this; - currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks); + function onPlaylistItemRemoved(e, info) { + const context = dlg; + if (info !== undefined) { + const playlistItemIds = info.playlistItemIds; + + for (let i = 0, length = playlistItemIds.length; i < length; i++) { + const listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); + + if (listItem) { + listItem.parentNode.removeChild(listItem); + } + } + } else { + onPlaylistUpdate(); + } + } + + function onPlaybackStopped(e, state) { + console.debug('remotecontrol event: ' + e.type); + const player = this; + + if (!state.NextMediaType) { + updatePlayerState(player, dlg, {}); + Emby.Page.back(); + } + } + + function onPlayPauseStateChanged(e) { + updatePlayPauseState(this.paused(), true); + } + + function onStateChanged(event, state) { + const player = this; + updatePlayerState(player, dlg, state); + onPlaylistUpdate(); + } + + function onTimeUpdate(e) { + const now = new Date().getTime(); + + if (!(now - lastUpdateTime < 700)) { + lastUpdateTime = now; + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + updateTimeDisplay(playbackManager.currentTime(player), currentRuntimeTicks); + } + } + + function onVolumeChanged(e) { + const player = this; + updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + } + + function releaseCurrentPlayer() { + const player = currentPlayer; + + if (player) { + events.off(player, 'playbackstart', onPlaybackStart); + events.off(player, 'statechange', onStateChanged); + events.off(player, 'repeatmodechange', onRepeatModeChange); + events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.off(player, 'playlistitemremove', onPlaylistItemRemoved); + events.off(player, 'playlistitemmove', onPlaylistUpdate); + events.off(player, 'playlistitemadd', onPlaylistUpdate); + events.off(player, 'playbackstop', onPlaybackStopped); + events.off(player, 'volumechange', onVolumeChanged); + events.off(player, 'pause', onPlayPauseStateChanged); + events.off(player, 'unpause', onPlayPauseStateChanged); + events.off(player, 'timeupdate', onTimeUpdate); + currentPlayer = null; + } + } + + function bindToPlayer(context, player) { + if (releaseCurrentPlayer(), currentPlayer = player, player) { + const state = playbackManager.getPlayerState(player); + onStateChanged.call(player, { + type: 'init' + }, state); + events.on(player, 'playbackstart', onPlaybackStart); + events.on(player, 'statechange', onStateChanged); + events.on(player, 'repeatmodechange', onRepeatModeChange); + events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); + events.on(player, 'playlistitemremove', onPlaylistItemRemoved); + events.on(player, 'playlistitemmove', onPlaylistUpdate); + events.on(player, 'playlistitemadd', onPlaylistUpdate); + events.on(player, 'playbackstop', onPlaybackStopped); + events.on(player, 'volumechange', onVolumeChanged); + events.on(player, 'pause', onPlayPauseStateChanged); + events.on(player, 'unpause', onPlayPauseStateChanged); + events.on(player, 'timeupdate', onTimeUpdate); + const playerInfo = playbackManager.getPlayerInfo(); + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + updateSupportedCommands(context, supportedCommands); + } + } + + function onBtnCommandClick() { + if (currentPlayer) { + if (this.classList.contains('repeatToggleButton')) { + toggleRepeat(); + } else { + playbackManager.sendCommand({ + Name: this.getAttribute('data-command') + }, currentPlayer); } } + } - function onVolumeChanged(e) { - var player = this; - updatePlayerVolumeState(dlg, player.isMuted(), player.getVolume()); + function getSaveablePlaylistItems() { + return getPlaylistItems(currentPlayer).then(function (items) { + return items.filter(function (i) { + return i.Id && i.ServerId; + }); + }); + } + + function savePlaylist() { + import('playlistEditor').then(({ default: playlistEditor }) => { + getSaveablePlaylistItems().then(function (items) { + const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); + new playlistEditor({ + items: items.map(function (i) { + return i.Id; + }), + serverId: serverId, + enableAddToPlayQueue: false, + defaultValue: 'new' + }); + }); + }); + } + + function bindEvents(context) { + const btnCommand = context.querySelectorAll('.btnCommand'); + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + + for (let i = 0, length = btnCommand.length; i < length; i++) { + btnCommand[i].addEventListener('click', onBtnCommandClick); } - function releaseCurrentPlayer() { - var player = currentPlayer; - - if (player) { - events.off(player, 'playbackstart', onPlaybackStart); - events.off(player, 'statechange', onStateChanged); - events.off(player, 'repeatmodechange', onRepeatModeChange); - events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.off(player, 'playlistitemremove', onPlaylistItemRemoved); - events.off(player, 'playlistitemmove', onPlaylistUpdate); - events.off(player, 'playlistitemadd', onPlaylistUpdate); - events.off(player, 'playbackstop', onPlaybackStopped); - events.off(player, 'volumechange', onVolumeChanged); - events.off(player, 'pause', onPlayPauseStateChanged); - events.off(player, 'unpause', onPlayPauseStateChanged); - events.off(player, 'timeupdate', onTimeUpdate); - currentPlayer = null; - } - } - - function bindToPlayer(context, player) { - if (releaseCurrentPlayer(), currentPlayer = player, player) { - var state = playbackManager.getPlayerState(player); - onStateChanged.call(player, { - type: 'init' - }, state); - events.on(player, 'playbackstart', onPlaybackStart); - events.on(player, 'statechange', onStateChanged); - events.on(player, 'repeatmodechange', onRepeatModeChange); - events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange); - events.on(player, 'playlistitemremove', onPlaylistItemRemoved); - events.on(player, 'playlistitemmove', onPlaylistUpdate); - events.on(player, 'playlistitemadd', onPlaylistUpdate); - events.on(player, 'playbackstop', onPlaybackStopped); - events.on(player, 'volumechange', onVolumeChanged); - events.on(player, 'pause', onPlayPauseStateChanged); - events.on(player, 'unpause', onPlayPauseStateChanged); - events.on(player, 'timeupdate', onTimeUpdate); - var playerInfo = playbackManager.getPlayerInfo(); - var supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; - updateSupportedCommands(context, supportedCommands); - } - } - - function onBtnCommandClick() { + context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { if (currentPlayer) { - if (this.classList.contains('repeatToggleButton')) { - toggleRepeat(); + playbackManager.sendCommand({ + Name: e.target.getAttribute('data-command') + }, currentPlayer); + } + }); + context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnSubtitles').addEventListener('click', function (e) { + if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { + showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); + } + }); + context.querySelector('.btnStop').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.stop(currentPlayer); + } + }); + context.querySelector('.btnPlayPause').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.playPause(currentPlayer); + } + }); + context.querySelector('.btnNextTrack').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.nextTrack(currentPlayer); + } + }); + context.querySelector('.btnRewind').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.rewind(currentPlayer); + } + }); + context.querySelector('.btnFastForward').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.fastForward(currentPlayer); + } + }); + for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { + shuffleButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(currentPlayer); + } + }); + } + + context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { + if (currentPlayer) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { + // Cancel this event if doubleclick is fired + if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { + return; + } + playbackManager.seekPercent(0, currentPlayer); + // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. + // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. + positionSlider.value = 0; } else { - playbackManager.sendCommand({ - Name: this.getAttribute('data-command') - }, currentPlayer); - } - } - } - - function getSaveablePlaylistItems() { - return getPlaylistItems(currentPlayer).then(function (items) { - return items.filter(function (i) { - return i.Id && i.ServerId; - }); - }); - } - - function savePlaylist() { - require(['playlistEditor'], function (playlistEditor) { - getSaveablePlaylistItems().then(function (items) { - var serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor.showEditor({ - items: items.map(function (i) { - return i.Id; - }), - serverId: serverId, - enableAddToPlayQueue: false, - defaultValue: 'new' - }); - }); - }); - } - - function bindEvents(context) { - var btnCommand = context.querySelectorAll('.btnCommand'); - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - - for (var i = 0, length = btnCommand.length; i < length; i++) { - btnCommand[i].addEventListener('click', onBtnCommandClick); - } - - context.querySelector('.btnToggleFullscreen').addEventListener('click', function (e) { - if (currentPlayer) { - playbackManager.sendCommand({ - Name: e.target.getAttribute('data-command') - }, currentPlayer); - } - }); - context.querySelector('.btnAudioTracks').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showAudioMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnSubtitles').addEventListener('click', function (e) { - if (currentPlayer && lastPlayerState && lastPlayerState.NowPlayingItem) { - showSubtitleMenu(context, currentPlayer, e.target, lastPlayerState.NowPlayingItem); - } - }); - context.querySelector('.btnStop').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.stop(currentPlayer); - } - }); - context.querySelector('.btnPlayPause').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.playPause(currentPlayer); - } - }); - context.querySelector('.btnNextTrack').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.nextTrack(currentPlayer); - } - }); - context.querySelector('.btnRewind').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.rewind(currentPlayer); - } - }); - context.querySelector('.btnFastForward').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.fastForward(currentPlayer); - } - }); - for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { - shuffleButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleQueueShuffleMode(currentPlayer); - } - }); - } - - context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { - if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { - // Cancel this event if doubleclick is fired - if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { - return; - } - playbackManager.seekPercent(0, currentPlayer); - // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. - // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. - positionSlider.value = 0; - } else { - playbackManager.previousTrack(currentPlayer); - } - } - }); - - context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { - if (currentPlayer) { playbackManager.previousTrack(currentPlayer); } - }); - positionSlider.addEventListener('change', function () { - var value = this.value; + } + }); - if (currentPlayer) { - var newPercent = parseFloat(value); - playbackManager.seekPercent(newPercent, currentPlayer); - } - }); + context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () { + if (currentPlayer) { + playbackManager.previousTrack(currentPlayer); + } + }); + positionSlider.addEventListener('change', function () { + const value = this.value; - positionSlider.getBubbleText = function (value) { - var state = lastPlayerState; + if (currentPlayer) { + const newPercent = parseFloat(value); + playbackManager.seekPercent(newPercent, currentPlayer); + } + }); - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return '--:--'; - } + positionSlider.getBubbleText = function (value) { + const state = lastPlayerState; - var ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - return datetime.getDisplayRunningTime(ticks); - }; + if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + return '--:--'; + } - context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { - playbackManager.setVolume(e.target.value, currentPlayer); - }); + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; + return datetime.getDisplayRunningTime(ticks); + }; - context.querySelector('.buttonMute').addEventListener('click', function () { - playbackManager.toggleMute(currentPlayer); - }); - var playlistContainer = context.querySelector('.playlist'); - playlistContainer.addEventListener('action-remove', function (e) { - playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); - }); - playlistContainer.addEventListener('itemdrop', function (e) { - var newIndex = e.detail.newIndex; - var playlistItemId = e.detail.playlistItemId; - playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); - }); - context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); - context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { - if (context.querySelector('.playlist').classList.contains('hide')) { - context.querySelector('.playlist').classList.remove('hide'); - context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.querySelector('.volumecontrol').classList.add('hide'); - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); - } - } else { - context.querySelector('.playlist').classList.add('hide'); - context.querySelector('.btnSavePlaylist').classList.add('hide'); - if (showMuteButton || showVolumeSlider) { - context.querySelector('.volumecontrol').classList.remove('hide'); - } - if (layoutManager.mobile) { - context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); - } - } - }); - } + context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => { + playbackManager.setVolume(e.target.value, currentPlayer); + }); - function onPlayerChange() { - bindToPlayer(dlg, playbackManager.getCurrentPlayer()); - } - - function onMessageSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'DisplayMessage', - Arguments: { - Header: form.querySelector('#txtMessageTitle').value, - Text: form.querySelector('#txtMessageText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Message sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function onSendStringSubmit(e) { - var form = e.target; - playbackManager.sendCommand({ - Name: 'SendString', - Arguments: { - String: form.querySelector('#txtTypeText', form).value - } - }, currentPlayer); - form.querySelector('input').value = ''; - - require(['toast'], function (toast) { - toast('Text sent.'); - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - - function init(ownerView, context) { - let volumecontrolHtml = '
'; - volumecontrolHtml += ``; - volumecontrolHtml += '
'; - volumecontrolHtml += '
'; - let optionsSection = context.querySelector('.playlistSectionButton'); - if (!layoutManager.mobile) { - context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); - optionsSection.classList.remove('align-items-center', 'justify-content-center'); - optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.buttonMute').addEventListener('click', function () { + playbackManager.toggleMute(currentPlayer); + }); + const playlistContainer = context.querySelector('.playlist'); + playlistContainer.addEventListener('action-remove', function (e) { + playbackManager.removeFromPlaylist([e.detail.playlistItemId], currentPlayer); + }); + playlistContainer.addEventListener('itemdrop', function (e) { + const newIndex = e.detail.newIndex; + const playlistItemId = e.detail.playlistItemId; + playbackManager.movePlaylistItem(playlistItemId, newIndex, currentPlayer); + }); + context.querySelector('.btnSavePlaylist').addEventListener('click', savePlaylist); + context.querySelector('.btnTogglePlaylist').addEventListener('click', function () { + if (context.querySelector('.playlist').classList.contains('hide')) { context.querySelector('.playlist').classList.remove('hide'); context.querySelector('.btnSavePlaylist').classList.remove('hide'); - context.classList.add('padded-bottom'); + context.querySelector('.volumecontrol').classList.add('hide'); + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent'); + } } else { - optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); - optionsSection.classList.add('playlistSectionButtonTransparent'); - context.querySelector('.btnTogglePlaylist').classList.remove('hide'); - context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); - context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); + context.querySelector('.playlist').classList.add('hide'); + context.querySelector('.btnSavePlaylist').classList.add('hide'); + if (showMuteButton || showVolumeSlider) { + context.querySelector('.volumecontrol').classList.remove('hide'); + } + if (layoutManager.mobile) { + context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent'); + } } + }); + } - bindEvents(context); - context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); - context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); - events.on(playbackManager, 'playerchange', onPlayerChange); + function onPlayerChange() { + bindToPlayer(dlg, playbackManager.getCurrentPlayer()); + } - if (layoutManager.tv) { - var positionSlider = context.querySelector('.nowPlayingPositionSlider'); - positionSlider.classList.add('focusable'); - positionSlider.enableKeyboardDragging(); + function onMessageSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'DisplayMessage', + Arguments: { + Header: form.querySelector('#txtMessageTitle').value, + Text: form.querySelector('#txtMessageText', form).value } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Message sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function onSendStringSubmit(e) { + const form = e.target; + playbackManager.sendCommand({ + Name: 'SendString', + Arguments: { + String: form.querySelector('#txtTypeText', form).value + } + }, currentPlayer); + form.querySelector('input').value = ''; + + import('toast').then(({ default: toast }) => { + toast('Text sent.'); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + } + + function init(ownerView, context) { + let volumecontrolHtml = '
'; + volumecontrolHtml += ``; + volumecontrolHtml += '
'; + volumecontrolHtml += '
'; + const optionsSection = context.querySelector('.playlistSectionButton'); + if (!layoutManager.mobile) { + context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml); + optionsSection.classList.remove('align-items-center', 'justify-content-center'); + optionsSection.classList.add('align-items-right', 'justify-content-flex-end'); + context.querySelector('.playlist').classList.remove('hide'); + context.querySelector('.btnSavePlaylist').classList.remove('hide'); + context.classList.add('padded-bottom'); + } else { + optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml); + optionsSection.classList.add('playlistSectionButtonTransparent'); + context.querySelector('.btnTogglePlaylist').classList.remove('hide'); + context.querySelector('.playlistSectionButton').classList.remove('justify-content-center'); + context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between'); } - function onDialogClosed(e) { - releaseCurrentPlayer(); - events.off(playbackManager, 'playerchange', onPlayerChange); - lastPlayerState = null; + bindEvents(context); + context.querySelector('.sendMessageForm').addEventListener('submit', onMessageSubmit); + context.querySelector('.typeTextForm').addEventListener('submit', onSendStringSubmit); + events.on(playbackManager, 'playerchange', onPlayerChange); + + if (layoutManager.tv) { + const positionSlider = context.querySelector('.nowPlayingPositionSlider'); + positionSlider.classList.add('focusable'); + positionSlider.enableKeyboardDragging(); } + } - function onShow(context, tab) { - bindToPlayer(context, playbackManager.getCurrentPlayer()); - } + function onDialogClosed(e) { + releaseCurrentPlayer(); + events.off(playbackManager, 'playerchange', onPlayerChange); + lastPlayerState = null; + } - var dlg; - var currentPlayer; - var lastPlayerState; - var currentPlayerSupportedCommands = []; - var lastUpdateTime = 0; - var currentRuntimeTicks = 0; - var self = this; + function onShow(context, tab) { + bindToPlayer(context, playbackManager.getCurrentPlayer()); + } - self.init = function (ownerView, context) { - dlg = context; - init(ownerView, dlg); - }; + let dlg; + let currentPlayer; + let lastPlayerState; + let currentPlayerSupportedCommands = []; + let lastUpdateTime = 0; + let currentRuntimeTicks = 0; + const self = this; - self.onShow = function () { - onShow(dlg, window.location.hash); - }; - - self.destroy = function () { - onDialogClosed(); - }; + self.init = function (ownerView, context) { + dlg = context; + init(ownerView, dlg); }; -}); + + self.onShow = function () { + onShow(dlg, window.location.hash); + }; + + self.destroy = function () { + onDialogClosed(); + }; +} diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 4ee31a6275..549cb9445c 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -251,7 +251,7 @@ import layoutManager from 'layoutManager'; * @return {ScrollerData} Scroller data. */ function getScrollerData(scroller, vertical) { - let data = {}; + const data = {}; if (!vertical) { data.scrollPos = scroller.scrollLeft; diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 38728ec6c6..82f541a116 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -2,667 +2,672 @@ * Image viewer component * @module components/slideshow/slideshow */ -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'; +import dialogHelper from 'dialogHelper'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import layoutManager from 'layoutManager'; +import focusManager from 'focusManager'; +import browser from 'browser'; +import appHost from 'apphost'; +import dom from 'dom'; +import 'css!./style'; +import 'material-icons'; +import 'paper-icon-button-light'; - browser = browser.default || browser; - focusManager = focusManager.default || focusManager; +/** + * Name of transition event. + */ +const transitionEndEventName = dom.whichTransitionEvent(); - /** - * 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; - /** - * 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. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's image. + */ +function getImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Primary'; - /** - * Retrieves an item's image URL from the API. - * @param {object|string} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's image. - */ - function getImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Primary'; + if (typeof (item) === 'string') { + return apiClient.getScaledImageUrl(item, options); + } - if (typeof (item) === 'string') { - return apiClient.getScaledImageUrl(item, options); + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + if (options.type === 'Primary') { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return apiClient.getScaledImageUrl(item.AlbumId, options); } + } - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return apiClient.getScaledImageUrl(item.Id, options); + return null; +} + +/** + * Retrieves a backdrop's image URL from the API. + * @param {object} item - Item used to generate the image URL. + * @param {object} options - Options of the image. + * @param {object} apiClient - API client instance used to retrieve the image. + * @returns {null|string} URL of the item's backdrop. + */ +function getBackdropImageUrl(item, options, apiClient) { + options = options || {}; + options.type = options.type || 'Backdrop'; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; + } + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } + + return null; +} + +/** + * Dispatches a request for an item's image to its respective handler. + * @param {object} item - Item used to generate the image URL. + * @returns {string} URL of the item's image. + */ +function getImgUrl(item, user) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const imageOptions = {}; + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + return getBackdropImageUrl(item, imageOptions, apiClient); + } else { + if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { + return apiClient.getItemDownloadUrl(item.Id); } + imageOptions.type = 'Primary'; + return getImageUrl(item, imageOptions, apiClient); + } +} - if (options.type === 'Primary') { - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); +/** + * Generates a button using the specified icon, classes and properties. + * @param {string} icon - Name of the material icon on the button + * @param {string} cssClass - CSS classes to assign to the button + * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. + * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. + * @returns {string} The HTML markup of the button. + */ +function getIcon(icon, cssClass, canFocus, autoFocus) { + const tabIndex = canFocus ? '' : ' tabindex="-1"'; + autoFocus = autoFocus ? ' autofocus' : ''; + return ''; +} + +/** + * Sets the viewport meta tag to enable or disable scaling by the user. + * @param {boolean} scalable - Flag to set the scalability of the viewport. + */ +function setUserScalable(scalable) { + try { + appHost.setUserScalable(scalable); + } catch (err) { + console.error('error in appHost.setUserScalable: ' + err); + } +} + +export default function (options) { + const self = this; + /** Initialized instance of Swiper. */ + let swiperInstance; + /** Initialized instance of the dialog containing the Swiper instance. */ + let dialog; + /** Options of the slideshow components */ + let currentOptions; + /** ID of the timeout used to hide the OSD. */ + let hideTimeout; + /** Last coordinates of the mouse pointer. */ + let lastMouseMoveData; + + /** + * Creates the HTML markup for the dialog and the OSD. + * @param {Object} options - Options used to create the dialog and slideshow. + */ + function createElements(options) { + currentOptions = options; + + dialog = dialogHelper.createDialog({ + exitAnimationDuration: options.interactive ? 400 : 800, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + + dialog.classList.add('slideshowDialog'); + + let html = ''; + + html += '
'; + + if (options.interactive && !layoutManager.tv) { + const actionButtonsOnTop = layoutManager.mobile; + + html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); + html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); + + html += '
'; + if (actionButtonsOnTop) { + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } } - } + html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); + html += '
'; - return null; - } + if (!actionButtonsOnTop) { + html += '
'; - /** - * Retrieves a backdrop's image URL from the API. - * @param {object} item - Item used to generate the image URL. - * @param {object} options - Options of the image. - * @param {object} apiClient - API client instance used to retrieve the image. - * @returns {null|string} URL of the item's backdrop. - */ - function getBackdropImageUrl(item, options, apiClient) { - options = options || {}; - options.type = options.type || 'Backdrop'; + html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); + if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + html += getIcon('file_download', 'btnDownload slideshowButton', true); + } + if (appHost.supports('sharing')) { + html += getIcon('share', 'btnShare slideshowButton', true); + } - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; - } - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - return null; - } - - /** - * Dispatches a request for an item's image to its respective handler. - * @param {object} item - Item used to generate the image URL. - * @returns {string} URL of the item's image. - */ - function getImgUrl(item, user) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var imageOptions = {}; - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - return getBackdropImageUrl(item, imageOptions, apiClient); + html += '
'; + } } else { - if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { - return apiClient.getItemDownloadUrl(item.Id); - } - imageOptions.type = 'Primary'; - return getImageUrl(item, imageOptions, apiClient); - } - } - - /** - * Generates a button using the specified icon, classes and properties. - * @param {string} icon - Name of the material icon on the button - * @param {string} cssClass - CSS classes to assign to the button - * @param {boolean} canFocus - Flag to set the tabindex attribute on the button to -1. - * @param {boolean} autoFocus - Flag to set the autofocus attribute on the button. - * @returns {string} The HTML markup of the button. - */ - function getIcon(icon, cssClass, canFocus, autoFocus) { - var tabIndex = canFocus ? '' : ' tabindex="-1"'; - autoFocus = autoFocus ? ' autofocus' : ''; - return ''; - } - - /** - * Sets the viewport meta tag to enable or disable scaling by the user. - * @param {boolean} scalable - Flag to set the scalability of the viewport. - */ - function setUserScalable(scalable) { - try { - appHost.setUserScalable(scalable); - } catch (err) { - console.error('error in appHost.setUserScalable: ' + err); - } - } - - return function (options) { - var self = this; - /** Initialized instance of Swiper. */ - var swiperInstance; - /** Initialized instance of the dialog containing the Swiper instance. */ - var dialog; - /** Options of the slideshow components */ - var currentOptions; - /** ID of the timeout used to hide the OSD. */ - var hideTimeout; - /** Last coordinates of the mouse pointer. */ - var lastMouseMoveData; - - /** - * Creates the HTML markup for the dialog and the OSD. - * @param {Object} options - Options used to create the dialog and slideshow. - */ - function createElements(options) { - currentOptions = options; - - dialog = dialogHelper.createDialog({ - exitAnimationDuration: options.interactive ? 400 : 800, - size: 'fullscreen', - autoFocus: false, - scrollY: false, - exitAnimation: 'fadeout', - removeOnClose: true - }); - - dialog.classList.add('slideshowDialog'); - - var html = ''; - - html += '
'; - - if (options.interactive && !layoutManager.tv) { - var actionButtonsOnTop = layoutManager.mobile; - - html += getIcon('keyboard_arrow_left', 'btnSlideshowPrevious slideshowButton hide-mouse-idle-tv', false); - html += getIcon('keyboard_arrow_right', 'btnSlideshowNext slideshowButton hide-mouse-idle-tv', false); - - html += '
'; - if (actionButtonsOnTop) { - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - } - html += getIcon('close', 'slideshowButton btnSlideshowExit hide-mouse-idle-tv', false); - html += '
'; - - if (!actionButtonsOnTop) { - html += '
'; - - html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { - html += getIcon('file_download', 'btnDownload slideshowButton', true); - } - if (appHost.supports('sharing')) { - html += getIcon('share', 'btnShare slideshowButton', true); - } - - html += '
'; - } - } else { - html += '

'; - } - - dialog.innerHTML = html; - - if (options.interactive && !layoutManager.tv) { - dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { - dialogHelper.close(dialog); - }); - - var btnPause = dialog.querySelector('.btnSlideshowPause'); - if (btnPause) { - btnPause.addEventListener('click', playPause); - } - - var btnDownload = dialog.querySelector('.btnDownload'); - if (btnDownload) { - btnDownload.addEventListener('click', download); - } - - var btnShare = dialog.querySelector('.btnShare'); - if (btnShare) { - btnShare.addEventListener('click', share); - } - } - - setUserScalable(true); - - dialogHelper.open(dialog).then(function () { - setUserScalable(false); - }); - - inputManager.on(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - - dialog.addEventListener('close', onDialogClosed); - - loadSwiper(dialog, options); + html += '

'; } - /** - * Handles OSD changes when the autoplay is started. - */ - function onAutoplayStart() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('play_arrow', 'pause'); - } - } + dialog.innerHTML = html; - /** - * Handles OSD changes when the autoplay is stopped. - */ - function onAutoplayStop() { - var btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); - if (btnSlideshowPause) { - btnSlideshowPause.classList.replace('pause', 'play_arrow'); - } - } - - /** - * 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. - * @param {Object} options - Options used to initialize the Swiper instance. - */ - function loadSwiper(dialog, options) { - var slides; - if (currentOptions.slides) { - slides = currentOptions.slides; - } else { - slides = currentOptions.items; - } - - require(['swiper'], function (Swiper) { - swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { - direction: 'horizontal', - // Loop is disabled due to the virtual slides option not supporting it. - loop: false, - zoom: { - minRatio: 1, - toggle: true - }, - autoplay: !options.interactive, - keyboard: { - enabled: true - }, - preloadImages: true, - slidesPerView: 1, - slidesPerColumn: 1, - initialSlide: options.startIndex || 0, - speed: 240, - navigation: { - nextEl: '.btnSlideshowNext', - prevEl: '.btnSlideshowPrevious' - }, - // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; - virtual: { - slides: slides, - cache: true, - renderSlide: getSwiperSlideHtml, - addSlidesBefore: 1, - addSlidesAfter: 1 - } - }); - - swiperInstance.on('autoplayStart', onAutoplayStart); - swiperInstance.on('autoplayStop', onAutoplayStop); - - if (useFakeZoomImage) { - swiperInstance.on('zoomChange', onZoomChange); - } - }); - } - - /** - * Renders the HTML markup of a slide for an item or a slide. - * @param {Object} item - The item used to render the slide. - * @param {number} index - The index of the item in the Swiper instance. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtml(item, index) { - if (currentOptions.slides) { - return getSwiperSlideHtmlFromSlide(item); - } else { - return getSwiperSlideHtmlFromItem(item); - } - } - - /** - * Renders the HTML markup of a slide for an item. - * @param {Object} item - Item used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromItem(item) { - return getSwiperSlideHtmlFromSlide({ - originalImage: getImgUrl(item, currentOptions.user), - Id: item.Id, - ServerId: item.ServerId - }); - } - - /** - * Renders the HTML markup of a slide for a slide object. - * @param {Object} item - Slide object used to generate the slide. - * @returns {string} The HTML markup of the slide. - */ - function getSwiperSlideHtmlFromSlide(item) { - var html = ''; - html += '
'; - html += '
'; - if (useFakeZoomImage) { - html += `
`; - } - html += ''; - html += '
'; - if (item.title || item.subtitle) { - html += '
'; - html += '
'; - if (item.title) { - html += '

'; - html += item.title; - html += '

'; - } - if (item.description) { - html += '
'; - html += item.description; - html += '
'; - } - html += '
'; - html += '
'; - } - html += '
'; - - return html; - } - - /** - * Fetches the information of the currently displayed slide. - * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. - */ - function getCurrentImageInfo() { - if (swiperInstance) { - var slide = document.querySelector('.swiper-slide-active'); - - if (slide) { - return { - url: slide.getAttribute('data-original'), - shareUrl: slide.getAttribute('data-original'), - itemId: slide.getAttribute('data-itemid'), - serverId: slide.getAttribute('data-serverid') - }; - } - return null; - } else { - return null; - } - } - - /** - * Starts a download for the currently displayed slide. - */ - function download() { - var imageInfo = getCurrentImageInfo(); - - require(['fileDownloader'], function (fileDownloader) { - fileDownloader.download([imageInfo]); - }); - } - - /** - * Shares the currently displayed slide using the browser's built-in sharing feature. - */ - function share() { - var imageInfo = getCurrentImageInfo(); - - navigator.share({ - url: imageInfo.shareUrl - }); - } - - /** - * Starts the autoplay feature of the Swiper instance. - */ - function play() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.start(); - } - } - - /** - * Pauses the autoplay feature of the Swiper instance; - */ - function pause() { - if (swiperInstance.autoplay) { - swiperInstance.autoplay.stop(); - } - } - - /** - * Toggles the autoplay feature of the Swiper instance. - */ - function playPause() { - var paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); - if (paused) { - play(); - } else { - pause(); - } - } - - /** - * Closes the dialog and destroys the Swiper instance. - */ - function onDialogClosed() { - var swiper = swiperInstance; - if (swiper) { - swiper.destroy(true, true); - swiperInstance = null; - } - - inputManager.off(window, onInputCommand); - /* eslint-disable-next-line compat/compat */ - document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); - // Shows page scrollbar - document.body.classList.remove('hide-scroll'); - document.body.classList.add('force-scroll'); - } - - /** - * Shows the OSD. - */ - function showOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideUpToShow(bottom); - startHideTimer(); - } - } - - /** - * Hides the OSD. - */ - function hideOsd() { - var bottom = dialog.querySelector('.slideshowBottomBar'); - if (bottom) { - slideDownToHide(bottom); - } - } - - /** - * Starts the timer used to automatically hide the OSD. - */ - function startHideTimer() { - stopHideTimer(); - hideTimeout = setTimeout(hideOsd, 3000); - } - - /** - * Stops the timer used to automatically hide the OSD. - */ - function stopHideTimer() { - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } - } - - /** - * Shows the OSD by sliding it into view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideUpToShow(element) { - if (!element.classList.contains('hide')) { - return; - } - - element.classList.remove('hide'); - - var onFinish = function () { - focusManager.focus(element.querySelector('.btnSlideshowPause')); - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Hides the OSD by sliding it out of view. - * @param {HTMLElement} element - Element containing the OSD. - */ - function slideDownToHide(element) { - if (element.classList.contains('hide')) { - return; - } - - var onFinish = function () { - element.classList.add('hide'); - }; - - if (!element.animate) { - onFinish(); - return; - } - - requestAnimationFrame(function () { - var keyframes = [ - { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, - { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } - ]; - var timing = { duration: 300, iterations: 1, easing: 'ease-out' }; - element.animate(keyframes, timing).onfinish = onFinish; - }); - } - - /** - * Shows the OSD when moving the mouse pointer or touching the screen. - * @param {Event} event - Pointer movement event. - */ - function onPointerMove(event) { - var pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); - - if (pointerType === 'mouse') { - var eventX = event.screenX || 0; - var eventY = event.screenY || 0; - - var obj = lastMouseMoveData; - if (!obj) { - lastMouseMoveData = { - x: eventX, - y: eventY - }; - return; - } - - // if coord are same, it didn't move - if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { - return; - } - - obj.x = eventX; - obj.y = eventY; - - showOsd(); - } - } - - /** - * Dispatches keyboard inputs to their proper handlers. - * @param {Event} event - Keyboard input event. - */ - function onInputCommand(event) { - switch (event.detail.command) { - case 'up': - case 'down': - case 'select': - case 'menu': - case 'info': - showOsd(); - break; - case 'play': - play(); - break; - case 'pause': - pause(); - break; - case 'playpause': - playPause(); - break; - default: - break; - } - } - - /** - * Shows the slideshow component. - */ - self.show = function () { - createElements(options); - // Hides page scrollbar - document.body.classList.remove('force-scroll'); - document.body.classList.add('hide-scroll'); - }; - - /** - * Hides the slideshow element. - */ - self.hide = function () { - if (dialog) { + if (options.interactive && !layoutManager.tv) { + dialog.querySelector('.btnSlideshowExit').addEventListener('click', function (e) { dialogHelper.close(dialog); + }); + + const btnPause = dialog.querySelector('.btnSlideshowPause'); + if (btnPause) { + btnPause.addEventListener('click', playPause); } + + const btnDownload = dialog.querySelector('.btnDownload'); + if (btnDownload) { + btnDownload.addEventListener('click', download); + } + + const btnShare = dialog.querySelector('.btnShare'); + if (btnShare) { + btnShare.addEventListener('click', share); + } + } + + setUserScalable(true); + + dialogHelper.open(dialog).then(function () { + setUserScalable(false); + }); + + inputManager.on(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + + dialog.addEventListener('close', onDialogClosed); + + loadSwiper(dialog, options); + } + + /** + * Handles OSD changes when the autoplay is started. + */ + function onAutoplayStart() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('play_arrow', 'pause'); + } + } + + /** + * Handles OSD changes when the autoplay is stopped. + */ + function onAutoplayStop() { + const btnSlideshowPause = dialog.querySelector('.btnSlideshowPause .material-icons'); + if (btnSlideshowPause) { + btnSlideshowPause.classList.replace('pause', 'play_arrow'); + } + } + + /** + * 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. + * @param {Object} options - Options used to initialize the Swiper instance. + */ + function loadSwiper(dialog, options) { + let slides; + if (currentOptions.slides) { + slides = currentOptions.slides; + } else { + slides = currentOptions.items; + } + + import('swiper').then(({default: Swiper}) => { + swiperInstance = new Swiper(dialog.querySelector('.slideshowSwiperContainer'), { + direction: 'horizontal', + // Loop is disabled due to the virtual slides option not supporting it. + loop: false, + zoom: { + minRatio: 1, + toggle: true + }, + autoplay: !options.interactive, + keyboard: { + enabled: true + }, + preloadImages: true, + slidesPerView: 1, + slidesPerColumn: 1, + initialSlide: options.startIndex || 0, + speed: 240, + navigation: { + nextEl: '.btnSlideshowNext', + prevEl: '.btnSlideshowPrevious' + }, + // Virtual slides reduce memory consumption for large libraries while allowing preloading of images; + virtual: { + slides: slides, + cache: true, + renderSlide: getSwiperSlideHtml, + addSlidesBefore: 1, + addSlidesAfter: 1 + } + }); + + swiperInstance.on('autoplayStart', onAutoplayStart); + swiperInstance.on('autoplayStop', onAutoplayStop); + + if (useFakeZoomImage) { + swiperInstance.on('zoomChange', onZoomChange); + } + }); + } + + /** + * Renders the HTML markup of a slide for an item or a slide. + * @param {Object} item - The item used to render the slide. + * @param {number} index - The index of the item in the Swiper instance. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtml(item, index) { + if (currentOptions.slides) { + return getSwiperSlideHtmlFromSlide(item); + } else { + return getSwiperSlideHtmlFromItem(item); + } + } + + /** + * Renders the HTML markup of a slide for an item. + * @param {Object} item - Item used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromItem(item) { + return getSwiperSlideHtmlFromSlide({ + originalImage: getImgUrl(item, currentOptions.user), + Id: item.Id, + ServerId: item.ServerId + }); + } + + /** + * Renders the HTML markup of a slide for a slide object. + * @param {Object} item - Slide object used to generate the slide. + * @returns {string} The HTML markup of the slide. + */ + function getSwiperSlideHtmlFromSlide(item) { + let html = ''; + html += '
'; + html += '
'; + if (useFakeZoomImage) { + html += `
`; + } + html += ''; + html += '
'; + if (item.title || item.subtitle) { + html += '
'; + html += '
'; + if (item.title) { + html += '

'; + html += item.title; + html += '

'; + } + if (item.description) { + html += '
'; + html += item.description; + html += '
'; + } + html += '
'; + html += '
'; + } + html += '
'; + + return html; + } + + /** + * Fetches the information of the currently displayed slide. + * @returns {null|{itemId: string, shareUrl: string, serverId: string, url: string}} Object containing the information of the currently displayed slide. + */ + function getCurrentImageInfo() { + if (swiperInstance) { + const slide = document.querySelector('.swiper-slide-active'); + + if (slide) { + return { + url: slide.getAttribute('data-original'), + shareUrl: slide.getAttribute('data-original'), + itemId: slide.getAttribute('data-itemid'), + serverId: slide.getAttribute('data-serverid') + }; + } + return null; + } else { + return null; + } + } + + /** + * Starts a download for the currently displayed slide. + */ + function download() { + const imageInfo = getCurrentImageInfo(); + + import('fileDownloader').then(({default: fileDownloader}) => { + fileDownloader.download([imageInfo]); + }); + } + + /** + * Shares the currently displayed slide using the browser's built-in sharing feature. + */ + function share() { + const imageInfo = getCurrentImageInfo(); + + navigator.share({ + url: imageInfo.shareUrl + }); + } + + /** + * Starts the autoplay feature of the Swiper instance. + */ + function play() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.start(); + } + } + + /** + * Pauses the autoplay feature of the Swiper instance; + */ + function pause() { + if (swiperInstance.autoplay) { + swiperInstance.autoplay.stop(); + } + } + + /** + * Toggles the autoplay feature of the Swiper instance. + */ + function playPause() { + const paused = !dialog.querySelector('.btnSlideshowPause .material-icons').classList.contains('pause'); + if (paused) { + play(); + } else { + pause(); + } + } + + /** + * Closes the dialog and destroys the Swiper instance. + */ + function onDialogClosed() { + const swiper = swiperInstance; + if (swiper) { + swiper.destroy(true, true); + swiperInstance = null; + } + + inputManager.off(window, onInputCommand); + /* eslint-disable-next-line compat/compat */ + document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove); + // Shows page scrollbar + document.body.classList.remove('hide-scroll'); + document.body.classList.add('force-scroll'); + } + + /** + * Shows the OSD. + */ + function showOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideUpToShow(bottom); + startHideTimer(); + } + } + + /** + * Hides the OSD. + */ + function hideOsd() { + const bottom = dialog.querySelector('.slideshowBottomBar'); + if (bottom) { + slideDownToHide(bottom); + } + } + + /** + * Starts the timer used to automatically hide the OSD. + */ + function startHideTimer() { + stopHideTimer(); + hideTimeout = setTimeout(hideOsd, 3000); + } + + /** + * Stops the timer used to automatically hide the OSD. + */ + function stopHideTimer() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + } + + /** + * Shows the OSD by sliding it into view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideUpToShow(element) { + if (!element.classList.contains('hide')) { + return; + } + + element.classList.remove('hide'); + + const onFinish = function () { + focusManager.focus(element.querySelector('.btnSlideshowPause')); }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 0 }, + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Hides the OSD by sliding it out of view. + * @param {HTMLElement} element - Element containing the OSD. + */ + function slideDownToHide(element) { + if (element.classList.contains('hide')) { + return; + } + + const onFinish = function () { + element.classList.add('hide'); + }; + + if (!element.animate) { + onFinish(); + return; + } + + requestAnimationFrame(function () { + const keyframes = [ + { transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 }, + { transform: 'translate3d(0,' + element.offsetHeight + 'px,0)', opacity: '.3', offset: 1 } + ]; + const timing = { duration: 300, iterations: 1, easing: 'ease-out' }; + element.animate(keyframes, timing).onfinish = onFinish; + }); + } + + /** + * Shows the OSD when moving the mouse pointer or touching the screen. + * @param {Event} event - Pointer movement event. + */ + function onPointerMove(event) { + const pointerType = event.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + + if (pointerType === 'mouse') { + const eventX = event.screenX || 0; + const eventY = event.screenY || 0; + + const obj = lastMouseMoveData; + if (!obj) { + lastMouseMoveData = { + x: eventX, + y: eventY + }; + return; + } + + // if coord are same, it didn't move + if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) { + return; + } + + obj.x = eventX; + obj.y = eventY; + + showOsd(); + } + } + + /** + * Dispatches keyboard inputs to their proper handlers. + * @param {Event} event - Keyboard input event. + */ + function onInputCommand(event) { + switch (event.detail.command) { + case 'up': + case 'down': + case 'select': + case 'menu': + case 'info': + showOsd(); + break; + case 'play': + play(); + break; + case 'pause': + pause(); + break; + case 'playpause': + playPause(); + break; + default: + break; + } + } + + /** + * Shows the slideshow component. + */ + self.show = function () { + createElements(options); + // Hides page scrollbar + document.body.classList.remove('force-scroll'); + document.body.classList.add('hide-scroll'); }; -}); + + /** + * Hides the slideshow element. + */ + self.hide = function () { + if (dialog) { + dialogHelper.close(dialog); + } + }; +} diff --git a/src/components/sortmenu/sortmenu.js b/src/components/sortmenu/sortmenu.js index d1cea0c490..d38d98c090 100644 --- a/src/components/sortmenu/sortmenu.js +++ b/src/components/sortmenu/sortmenu.js @@ -1,49 +1,51 @@ -define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'globalize', 'userSettings', 'emby-select', 'paper-icon-button-light', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, layoutManager, connectionManager, globalize, userSettings) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import 'emby-select'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; +import 'flexStyles'; - focusManager = focusManager.default || focusManager; +function onSubmit(e) { + e.preventDefault(); + return false; +} - function onSubmit(e) { - e.preventDefault(); - return false; - } +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); - function initEditor(context, settings) { - context.querySelector('form').addEventListener('submit', onSubmit); + context.querySelector('.selectSortOrder').value = settings.sortOrder; + context.querySelector('.selectSortBy').value = settings.sortBy; +} - context.querySelector('.selectSortOrder').value = settings.sortOrder; - context.querySelector('.selectSortBy').value = settings.sortBy; - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - scrollHelper = scrollHelper.default || scrollHelper; - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } +function fillSortBy(context, options) { + const selectSortBy = context.querySelector('.selectSortBy'); - function fillSortBy(context, options) { - var selectSortBy = context.querySelector('.selectSortBy'); + selectSortBy.innerHTML = options.map(function (o) { + return ''; + }).join(''); +} - selectSortBy.innerHTML = options.map(function (o) { - return ''; - }).join(''); - } +function saveValues(context, settingsKey) { + userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); + userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); +} - function saveValues(context, settings, settingsKey) { - userSettings.setFilter(settingsKey + '-sortorder', context.querySelector('.selectSortOrder').value); - userSettings.setFilter(settingsKey + '-sortby', context.querySelector('.selectSortBy').value); - } - - function SortMenu() { - - } - - SortMenu.prototype.show = function (options) { +class SortMenu { + show(options) { return new Promise(function (resolve, reject) { - require(['text!./sortmenu.template.html'], function (template) { - var dialogOptions = { + import('text!./sortmenu.template.html').then(({default: template}) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -54,11 +56,11 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; + let html = ''; html += '
'; html += ''; @@ -81,7 +83,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana centerFocus(dlg.querySelector('.formDialogContent'), false, true); } - var submitted; + let submitted; dlg.querySelector('form').addEventListener('change', function () { submitted = true; @@ -93,7 +95,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana } if (submitted) { - saveValues(dlg, options.settings, options.settingsKey); + saveValues(dlg, options.settingsKey); resolve(); return; } @@ -102,7 +104,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'layoutMana }); }); }); - }; + } +} - return SortMenu; -}); +export default SortMenu; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index b52b911cb9..dfd7f9446c 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -1,428 +1,437 @@ -define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', 'connectionManager', 'loading', 'focusManager', 'dom', 'apphost', 'emby-select', 'listViewStyle', 'paper-icon-button-light', 'css!./../formdialog', 'material-icons', 'css!./subtitleeditor', 'emby-button', 'flexStyles'], function (dialogHelper, require, layoutManager, globalize, userSettings, connectionManager, loading, focusManager, dom, appHost) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import * as userSettings from 'userSettings'; +import connectionManager from 'connectionManager'; +import loading from 'loading'; +import focusManager from 'focusManager'; +import dom from 'dom'; +import 'emby-select'; +import 'listViewStyle'; +import 'paper-icon-button-light'; +import 'css!./../formdialog'; +import 'material-icons'; +import 'css!./subtitleeditor'; +import 'emby-button'; +import 'flexStyles'; - loading = loading.default || loading; - focusManager = focusManager.default || focusManager; +let currentItem; +let hasChanges; - var currentItem; - var hasChanges; +function downloadRemoteSubtitles(context, id) { + let url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; - function downloadRemoteSubtitles(context, id) { - var url = 'Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + id; + let apiClient = connectionManager.getApiClient(currentItem.ServerId); + apiClient.ajax({ - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(url) - type: 'POST', - url: apiClient.getUrl(url) + }).then(function () { + hasChanges = true; + + import('toast').then(({default: toast}) => { + toast(globalize.translate('MessageDownloadQueued')); + }); + + focusManager.autoFocus(context); + }); +} + +function deleteLocalSubtitle(context, index) { + let msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); + + import('confirm').then(({default: confirm}) => { + confirm({ + + title: globalize.translate('ConfirmDeletion'), + text: msg, + confirmText: globalize.translate('Delete'), + primary: 'delete' }).then(function () { - hasChanges = true; + loading.show(); - require(['toast'], function (toast) { - toast(globalize.translate('MessageDownloadQueued')); - }); + let itemId = currentItem.Id; + let url = 'Videos/' + itemId + '/Subtitles/' + index; - focusManager.autoFocus(context); - }); - } + let apiClient = connectionManager.getApiClient(currentItem.ServerId); - function deleteLocalSubtitle(context, index) { - var msg = globalize.translate('MessageAreYouSureDeleteSubtitles'); + apiClient.ajax({ - require(['confirm'], function (confirm) { - confirm.default({ - - title: globalize.translate('ConfirmDeletion'), - text: msg, - confirmText: globalize.translate('Delete'), - primary: 'delete' + type: 'DELETE', + url: apiClient.getUrl(url) }).then(function () { - loading.show(); - - var itemId = currentItem.Id; - var url = 'Videos/' + itemId + '/Subtitles/' + index; - - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - - apiClient.ajax({ - - type: 'DELETE', - url: apiClient.getUrl(url) - - }).then(function () { - hasChanges = true; - reload(context, apiClient, itemId); - }); + hasChanges = true; + reload(context, apiClient, itemId); }); }); - } + }); +} - function fillSubtitleList(context, item) { - var streams = item.MediaStreams || []; +function fillSubtitleList(context, item) { + let streams = item.MediaStreams || []; - var subs = streams.filter(function (s) { - return s.Type === 'Subtitle'; - }); + let subs = streams.filter(function (s) { + return s.Type === 'Subtitle'; + }); - var html = ''; + let html = ''; - if (subs.length) { - html += '

' + globalize.translate('MySubtitles') + '

'; + if (subs.length) { + html += '

' + globalize.translate('MySubtitles') + '

'; - html += '
'; + html += '
'; - html += subs.map(function (s) { - var itemHtml = ''; + html += subs.map(function (s) { + let itemHtml = ''; - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv && s.Path ? 'listItem listItem-border btnDelete' : 'listItem listItem-border'; - if (layoutManager.tv) { - className += ' listItem-focusscale listItem-button'; - } - - className += ' listItem-noborder'; - - itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - - itemHtml += ''; - - itemHtml += '
'; - - itemHtml += '
'; - itemHtml += s.DisplayTitle || ''; - itemHtml += '
'; - - if (s.Path) { - itemHtml += '
' + (s.Path) + '
'; - } - - itemHtml += ''; - itemHtml += '
'; - - if (!layoutManager.tv) { - if (s.Path) { - itemHtml += ''; - } - } - - itemHtml += ''; - - return itemHtml; - }).join(''); - - html += '
'; - } - - var elem = context.querySelector('.subtitleList'); - - if (subs.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.innerHTML = html; - } - - function fillLanguages(context, apiClient, languages) { - var selectLanguage = context.querySelector('#selectLanguage'); - - selectLanguage.innerHTML = languages.map(function (l) { - return ''; - }); - - var lastLanguage = userSettings.get('subtitleeditor-language'); - if (lastLanguage) { - selectLanguage.value = lastLanguage; - } else { - apiClient.getCurrentUser().then(function (user) { - var lang = user.Configuration.SubtitleLanguagePreference; - - if (lang) { - selectLanguage.value = lang; - } - }); - } - } - - function renderSearchResults(context, results) { - var lastProvider = ''; - var html = ''; - - if (!results.length) { - context.querySelector('.noSearchResults').classList.remove('hide'); - context.querySelector('.subtitleResults').innerHTML = ''; - loading.hide(); - return; - } - - context.querySelector('.noSearchResults').classList.add('hide'); - - for (var i = 0, length = results.length; i < length; i++) { - var result = results[i]; - - var provider = result.ProviderName; - - if (provider !== lastProvider) { - if (i > 0) { - html += '
'; - } - html += '

' + provider + '

'; - html += '
'; - lastProvider = provider; - } - - var tagName = layoutManager.tv ? 'button' : 'div'; - var className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; if (layoutManager.tv) { className += ' listItem-focusscale listItem-button'; } - html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + className += ' listItem-noborder'; - html += ''; + itemHtml += '<' + tagName + ' class="' + className + '" data-index="' + s.Index + '">'; - var bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + itemHtml += ''; - html += '
'; + itemHtml += '
'; - html += '
' + (result.Name) + '
'; - html += '
'; + itemHtml += '
'; + itemHtml += s.DisplayTitle || ''; + itemHtml += '
'; - if (result.Format) { - html += '' + globalize.translate('FormatValue', result.Format) + ''; + if (s.Path) { + itemHtml += '
' + (s.Path) + '
'; } - if (result.DownloadCount != null) { - html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; - } - html += '
'; - - if (result.Comment) { - html += '
' + (result.Comment) + '
'; - } - - if (result.IsHashMatch) { - html += '
' + globalize.translate('PerfectMatch') + '
'; - } - - html += '
'; + itemHtml += ''; + itemHtml += '
'; if (!layoutManager.tv) { - html += ''; + if (s.Path) { + itemHtml += ''; + } } - html += ''; + itemHtml += ''; + + return itemHtml; + }).join(''); + + html += '
'; + } + + let elem = context.querySelector('.subtitleList'); + + if (subs.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + elem.innerHTML = html; +} + +function fillLanguages(context, apiClient, languages) { + let selectLanguage = context.querySelector('#selectLanguage'); + + selectLanguage.innerHTML = languages.map(function (l) { + return ''; + }); + + let lastLanguage = userSettings.get('subtitleeditor-language'); + if (lastLanguage) { + selectLanguage.value = lastLanguage; + } else { + apiClient.getCurrentUser().then(function (user) { + let lang = user.Configuration.SubtitleLanguagePreference; + + if (lang) { + selectLanguage.value = lang; + } + }); + } +} + +function renderSearchResults(context, results) { + let lastProvider = ''; + let html = ''; + + if (!results.length) { + context.querySelector('.noSearchResults').classList.remove('hide'); + context.querySelector('.subtitleResults').innerHTML = ''; + loading.hide(); + return; + } + + context.querySelector('.noSearchResults').classList.add('hide'); + + for (let i = 0, length = results.length; i < length; i++) { + let result = results[i]; + + let provider = result.ProviderName; + + if (provider !== lastProvider) { + if (i > 0) { + html += '
'; + } + html += '

' + provider + '

'; + html += '
'; + lastProvider = provider; } - if (results.length) { - html += '
'; + let tagName = layoutManager.tv ? 'button' : 'div'; + let className = layoutManager.tv ? 'listItem listItem-border btnOptions' : 'listItem listItem-border'; + if (layoutManager.tv) { + className += ' listItem-focusscale listItem-button'; } - var elem = context.querySelector('.subtitleResults'); - elem.innerHTML = html; + html += '<' + tagName + ' class="' + className + '" data-subid="' + result.Id + '">'; + + html += ''; + + let bodyClass = result.Comment || result.IsHashMatch ? 'three-line' : 'two-line'; + + html += '
'; + + html += '
' + (result.Name) + '
'; + html += '
'; + + if (result.Format) { + html += '' + globalize.translate('FormatValue', result.Format) + ''; + } + + if (result.DownloadCount != null) { + html += '' + globalize.translate('DownloadsValue', result.DownloadCount) + ''; + } + html += '
'; + + if (result.Comment) { + html += '
' + (result.Comment) + '
'; + } + + if (result.IsHashMatch) { + html += '
' + globalize.translate('PerfectMatch') + '
'; + } + + html += '
'; + + if (!layoutManager.tv) { + html += ''; + } + + html += ''; + } + + if (results.length) { + html += '
'; + } + + let elem = context.querySelector('.subtitleResults'); + elem.innerHTML = html; + + loading.hide(); +} + +function searchForSubtitles(context, language) { + userSettings.set('subtitleeditor-language', language); + + loading.show(); + + let apiClient = connectionManager.getApiClient(currentItem.ServerId); + let url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + + apiClient.getJSON(url).then(function (results) { + renderSearchResults(context, results); + }); +} + +function reload(context, apiClient, itemId) { + context.querySelector('.noSearchResults').classList.add('hide'); + + function onGetItem(item) { + currentItem = item; + + fillSubtitleList(context, item); + let file = item.Path || ''; + let index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); + if (index > -1) { + file = file.substring(index + 1); + } + + if (file) { + context.querySelector('.pathValue').innerHTML = file; + context.querySelector('.originalFile').classList.remove('hide'); + } else { + context.querySelector('.pathValue').innerHTML = ''; + context.querySelector('.originalFile').classList.add('hide'); + } loading.hide(); } - function searchForSubtitles(context, language) { - userSettings.set('subtitleeditor-language', language); + if (typeof itemId === 'string') { + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + } else { + onGetItem(itemId); + } +} - loading.show(); +function onSearchSubmit(e) { + let form = this; - var apiClient = connectionManager.getApiClient(currentItem.ServerId); - var url = apiClient.getUrl('Items/' + currentItem.Id + '/RemoteSearch/Subtitles/' + language); + let lang = form.querySelector('#selectLanguage', form).value; - apiClient.getJSON(url).then(function (results) { - renderSearchResults(context, results); - }); + searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); + + e.preventDefault(); + return false; +} + +function onSubtitleListClick(e) { + let btnDelete = dom.parentWithClass(e.target, 'btnDelete'); + if (btnDelete) { + let index = btnDelete.getAttribute('data-index'); + let context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); + deleteLocalSubtitle(context, index); + } +} + +function onSubtitleResultsClick(e) { + let subtitleId; + let context; + + let btnOptions = dom.parentWithClass(e.target, 'btnOptions'); + if (btnOptions) { + subtitleId = btnOptions.getAttribute('data-subid'); + context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); + showDownloadOptions(btnOptions, context, subtitleId); } - function reload(context, apiClient, itemId) { - context.querySelector('.noSearchResults').classList.add('hide'); + let btnDownload = dom.parentWithClass(e.target, 'btnDownload'); + if (btnDownload) { + subtitleId = btnDownload.getAttribute('data-subid'); + context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); + downloadRemoteSubtitles(context, subtitleId); + } +} - function onGetItem(item) { - currentItem = item; +function showDownloadOptions(button, context, subtitleId) { + let items = []; - fillSubtitleList(context, item); - var file = item.Path || ''; - var index = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\')); - if (index > -1) { - file = file.substring(index + 1); + items.push({ + name: globalize.translate('Download'), + id: 'download' + }); + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: items, + positionTo: button + + }).then(function (id) { + switch (id) { + case 'download': + downloadRemoteSubtitles(context, subtitleId); + break; + default: + break; } + }); + }); +} - if (file) { - context.querySelector('.pathValue').innerHTML = file; - context.querySelector('.originalFile').classList.remove('hide'); - } else { - context.querySelector('.pathValue').innerHTML = ''; - context.querySelector('.originalFile').classList.add('hide'); - } +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + let fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - loading.hide(); - } +function showEditorInternal(itemId, serverId, template) { + hasChanges = false; - if (typeof itemId === 'string') { - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(onGetItem); + let apiClient = connectionManager.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + let dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - onGetItem(itemId); - } - } - - function onSearchSubmit(e) { - var form = this; - - var lang = form.querySelector('#selectLanguage', form).value; - - searchForSubtitles(dom.parentWithClass(form, 'formDialogContent'), lang); - - e.preventDefault(); - return false; - } - - function onSubtitleListClick(e) { - var btnDelete = dom.parentWithClass(e.target, 'btnDelete'); - if (btnDelete) { - var index = btnDelete.getAttribute('data-index'); - var context = dom.parentWithClass(btnDelete, 'subtitleEditorDialog'); - deleteLocalSubtitle(context, index); - } - } - - function onSubtitleResultsClick(e) { - var subtitleId; - var context; - - var btnOptions = dom.parentWithClass(e.target, 'btnOptions'); - if (btnOptions) { - subtitleId = btnOptions.getAttribute('data-subid'); - context = dom.parentWithClass(btnOptions, 'subtitleEditorDialog'); - showDownloadOptions(btnOptions, context, subtitleId); + dialogOptions.size = 'small'; } - var btnDownload = dom.parentWithClass(e.target, 'btnDownload'); - if (btnDownload) { - subtitleId = btnDownload.getAttribute('data-subid'); - context = dom.parentWithClass(btnDownload, 'subtitleEditorDialog'); - downloadRemoteSubtitles(context, subtitleId); + let dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + dlg.classList.add('subtitleEditorDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); + + dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); + + let btnSubmit = dlg.querySelector('.btnSubmit'); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); + } else { + btnSubmit.classList.add('hide'); } - } - function showDownloadOptions(button, context, subtitleId) { - var items = []; + let editorContent = dlg.querySelector('.formDialogContent'); - items.push({ - name: globalize.translate('Download'), - id: 'download' + dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); + dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); + + apiClient.getCultures().then(function (languages) { + fillLanguages(editorContent, apiClient, languages); }); - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: items, - positionTo: button - - }).then(function (id) { - switch (id) { - case 'download': - downloadRemoteSubtitles(context, subtitleId); - break; - default: - break; - } - }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); }); - } - - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - scrollHelper = scrollHelper.default || scrollHelper; - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } - - function showEditorInternal(itemId, serverId, template) { - hasChanges = false; - - var apiClient = connectionManager.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('subtitleEditorDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.querySelector('.originalSubtitleFileLabel').innerHTML = globalize.translate('File'); - - dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); - - var btnSubmit = dlg.querySelector('.btnSubmit'); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - dlg.querySelector('.btnSearchSubtitles').classList.add('hide'); - } else { - btnSubmit.classList.add('hide'); - } - - var editorContent = dlg.querySelector('.formDialogContent'); - - dlg.querySelector('.subtitleList').addEventListener('click', onSubtitleListClick); - dlg.querySelector('.subtitleResults').addEventListener('click', onSubtitleResultsClick); - - apiClient.getCultures().then(function (languages) { - fillLanguages(editorContent, apiClient, languages); - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - return new Promise(function (resolve, reject) { - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(editorContent, apiClient, item); - }); - }); - } - - function showEditor(itemId, serverId) { - loading.show(); return new Promise(function (resolve, reject) { - require(['text!./subtitleeditor.template.html'], function (template) { - showEditorInternal(itemId, serverId, template).then(resolve, reject); - }); - }); - } + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } - return { - show: showEditor - }; -}); + if (hasChanges) { + resolve(); + } else { + reject(); + } + }); + + dialogHelper.open(dlg); + + reload(editorContent, apiClient, item); + }); + }); +} + +function showEditor(itemId, serverId) { + loading.show(); + + return new Promise(function (resolve, reject) { + import('text!./subtitleeditor.template.html').then(({default: template}) => { + showEditorInternal(itemId, serverId, template).then(resolve, reject); + }); + }); +} + +export default { + show: showEditor +}; diff --git a/src/components/subtitlesettings/subtitleappearancehelper.js b/src/components/subtitlesettings/subtitleappearancehelper.js index 904c018bfc..1834cafa17 100644 --- a/src/components/subtitlesettings/subtitleappearancehelper.js +++ b/src/components/subtitlesettings/subtitleappearancehelper.js @@ -4,7 +4,7 @@ */ function getTextStyles(settings, preview) { - let list = []; + const list = []; switch (settings.textSize || '') { case 'smaller': @@ -130,14 +130,14 @@ export function getStyles(settings, preview) { function applyStyleList(styles, elem) { for (let i = 0, length = styles.length; i < length; i++) { - let style = styles[i]; + const style = styles[i]; elem.style[style.name] = style.value; } } export function applyStyles(elements, appearanceSettings) { - let styles = getStyles(appearanceSettings, !!elements.preview); + const styles = getStyles(appearanceSettings, !!elements.preview); if (elements.text) { applyStyleList(styles.text, elements.text); diff --git a/src/components/subtitlesettings/subtitlesettings.js b/src/components/subtitlesettings/subtitlesettings.js index 12e230b1e7..46760be4c8 100644 --- a/src/components/subtitlesettings/subtitlesettings.js +++ b/src/components/subtitlesettings/subtitlesettings.js @@ -23,7 +23,7 @@ import 'css!./subtitlesettings'; */ function getSubtitleAppearanceObject(context) { - let appearanceSettings = {}; + const appearanceSettings = {}; appearanceSettings.textSize = context.querySelector('#selectTextSize').value; appearanceSettings.dropShadow = context.querySelector('#selectDropShadow').value; @@ -41,7 +41,7 @@ function loadForm(context, user, userSettings, appearanceSettings, apiClient) { context.querySelector('.fldBurnIn').classList.remove('hide'); } - let selectSubtitleLanguage = context.querySelector('#selectSubtitleLanguage'); + const selectSubtitleLanguage = context.querySelector('#selectSubtitleLanguage'); settingsHelper.populateLanguages(selectSubtitleLanguage, allCultures); @@ -101,9 +101,9 @@ function save(instance, context, userId, userSettings, apiClient, enableSaveConf } function onSubtitleModeChange(e) { - let view = dom.parentWithClass(e.target, 'subtitlesettings'); + const view = dom.parentWithClass(e.target, 'subtitlesettings'); - let subtitlesHelp = view.querySelectorAll('.subtitlesHelp'); + const subtitlesHelp = view.querySelectorAll('.subtitlesHelp'); for (let i = 0, length = subtitlesHelp.length; i < length; i++) { subtitlesHelp[i].classList.add('hide'); } @@ -111,11 +111,11 @@ function onSubtitleModeChange(e) { } function onAppearanceFieldChange(e) { - let view = dom.parentWithClass(e.target, 'subtitlesettings'); + const view = dom.parentWithClass(e.target, 'subtitlesettings'); - let appearanceSettings = getSubtitleAppearanceObject(view); + const appearanceSettings = getSubtitleAppearanceObject(view); - let elements = { + const elements = { window: view.querySelector('.subtitleappearance-preview-window'), text: view.querySelector('.subtitleappearance-preview-text'), preview: true @@ -226,20 +226,20 @@ export class SubtitleSettings { } loadData() { - let self = this; - let context = self.options.element; + const self = this; + const context = self.options.element; loading.show(); - let userId = self.options.userId; - let apiClient = connectionManager.getApiClient(self.options.serverId); - let userSettings = self.options.userSettings; + const userId = self.options.userId; + const apiClient = connectionManager.getApiClient(self.options.serverId); + const userSettings = self.options.userSettings; apiClient.getUser(userId).then(function (user) { userSettings.setUserInfo(userId, apiClient).then(function () { self.dataLoaded = true; - let appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); + const appearanceSettings = userSettings.getSubtitleAppearanceSettings(self.options.appearanceKey); loadForm(context, user, userSettings, appearanceSettings, apiClient); }); @@ -256,12 +256,12 @@ export class SubtitleSettings { onSubmit(e) { const self = this; - let apiClient = connectionManager.getApiClient(self.options.serverId); - let userId = self.options.userId; - let userSettings = self.options.userSettings; + const apiClient = connectionManager.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; userSettings.setUserInfo(userId, apiClient).then(function () { - let enableSaveConfirmation = self.options.enableSaveConfirmation; + const enableSaveConfirmation = self.options.enableSaveConfirmation; save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); }); diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 203d88535f..efb2087a1b 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -1,147 +1,148 @@ -define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) { - 'use strict'; +import playbackManager from 'playbackManager'; +import layoutManager from 'layoutManager'; +import template from 'text!./subtitlesync.template.html'; +import 'css!./subtitlesync'; - playbackManager = playbackManager.default || playbackManager; +let player; +let subtitleSyncSlider; +let subtitleSyncTextField; +let subtitleSyncCloseButton; +let subtitleSyncContainer; - var player; - var subtitleSyncSlider; - var subtitleSyncTextField; - var subtitleSyncCloseButton; - var subtitleSyncContainer; +function init(instance) { + const parent = document.createElement('div'); + document.body.appendChild(parent); + parent.innerHTML = template; - function init(instance) { - var parent = document.createElement('div'); - document.body.appendChild(parent); - parent.innerHTML = template; + subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); + subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); + subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); + subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); - subtitleSyncSlider = parent.querySelector('.subtitleSyncSlider'); - subtitleSyncTextField = parent.querySelector('.subtitleSyncTextField'); - subtitleSyncCloseButton = parent.querySelector('.subtitleSync-closeButton'); - subtitleSyncContainer = parent.querySelector('.subtitleSyncContainer'); + if (layoutManager.tv) { + subtitleSyncSlider.classList.add('focusable'); + // HACK: Delay to give time for registered element attach (Firefox) + setTimeout(function () { + subtitleSyncSlider.enableKeyboardDragging(); + }, 0); + } - if (layoutManager.tv) { - subtitleSyncSlider.classList.add('focusable'); - // HACK: Delay to give time for registered element attach (Firefox) - setTimeout(function () { - subtitleSyncSlider.enableKeyboardDragging(); - }, 0); - } + subtitleSyncContainer.classList.add('hide'); - subtitleSyncContainer.classList.add('hide'); + subtitleSyncTextField.updateOffset = function (offset) { + this.textContent = offset + 's'; + }; - subtitleSyncTextField.updateOffset = function(offset) { - this.textContent = offset + 's'; - }; + subtitleSyncTextField.addEventListener('click', function () { + // keep focus to prevent fade with osd + this.hasFocus = true; + }); - subtitleSyncTextField.addEventListener('click', function () { + subtitleSyncTextField.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + // if input key is enter search for float pattern + let inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); + if (inputOffset) { + inputOffset = inputOffset[0]; + + // replace current text by considered offset + this.textContent = inputOffset + 's'; + + inputOffset = parseFloat(inputOffset); + // set new offset + playbackManager.setSubtitleOffset(inputOffset, player); + // synchronize with slider value + subtitleSyncSlider.updateOffset( + getPercentageFromOffset(inputOffset)); + } else { + this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; + } + this.hasFocus = false; + event.preventDefault(); + } else { // keep focus to prevent fade with osd this.hasFocus = true; - }); - - subtitleSyncTextField.addEventListener('keydown', function(event) { - if (event.key === 'Enter') { - // if input key is enter search for float pattern - var inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); - if (inputOffset) { - inputOffset = inputOffset[0]; - - // replace current text by considered offset - this.textContent = inputOffset + 's'; - - inputOffset = parseFloat(inputOffset); - // set new offset - playbackManager.setSubtitleOffset(inputOffset, player); - // synchronize with slider value - subtitleSyncSlider.updateOffset( - getPercentageFromOffset(inputOffset)); - } else { - this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; - } - this.hasFocus = false; + if (event.key.match(/[+-\d.s]/) === null) { event.preventDefault(); - } else { - // keep focus to prevent fade with osd - this.hasFocus = true; - if (event.key.match(/[+-\d.s]/) === null) { - event.preventDefault(); - } } + } - // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable - event.stopPropagation(); - }); + // FIXME: TV layout will require special handling for navigation keys. But now field is not focusable + event.stopPropagation(); + }); - subtitleSyncTextField.blur = function() { - // prevent textfield to blur while element has focus - if (!this.hasFocus && this.prototype) { - this.prototype.blur(); - } - }; + subtitleSyncTextField.blur = function () { + // prevent textfield to blur while element has focus + if (!this.hasFocus && this.prototype) { + this.prototype.blur(); + } + }; - subtitleSyncSlider.updateOffset = function(percent) { - // default value is 0s = 50% - this.value = percent === undefined ? 50 : percent; - }; + subtitleSyncSlider.updateOffset = function (percent) { + // default value is 0s = 50% + this.value = percent === undefined ? 50 : percent; + }; - subtitleSyncSlider.addEventListener('change', function () { - // set new offset - playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); - // synchronize with textField value - subtitleSyncTextField.updateOffset( - getOffsetFromPercentage(this.value)); - }); + subtitleSyncSlider.addEventListener('change', function () { + // set new offset + playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + // synchronize with textField value + subtitleSyncTextField.updateOffset( + getOffsetFromPercentage(this.value)); + }); - subtitleSyncSlider.getBubbleHtml = function (value) { - var newOffset = getOffsetFromPercentage(value); - return '

' + + subtitleSyncSlider.getBubbleHtml = function (value) { + const newOffset = getOffsetFromPercentage(value); + return '

' + (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' + '

'; - }; + }; - subtitleSyncCloseButton.addEventListener('click', function() { - playbackManager.disableShowingSubtitleOffset(player); - SubtitleSync.prototype.toggle('forceToHide'); - }); + subtitleSyncCloseButton.addEventListener('click', function () { + playbackManager.disableShowingSubtitleOffset(player); + SubtitleSync.prototype.toggle('forceToHide'); + }); - instance.element = parent; - } + instance.element = parent; +} - function getOffsetFromPercentage(value) { - // convert percent to fraction - var offset = (value - 50) / 50; - // multiply by offset min/max range value (-x to +x) : - offset *= 30; - return offset.toFixed(1); - } +function getOffsetFromPercentage(value) { + // convert percent to fraction + let offset = (value - 50) / 50; + // multiply by offset min/max range value (-x to +x) : + offset *= 30; + return offset.toFixed(1); +} - function getPercentageFromOffset(value) { - // divide by offset min/max range value (-x to +x) : - var percentValue = value / 30; - // convert fraction to percent - percentValue *= 50; - percentValue += 50; - return Math.min(100, Math.max(0, percentValue.toFixed())); - } +function getPercentageFromOffset(value) { + // divide by offset min/max range value (-x to +x) : + let percentValue = value / 30; + // convert fraction to percent + percentValue *= 50; + percentValue += 50; + return Math.min(100, Math.max(0, percentValue.toFixed())); +} - function SubtitleSync(currentPlayer) { +class SubtitleSync { + constructor(currentPlayer) { player = currentPlayer; init(this); } - SubtitleSync.prototype.destroy = function() { + destroy() { SubtitleSync.prototype.toggle('forceToHide'); if (player) { playbackManager.disableShowingSubtitleOffset(player); playbackManager.setSubtitleOffset(0, player); } - var elem = this.element; + const elem = this.element; if (elem) { elem.parentNode.removeChild(elem); this.element = null; } - }; + } - SubtitleSync.prototype.toggle = function(action) { + toggle(action) { if (player && playbackManager.supportSubtitleOffset(player)) { /* eslint-disable no-fallthrough */ switch (action) { @@ -170,7 +171,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', } /* eslint-enable no-fallthrough */ } - }; + } +} - return SubtitleSync; -}); +export default SubtitleSync; diff --git a/src/components/tabbedview/tabbedview.js b/src/components/tabbedview/tabbedview.js index 8bd3afd372..710a0e3c40 100644 --- a/src/components/tabbedview/tabbedview.js +++ b/src/components/tabbedview/tabbedview.js @@ -1,30 +1,33 @@ -define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function (backdrop, mainTabsManager, layoutManager) { - 'use strict'; +import backdrop from 'backdrop'; +import * as mainTabsManager from 'mainTabsManager'; +import layoutManager from 'layoutManager'; +import 'emby-tabs'; - function onViewDestroy(e) { - var tabControllers = this.tabControllers; +function onViewDestroy(e) { + var tabControllers = this.tabControllers; - if (tabControllers) { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + if (tabControllers) { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); - this.tabControllers = null; - } - - this.view = null; - this.params = null; - this.currentTabController = null; - this.initialTabIndex = null; + this.tabControllers = null; } - function onBeforeTabChange() { + this.view = null; + this.params = null; + this.currentTabController = null; + this.initialTabIndex = null; +} - } +function onBeforeTabChange() { - function TabbedView(view, params) { +} + +class TabbedView { + constructor(view, params) { this.tabControllers = []; this.view = view; this.params = params; @@ -85,7 +88,7 @@ define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function ( view.addEventListener('viewdestroy', onViewDestroy.bind(this)); } - TabbedView.prototype.onResume = function (options) { + onResume(options) { this.setTitle(); backdrop.clearBackdrop(); @@ -96,19 +99,18 @@ define(['backdrop', 'mainTabsManager', 'layoutManager', 'emby-tabs'], function ( } else if (currentTabController && currentTabController.onResume) { currentTabController.onResume({}); } - }; + } - TabbedView.prototype.onPause = function () { + onPause() { var currentTabController = this.currentTabController; if (currentTabController && currentTabController.onPause) { currentTabController.onPause(); } - }; - - TabbedView.prototype.setTitle = function () { + } + setTitle() { Emby.Page.setTitle(''); - }; + } +} - return TabbedView; -}); +export default TabbedView; 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 5d597f6654..6b2b1c00e6 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -3,6 +3,7 @@ define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize' browser = browser.default || browser; loading = loading.default || loading; + layoutManager = layoutManager.default || layoutManager; focusManager = focusManager.default || focusManager; scrollHelper = scrollHelper.default || scrollHelper; diff --git a/src/components/tvproviders/schedulesdirect.template.html b/src/components/tvproviders/schedulesdirect.template.html index abe19b50f5..8783987875 100644 --- a/src/components/tvproviders/schedulesdirect.template.html +++ b/src/components/tvproviders/schedulesdirect.template.html @@ -27,7 +27,7 @@
- +
@@ -65,7 +65,7 @@

- +
diff --git a/src/components/tvproviders/xmltv.template.html b/src/components/tvproviders/xmltv.template.html index 72c29904b3..010e66ea0d 100644 --- a/src/components/tvproviders/xmltv.template.html +++ b/src/components/tvproviders/xmltv.template.html @@ -56,7 +56,7 @@
- +
diff --git a/src/components/viewSettings/viewSettings.js b/src/components/viewSettings/viewSettings.js index 9207f8ddbb..fd5b5c3f9e 100644 --- a/src/components/viewSettings/viewSettings.js +++ b/src/components/viewSettings/viewSettings.js @@ -1,58 +1,66 @@ -define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize, userSettings) { - 'use strict'; +import dialogHelper from 'dialogHelper'; +import layoutManager from 'layoutManager'; +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'; - function onSubmit(e) { - e.preventDefault(); - return false; +function onSubmit(e) { + e.preventDefault(); + return false; +} + +function initEditor(context, settings) { + context.querySelector('form').addEventListener('submit', onSubmit); + + const elems = context.querySelectorAll('.viewSetting-checkboxContainer'); + + for (const elem of elems) { + elem.querySelector('input').checked = settings[elem.getAttribute('data-settingname')] || false; } - function initEditor(context, settings) { - context.querySelector('form').addEventListener('submit', onSubmit); + context.querySelector('.selectImageType').value = settings.imageType || 'primary'; +} - var elems = context.querySelectorAll('.viewSetting-checkboxContainer'); - - for (var i = 0, length = elems.length; i < length; i++) { - elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false; - } - - context.querySelector('.selectImageType').value = settings.imageType || 'primary'; +function saveValues(context, settings, settingsKey) { + const elems = context.querySelectorAll('.viewSetting-checkboxContainer'); + for (const elem of elems) { + userSettings.set(settingsKey + '-' + elem.getAttribute('data-settingname'), elem.querySelector('input').checked); } - function saveValues(context, settings, settingsKey) { - var elems = context.querySelectorAll('.viewSetting-checkboxContainer'); - for (var i = 0, length = elems.length; i < length; i++) { - userSettings.set(settingsKey + '-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input').checked); - } + userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value); +} - userSettings.set(settingsKey + '-imageType', context.querySelector('.selectImageType').value); +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +function showIfAllowed(context, selector, visible) { + const elem = context.querySelector(selector); + + if (visible && !elem.classList.contains('hiddenFromViewSettings')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - scrollHelper = scrollHelper.default || scrollHelper; - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); +class ViewSettings { + constructor() { } - - function showIfAllowed(context, selector, visible) { - var elem = context.querySelector(selector); - - if (visible && !elem.classList.contains('hiddenFromViewSettings')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - } - - function ViewSettings() { - - } - - ViewSettings.prototype.show = function (options) { + show(options) { return new Promise(function (resolve, reject) { - require(['text!./viewSettings.template.html'], function (template) { - var dialogOptions = { + import('text!./viewSettings.template.html').then(({default: template}) => { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -63,11 +71,11 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; + let html = ''; html += '
'; html += ''; @@ -79,14 +87,14 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne dlg.innerHTML = globalize.translateHtml(html, 'core'); - var settingElements = dlg.querySelectorAll('.viewSetting'); - for (var i = 0, length = settingElements.length; i < length; i++) { - if (options.visibleSettings.indexOf(settingElements[i].getAttribute('data-settingname')) === -1) { - settingElements[i].classList.add('hide'); - settingElements[i].classList.add('hiddenFromViewSettings'); + const settingElements = dlg.querySelectorAll('.viewSetting'); + for (const settingElement of settingElements) { + if (options.visibleSettings.indexOf(settingElement.getAttribute('data-settingname')) === -1) { + settingElement.classList.add('hide'); + settingElement.classList.add('hiddenFromViewSettings'); } else { - settingElements[i].classList.remove('hide'); - settingElements[i].classList.remove('hiddenFromViewSettings'); + settingElement.classList.remove('hide'); + settingElement.classList.remove('hiddenFromViewSettings'); } } @@ -105,7 +113,7 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne centerFocus(dlg.querySelector('.formDialogContent'), false, true); } - var submitted; + let submitted; dlg.querySelector('.selectImageType').dispatchEvent(new CustomEvent('change', {})); @@ -128,7 +136,7 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne }); }); }); - }; + } +} - return ViewSettings; -}); +export default ViewSettings; diff --git a/src/apikeys.html b/src/controllers/dashboard/apikeys.html similarity index 94% rename from src/apikeys.html rename to src/controllers/dashboard/apikeys.html index 3cb7cd6de1..400a3e5f60 100644 --- a/src/apikeys.html +++ b/src/controllers/dashboard/apikeys.html @@ -3,7 +3,7 @@

${HeaderApiKeys}

-
diff --git a/src/dashboard.html b/src/controllers/dashboard/dashboard.html similarity index 100% rename from src/dashboard.html rename to src/controllers/dashboard/dashboard.html diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 61fc345382..a36aa3ce68 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -24,7 +24,7 @@ import 'emby-itemscontainer'; function showPlaybackInfo(btn, session) { import('alert').then(({default: alert}) => { let title; - let text = []; + const text = []; const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); if (displayPlayMethod === 'DirectStream') { diff --git a/src/device.html b/src/controllers/dashboard/devices/device.html similarity index 95% rename from src/device.html rename to src/controllers/dashboard/devices/device.html index 093b311208..4d8fb86537 100644 --- a/src/device.html +++ b/src/controllers/dashboard/devices/device.html @@ -15,7 +15,7 @@
diff --git a/src/devices.html b/src/controllers/dashboard/devices/devices.html similarity index 100% rename from src/devices.html rename to src/controllers/dashboard/devices/devices.html diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 93ae62c43f..bc9dd19764 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -38,7 +38,7 @@ import 'cardStyle'; } function showDeviceMenu(view, btn, deviceId) { - let menuItems = []; + const menuItems = []; if (canEdit) { menuItems.push({ diff --git a/src/dlnaprofile.html b/src/controllers/dashboard/dlna/profile.html similarity index 99% rename from src/dlnaprofile.html rename to src/controllers/dashboard/dlna/profile.html index 01636e6019..02a85b4ec7 100644 --- a/src/dlnaprofile.html +++ b/src/controllers/dashboard/dlna/profile.html @@ -10,7 +10,7 @@
${TabInfo} ${TabDirectPlay} - ${TabTranscoding} + ${Transcoding} ${TabContainers} ${TabCodecs} ${TabResponses} @@ -96,14 +96,14 @@

${HeaderHttpHeaders}

-
-
+

@@ -209,7 +209,7 @@

${HeaderSubtitleProfilesHelp}


@@ -219,7 +219,7 @@

${HeaderXmlDocumentAttributes}

-
@@ -262,7 +262,7 @@
diff --git a/src/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html similarity index 98% rename from src/encodingsettings.html rename to src/controllers/dashboard/encodingsettings.html index 858375b145..402532e600 100644 --- a/src/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -4,7 +4,7 @@
-

${TabTranscoding}

+

${Transcoding}

${Help}
@@ -176,7 +176,7 @@
diff --git a/src/dashboardgeneral.html b/src/controllers/dashboard/general.html similarity index 98% rename from src/dashboardgeneral.html rename to src/controllers/dashboard/general.html index 99e6414dd6..9ac0a90832 100644 --- a/src/dashboardgeneral.html +++ b/src/controllers/dashboard/general.html @@ -63,7 +63,7 @@
diff --git a/src/library.html b/src/controllers/dashboard/library.html similarity index 100% rename from src/library.html rename to src/controllers/dashboard/library.html diff --git a/src/controllers/dashboard/mediaLibrary.js b/src/controllers/dashboard/library.js similarity index 99% rename from src/controllers/dashboard/mediaLibrary.js rename to src/controllers/dashboard/library.js index 4fee65e674..0400df7570 100644 --- a/src/controllers/dashboard/mediaLibrary.js +++ b/src/controllers/dashboard/library.js @@ -363,10 +363,10 @@ import 'emby-itemrefreshindicator'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/librarydisplay.html b/src/controllers/dashboard/librarydisplay.html similarity index 98% rename from src/librarydisplay.html rename to src/controllers/dashboard/librarydisplay.html index 25dce48223..04a8554823 100644 --- a/src/librarydisplay.html +++ b/src/controllers/dashboard/librarydisplay.html @@ -49,7 +49,7 @@
diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index bbc7e633d1..06e366b988 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -12,10 +12,10 @@ import 'emby-button'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/log.html b/src/controllers/dashboard/logs.html similarity index 100% rename from src/log.html rename to src/controllers/dashboard/logs.html diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index 02e01736ea..649ca9ac31 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -55,10 +55,10 @@ import 'listViewStyle'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/metadataimages.html b/src/controllers/dashboard/metadataimages.html similarity index 92% rename from src/metadataimages.html rename to src/controllers/dashboard/metadataimages.html index 8ad129256c..48a9977054 100644 --- a/src/metadataimages.html +++ b/src/controllers/dashboard/metadataimages.html @@ -17,7 +17,7 @@

-
+
diff --git a/src/metadatanfo.html b/src/controllers/dashboard/metadatanfo.html similarity index 97% rename from src/metadatanfo.html rename to src/controllers/dashboard/metadatanfo.html index 4005c74f67..44d1cc06f6 100644 --- a/src/metadatanfo.html +++ b/src/controllers/dashboard/metadatanfo.html @@ -41,7 +41,7 @@
${LabelKodiMetadataEnableExtraThumbsHelp}
-
+
diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index f1b768d18b..f33971b369 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -52,10 +52,10 @@ import globalize from 'globalize'; name: globalize.translate('HeaderLibraries') }, { href: 'librarydisplay.html', - name: globalize.translate('TabDisplay') + name: globalize.translate('Display') }, { href: 'metadataimages.html', - name: globalize.translate('TabMetadata') + name: globalize.translate('Metadata') }, { href: 'metadatanfo.html', name: globalize.translate('TabNfoSettings') diff --git a/src/networking.html b/src/controllers/dashboard/networking.html similarity index 99% rename from src/networking.html rename to src/controllers/dashboard/networking.html index 74ddefb87b..899d0976b7 100644 --- a/src/networking.html +++ b/src/controllers/dashboard/networking.html @@ -112,7 +112,7 @@
diff --git a/src/controllers/dashboard/notifications/notification/index.html b/src/controllers/dashboard/notifications/notification/index.html index 67b35981f1..99bed59887 100644 --- a/src/controllers/dashboard/notifications/notification/index.html +++ b/src/controllers/dashboard/notifications/notification/index.html @@ -54,7 +54,7 @@

-
+
diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 6c666b4646..5cc1dd3215 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -1,143 +1,146 @@ -define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'emby-button'], function ($, loading, libraryMenu, globalize, connectionManager) { - 'use strict'; +import $ from 'jQuery'; +import loading from 'loading'; +import globalize from 'globalize'; +import 'emby-button'; - loading = loading.default || loading; +function populateHistory(packageInfo, page) { + let html = ''; + const length = Math.min(packageInfo.versions.length, 10); - function populateHistory(packageInfo, page) { - var html = ''; - var length = Math.min(packageInfo.versions.length, 10); - - for (var i = 0; i < length; i++) { - var version = packageInfo.versions[i]; - html += '

' + version.version + '

'; - html += '
' + version.changelog + '
'; - } - - $('#revisionHistory', page).html(html); + for (let i = 0; i < length; i++) { + const version = packageInfo.versions[i]; + html += '

' + version.version + '

'; + html += '
' + version.changelog + '
'; } - function populateVersions(packageInfo, page, installedPlugin) { - var html = ''; + $('#revisionHistory', page).html(html); +} - for (var i = 0; i < packageInfo.versions.length; i++) { - var version = packageInfo.versions[i]; - html += ''; - } +function populateVersions(packageInfo, page, installedPlugin) { + let html = ''; - var selectmenu = $('#selectVersion', page).html(html); - - if (!installedPlugin) { - $('#pCurrentVersion', page).hide().html(''); - } - - var packageVersion = packageInfo.versions[0]; - if (packageVersion) { - selectmenu.val(packageVersion.version); - } + for (let i = 0; i < packageInfo.versions.length; i++) { + const version = packageInfo.versions[i]; + html += ''; } - function renderPackage(pkg, installedPlugins, page) { - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Name == pkg.name; - })[0]; + const selectmenu = $('#selectVersion', page).html(html); - populateVersions(pkg, page, installedPlugin); - populateHistory(pkg, page); - - $('.pluginName', page).html(pkg.name); - $('#btnInstallDiv', page).removeClass('hide'); - $('#pSelectVersion', page).removeClass('hide'); - - if (pkg.overview) { - $('#overview', page).show().html(pkg.overview); - } else { - $('#overview', page).hide(); - } - - $('#description', page).html(pkg.description); - $('#developer', page).html(pkg.owner); - - if (installedPlugin) { - var currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); - $('#pCurrentVersion', page).show().html(currentVersionText); - } else { - $('#pCurrentVersion', page).hide().html(''); - } - - loading.hide(); + if (!installedPlugin) { + $('#pCurrentVersion', page).hide().html(''); } - function alertText(options) { - require(['alert'], function ({default: alert}) { - alert(options); - }); + const packageVersion = packageInfo.versions[0]; + if (packageVersion) { + selectmenu.val(packageVersion.version); + } +} + +function renderPackage(pkg, installedPlugins, page) { + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Name == pkg.name; + })[0]; + + populateVersions(pkg, page, installedPlugin); + populateHistory(pkg, page); + + $('.pluginName', page).html(pkg.name); + $('#btnInstallDiv', page).removeClass('hide'); + $('#pSelectVersion', page).removeClass('hide'); + + if (pkg.overview) { + $('#overview', page).show().html(pkg.overview); + } else { + $('#overview', page).hide(); } - function performInstallation(page, name, guid, version) { - var developer = $('#developer', page).html().toLowerCase(); + $('#description', page).html(pkg.description); + $('#developer', page).html(pkg.owner); - var alertCallback = function () { - loading.show(); - page.querySelector('#btnInstall').disabled = true; - ApiClient.installPlugin(name, guid, version).then(function () { - loading.hide(); - alertText(globalize.translate('MessagePluginInstalled')); - }); - }; + if (installedPlugin) { + const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); + $('#pCurrentVersion', page).show().html(currentVersionText); + } else { + $('#pCurrentVersion', page).hide().html(''); + } - if (developer !== 'jellyfin') { + loading.hide(); +} + +function alertText(options) { + import('alert').then(({default: alert}) => { + alert(options); + }); +} + +function performInstallation(page, name, guid, version) { + const developer = $('#developer', page).html().toLowerCase(); + + const alertCallback = function () { + loading.show(); + page.querySelector('#btnInstall').disabled = true; + ApiClient.installPlugin(name, guid, version).then(() => { loading.hide(); - var msg = globalize.translate('MessagePluginInstallDisclaimer'); - msg += '
'; - msg += '
'; - msg += globalize.translate('PleaseConfirmPluginInstallation'); - - require(['confirm'], function (confirm) { - confirm.default(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { - alertCallback(); - }, function () { - console.debug('plugin not installed'); - }); - }); - } else { - alertCallback(); - } - } - - return function (view, params) { - $('.addPluginForm', view).on('submit', function () { - loading.show(); - var page = $(this).parents('#addPluginPage')[0]; - var name = params.name; - var guid = params.guid; - ApiClient.getInstalledPlugins().then(function (plugins) { - var installedPlugin = plugins.filter(function (plugin) { - return plugin.Name == name; - })[0]; - - var version = $('#selectVersion', page).val(); - if (installedPlugin && installedPlugin.Version === version) { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('MessageAlreadyInstalled'), - title: globalize.translate('HeaderPluginInstallation') - }); - } else { - performInstallation(page, name, guid, version); - } - }); - return false; - }); - view.addEventListener('viewshow', function () { - var page = this; - loading.show(); - var name = params.name; - var guid = params.guid; - var promise1 = ApiClient.getPackageInfo(name, guid); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - renderPackage(responses[0], responses[1], page); - }); + alertText(globalize.translate('MessagePluginInstalled')); + }).catch(() => { + alertText(globalize.translate('MessagePluginInstallError')); }); }; -}); + + if (developer !== 'jellyfin') { + loading.hide(); + let msg = globalize.translate('MessagePluginInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmPluginInstallation'); + + import('confirm').then(({default: confirm}) => { + confirm(msg, globalize.translate('HeaderConfirmPluginInstallation')).then(function () { + alertCallback(); + }).catch(() => { + console.debug('plugin not installed'); + }); + }); + } else { + alertCallback(); + } +} + +export default function(view, params) { + $('.addPluginForm', view).on('submit', function () { + loading.show(); + const page = $(this).parents('#addPluginPage')[0]; + const name = params.name; + const guid = params.guid; + ApiClient.getInstalledPlugins().then(function (plugins) { + const installedPlugin = plugins.filter(function (plugin) { + return plugin.Name == name; + })[0]; + + const version = $('#selectVersion', page).val(); + if (installedPlugin && installedPlugin.Version === version) { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('MessageAlreadyInstalled'), + title: globalize.translate('HeaderPluginInstallation') + }); + } else { + performInstallation(page, name, guid, version); + } + }).catch(() => { + alertText(globalize.translate('MessageGetInstalledPluginsError')); + }); + return false; + }); + view.addEventListener('viewshow', function () { + const page = this; + loading.show(); + const name = params.name; + const guid = params.guid; + const promise1 = ApiClient.getPackageInfo(name, guid); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + renderPackage(responses[0], responses[1], page); + }); + }); +} diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index e38365ef55..22b28e0f5a 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,143 +1,142 @@ -define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby-checkbox', 'emby-select'], function (loading, libraryMenu, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; - loading = loading.default || loading; - - function reloadList(page) { - loading.show(); - var promise1 = ApiClient.getAvailablePlugins(); - var promise2 = ApiClient.getInstalledPlugins(); - Promise.all([promise1, promise2]).then(function (responses) { - populateList({ - catalogElement: page.querySelector('#pluginTiles'), - noItemsElement: page.querySelector('#noPlugins'), - availablePlugins: responses[0], - installedPlugins: responses[1] - }); +function reloadList(page) { + loading.show(); + const promise1 = ApiClient.getAvailablePlugins(); + const promise2 = ApiClient.getInstalledPlugins(); + Promise.all([promise1, promise2]).then(function (responses) { + populateList({ + catalogElement: page.querySelector('#pluginTiles'), + noItemsElement: page.querySelector('#noPlugins'), + availablePlugins: responses[0], + installedPlugins: responses[1] }); + }); +} + +function getHeaderText(category) { + category = category.replace(' ', ''); + // TODO: Replace with switch + if (category === 'Channel') { + category = 'Channels'; + } else if (category === 'Theme') { + category = 'Themes'; + } else if (category === 'LiveTV') { + category = 'HeaderLiveTV'; + } else if (category === 'ScreenSaver') { + category = 'HeaderScreenSavers'; } - function getHeaderText(category) { - category = category.replace(' ', ''); - if (category === 'Channel') { - category = 'Channels'; - } else if (category === 'Theme') { - category = 'Themes'; - } else if (category === 'LiveTV') { - category = 'HeaderLiveTV'; - } else if (category === 'ScreenSaver') { - category = 'HeaderScreenSavers'; + return globalize.translate(category); +} + +function populateList(options) { + const availablePlugins = options.availablePlugins; + const installedPlugins = options.installedPlugins; + + availablePlugins.forEach(function (plugin, index, array) { + plugin.category = plugin.category || 'General'; + plugin.categoryDisplayName = getHeaderText(plugin.category); + array[index] = plugin; + }); + + availablePlugins.sort(function (a, b) { + if (a.category > b.category) { + return 1; + } else if (b.category > a.category) { + return -1; } + if (a.name > b.name) { + return 1; + } else if (b.name > a.name) { + return -1; + } + return 0; + }); - return globalize.translate(category); - } + let currentCategory = null; + let html = ''; - function populateList(options) { - var availablePlugins = options.availablePlugins; - var installedPlugins = options.installedPlugins; - - availablePlugins.forEach(function (plugin, index, array) { - plugin.category = plugin.category || 'General'; - plugin.categoryDisplayName = getHeaderText(plugin.category); - array[index] = plugin; - }); - - availablePlugins.sort(function (a, b) { - if (a.category > b.category) { - return 1; - } else if (b.category > a.category) { - return -1; + for (let i = 0; i < availablePlugins.length; i++) { + const plugin = availablePlugins[i]; + const category = plugin.categoryDisplayName; + if (category != currentCategory) { + if (currentCategory) { + html += ''; + html += ''; } - if (a.name > b.name) { - return 1; - } else if (b.name > a.name) { - return -1; - } - return 0; - }); - - var currentCategory = null; - var html = ''; - - for (var i = 0; i < availablePlugins.length; i++) { - var plugin = availablePlugins[i]; - var category = plugin.categoryDisplayName; - if (category != currentCategory) { - if (currentCategory) { - html += ''; - html += ''; - } - html += '
'; - html += '

' + category + '

'; - html += '
'; - currentCategory = category; - } - html += getPluginHtml(plugin, options, installedPlugins); + html += '
'; + html += '

' + category + '

'; + html += '
'; + currentCategory = category; } - html += '
'; - html += '
'; + html += getPluginHtml(plugin, options, installedPlugins); + } + html += '
'; + html += '
'; - if (!availablePlugins.length && options.noItemsElement) { - options.noItemsElement.classList.remove('hide'); - } - - options.catalogElement.innerHTML = html; - loading.hide(); + if (!availablePlugins.length && options.noItemsElement) { + options.noItemsElement.classList.remove('hide'); } - function getPluginHtml(plugin, options, installedPlugins) { - var html = ''; - var href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; + options.catalogElement.innerHTML = html; + loading.hide(); +} - if (options.context) { - href += '&context=' + options.context; - } +function getPluginHtml(plugin, options, installedPlugins) { + let html = ''; + let href = plugin.externalUrl ? plugin.externalUrl : 'addplugin.html?name=' + encodeURIComponent(plugin.name) + '&guid=' + plugin.guid; - var target = plugin.externalUrl ? ' target="_blank"' : ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += '
'; - html += '
'; - html += "
"; - html += plugin.name; - html += '
'; - var installedPlugin = installedPlugins.filter(function (ip) { - return ip.Id == plugin.guid; - })[0]; - html += "
"; - html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; - html += '
'; - html += '
'; - html += '
'; - return html += '
'; + if (options.context) { + href += '&context=' + options.context; } - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } + const target = plugin.externalUrl ? ' target="_blank"' : ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; + html += '
'; + html += "
"; + html += plugin.name; + html += '
'; + const installedPlugin = installedPlugins.filter(function (ip) { + return ip.Id == plugin.guid; + })[0]; + html += "
"; + html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : ' '; + html += '
'; + html += '
'; + html += '
'; + return html += '
'; +} - window.PluginCatalog = { - renderCatalog: populateList - }; +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} - return function (view, params) { - view.addEventListener('viewshow', function () { - libraryMenu.setTabs('plugins', 1, getTabs); - reloadList(this); - }); - }; -}); +export default function (view) { + view.addEventListener('viewshow', function () { + libraryMenu.setTabs('plugins', 1, getTabs); + reloadList(this); + }); +} diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 80577d227b..e5f5b92413 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,192 +1,193 @@ -define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button'], function (loading, libraryMenu, dom, globalize) { - 'use strict'; +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import dom from 'dom'; +import globalize from 'globalize'; +import 'cardStyle'; +import 'emby-button'; - loading = loading.default || loading; +function deletePlugin(page, uniqueid, name) { + const msg = globalize.translate('UninstallPluginConfirmation', name); - function deletePlugin(page, uniqueid, name) { - var msg = globalize.translate('UninstallPluginConfirmation', name); - - require(['confirm'], function (confirm) { - confirm.default({ - title: globalize.translate('HeaderUninstallPlugin'), - text: msg, - primary: 'delete', - confirmText: globalize.translate('HeaderUninstallPlugin') - }).then(function () { - loading.show(); - ApiClient.uninstallPlugin(uniqueid).then(function () { - reloadList(page); - }); + import('confirm').then(({default: confirm}) => { + confirm.default({ + title: globalize.translate('HeaderUninstallPlugin'), + text: msg, + primary: 'delete', + confirmText: globalize.translate('HeaderUninstallPlugin') + }).then(function () { + loading.show(); + ApiClient.uninstallPlugin(uniqueid).then(function () { + reloadList(page); }); }); - } + }); +} - function showNoConfigurationMessage() { - Dashboard.alert({ - message: globalize.translate('MessageNoPluginConfiguration') - }); - } +function showNoConfigurationMessage() { + Dashboard.alert({ + message: globalize.translate('MessageNoPluginConfiguration') + }); +} - function showConnectMessage() { - Dashboard.alert({ - message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') - }); - } +function showConnectMessage() { + Dashboard.alert({ + message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') + }); +} - function getPluginCardHtml(plugin, pluginConfigurationPages) { - var configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { - return pluginConfigurationPage.PluginId == plugin.Id; - })[0]; - var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; - var html = ''; - html += "
"; - html += '
'; - html += '
'; - html += '
'; - html += configPageUrl ? '' : ''; +function getPluginCardHtml(plugin, pluginConfigurationPages) { + const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { + return pluginConfigurationPage.PluginId == plugin.Id; + })[0]; + const configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; + let html = ''; + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += configPageUrl ? '' : ''; + html += '
'; + html += '
'; + + if (configPage || plugin.CanUninstall) { + html += '
'; + html += ''; html += '
'; - html += '
'; + } - if (configPage || plugin.CanUninstall) { - html += '
'; - html += ''; - html += '
'; + html += "
"; + html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; + html += '
'; + html += "
"; + html += plugin.Version; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + return html; +} + +function renderPlugins(page, plugins) { + ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { + populateList(page, plugins, configPages); + }); +} + +function populateList(page, plugins, pluginConfigurationPages) { + plugins = plugins.sort(function (plugin1, plugin2) { + if (plugin1.Name > plugin2.Name) { + return 1; } - html += "
"; - html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; - html += '
'; - html += "
"; - html += plugin.Version; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - return html; - } - - function renderPlugins(page, plugins) { - ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { - populateList(page, plugins, configPages); - }); - } - - function populateList(page, plugins, pluginConfigurationPages) { - plugins = plugins.sort(function (plugin1, plugin2) { - if (plugin1.Name > plugin2.Name) { - return 1; - } - - return -1; - }); - - var html = plugins.map(function (p) { - return getPluginCardHtml(p, pluginConfigurationPages); - }).join(''); - - var installedPluginsElement = page.querySelector('.installedPlugins'); - installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); - installedPluginsElement.addEventListener('click', onInstalledPluginsClick); - - if (plugins.length) { - installedPluginsElement.classList.add('itemsContainer'); - installedPluginsElement.classList.add('vertical-wrap'); - } else { - html += '
'; - html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; - html += '

'; - html += globalize.translate('MessageBrowsePluginCatalog'); - html += '

'; - html += '
'; - } - - installedPluginsElement.innerHTML = html; - loading.hide(); - } - - function showPluginMenu(page, elem) { - 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 = []; - - if (configHref) { - menuItems.push({ - name: globalize.translate('ButtonSettings'), - id: 'open', - icon: 'mode_edit' - }); - } - - if (removable === 'true') { - menuItems.push({ - name: globalize.translate('ButtonUninstall'), - id: 'delete', - icon: 'delete' - }); - } - - require(['actionsheet'], function (actionsheet) { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'open': - Dashboard.navigate(configHref); - break; - case 'delete': - deletePlugin(page, id, name); - break; - } - } - }); - }); - } - - function reloadList(page) { - loading.show(); - ApiClient.getInstalledPlugins().then(function (plugins) { - renderPlugins(page, plugins); - }); - } - - function getTabs() { - return [{ - href: 'installedplugins.html', - name: globalize.translate('TabMyPlugins') - }, { - href: 'availableplugins.html', - name: globalize.translate('TabCatalog') - }, { - href: 'repositories.html', - name: globalize.translate('TabRepositories') - }]; - } - - function onInstalledPluginsClick(e) { - if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { - showNoConfigurationMessage(); - } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { - showConnectMessage(); - } else { - var btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); - if (btnCardMenu) { - showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); - } - } - } - - pageIdOn('pageshow', 'pluginsPage', function () { - libraryMenu.setTabs('plugins', 0, getTabs); - reloadList(this); + return -1; }); - window.PluginsPage = { - renderPlugins: renderPlugins - }; + let html = plugins.map(function (p) { + return getPluginCardHtml(p, pluginConfigurationPages); + }).join(''); + + const installedPluginsElement = page.querySelector('.installedPlugins'); + installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); + installedPluginsElement.addEventListener('click', onInstalledPluginsClick); + + if (plugins.length) { + installedPluginsElement.classList.add('itemsContainer'); + installedPluginsElement.classList.add('vertical-wrap'); + } else { + html += '
'; + html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; + html += '

'; + html += globalize.translate('MessageBrowsePluginCatalog'); + html += '

'; + html += '
'; + } + + installedPluginsElement.innerHTML = html; + loading.hide(); +} + +function showPluginMenu(page, elem) { + const card = dom.parentWithClass(elem, 'card'); + const id = card.getAttribute('data-id'); + const name = card.getAttribute('data-name'); + const removable = card.getAttribute('data-removable'); + const configHref = card.querySelector('.cardContent').getAttribute('href'); + const menuItems = []; + + if (configHref) { + menuItems.push({ + name: globalize.translate('ButtonSettings'), + id: 'open', + icon: 'mode_edit' + }); + } + + if (removable === 'true') { + menuItems.push({ + name: globalize.translate('ButtonUninstall'), + id: 'delete', + icon: 'delete' + }); + } + + import('actionsheet').then(({default: actionsheet}) => { + actionsheet.show({ + items: menuItems, + positionTo: elem, + callback: function (resultId) { + switch (resultId) { + case 'open': + Dashboard.navigate(configHref); + break; + case 'delete': + deletePlugin(page, id, name); + break; + } + } + }); + }); +} + +function reloadList(page) { + loading.show(); + ApiClient.getInstalledPlugins().then(function (plugins) { + renderPlugins(page, plugins); + }); +} + +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} + +function onInstalledPluginsClick(e) { + if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { + showNoConfigurationMessage(); + } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { + showConnectMessage(); + } else { + const btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); + if (btnCardMenu) { + showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); + } + } +} + +pageIdOn('pageshow', 'pluginsPage', function () { + libraryMenu.setTabs('plugins', 0, getTabs); + reloadList(this); }); + +window.PluginsPage = { + renderPlugins: renderPlugins +}; diff --git a/src/controllers/dashboard/plugins/repositories/index.html b/src/controllers/dashboard/plugins/repositories/index.html index ff3406fb95..cea20d5a4a 100644 --- a/src/controllers/dashboard/plugins/repositories/index.html +++ b/src/controllers/dashboard/plugins/repositories/index.html @@ -3,7 +3,7 @@

${TabRepositories}

-
diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 3087cdd927..c4c0885375 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -105,7 +105,7 @@ export default function(view, params) { }); view.querySelector('.btnNewRepository').addEventListener('click', () => { - let dialog = dialogHelper.createDialog({ + const dialog = dialogHelper.createDialog({ scrollY: false, size: 'large', modal: false, @@ -127,7 +127,7 @@ export default function(view, params) { html += ``; html += `
${globalize.translate('LabelRepositoryUrlHelp')}
`; html += '
'; - html += ``; + html += ``; html += '
'; html += ''; diff --git a/src/scheduledtask.html b/src/controllers/dashboard/scheduledtasks/scheduledtask.html similarity index 99% rename from src/scheduledtask.html rename to src/controllers/dashboard/scheduledtasks/scheduledtask.html index c7e82594ce..4dab484905 100644 --- a/src/scheduledtask.html +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.html @@ -73,7 +73,7 @@
diff --git a/src/useredit.html b/src/controllers/dashboard/users/useredit.html similarity index 99% rename from src/useredit.html rename to src/controllers/dashboard/users/useredit.html index c3a613bed4..a097144cc3 100644 --- a/src/useredit.html +++ b/src/controllers/dashboard/users/useredit.html @@ -192,7 +192,7 @@
diff --git a/src/usernew.html b/src/controllers/dashboard/users/usernew.html similarity index 98% rename from src/usernew.html rename to src/controllers/dashboard/users/usernew.html index fac036aa8d..26142c9ca2 100644 --- a/src/usernew.html +++ b/src/controllers/dashboard/users/usernew.html @@ -49,7 +49,7 @@
@@ -40,7 +40,7 @@

${HeaderAccessSchedule}

-
@@ -51,7 +51,7 @@
diff --git a/src/userpassword.html b/src/controllers/dashboard/users/userpassword.html similarity index 97% rename from src/userpassword.html rename to src/controllers/dashboard/users/userpassword.html index 119a0212de..51df2e2cb0 100644 --- a/src/userpassword.html +++ b/src/controllers/dashboard/users/userpassword.html @@ -29,7 +29,7 @@

- + @@ -58,7 +58,7 @@

- +
diff --git a/src/livetvstatus.html b/src/controllers/livetvstatus.html similarity index 97% rename from src/livetvstatus.html rename to src/controllers/livetvstatus.html index 3aa27637d0..1a894bff94 100644 --- a/src/livetvstatus.html +++ b/src/controllers/livetvstatus.html @@ -7,7 +7,7 @@

${HeaderTunerDevices}

- ${Help} @@ -20,7 +20,7 @@

${HeaderGuideProviders}

-
diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index f37760808a..7555b04b99 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -15,12 +15,10 @@ import 'emby-button'; const enableFocusTransform = !browser.slow && !browser.edge; function getDeviceHtml(device) { - let padderClass; + const padderClass = 'cardPadder-backdrop'; + let cssClass = 'card scalableCard backdropCard backdropCard-scalable'; + const cardBoxCssClass = 'cardBox visualCardBox'; let html = ''; - let cssClass = 'card scalableCard'; - let cardBoxCssClass = 'cardBox visualCardBox'; - cssClass += ' backdropCard backdropCard-scalable'; - padderClass = 'cardPadder-backdrop'; // TODO move card creation code to Card component diff --git a/src/livetvtuner.html b/src/controllers/livetvtuner.html similarity index 98% rename from src/livetvtuner.html rename to src/controllers/livetvtuner.html index f45a3a1d04..eab958c1c0 100644 --- a/src/livetvtuner.html +++ b/src/controllers/livetvtuner.html @@ -67,7 +67,7 @@
@@ -85,7 +85,7 @@ - @@ -132,7 +132,7 @@ - - -
diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index a8bd0e01f3..73540cd636 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -172,9 +172,7 @@ import 'css!assets/css/videoosd'; } setTitle(displayItem, parentName); - let titleElement; - const osdTitle = view.querySelector('.osdTitle'); - titleElement = osdTitle; + const titleElement = view.querySelector('.osdTitle'); let displayName = itemHelper.getDisplayName(displayItem, { includeParentInfo: displayItem.Type !== 'Program', includeIndexNumber: displayItem.Type !== 'Program' @@ -1619,7 +1617,7 @@ import 'css!assets/css/videoosd'; const item = currentItem; if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) { - let html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks); + const html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks); if (html) { return html; diff --git a/src/search.html b/src/controllers/search.html similarity index 70% rename from src/search.html rename to src/controllers/search.html index acf65e7317..b0277aa7e0 100644 --- a/src/search.html +++ b/src/controllers/search.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/tv.html b/src/controllers/shows/tvrecommended.html similarity index 100% rename from src/tv.html rename to src/controllers/shows/tvrecommended.html diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 585449d0c0..c519e0ac58 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -19,7 +19,7 @@ import 'emby-button'; return [{ name: globalize.translate('TabShows') }, { - name: globalize.translate('TabSuggestions') + name: globalize.translate('Suggestions') }, { name: globalize.translate('TabLatest') }, { diff --git a/src/controllers/user/display/index.html b/src/controllers/user/display/index.html index bee49754af..5482b62fe8 100644 --- a/src/controllers/user/display/index.html +++ b/src/controllers/user/display/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/user/menu/index.html b/src/controllers/user/menu/index.html index 2c3ca0edd9..851a817203 100644 --- a/src/controllers/user/menu/index.html +++ b/src/controllers/user/menu/index.html @@ -16,7 +16,7 @@
-
${HeaderDisplay}
+
${Display}
diff --git a/src/controllers/user/profile/index.html b/src/controllers/user/profile/index.html index 3765ac5e75..59eb7d416f 100644 --- a/src/controllers/user/profile/index.html +++ b/src/controllers/user/profile/index.html @@ -32,7 +32,7 @@
+

diff --git a/src/elements/emby-checkbox/emby-checkbox.js b/src/elements/emby-checkbox/emby-checkbox.js index d3f24d6f82..84961848c6 100644 --- a/src/elements/emby-checkbox/emby-checkbox.js +++ b/src/elements/emby-checkbox/emby-checkbox.js @@ -5,7 +5,7 @@ import 'webcomponents'; /* eslint-disable indent */ - let EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); + const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); function onKeyDown(e) { // Don't submit form on enter @@ -26,7 +26,7 @@ import 'webcomponents'; const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; function forceRefresh(loading) { - let elem = this.parentNode; + const elem = this.parentNode; elem.style.webkitAnimationName = 'repaintChrome'; elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); diff --git a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js index 67eacf3db9..51f3fc5be9 100644 --- a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js +++ b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js @@ -40,7 +40,7 @@ import 'webcomponents'; } } - let EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); EmbyItemRefreshIndicatorPrototype.createdCallback = function () { // base method diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 691552c074..ef30d1e663 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -18,7 +18,7 @@ import 'webcomponents'; function onClick(e) { const itemsContainer = this; - let multiSelect = itemsContainer.multiSelect; + const multiSelect = itemsContainer.multiSelect; if (multiSelect) { if (multiSelect.onContainerClick.call(itemsContainer, e) === false) { @@ -164,7 +164,7 @@ import 'webcomponents'; } function getEventsToMonitor(itemsContainer) { - let monitor = itemsContainer.getAttribute('data-monitor'); + const monitor = itemsContainer.getAttribute('data-monitor'); if (monitor) { return monitor.split(','); } @@ -356,7 +356,7 @@ import 'webcomponents'; ItemsContainerPrototype.resume = function (options) { this.paused = false; - let refreshIntervalEndTime = this.refreshIntervalEndTime; + const refreshIntervalEndTime = this.refreshIntervalEndTime; if (refreshIntervalEndTime) { const remainingMs = refreshIntervalEndTime - new Date().getTime(); if (remainingMs > 0 && !this.needsRefresh) { @@ -395,7 +395,7 @@ import 'webcomponents'; return; } - let timeout = this.refreshTimeout; + const timeout = this.refreshTimeout; if (timeout) { clearTimeout(timeout); } @@ -434,7 +434,7 @@ import 'webcomponents'; function onDataFetched(result) { const items = result.Items || result; - let parentContainer = this.parentContainer; + const parentContainer = this.parentContainer; if (parentContainer) { if (items.length) { parentContainer.classList.remove('hide'); diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js index 7b5c344095..322b7b3722 100644 --- a/src/elements/emby-playstatebutton/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -75,7 +75,7 @@ import EmbyButtonPrototype from 'emby-button'; button.title = globalize.translate('Played'); } - let text = button.querySelector('.button-text'); + const text = button.querySelector('.button-text'); if (text) { text.innerHTML = button.title; } diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js index 54fcb1999e..e232bbcde0 100644 --- a/src/elements/emby-progressbar/emby-progressbar.js +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -1,6 +1,6 @@ /* eslint-disable indent */ - let ProgressBarPrototype = Object.create(HTMLDivElement.prototype); + const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); function onAutoTimeProgress() { const start = parseInt(this.getAttribute('data-starttime')); diff --git a/src/elements/emby-progressring/emby-progressring.js b/src/elements/emby-progressring/emby-progressring.js index 10db8b9a27..929b80a573 100644 --- a/src/elements/emby-progressring/emby-progressring.js +++ b/src/elements/emby-progressring/emby-progressring.js @@ -3,7 +3,7 @@ import 'webcomponents'; /* eslint-disable indent */ - let EmbyProgressRing = Object.create(HTMLDivElement.prototype); + const EmbyProgressRing = Object.create(HTMLDivElement.prototype); EmbyProgressRing.createdCallback = function () { this.classList.add('progressring'); @@ -79,7 +79,7 @@ import 'webcomponents'; }; EmbyProgressRing.detachedCallback = function () { - let observer = this.observer; + const observer = this.observer; if (observer) { // later, you can stop observing diff --git a/src/elements/emby-radio/emby-radio.js b/src/elements/emby-radio/emby-radio.js index b31d436444..6fd2529085 100644 --- a/src/elements/emby-radio/emby-radio.js +++ b/src/elements/emby-radio/emby-radio.js @@ -4,7 +4,7 @@ import 'webcomponents'; /* eslint-disable indent */ - let EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); + const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); function onKeyDown(e) { // Don't submit form on enter @@ -35,7 +35,7 @@ import 'webcomponents'; this.classList.add('mdl-radio__button'); - let labelElement = this.parentNode; + const labelElement = this.parentNode; labelElement.classList.add('mdl-radio'); labelElement.classList.add('mdl-js-radio'); labelElement.classList.add('mdl-js-ripple-effect'); @@ -43,7 +43,7 @@ import 'webcomponents'; labelElement.classList.add('show-focus'); } - let labelTextElement = labelElement.querySelector('span'); + const labelTextElement = labelElement.querySelector('span'); labelTextElement.classList.add('radioButtonLabel'); labelTextElement.classList.add('mdl-radio__label'); diff --git a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js index fcff587392..f7665c0618 100644 --- a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -118,7 +118,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); } function onScrollButtonClick(e) { - let scroller = this.parentNode.nextSibling; + const scroller = this.parentNode.nextSibling; const direction = this.getAttribute('data-direction'); const scrollSize = getScrollSize(scroller); @@ -161,7 +161,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); const parent = this.scroller; this.scroller = null; - let scrollHandler = this.scrollHandler; + const scrollHandler = this.scrollHandler; if (parent && scrollHandler) { parent.removeScrollEventListener(scrollHandler, { capture: false, diff --git a/src/elements/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js index fb903d839c..d7133e317a 100644 --- a/src/elements/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -9,7 +9,7 @@ import 'css!./emby-scroller'; /* eslint-disable indent */ - let ScrollerPrototype = Object.create(HTMLDivElement.prototype); + const ScrollerPrototype = Object.create(HTMLDivElement.prototype); ScrollerPrototype.createdCallback = function () { this.classList.add('emby-scroller'); diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index b39e24f5e4..40d2e69bbb 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -8,7 +8,7 @@ import 'emby-input'; /* eslint-disable indent */ - let EmbySliderPrototype = Object.create(HTMLInputElement.prototype); + const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); let supportsValueSetOverride = false; @@ -94,7 +94,7 @@ import 'emby-input'; // Keep only one per slider frame request cancelAnimationFrame(range.updateValuesFrame); range.updateValuesFrame = requestAnimationFrame(function () { - let backgroundLower = range.backgroundLower; + const backgroundLower = range.backgroundLower; if (backgroundLower) { let fraction = (value - range.min) / (range.max - range.min); diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index 320a14e2cb..db7ad56f5d 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -8,7 +8,7 @@ import 'scrollStyles'; /* eslint-disable indent */ - let EmbyTabs = Object.create(HTMLDivElement.prototype); + const EmbyTabs = Object.create(HTMLDivElement.prototype); const buttonClass = 'emby-tab-button'; const activeButtonClass = buttonClass + '-active'; @@ -21,7 +21,7 @@ import 'scrollStyles'; } function removeActivePanelClass(tabs, index) { - let tabPanel = getTabPanel(tabs, index); + const tabPanel = getTabPanel(tabs, index); if (tabPanel) { tabPanel.classList.remove('is-active'); } @@ -52,7 +52,7 @@ import 'scrollStyles'; removeActivePanelClass(tabs, previousIndex); } - let newPanel = getTabPanel(tabs, index); + const newPanel = getTabPanel(tabs, index); if (newPanel) { // animate new panel ? @@ -225,7 +225,7 @@ import 'scrollStyles'; } })); - let currentTabButton = tabButtons[current]; + const currentTabButton = tabButtons[current]; setActiveTabButton(tabs, tabButtons[selected], currentTabButton, false); if (current !== selected && currentTabButton) { diff --git a/src/index.html b/src/index.html index 797fce8a94..c689a42f30 100644 --- a/src/index.html +++ b/src/index.html @@ -137,8 +137,7 @@ } @media screen - and (min-device-width: 992px) - and (-webkit-min-device-pixel-ratio: 1) { + and (min-device-width: 992px) { .splashLogo { background-image: url(assets/img/banner-light.png); } diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index 557b31e0f4..5c24ec63d0 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -1,134 +1,128 @@ -define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) { - "use strict"; +import events from 'events'; +import playbackManager from 'playbackManager'; +import pluginManager from 'pluginManager'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import * as userSettings from 'userSettings'; - playbackManager = playbackManager.default || playbackManager; +function getMinIdleTime() { + // Returns the minimum amount of idle time required before the screen saver can be displayed + //time units used Millisecond + return 180000; +} - function getMinIdleTime() { - // Returns the minimum amount of idle time required before the screen saver can be displayed - //time units used Millisecond - return 180000; +let lastFunctionalEvent = 0; + +function getFunctionalEventIdleTime() { + return new Date().getTime() - lastFunctionalEvent; +} + +events.on(playbackManager, 'playbackstop', function (e, stopInfo) { + const state = stopInfo.state; + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { + lastFunctionalEvent = new Date().getTime(); } - - var lastFunctionalEvent = 0; - - function getFunctionalEventIdleTime() { - return new Date().getTime() - lastFunctionalEvent; - } - - events.on(playbackManager, "playbackstop", function (e, stopInfo) { - var state = stopInfo.state; - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") { - lastFunctionalEvent = new Date().getTime(); - } - }); - - function getScreensaverPlugin(isLoggedIn) { - - var option; - try { - option = userSettings.get("screensaver", false); - } catch (err) { - option = isLoggedIn ? "backdropscreensaver" : "logoscreensaver"; - } - - var plugins = pluginManager.ofType("screensaver"); - - for (var i = 0, length = plugins.length; i < length; i++) { - var plugin = plugins[i]; - - if (plugin.id === option) { - return plugin; - } - } - - return null; - } - - function ScreenSaverManager() { - - var self = this; - var activeScreenSaver; - - function showScreenSaver(screensaver) { - - if (activeScreenSaver) { - throw new Error("An existing screensaver is already active."); - } - - console.debug("Showing screensaver " + screensaver.name); - - screensaver.show(); - activeScreenSaver = screensaver; - - if (screensaver.hideOnClick !== false) { - window.addEventListener("click", hide, true); - } - if (screensaver.hideOnMouse !== false) { - window.addEventListener("mousemove", hide, true); - } - if (screensaver.hideOnKey !== false) { - window.addEventListener("keydown", hide, true); - } - } - - function hide() { - if (activeScreenSaver) { - console.debug("Hiding screensaver"); - activeScreenSaver.hide(); - activeScreenSaver = null; - } - - window.removeEventListener("click", hide, true); - window.removeEventListener("mousemove", hide, true); - window.removeEventListener("keydown", hide, true); - } - - self.isShowing = function () { - return activeScreenSaver != null; - }; - - self.show = function () { - var isLoggedIn; - var apiClient = connectionManager.currentApiClient(); - - if (apiClient && apiClient.isLoggedIn()) { - isLoggedIn = true; - } - - var screensaver = getScreensaverPlugin(isLoggedIn); - - if (screensaver) { - showScreenSaver(screensaver); - } - }; - - self.hide = function () { - hide(); - }; - - function onInterval() { - - if (self.isShowing()) { - return; - } - - if (inputManager.idleTime() < getMinIdleTime()) { - return; - } - - if (getFunctionalEventIdleTime < getMinIdleTime()) { - return; - } - - if (playbackManager.isPlayingVideo()) { - return; - } - - self.show(); - } - - setInterval(onInterval, 10000); - } - - return new ScreenSaverManager(); }); + +function getScreensaverPlugin(isLoggedIn) { + let option; + try { + option = userSettings.get('screensaver', false); + } catch (err) { + option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver'; + } + + const plugins = pluginManager.ofType('screensaver'); + + for (const plugin of plugins) { + if (plugin.id === option) { + return plugin; + } + } + + return null; +} + +function ScreenSaverManager() { + let activeScreenSaver; + + function showScreenSaver(screensaver) { + if (activeScreenSaver) { + throw new Error('An existing screensaver is already active.'); + } + + console.debug('Showing screensaver ' + screensaver.name); + + screensaver.show(); + activeScreenSaver = screensaver; + + if (screensaver.hideOnClick !== false) { + window.addEventListener('click', hide, true); + } + if (screensaver.hideOnMouse !== false) { + window.addEventListener('mousemove', hide, true); + } + if (screensaver.hideOnKey !== false) { + window.addEventListener('keydown', hide, true); + } + } + + function hide() { + if (activeScreenSaver) { + console.debug('Hiding screensaver'); + activeScreenSaver.hide(); + activeScreenSaver = null; + } + + window.removeEventListener('click', hide, true); + window.removeEventListener('mousemove', hide, true); + window.removeEventListener('keydown', hide, true); + } + + this.isShowing = () => { + return activeScreenSaver != null; + }; + + this.show = function () { + let isLoggedIn; + const apiClient = connectionManager.currentApiClient(); + + if (apiClient && apiClient.isLoggedIn()) { + isLoggedIn = true; + } + + const screensaver = getScreensaverPlugin(isLoggedIn); + + if (screensaver) { + showScreenSaver(screensaver); + } + }; + + this.hide = function () { + hide(); + }; + + const onInterval = () => { + if (this.isShowing()) { + return; + } + + if (inputManager.idleTime() < getMinIdleTime()) { + return; + } + + if (getFunctionalEventIdleTime < getMinIdleTime()) { + return; + } + + if (playbackManager.isPlayingVideo()) { + return; + } + + this.show(); + }; + + setInterval(onInterval, 10000); +} + +export default new ScreenSaverManager; diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index c167046cb9..415bd7b958 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -27,16 +27,16 @@ export class BookPlayer { this._loaded = false; loading.show(); - let elem = this.createMediaElement(); + const elem = this.createMediaElement(); return this.setCurrentSrc(elem, options); } stop() { this.unbindEvents(); - let elem = this._mediaElement; - let tocElement = this._tocElement; - let rendition = this._rendition; + const elem = this._mediaElement; + const tocElement = this._tocElement; + const rendition = this._rendition; if (elem) { dialogHelper.close(elem); @@ -93,11 +93,11 @@ export class BookPlayer { } onWindowKeyUp(e) { - let key = keyboardnavigation.getKeyName(e); + const key = keyboardnavigation.getKeyName(e); // TODO: depending on the event this can be the document or the rendition itself - let rendition = this._rendition || this; - let book = rendition.book; + const rendition = this._rendition || this; + const book = rendition.book; if (this._loaded === false) return; switch (key) { @@ -125,8 +125,8 @@ export class BookPlayer { onTouchStart(e) { // TODO: depending on the event this can be the document or the rendition itself - let rendition = this._rendition || this; - let book = rendition.book; + const rendition = this._rendition || this; + const book = rendition.book; // check that the event is from the book or the document if (!book || this._loaded === false) return; @@ -134,7 +134,8 @@ export class BookPlayer { // epubjs stores pages off the screen or something for preloading // get the modulus of the touch event to account for the increased width if (!e.touches || e.touches.length === 0) return; - let touch = e.touches[0].clientX % dom.getWindowSize().innerWidth; + + const touch = e.touches[0].clientX % dom.getWindowSize().innerWidth; if (touch < dom.getWindowSize().innerWidth / 2) { book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); } else { @@ -147,7 +148,7 @@ export class BookPlayer { } bindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); @@ -166,7 +167,7 @@ export class BookPlayer { } unbindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed); @@ -231,7 +232,7 @@ export class BookPlayer { } setCurrentSrc(elem, options) { - let item = options.items[0]; + const item = options.items[0]; this._currentItem = item; this.streamInfo = { started: true, @@ -241,25 +242,25 @@ export class BookPlayer { } }; - let serverId = item.ServerId; - let apiClient = connectionManager.getApiClient(serverId); + const serverId = item.ServerId; + const apiClient = connectionManager.getApiClient(serverId); return new Promise((resolve, reject) => { import('epubjs').then(({default: epubjs}) => { - let downloadHref = apiClient.getItemDownloadUrl(item.Id); - let book = epubjs(downloadHref, {openAs: 'epub'}); - let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + const downloadHref = apiClient.getItemDownloadUrl(item.Id); + const book = epubjs(downloadHref, {openAs: 'epub'}); + const rendition = book.renderTo(elem, {width: '100%', height: '97%'}); this._currentSrc = downloadHref; this._rendition = rendition; - let cancellationToken = { + const cancellationToken = { shouldCancel: false }; this._cancellationToken = cancellationToken; return rendition.display().then(() => { - let epubElem = document.querySelector('.epub-container'); + const epubElem = document.querySelector('.epub-container'); epubElem.style.display = 'none'; this.bindEvents(); diff --git a/src/plugins/bookPlayer/tableOfContents.js b/src/plugins/bookPlayer/tableOfContents.js index 23e288aff5..a1c5d8f220 100644 --- a/src/plugins/bookPlayer/tableOfContents.js +++ b/src/plugins/bookPlayer/tableOfContents.js @@ -11,7 +11,7 @@ export default class TableOfContents { } destroy() { - let elem = this._elem; + const elem = this._elem; if (elem) { this.unbindEvents(); dialogHelper.close(elem); @@ -21,14 +21,14 @@ export default class TableOfContents { } bindEvents() { - let elem = this._elem; + const elem = this._elem; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); } unbindEvents() { - let elem = this._elem; + const elem = this._elem; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); @@ -39,10 +39,10 @@ export default class TableOfContents { } replaceLinks(contents, f) { - let links = contents.querySelectorAll('a[href]'); + const links = contents.querySelectorAll('a[href]'); links.forEach((link) => { - let href = link.getAttribute('href'); + const href = link.getAttribute('href'); link.onclick = () => { f(href); @@ -52,9 +52,9 @@ export default class TableOfContents { } createMediaElement() { - let rendition = this._rendition; + const rendition = this._rendition; - let elem = dialogHelper.createDialog({ + const elem = dialogHelper.createDialog({ size: 'small', autoFocus: false, removeOnClose: true @@ -69,7 +69,7 @@ export default class TableOfContents { rendition.book.navigation.forEach((chapter) => { tocHtml += '
  • '; // Remove '../' from href - let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + const link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; tocHtml += `${chapter.label}`; tocHtml += '
  • '; }); @@ -78,7 +78,7 @@ export default class TableOfContents { elem.innerHTML = tocHtml; this.replaceLinks(elem, (href) => { - let relative = rendition.book.path.relative(href); + const relative = rendition.book.path.relative(href); rendition.display(relative); this.destroy(); }); diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 22b49d6faf..b7e6d05969 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -1,66 +1,71 @@ -define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', 'globalize', 'events', 'require', 'castSenderApiLoader'], function (appSettings, userSettings, playbackManager, connectionManager, globalize, events, require, castSenderApiLoader) { - 'use strict'; +import appSettings from 'appSettings'; +import * as userSettings from 'userSettings'; +import playbackManager from 'playbackManager'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; +import events from 'events'; +import castSenderApiLoader from 'castSenderApiLoader'; - playbackManager = playbackManager.default || playbackManager; +// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js - var currentResolve; - var currentReject; +let currentResolve; +let currentReject; - var PlayerName = 'Google Cast'; +const PlayerName = 'Google Cast'; - function sendConnectionResult(isOk) { - var resolve = currentResolve; - var reject = currentReject; +function sendConnectionResult(isOk) { + const resolve = currentResolve; + const reject = currentReject; - currentResolve = null; - currentReject = null; + currentResolve = null; + currentReject = null; - if (isOk) { - if (resolve) { - resolve(); - } + if (isOk) { + if (resolve) { + resolve(); + } + } else { + if (reject) { + reject(); } else { - if (reject) { - reject(); - } else { - playbackManager.removeActivePlayer(PlayerName); - } + playbackManager.removeActivePlayer(PlayerName); } } +} - /** - * Constants of states for Chromecast device - **/ - var DEVICE_STATE = { - 'IDLE': 0, - 'ACTIVE': 1, - 'WARNING': 2, - 'ERROR': 3 - }; +/** + * Constants of states for Chromecast device + **/ +const DEVICE_STATE = { + 'IDLE': 0, + 'ACTIVE': 1, + 'WARNING': 2, + 'ERROR': 3 +}; - /** - * Constants of states for CastPlayer - **/ - var PLAYER_STATE = { - 'IDLE': 'IDLE', - 'LOADING': 'LOADING', - 'LOADED': 'LOADED', - 'PLAYING': 'PLAYING', - 'PAUSED': 'PAUSED', - 'STOPPED': 'STOPPED', - 'SEEKING': 'SEEKING', - 'ERROR': 'ERROR' - }; +/** + * Constants of states for CastPlayer + **/ +const PLAYER_STATE = { + 'IDLE': 'IDLE', + 'LOADING': 'LOADING', + 'LOADED': 'LOADED', + 'PLAYING': 'PLAYING', + 'PAUSED': 'PAUSED', + 'STOPPED': 'STOPPED', + 'SEEKING': 'SEEKING', + 'ERROR': 'ERROR' +}; - // production version registered with google - // replace this value if you want to test changes on another instance - var applicationStable = 'F007D354'; - var applicationUnstable = '6F511C87'; +// production version registered with google +// replace this value if you want to test changes on another instance +const applicationStable = 'F007D354'; +const applicationUnstable = '6F511C87'; - var messageNamespace = 'urn:x-cast:com.connectsdk'; +const messageNamespace = 'urn:x-cast:com.connectsdk'; - var CastPlayer = function () { +class CastPlayer { + constructor() { /* device variables */ // @type {DEVICE_STATE} A state for device this.deviceState = DEVICE_STATE.IDLE; @@ -81,7 +86,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this); this.initializeCastPlayer(); - }; + } /** * Initialize Cast media player @@ -89,8 +94,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * invoked once the API has finished initialization. The sessionListener and * receiverListener may be invoked at any time afterwards, and possibly more than once. */ - CastPlayer.prototype.initializeCastPlayer = function () { - var chrome = window.chrome; + initializeCastPlayer() { + const chrome = window.chrome; if (!chrome) { return; } @@ -100,35 +105,35 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return; } - var applicationID = applicationStable; + let applicationID = applicationStable; if (userSettings.chromecastVersion() === 'unstable') { applicationID = applicationUnstable; } // request session - var sessionRequest = new chrome.cast.SessionRequest(applicationID); - var apiConfig = new chrome.cast.ApiConfig(sessionRequest, + const sessionRequest = new chrome.cast.SessionRequest(applicationID); + const apiConfig = new chrome.cast.ApiConfig(sessionRequest, this.sessionListener.bind(this), this.receiverListener.bind(this)); console.debug('chromecast.initialize'); chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); - }; + } /** * Callback function for init success */ - CastPlayer.prototype.onInitSuccess = function () { + onInitSuccess() { this.isInitialized = true; console.debug('chromecast init success'); - }; + } /** * Generic error callback function */ - CastPlayer.prototype.onError = function () { + onError() { console.debug('chromecast error'); - }; + } /** * @param {!Object} e A new session @@ -137,7 +142,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' * join existing session and occur in Cast mode and media * status gets synced up with current media of the session */ - CastPlayer.prototype.sessionListener = function (e) { + sessionListener(e) { this.session = e; if (this.session) { if (this.session.media[0]) { @@ -146,24 +151,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.onSessionConnected(e); } - }; - - function alertText(text, title) { - require(['alert'], function (alert) { - alert.default({ - text: text, - title: title - }); - }); } - CastPlayer.prototype.messageListener = function (namespace, message) { + messageListener(namespace, message) { if (typeof (message) === 'string') { message = JSON.parse(message); } if (message.type === 'playbackerror') { - var errorCode = message.data; + const errorCode = message.data; setTimeout(function () { alertText(globalize.translate('MessagePlaybackError' + errorCode), globalize.translate('HeaderPlaybackError')); }, 300); @@ -174,14 +170,14 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } else if (message.type) { events.trigger(this, message.type, [message.data]); } - }; + } /** * @param {string} e Receiver availability * This indicates availability of receivers but * does not provide a list of device IDs */ - CastPlayer.prototype.receiverListener = function (e) { + receiverListener(e) { if (e === 'available') { console.debug('chromecast receiver found'); this.hasReceivers = true; @@ -189,12 +185,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' console.debug('chromecast receiver list empty'); this.hasReceivers = false; } - }; + } /** * session update listener */ - CastPlayer.prototype.sessionUpdateListener = function (isAlive) { + sessionUpdateListener(isAlive) { if (isAlive) { console.debug('sessionUpdateListener: already alive'); } else { @@ -209,28 +205,28 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' sendConnectionResult(false); } - }; + } /** * Requests that a receiver application session be created or joined. By default, the SessionRequest * passed to the API at initialization time is used; this may be overridden by passing a different * session request in opt_sessionRequest. */ - CastPlayer.prototype.launchApp = function () { + launchApp() { console.debug('chromecast launching app...'); chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); - }; + } /** * Callback function for request session success * @param {Object} e A chrome.cast.Session object */ - CastPlayer.prototype.onRequestSessionSuccess = function (e) { + onRequestSessionSuccess(e) { console.debug('chromecast session success: ' + e.sessionId); this.onSessionConnected(e); - }; + } - CastPlayer.prototype.onSessionConnected = function (session) { + onSessionConnected(session) { this.session = session; this.deviceState = DEVICE_STATE.ACTIVE; @@ -246,46 +242,38 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: {}, command: 'Identify' }); - }; - - function onVolumeUpKeyDown() { - playbackManager.volumeUp(); - } - - function onVolumeDownKeyDown() { - playbackManager.volumeDown(); } /** * session update listener */ - CastPlayer.prototype.sessionMediaListener = function (e) { + sessionMediaListener(e) { this.currentMediaSession = e; this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for launch error */ - CastPlayer.prototype.onLaunchError = function () { + onLaunchError() { console.debug('chromecast launch error'); this.deviceState = DEVICE_STATE.ERROR; sendConnectionResult(false); - }; + } /** * Stops the running receiver application associated with the session. */ - CastPlayer.prototype.stopApp = function () { + stopApp() { if (this.session) { this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'), this.errorHandler); } - }; + } /** * Callback function for stop app success */ - CastPlayer.prototype.onStopAppSuccess = function (message) { + onStopAppSuccess(message) { console.debug(message); this.deviceState = DEVICE_STATE.IDLE; @@ -294,13 +282,13 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' document.removeEventListener('volumedownbutton', onVolumeDownKeyDown, false); this.currentMediaSession = null; - }; + } /** * Loads media into a running receiver application * @param {Number} mediaIndex An index number to indicate current media content */ - CastPlayer.prototype.loadMedia = function (options, command) { + loadMedia(options, command) { if (!this.session) { console.debug('no session'); return Promise.reject(); @@ -322,20 +310,20 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' options: options, command: command }); - }; + } - CastPlayer.prototype.sendMessage = function (message) { - var player = this; + sendMessage(message) { + const player = this; - var receiverName = null; + let receiverName = null; - var session = player.session; + const session = player.session; if (session && session.receiver && session.receiver.friendlyName) { receiverName = session.receiver.friendlyName; } - var apiClient; + let apiClient; if (message.options && message.options.ServerId) { apiClient = connectionManager.getApiClient(message.options.ServerId); } else if (message.options && message.options.items && message.options.items.length) { @@ -354,7 +342,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' receiverName: receiverName }); - var bitrateSetting = appSettings.maxChromecastBitrate(); + const bitrateSetting = appSettings.maxChromecastBitrate(); if (bitrateSetting) { message.maxBitrate = bitrateSetting; } @@ -365,31 +353,31 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return new Promise(function (resolve, reject) { - require(['./chromecastHelper'], function (chromecastHelper) { + import('./chromecastHelper').then(({ default: chromecastHelper }) => { chromecastHelper.getServerAddress(apiClient).then(function (serverAddress) { message.serverAddress = serverAddress; player.sendMessageInternal(message).then(resolve, reject); }, reject); }); }); - }; + } - CastPlayer.prototype.sendMessageInternal = function (message) { + sendMessageInternal(message) { message = JSON.stringify(message); this.session.sendMessage(messageNamespace, message, this.onPlayCommandSuccess.bind(this), this.errorHandler); return Promise.resolve(); - }; + } - CastPlayer.prototype.onPlayCommandSuccess = function () { + onPlayCommandSuccess() { console.debug('Message was sent to receiver ok.'); - }; + } /** * Callback function for loadMedia success * @param {Object} mediaSession A new media object. */ - CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { + onMediaDiscovered(how, mediaSession) { console.debug('chromecast new media session ID:' + mediaSession.mediaSessionId + ' (' + how + ')'); this.currentMediaSession = mediaSession; @@ -402,24 +390,24 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; + } /** * Callback function for media status update from receiver * @param {!Boolean} e true/false */ - CastPlayer.prototype.onMediaStatusUpdate = function (e) { + onMediaStatusUpdate(e) { console.debug('chromecast updating media: ' + e); if (e === false) { this.castPlayerState = PLAYER_STATE.IDLE; } - }; + } /** * Set media volume in Cast mode * @param {Boolean} mute A boolean */ - CastPlayer.prototype.setReceiverVolume = function (mute, vol) { + setReceiverVolume(mute, vol) { if (!this.currentMediaSession) { console.debug('this.currentMediaSession is null'); return; @@ -434,142 +422,161 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.mediaCommandSuccessCallback.bind(this), this.errorHandler); } - }; + } /** * Mute CC */ - CastPlayer.prototype.mute = function () { + mute() { this.setReceiverVolume(true); - }; + } /** * Callback function for media command success */ - CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { + mediaCommandSuccessCallback(info, e) { console.debug(info); - }; + } +} - function normalizeImages(state) { - if (state && state.NowPlayingItem) { - var item = state.NowPlayingItem; +function alertText(text, title) { + import('alert').then(({default: alert}) => { + alert({ + text: text, + title: title + }); + }); +} - if (!item.ImageTags || !item.ImageTags.Primary) { - if (item.PrimaryImageTag) { - item.ImageTags = item.ImageTags || {}; - item.ImageTags.Primary = item.PrimaryImageTag; - } - } - if (item.BackdropImageTag && item.BackdropItemId === item.Id) { - item.BackdropImageTags = [item.BackdropImageTag]; - } - if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { - item.ParentBackdropImageTags = [item.BackdropImageTag]; - item.ParentBackdropItemId = item.BackdropItemId; +function onVolumeUpKeyDown() { + playbackManager.volumeUp(); +} + +function onVolumeDownKeyDown() { + playbackManager.volumeDown(); +} + +function normalizeImages(state) { + if (state && state.NowPlayingItem) { + const item = state.NowPlayingItem; + + if (!item.ImageTags || !item.ImageTags.Primary) { + if (item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; } } + if (item.BackdropImageTag && item.BackdropItemId === item.Id) { + item.BackdropImageTags = [item.BackdropImageTag]; + } + if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { + item.ParentBackdropImageTags = [item.BackdropImageTag]; + item.ParentBackdropItemId = item.BackdropItemId; + } } +} - function getItemsForPlayback(apiClient, query) { - var userId = apiClient.getCurrentUserId(); +function getItemsForPlayback(apiClient, query) { + const userId = apiClient.getCurrentUserId(); - if (query.Ids && query.Ids.split(',').length === 1) { - return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { - return { - Items: [item], - TotalRecordCount: 1 - }; - }); + if (query.Ids && query.Ids.split(',').length === 1) { + return apiClient.getItem(userId, query.Ids.split(',')).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } else { + query.Limit = query.Limit || 100; + query.ExcludeLocationTypes = 'Virtual'; + query.EnableTotalRecordCount = false; + + return apiClient.getItems(userId, query); + } +} + +function bindEventForRelay(instance, eventName) { + events.on(instance._castPlayer, eventName, function (e, data) { + console.debug('cc: ' + eventName); + const state = instance.getPlayerStateInternal(data); + + events.trigger(instance, eventName, [state]); + }); +} + +function initializeChromecast() { + const instance = this; + instance._castPlayer = new CastPlayer(); + + // To allow the native android app to override + document.dispatchEvent(new CustomEvent('chromecastloaded', { + detail: { + player: instance + } + })); + + events.on(instance._castPlayer, 'connect', function (e) { + if (currentResolve) { + sendConnectionResult(true); } else { - query.Limit = query.Limit || 100; - query.ExcludeLocationTypes = 'Virtual'; - query.EnableTotalRecordCount = false; - - return apiClient.getItems(userId, query); + playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); } - } - function bindEventForRelay(instance, eventName) { - events.on(instance._castPlayer, eventName, function (e, data) { - console.debug('cc: ' + eventName); - var state = instance.getPlayerStateInternal(data); + console.debug('cc: connect'); + // Reset this so that statechange will fire + instance.lastPlayerData = null; + }); - events.trigger(instance, eventName, [state]); - }); - } + events.on(instance._castPlayer, 'playbackstart', function (e, data) { + console.debug('cc: playbackstart'); - function initializeChromecast() { - var instance = this; - instance._castPlayer = new CastPlayer(); + instance._castPlayer.initializeCastPlayer(); - // To allow the native android app to override - document.dispatchEvent(new CustomEvent('chromecastloaded', { - detail: { - player: instance - } - })); + const state = instance.getPlayerStateInternal(data); + events.trigger(instance, 'playbackstart', [state]); + }); - events.on(instance._castPlayer, 'connect', function (e) { - if (currentResolve) { - sendConnectionResult(true); - } else { - playbackManager.setActivePlayer(PlayerName, instance.getCurrentTargetInfo()); - } + events.on(instance._castPlayer, 'playbackstop', function (e, data) { + console.debug('cc: playbackstop'); + let state = instance.getPlayerStateInternal(data); - console.debug('cc: connect'); - // Reset this so that statechange will fire - instance.lastPlayerData = null; - }); + events.trigger(instance, 'playbackstop', [state]); - events.on(instance._castPlayer, 'playbackstart', function (e, data) { - console.debug('cc: playbackstart'); + state = instance.lastPlayerData.PlayState || {}; + const volume = state.VolumeLevel || 0.5; + const mute = state.IsMuted || false; - instance._castPlayer.initializeCastPlayer(); + // Reset this so the next query doesn't make it appear like content is playing. + instance.lastPlayerData = {}; + instance.lastPlayerData.PlayState = {}; + instance.lastPlayerData.PlayState.VolumeLevel = volume; + instance.lastPlayerData.PlayState.IsMuted = mute; + }); - var state = instance.getPlayerStateInternal(data); - events.trigger(instance, 'playbackstart', [state]); - }); + events.on(instance._castPlayer, 'playbackprogress', function (e, data) { + console.debug('cc: positionchange'); + const state = instance.getPlayerStateInternal(data); - events.on(instance._castPlayer, 'playbackstop', function (e, data) { - console.debug('cc: playbackstop'); - var state = instance.getPlayerStateInternal(data); + events.trigger(instance, 'timeupdate', [state]); + }); - events.trigger(instance, 'playbackstop', [state]); + bindEventForRelay(instance, 'timeupdate'); + bindEventForRelay(instance, 'pause'); + bindEventForRelay(instance, 'unpause'); + bindEventForRelay(instance, 'volumechange'); + bindEventForRelay(instance, 'repeatmodechange'); + bindEventForRelay(instance, 'shufflequeuemodechange'); - state = instance.lastPlayerData.PlayState || {}; - var volume = state.VolumeLevel || 0.5; - var mute = state.IsMuted || false; + events.on(instance._castPlayer, 'playstatechange', function (e, data) { + console.debug('cc: playstatechange'); + const state = instance.getPlayerStateInternal(data); - // Reset this so the next query doesn't make it appear like content is playing. - instance.lastPlayerData = {}; - instance.lastPlayerData.PlayState = {}; - instance.lastPlayerData.PlayState.VolumeLevel = volume; - instance.lastPlayerData.PlayState.IsMuted = mute; - }); + events.trigger(instance, 'pause', [state]); + }); +} - events.on(instance._castPlayer, 'playbackprogress', function (e, data) { - console.debug('cc: positionchange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'timeupdate', [state]); - }); - - bindEventForRelay(instance, 'timeupdate'); - bindEventForRelay(instance, 'pause'); - bindEventForRelay(instance, 'unpause'); - bindEventForRelay(instance, 'volumechange'); - bindEventForRelay(instance, 'repeatmodechange'); - bindEventForRelay(instance, 'shufflequeuemodechange'); - - events.on(instance._castPlayer, 'playstatechange', function (e, data) { - console.debug('cc: playstatechange'); - var state = instance.getPlayerStateInternal(data); - - events.trigger(instance, 'pause', [state]); - }); - } - - function ChromecastPlayer() { +class ChromecastPlayer { + constructor() { // playbackManager needs this this.name = PlayerName; this.type = 'mediaplayer'; @@ -577,11 +584,11 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' this.isLocalPlayer = false; this.lastPlayerData = {}; - new castSenderApiLoader.default().load().then(initializeChromecast.bind(this)); + new castSenderApiLoader().load().then(initializeChromecast.bind(this)); } - ChromecastPlayer.prototype.tryPair = function (target) { - var castPlayer = this._castPlayer; + tryPair(target) { + const castPlayer = this._castPlayer; if (castPlayer.deviceState !== DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { return new Promise(function (resolve, reject) { @@ -595,23 +602,23 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return Promise.reject(); } - }; + } - ChromecastPlayer.prototype.getTargets = function () { - var targets = []; + getTargets() { + const targets = []; if (this._castPlayer && this._castPlayer.hasReceivers) { targets.push(this.getCurrentTargetInfo()); } return Promise.resolve(targets); - }; + } // This is a privately used method - ChromecastPlayer.prototype.getCurrentTargetInfo = function () { - var appName = null; + getCurrentTargetInfo() { + let appName = null; - var castPlayer = this._castPlayer; + const castPlayer = this._castPlayer; if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { appName = castPlayer.session.receiver.friendlyName; @@ -642,10 +649,10 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' 'PlayTrailers' ] }; - }; + } - ChromecastPlayer.prototype.getPlayerStateInternal = function (data) { - var triggerStateChange = false; + getPlayerStateInternal(data) { + let triggerStateChange = false; if (data && !this.lastPlayerData) { triggerStateChange = true; } @@ -662,12 +669,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return data; - }; + } - ChromecastPlayer.prototype.playWithCommand = function (options, command) { + playWithCommand(options, command) { if (!options.items) { - var apiClient = connectionManager.getApiClient(options.serverId); - var instance = this; + const apiClient = connectionManager.getApiClient(options.serverId); + const instance = this; return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) { options.items = [item]; @@ -683,9 +690,9 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' } return this._castPlayer.loadMedia(options, command); - }; + } - ChromecastPlayer.prototype.seek = function (position) { + seek(position) { position = parseInt(position); position = position / 10000000; @@ -696,55 +703,55 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'Seek' }); - }; + } - ChromecastPlayer.prototype.setAudioStreamIndex = function (index) { + setAudioStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetAudioStreamIndex' }); - }; + } - ChromecastPlayer.prototype.setSubtitleStreamIndex = function (index) { + setSubtitleStreamIndex(index) { this._castPlayer.sendMessage({ options: { index: index }, command: 'SetSubtitleStreamIndex' }); - }; + } - ChromecastPlayer.prototype.setMaxStreamingBitrate = function (options) { + setMaxStreamingBitrate(options) { this._castPlayer.sendMessage({ options: options, command: 'SetMaxStreamingBitrate' }); - }; + } - ChromecastPlayer.prototype.isFullscreen = function () { - var state = this.lastPlayerData || {}; + isFullscreen() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsFullscreen; - }; + } - ChromecastPlayer.prototype.nextTrack = function () { + nextTrack() { this._castPlayer.sendMessage({ options: {}, command: 'NextTrack' }); - }; + } - ChromecastPlayer.prototype.previousTrack = function () { + previousTrack() { this._castPlayer.sendMessage({ options: {}, command: 'PreviousTrack' }); - }; + } - ChromecastPlayer.prototype.volumeDown = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeDown() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -752,20 +759,20 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.max(vol, 0); this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.endSession = function () { - var instance = this; + endSession() { + const instance = this; this.stop().then(function () { setTimeout(function () { instance._castPlayer.stopApp(); }, 1000); }); - }; + } - ChromecastPlayer.prototype.volumeUp = function () { - var vol = this._castPlayer.session.receiver.volume.level; + volumeUp() { + let vol = this._castPlayer.session.receiver.volume.level; if (vol == null) { vol = 0.5; } @@ -773,53 +780,53 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' vol = Math.min(vol, 1); this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.setVolume = function (vol) { + setVolume(vol) { vol = Math.min(vol, 100); vol = Math.max(vol, 0); vol = vol / 100; this._castPlayer.session.setReceiverVolumeLevel(vol); - }; + } - ChromecastPlayer.prototype.unpause = function () { + unpause() { this._castPlayer.sendMessage({ options: {}, command: 'Unpause' }); - }; + } - ChromecastPlayer.prototype.playPause = function () { + playPause() { this._castPlayer.sendMessage({ options: {}, command: 'PlayPause' }); - }; + } - ChromecastPlayer.prototype.pause = function () { + pause() { this._castPlayer.sendMessage({ options: {}, command: 'Pause' }); - }; + } - ChromecastPlayer.prototype.stop = function () { + stop() { return this._castPlayer.sendMessage({ options: {}, command: 'Stop' }); - }; + } - ChromecastPlayer.prototype.displayContent = function (options) { + displayContent(options) { this._castPlayer.sendMessage({ options: options, command: 'DisplayContent' }); - }; + } - ChromecastPlayer.prototype.setMute = function (isMuted) { - var castPlayer = this._castPlayer; + setMute(isMuted) { + const castPlayer = this._castPlayer; if (isMuted) { castPlayer.sendMessage({ @@ -832,21 +839,21 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' command: 'Unmute' }); } - }; + } - ChromecastPlayer.prototype.getRepeatMode = function () { - var state = this.lastPlayerData || {}; + getRepeatMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.RepeatMode; - }; + } - ChromecastPlayer.prototype.getQueueShuffleMode = function () { - var state = this.lastPlayerData || {}; + getQueueShuffleMode() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.ShuffleMode; - }; + } - ChromecastPlayer.prototype.playTrailers = function (item) { + playTrailers(item) { this._castPlayer.sendMessage({ options: { ItemId: item.Id, @@ -854,177 +861,173 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' }, command: 'PlayTrailers' }); - }; + } - ChromecastPlayer.prototype.setRepeatMode = function (mode) { + setRepeatMode(mode) { this._castPlayer.sendMessage({ options: { RepeatMode: mode }, command: 'SetRepeatMode' }); - }; + } - ChromecastPlayer.prototype.setQueueShuffleMode = function (value) { + setQueueShuffleMode(value) { this._castPlayer.sendMessage({ options: { ShuffleMode: value }, command: 'SetShuffleQueue' }); - }; + } - ChromecastPlayer.prototype.toggleMute = function () { + toggleMute() { this._castPlayer.sendMessage({ options: {}, command: 'ToggleMute' }); - }; + } - ChromecastPlayer.prototype.audioTracks = function () { - var state = this.lastPlayerData || {}; + audioTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Audio'; }); - }; + } - ChromecastPlayer.prototype.getAudioStreamIndex = function () { - var state = this.lastPlayerData || {}; + getAudioStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.AudioStreamIndex; - }; + } - ChromecastPlayer.prototype.subtitleTracks = function () { - var state = this.lastPlayerData || {}; + subtitleTracks() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; - var streams = state.MediaStreams || []; + const streams = state.MediaStreams || []; return streams.filter(function (s) { return s.Type === 'Subtitle'; }); - }; + } - ChromecastPlayer.prototype.getSubtitleStreamIndex = function () { - var state = this.lastPlayerData || {}; + getSubtitleStreamIndex() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.SubtitleStreamIndex; - }; + } - ChromecastPlayer.prototype.getMaxStreamingBitrate = function () { - var state = this.lastPlayerData || {}; + getMaxStreamingBitrate() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.MaxStreamingBitrate; - }; + } - ChromecastPlayer.prototype.getVolume = function () { - var state = this.lastPlayerData || {}; + getVolume() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.VolumeLevel == null ? 100 : state.VolumeLevel; - }; + } - ChromecastPlayer.prototype.isPlaying = function () { - var state = this.lastPlayerData || {}; + isPlaying() { + const state = this.lastPlayerData || {}; return state.NowPlayingItem != null; - }; + } - ChromecastPlayer.prototype.isPlayingVideo = function () { - var state = this.lastPlayerData || {}; + isPlayingVideo() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Video'; - }; + } - ChromecastPlayer.prototype.isPlayingAudio = function () { - var state = this.lastPlayerData || {}; + isPlayingAudio() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.MediaType === 'Audio'; - }; + } - ChromecastPlayer.prototype.currentTime = function (val) { + currentTime(val) { if (val != null) { return this.seek(val); } - var state = this.lastPlayerData || {}; + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.PositionTicks; - }; + } - ChromecastPlayer.prototype.duration = function () { - var state = this.lastPlayerData || {}; + duration() { + let state = this.lastPlayerData || {}; state = state.NowPlayingItem || {}; return state.RunTimeTicks; - }; + } - ChromecastPlayer.prototype.getBufferedRanges = function () { - var state = this.lastPlayerData || {}; + getBufferedRanges() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.BufferedRanges || []; - }; + } - ChromecastPlayer.prototype.paused = function () { - var state = this.lastPlayerData || {}; + paused() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsPaused; - }; + } - ChromecastPlayer.prototype.isMuted = function () { - var state = this.lastPlayerData || {}; + isMuted() { + let state = this.lastPlayerData || {}; state = state.PlayState || {}; return state.IsMuted; - }; + } - ChromecastPlayer.prototype.shuffle = function (item) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); + shuffle(item) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { instance.playWithCommand({ - items: [item] - }, 'Shuffle'); }); - }; + } - ChromecastPlayer.prototype.instantMix = function (item) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var userId = apiClient.getCurrentUserId(); + instantMix(item) { + const apiClient = connectionManager.getApiClient(item.ServerId); + const userId = apiClient.getCurrentUserId(); - var instance = this; + const instance = this; apiClient.getItem(userId, item.Id).then(function (item) { instance.playWithCommand({ - items: [item] - }, 'InstantMix'); }); - }; + } - ChromecastPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { mediaType = (mediaType || '').toLowerCase(); return mediaType === 'audio' || mediaType === 'video'; - }; + } - ChromecastPlayer.prototype.canQueueMediaType = function (mediaType) { + canQueueMediaType(mediaType) { return this.canPlayMediaType(mediaType); - }; + } - ChromecastPlayer.prototype.queue = function (options) { + queue(options) { this.playWithCommand(options, 'PlayLast'); - }; + } - ChromecastPlayer.prototype.queueNext = function (options) { + queueNext(options) { this.playWithCommand(options, 'PlayNext'); - }; + } - ChromecastPlayer.prototype.play = function (options) { + play(options) { if (options.items) { return this.playWithCommand(options, 'PlayNow'); } else { @@ -1032,50 +1035,48 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' throw new Error('serverId required!'); } - var instance = this; - var apiClient = connectionManager.getApiClient(options.serverId); + const instance = this; + const apiClient = connectionManager.getApiClient(options.serverId); return getItemsForPlayback(apiClient, { - Ids: options.ids.join(',') - }).then(function (result) { options.items = result.Items; return instance.playWithCommand(options, 'PlayNow'); }); } - }; + } - ChromecastPlayer.prototype.toggleFullscreen = function () { + toggleFullscreen() { // not supported - }; + } - ChromecastPlayer.prototype.beginPlayerUpdates = function () { + beginPlayerUpdates() { // Setup polling here - }; + } - ChromecastPlayer.prototype.endPlayerUpdates = function () { + endPlayerUpdates() { // Stop polling here - }; + } - ChromecastPlayer.prototype.getPlaylist = function () { + getPlaylist() { return Promise.resolve([]); - }; + } - ChromecastPlayer.prototype.getCurrentPlaylistItemId = function () { - }; + getCurrentPlaylistItemId() { + } - ChromecastPlayer.prototype.setCurrentPlaylistItem = function (playlistItemId) { + setCurrentPlaylistItem(playlistItemId) { return Promise.resolve(); - }; + } - ChromecastPlayer.prototype.removeFromPlaylist = function (playlistItemIds) { + removeFromPlaylist(playlistItemIds) { return Promise.resolve(); - }; + } - ChromecastPlayer.prototype.getPlayerState = function () { + getPlayerState() { return this.getPlayerStateInternal() || {}; - }; + } +} - return ChromecastPlayer; -}); +export default ChromecastPlayer; diff --git a/src/plugins/experimentalWarnings/plugin.js b/src/plugins/experimentalWarnings/plugin.js index c39612d45b..76b45fad4e 100644 --- a/src/plugins/experimentalWarnings/plugin.js +++ b/src/plugins/experimentalWarnings/plugin.js @@ -1,6 +1,8 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function (connectionManager, globalize, userSettings, appHost) { 'use strict'; + appHost = appHost.default || appHost; + // TODO: Replace with date-fns // https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php function getWeek(date) { diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 16fce8c9d1..acce15df88 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -1,91 +1,92 @@ -define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelper'], function (events, browser, require, appHost, appSettings, htmlMediaHelper) { - 'use strict'; +import events from 'events'; +import browser from 'browser'; +import appHost from 'apphost'; +import * as htmlMediaHelper from 'htmlMediaHelper'; - function getDefaultProfile() { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile'], function (profileBuilder) { - resolve(profileBuilder({})); - }); +function getDefaultProfile() { + return import('browserdeviceprofile').then(({ default: profileBuilder }) => { + return profileBuilder({}); + }); +} + +let fadeTimeout; +function fade(instance, elem, startingVolume) { + instance._isFadingOut = true; + + // Need to record the starting volume on each pass rather than querying elem.volume + // This is due to iOS safari not allowing volume changes and always returning the system volume value + const newVolume = Math.max(0, startingVolume - 0.15); + console.debug('fading volume to ' + newVolume); + elem.volume = newVolume; + + if (newVolume <= 0) { + instance._isFadingOut = false; + return Promise.resolve(); + } + + return new Promise(function (resolve, reject) { + cancelFadeTimeout(); + fadeTimeout = setTimeout(function () { + fade(instance, elem, newVolume).then(resolve, reject); + }, 100); + }); +} + +function cancelFadeTimeout() { + const timeout = fadeTimeout; + if (timeout) { + clearTimeout(timeout); + fadeTimeout = null; + } +} + +function supportsFade() { + if (browser.tv) { + // Not working on tizen. + // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive + return false; + } + + return true; +} + +function requireHlsPlayer(callback) { + import('hlsjs').then(({ default: hls }) => { + window.Hls = hls; + callback(); + }); +} + +function enableHlsPlayer(url, item, mediaSource, mediaType) { + if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { + return Promise.reject(); + } + + if (url.indexOf('.m3u8') !== -1) { + return Promise.resolve(); + } + + // issue head request to get content type + return new Promise(function (resolve, reject) { + import('fetchHelper').then((fetchHelper) => { + fetchHelper.ajax({ + url: url, + type: 'HEAD' + }).then(function (response) { + const contentType = (response.headers.get('Content-Type') || '').toLowerCase(); + if (contentType === 'application/x-mpegurl') { + resolve(); + } else { + reject(); + } + }, reject); }); - } + }); +} - var fadeTimeout; - function fade(instance, elem, startingVolume) { - instance._isFadingOut = true; - - // Need to record the starting volume on each pass rather than querying elem.volume - // This is due to iOS safari not allowing volume changes and always returning the system volume value - var newVolume = Math.max(0, startingVolume - 0.15); - console.debug('fading volume to ' + newVolume); - elem.volume = newVolume; - - if (newVolume <= 0) { - instance._isFadingOut = false; - return Promise.resolve(); - } - - return new Promise(function (resolve, reject) { - cancelFadeTimeout(); - fadeTimeout = setTimeout(function () { - fade(instance, elem, newVolume).then(resolve, reject); - }, 100); - }); - } - - function cancelFadeTimeout() { - var timeout = fadeTimeout; - if (timeout) { - clearTimeout(timeout); - fadeTimeout = null; - } - } - - function supportsFade() { - if (browser.tv) { - // Not working on tizen. - // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive - return false; - } - - return true; - } - - function requireHlsPlayer(callback) { - require(['hlsjs'], function (hls) { - window.Hls = hls; - callback(); - }); - } - - function enableHlsPlayer(url, item, mediaSource, mediaType) { - if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) { - return Promise.reject(); - } - - if (url.indexOf('.m3u8') !== -1) { - return Promise.resolve(); - } - - // issue head request to get content type - return new Promise(function (resolve, reject) { - require(['fetchHelper'], function (fetchHelper) { - fetchHelper.ajax({ - url: url, - type: 'HEAD' - }).then(function (response) { - var contentType = (response.headers.get('Content-Type') || '').toLowerCase(); - if (contentType === 'application/x-mpegurl') { - resolve(); - } else { - reject(); - } - }, reject); - }); - }); - } - - function HtmlAudioPlayer() { - var self = this; +class HtmlAudioPlayer { + constructor() { + const self = this; self.name = 'Html Audio Player'; self.type = 'mediaplayer'; @@ -99,7 +100,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._timeUpdated = false; self._currentTime = null; - var elem = createMediaElement(); + const elem = createMediaElement(); return setCurrentSrc(elem, options); }; @@ -109,11 +110,11 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp unBindEvents(elem); bindEvents(elem); - var val = options.url; + let val = options.url; console.debug('playing url: ' + val); // Convert to seconds - var seconds = (options.playerStartPositionTicks || 0) / 10000000; + const seconds = (options.playerStartPositionTicks || 0) / 10000000; if (seconds) { val += '#t=' + seconds; } @@ -122,7 +123,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self._currentPlayOptions = options; - var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); + const crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; } @@ -130,9 +131,9 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () { return new Promise(function (resolve, reject) { requireHlsPlayer(function () { - var hls = new Hls({ + const hls = new Hls({ manifestLoadingTimeOut: 20000, - xhrSetup: function(xhr, url) { + xhrSetup: function (xhr, url) { xhr.withCredentials = true; } }); @@ -183,8 +184,8 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp self.stop = function (destroyPlayer) { cancelFadeTimeout(); - var elem = self._mediaElement; - var src = self._currentSrc; + const elem = self._mediaElement; + const src = self._currentSrc; if (elem && src) { if (!destroyPlayer || !supportsFade()) { @@ -198,7 +199,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return Promise.resolve(); } - var originalVolume = elem.volume; + const originalVolume = elem.volume; return fade(self, elem, elem.volume).then(function () { elem.pause(); @@ -219,7 +220,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; function createMediaElement() { - var elem = self._mediaElement; + let elem = self._mediaElement; if (elem) { return elem; @@ -248,7 +249,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp function onTimeUpdate() { // Get the player position + the transcoding offset - var time = this.currentTime; + const time = this.currentTime; // Don't trigger events after user stop if (!self._isFadingOut) { @@ -287,11 +288,11 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } function onError() { - var errorCode = this.error ? (this.error.code || 0) : 0; - var errorMessage = this.error ? (this.error.message || '') : ''; + const errorCode = this.error ? (this.error.code || 0) : 0; + const errorMessage = this.error ? (this.error.message || '') : ''; console.error('media element error: ' + errorCode.toString() + ' ' + errorMessage); - var type; + let type; switch (errorCode) { case 1: @@ -325,59 +326,59 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp } } - HtmlAudioPlayer.prototype.currentSrc = function () { + currentSrc() { return this._currentSrc; - }; + } - HtmlAudioPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'audio'; - }; + } - HtmlAudioPlayer.prototype.getDeviceProfile = function (item) { + getDeviceProfile(item) { if (appHost.getDeviceProfile) { return appHost.getDeviceProfile(item); } return getDefaultProfile(); - }; + } // Save this for when playback stops, because querying the time at that point might return 0 - HtmlAudioPlayer.prototype.currentTime = function (val) { - var mediaElement = this._mediaElement; + currentTime(val) { + const mediaElement = this._mediaElement; if (mediaElement) { if (val != null) { mediaElement.currentTime = val / 1000; return; } - var currentTime = this._currentTime; + const currentTime = this._currentTime; if (currentTime) { return currentTime * 1000; } return (mediaElement.currentTime || 0) * 1000; } - }; + } - HtmlAudioPlayer.prototype.duration = function (val) { - var mediaElement = this._mediaElement; + duration(val) { + const mediaElement = this._mediaElement; if (mediaElement) { - var duration = mediaElement.duration; + const duration = mediaElement.duration; if (htmlMediaHelper.isValidDuration(duration)) { return duration * 1000; } } return null; - }; + } - HtmlAudioPlayer.prototype.seekable = function () { - var mediaElement = this._mediaElement; + seekable() { + const mediaElement = this._mediaElement; if (mediaElement) { - var seekable = mediaElement.seekable; + const seekable = mediaElement.seekable; if (seekable && seekable.length) { - var start = seekable.start(0); - var end = seekable.end(0); + let start = seekable.start(0); + let end = seekable.end(0); if (!htmlMediaHelper.isValidDuration(start)) { start = 0; @@ -391,124 +392,120 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return false; } - }; + } - HtmlAudioPlayer.prototype.getBufferedRanges = function () { - var mediaElement = this._mediaElement; + getBufferedRanges() { + const mediaElement = this._mediaElement; if (mediaElement) { return htmlMediaHelper.getBufferedRanges(this, mediaElement); } return []; - }; + } - HtmlAudioPlayer.prototype.pause = function () { - var mediaElement = this._mediaElement; + pause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.pause(); } - }; + } // This is a retry after error - HtmlAudioPlayer.prototype.resume = function () { - var mediaElement = this._mediaElement; + resume() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.unpause = function () { - var mediaElement = this._mediaElement; + unpause() { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.play(); } - }; + } - HtmlAudioPlayer.prototype.paused = function () { - var mediaElement = this._mediaElement; + paused() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.paused; } return false; - }; + } - HtmlAudioPlayer.prototype.setPlaybackRate = function (value) { - var mediaElement = this._mediaElement; + setPlaybackRate(value) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.playbackRate = value; } - }; + } - HtmlAudioPlayer.prototype.getPlaybackRate = function () { - var mediaElement = this._mediaElement; + getPlaybackRate() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.playbackRate; } return null; - }; + } - HtmlAudioPlayer.prototype.setVolume = function (val) { - var mediaElement = this._mediaElement; + setVolume(val) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.volume = val / 100; } - }; + } - HtmlAudioPlayer.prototype.getVolume = function () { - var mediaElement = this._mediaElement; + getVolume() { + const mediaElement = this._mediaElement; if (mediaElement) { return Math.min(Math.round(mediaElement.volume * 100), 100); } - }; + } - HtmlAudioPlayer.prototype.volumeUp = function () { + volumeUp() { this.setVolume(Math.min(this.getVolume() + 2, 100)); - }; + } - HtmlAudioPlayer.prototype.volumeDown = function () { + volumeDown() { this.setVolume(Math.max(this.getVolume() - 2, 0)); - }; + } - HtmlAudioPlayer.prototype.setMute = function (mute) { - var mediaElement = this._mediaElement; + setMute(mute) { + const mediaElement = this._mediaElement; if (mediaElement) { mediaElement.muted = mute; } - }; + } - HtmlAudioPlayer.prototype.isMuted = function () { - var mediaElement = this._mediaElement; + isMuted() { + const mediaElement = this._mediaElement; if (mediaElement) { return mediaElement.muted; } return false; - }; - - HtmlAudioPlayer.prototype.destroy = function () { - - }; - - var supportedFeatures; - - function getSupportedFeatures() { - var list = []; - var audio = document.createElement('audio'); - - if (typeof audio.playbackRate === 'number') { - list.push('PlaybackRate'); - } - - return list; } - HtmlAudioPlayer.prototype.supports = function (feature) { + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); } return supportedFeatures.indexOf(feature) !== -1; - }; + } +} - return HtmlAudioPlayer; -}); +let supportedFeatures; + +function getSupportedFeatures() { + const list = []; + const audio = document.createElement('audio'); + + if (typeof audio.playbackRate === 'number') { + list.push('PlaybackRate'); + } + + return list; +} + +export default HtmlAudioPlayer; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 525372ac88..ba497729bb 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -105,7 +105,7 @@ function tryRemoveElement(elem) { } function hidePrePlaybackPage() { - let animatedPage = document.querySelector('.page:not(.hide)'); + const animatedPage = document.querySelector('.page:not(.hide)'); animatedPage.classList.add('hide'); // At this point, we must hide the scrollbar placeholder, so it's not being displayed while the item is being loaded document.body.classList.remove('force-scroll'); @@ -1299,7 +1299,7 @@ function tryRemoveElement(elem) { } let html = ''; - let cssClass = 'htmlvideoplayer'; + const cssClass = 'htmlvideoplayer'; // Can't autoplay in these browsers so we need to use the full controls, at least until playback starts if (!appHost.supports('htmlvideoautoplay')) { diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js index bdd1d34e79..61b8f8a6d6 100644 --- a/src/plugins/logoScreensaver/plugin.js +++ b/src/plugins/logoScreensaver/plugin.js @@ -1,165 +1,165 @@ -define(['pluginManager'], function (pluginManager) { - return function () { - var self = this; +import pluginManager from 'pluginManager'; - self.name = 'Logo ScreenSaver'; - self.type = 'screensaver'; - self.id = 'logoscreensaver'; - self.supportsAnonymous = true; +export default function () { + const self = this; - var interval; + self.name = 'Logo ScreenSaver'; + self.type = 'screensaver'; + self.id = 'logoscreensaver'; + self.supportsAnonymous = true; - function animate() { - var animations = [ + let interval; - bounceInLeft, - bounceInRight, - swing, - tada, - wobble, - rotateIn, - rotateOut - ]; + function animate() { + const animations = [ - var elem = document.querySelector('.logoScreenSaverImage'); + bounceInLeft, + bounceInRight, + swing, + tada, + wobble, + rotateIn, + rotateOut + ]; - if (elem && elem.animate) { - var random = getRandomInt(0, animations.length - 1); + const elem = document.querySelector('.logoScreenSaverImage'); - animations[random](elem, 1); + if (elem && elem.animate) { + const random = getRandomInt(0, animations.length - 1); + + animations[random](elem, 1); + } + } + + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + function bounceInLeft(elem, iterations) { + const keyframes = [ + { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(-100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + const timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function bounceInRight(elem, iterations) { + const keyframes = [ + { transform: 'translate3d(3000px, 0, 0)', opacity: '0', offset: 0 }, + { transform: 'translate3d(-25px, 0, 0)', opacity: '1', offset: 0.6 }, + { transform: 'translate3d(100px, 0, 0)', offset: 0.75 }, + { transform: 'translate3d(-5px, 0, 0)', offset: 0.9 }, + { transform: 'none', opacity: '1', offset: 1 }]; + const timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; + return elem.animate(keyframes, timing); + } + + function swing(elem, iterations) { + const keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 15deg)', offset: 0.2 }, + { transform: 'rotate3d(0, 0, 1, -10deg)', offset: 0.4 }, + { transform: 'rotate3d(0, 0, 1, 5deg)', offset: 0.6 }, + { transform: 'rotate3d(0, 0, 1, -5deg)', offset: 0.8 }, + { transform: 'rotate3d(0, 0, 1, 0deg)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function tada(elem, iterations) { + const keyframes = [ + { transform: 'scale3d(1, 1, 1)', offset: 0 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.1 }, + { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.2 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.3 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.4 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.5 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.6 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.7 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.8 }, + { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.9 }, + { transform: 'scale3d(1, 1, 1)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function wobble(elem, iterations) { + const keyframes = [ + { transform: 'translate(0%)', offset: 0 }, + { transform: 'translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)', offset: 0.15 }, + { transform: 'translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)', offset: 0.45 }, + { transform: 'translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)', offset: 0.6 }, + { transform: 'translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)', offset: 0.75 }, + { transform: 'translateX(0%)', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateIn(elem, iterations) { + const keyframes = [{ transform: 'rotate3d(0, 0, 1, -200deg)', opacity: '0', transformOrigin: 'center', offset: 0 }, + { transform: 'none', opacity: '1', transformOrigin: 'center', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function rotateOut(elem, iterations) { + const keyframes = [{ transform: 'none', opacity: '1', transformOrigin: 'center', offset: 0 }, + { transform: 'rotate3d(0, 0, 1, 200deg)', opacity: '0', transformOrigin: 'center', offset: 1 }]; + const timing = { duration: 900, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function fadeOut(elem, iterations) { + const keyframes = [ + { opacity: '1', offset: 0 }, + { opacity: '0', offset: 1 }]; + const timing = { duration: 400, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function stopInterval() { + if (interval) { + clearInterval(interval); + interval = null; + } + } + + self.show = function () { + import('css!' + pluginManager.mapPath(self, 'style.css')).then(() => { + let elem = document.querySelector('.logoScreenSaver'); + + if (!elem) { + elem = document.createElement('div'); + elem.classList.add('logoScreenSaver'); + document.body.appendChild(elem); + + elem.innerHTML = ''; } - } - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - function bounceInLeft(elem, iterations) { - var keyframes = [ - { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, - { transform: 'translate3d(25px, 0, 0)', opacity: '1', offset: 0.6 }, - { transform: 'translate3d(-100px, 0, 0)', offset: 0.75 }, - { transform: 'translate3d(5px, 0, 0)', offset: 0.9 }, - { transform: 'none', opacity: '1', offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; - return elem.animate(keyframes, timing); - } - - function bounceInRight(elem, iterations) { - var keyframes = [ - { transform: 'translate3d(3000px, 0, 0)', opacity: '0', offset: 0 }, - { transform: 'translate3d(-25px, 0, 0)', opacity: '1', offset: 0.6 }, - { transform: 'translate3d(100px, 0, 0)', offset: 0.75 }, - { transform: 'translate3d(-5px, 0, 0)', offset: 0.9 }, - { transform: 'none', opacity: '1', offset: 1 }]; - var timing = { duration: 900, iterations: iterations, easing: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)' }; - return elem.animate(keyframes, timing); - } - - function swing(elem, iterations) { - var keyframes = [ - { transform: 'translate(0%)', offset: 0 }, - { transform: 'rotate3d(0, 0, 1, 15deg)', offset: 0.2 }, - { transform: 'rotate3d(0, 0, 1, -10deg)', offset: 0.4 }, - { transform: 'rotate3d(0, 0, 1, 5deg)', offset: 0.6 }, - { transform: 'rotate3d(0, 0, 1, -5deg)', offset: 0.8 }, - { transform: 'rotate3d(0, 0, 1, 0deg)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function tada(elem, iterations) { - var keyframes = [ - { transform: 'scale3d(1, 1, 1)', offset: 0 }, - { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.1 }, - { transform: 'scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)', offset: 0.2 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.3 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.4 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.5 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.6 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.7 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)', offset: 0.8 }, - { transform: 'scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)', offset: 0.9 }, - { transform: 'scale3d(1, 1, 1)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function wobble(elem, iterations) { - var keyframes = [ - { transform: 'translate(0%)', offset: 0 }, - { transform: 'translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)', offset: 0.15 }, - { transform: 'translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)', offset: 0.45 }, - { transform: 'translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)', offset: 0.6 }, - { transform: 'translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)', offset: 0.75 }, - { transform: 'translateX(0%)', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateIn(elem, iterations) { - var keyframes = [{ transform: 'rotate3d(0, 0, 1, -200deg)', opacity: '0', transformOrigin: 'center', offset: 0 }, - { transform: 'none', opacity: '1', transformOrigin: 'center', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function rotateOut(elem, iterations) { - var keyframes = [{ transform: 'none', opacity: '1', transformOrigin: 'center', offset: 0 }, - { transform: 'rotate3d(0, 0, 1, 200deg)', opacity: '0', transformOrigin: 'center', offset: 1 }]; - var timing = { duration: 900, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function fadeOut(elem, iterations) { - var keyframes = [ - { opacity: '1', offset: 0 }, - { opacity: '0', offset: 1 }]; - var timing = { duration: 400, iterations: iterations }; - return elem.animate(keyframes, timing); - } - - function stopInterval() { - if (interval) { - clearInterval(interval); - interval = null; - } - } - - self.show = function () { - require(['css!' + pluginManager.mapPath(self, 'style.css')], function () { - var elem = document.querySelector('.logoScreenSaver'); - - if (!elem) { - elem = document.createElement('div'); - elem.classList.add('logoScreenSaver'); - document.body.appendChild(elem); - - elem.innerHTML = ''; - } - - stopInterval(); - interval = setInterval(animate, 3000); - }); - }; - - self.hide = function () { stopInterval(); - - var elem = document.querySelector('.logoScreenSaver'); - - if (elem) { - var onAnimationFinish = function () { - elem.parentNode.removeChild(elem); - }; - - if (elem.animate) { - var animation = fadeOut(elem, 1); - animation.onfinish = onAnimationFinish; - } else { - onAnimationFinish(); - } - } - }; + interval = setInterval(animate, 3000); + }); }; -}); + + self.hide = function () { + stopInterval(); + + const elem = document.querySelector('.logoScreenSaver'); + + if (elem) { + const onAnimationFinish = function () { + elem.parentNode.removeChild(elem); + }; + + if (elem.animate) { + const animation = fadeOut(elem, 1); + animation.onfinish = onAnimationFinish; + } else { + onAnimationFinish(); + } + } + }; +} diff --git a/src/plugins/playAccessValidation/plugin.js b/src/plugins/playAccessValidation/plugin.js index 5148d2b821..a9fbeda9a9 100644 --- a/src/plugins/playAccessValidation/plugin.js +++ b/src/plugins/playAccessValidation/plugin.js @@ -1,33 +1,26 @@ -define(['connectionManager', 'globalize'], function (connectionManager, globalize) { - 'use strict'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } +function showErrorMessage() { + return import('alert').then(({default: alert}) => { + return alert(globalize.translate('MessagePlayAccessRestricted')); + }); +} - function showErrorMessage() { - return getRequirePromise(['alert']).then(function (alert) { - return alert(globalize.translate('MessagePlayAccessRestricted')).then(function () { - return Promise.reject(); - }); - }); - } - - function PlayAccessValidation() { +class PlayAccessValidation { + constructor() { this.name = 'Playback validation'; this.type = 'preplayintercept'; this.id = 'playaccessvalidation'; this.order = -2; } - PlayAccessValidation.prototype.intercept = function (options) { - var item = options.item; + intercept(options) { + const item = options.item; if (!item) { return Promise.resolve(); } - var serverId = item.ServerId; + const serverId = item.ServerId; if (!serverId) { return Promise.resolve(); } @@ -44,7 +37,7 @@ define(['connectionManager', 'globalize'], function (connectionManager, globaliz return showErrorMessage(); }); - }; + } +} - return PlayAccessValidation; -}); +export default PlayAccessValidation; diff --git a/src/scripts/apploader.js b/src/scripts/apploader.js index 183b765d16..e98c82e693 100644 --- a/src/scripts/apploader.js +++ b/src/scripts/apploader.js @@ -1,12 +1,10 @@ (function() { - 'use strict'; - function injectScriptElement(src, onload) { if (!src) { return; } - var script = document.createElement('script'); + const script = document.createElement('script'); if (self.dashboardVersion) { src += `?v=${self.dashboardVersion}`; } diff --git a/src/scripts/autoThemes.js b/src/scripts/autoThemes.js index dad8c10322..75309f377b 100644 --- a/src/scripts/autoThemes.js +++ b/src/scripts/autoThemes.js @@ -3,6 +3,10 @@ import skinManager from 'skinManager'; import connectionManager from 'connectionManager'; import events from 'events'; +// Set the default theme when loading +skinManager.setTheme(userSettings.theme()); + +// Set the user's prefered theme when signing in events.on(connectionManager, 'localusersignedin', function (e, user) { skinManager.setTheme(userSettings.theme()); }); diff --git a/src/scripts/datetime.js b/src/scripts/datetime.js index dcac41089e..c6baa28ed3 100644 --- a/src/scripts/datetime.js +++ b/src/scripts/datetime.js @@ -24,7 +24,7 @@ import globalize from 'globalize'; // parse strings, leading zeros into proper ints const a = [1, 2, 3, 4, 5, 6, 10, 11]; - for (let i in a) { + for (const i in a) { d[a[i]] = parseInt(d[a[i]], 10); } d[7] = parseFloat(d[7]); diff --git a/src/scripts/deleteHelper.js b/src/scripts/deleteHelper.js index e10a1568c7..44876c75b3 100644 --- a/src/scripts/deleteHelper.js +++ b/src/scripts/deleteHelper.js @@ -15,7 +15,7 @@ export function deleteItem(options) { const item = options.item; const parentId = item.SeasonId || item.SeriesId || item.ParentId; - let apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); return confirm({ @@ -34,7 +34,7 @@ export function deleteItem(options) { } } }, function (err) { - let result = function () { + const result = function () { return Promise.reject(err); }; diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js index 870429ee09..28103bb2db 100644 --- a/src/scripts/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -22,6 +22,8 @@ require(['apphost'], function (appHost) { 'use strict'; + appHost = appHost.default || appHost; + var _GAMEPAD_A_BUTTON_INDEX = 0; var _GAMEPAD_B_BUTTON_INDEX = 1; var _GAMEPAD_DPAD_UP_BUTTON_INDEX = 12; diff --git a/src/scripts/globalize.js b/src/scripts/globalize.js index 4af1ea6cbf..d237fdcce0 100644 --- a/src/scripts/globalize.js +++ b/src/scripts/globalize.js @@ -63,11 +63,11 @@ import events from 'events'; } function ensureTranslations(culture) { - for (let i in allTranslations) { + for (const i in allTranslations) { ensureTranslation(allTranslations[i], culture); } if (culture !== fallbackCulture) { - for (let i in allTranslations) { + for (const i in allTranslations) { ensureTranslation(allTranslations[i], fallbackCulture); } } diff --git a/src/scripts/inputManager.js b/src/scripts/inputManager.js index 3432c9e351..077af39bf7 100644 --- a/src/scripts/inputManager.js +++ b/src/scripts/inputManager.js @@ -39,7 +39,7 @@ import appHost from 'apphost'; dom.removeEventListener(scope, 'command', fn, {}); } - let commandTimes = {}; + const commandTimes = {}; function checkCommandTime(command) { const last = commandTimes[command] || 0; diff --git a/src/scripts/itembynamedetailpage.js b/src/scripts/itembynamedetailpage.js index eaeecba275..147dc66ae0 100644 --- a/src/scripts/itembynamedetailpage.js +++ b/src/scripts/itembynamedetailpage.js @@ -1,369 +1,374 @@ -define(['connectionManager', 'listView', 'cardBuilder', 'imageLoader', 'libraryBrowser', 'globalize', 'emby-itemscontainer', 'emby-button'], function (connectionManager, listView, cardBuilder, imageLoader, libraryBrowser, globalize) { - 'use strict'; +import connectionManager from 'connectionManager'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; +import 'emby-button'; - function renderItems(page, item) { - var sections = []; +function renderItems(page, item) { + const sections = []; - if (item.ArtistCount) { - sections.push({ - name: globalize.translate('TabArtists'), - type: 'MusicArtist' - }); - } - - if (item.ProgramCount && item.Type == 'Person') { - sections.push({ - name: globalize.translate('HeaderUpcomingOnTV'), - type: 'Program' - }); - } - - if (item.MovieCount) { - sections.push({ - name: globalize.translate('TabMovies'), - type: 'Movie' - }); - } - - if (item.SeriesCount) { - sections.push({ - name: globalize.translate('TabShows'), - type: 'Series' - }); - } - - if (item.EpisodeCount) { - sections.push({ - name: globalize.translate('TabEpisodes'), - type: 'Episode' - }); - } - - if (item.TrailerCount) { - sections.push({ - name: globalize.translate('TabTrailers'), - type: 'Trailer' - }); - } - - if (item.AlbumCount) { - sections.push({ - name: globalize.translate('TabAlbums'), - type: 'MusicAlbum' - }); - } - - if (item.MusicVideoCount) { - sections.push({ - name: globalize.translate('TabMusicVideos'), - type: 'MusicVideo' - }); - } - - var elem = page.querySelector('#childrenContent'); - elem.innerHTML = sections.map(function (section) { - var html = ''; - var sectionClass = 'verticalSection'; - - if (section.type === 'Audio') { - sectionClass += ' verticalSection-extrabottompadding'; - } - - html += '
    '; - html += '
    '; - html += '

    '; - html += section.name; - html += '

    '; - html += ''; - html += '
    '; - html += '
    '; - html += '
    '; - return html += '
    '; - }).join(''); - var sectionElems = elem.querySelectorAll('.verticalSection'); - - for (var i = 0, length = sectionElems.length; i < length; i++) { - renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute('data-type')); - } - } - - function renderSection(page, item, element, type) { - switch (type) { - case 'Program': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Program', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'StartDate' - }, { - shape: 'overflowBackdrop', - showTitle: true, - centerText: true, - overlayMoreButton: true, - preferThumb: true, - overlayText: false, - showAirTime: true, - showAirDateTime: true, - showChannelName: true - }); - break; - - case 'Movie': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Movie', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayMoreButton: true, - overlayText: false, - showYear: true - }); - break; - - case 'MusicVideo': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicVideo', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Trailer': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Trailer', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Series': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Series', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 10, - SortBy: 'SortName' - }, { - shape: 'overflowPortrait', - showTitle: true, - centerText: true, - overlayMoreButton: true - }); - break; - - case 'MusicAlbum': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicAlbum', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - SortOrder: 'Descending', - SortBy: 'ProductionYear,Sortname' - }, { - shape: 'overflowSquare', - playFromHere: true, - showTitle: true, - showYear: true, - coverImage: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'MusicArtist': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'MusicArtist', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 8, - SortBy: 'SortName' - }, { - shape: 'overflowSquare', - playFromHere: true, - showTitle: true, - showParentTitle: true, - coverImage: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Episode': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Episode', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - Limit: 6, - SortBy: 'SortName' - }, { - shape: 'overflowBackdrop', - showTitle: true, - showParentTitle: true, - centerText: true, - overlayPlayButton: true - }); - break; - - case 'Audio': - loadItems(element, item, type, { - MediaTypes: '', - IncludeItemTypes: 'Audio', - PersonTypes: '', - ArtistIds: '', - AlbumArtistIds: '', - SortBy: 'AlbumArtist,Album,SortName' - }, { - playFromHere: true, - action: 'playallfromhere', - smallIcon: true, - artist: true - }); - } - } - - function loadItems(element, item, type, query, listOptions) { - query = getQuery(query, item); - getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) { - var html = ''; - - if (query.Limit && result.TotalRecordCount > query.Limit) { - var link = element.querySelector('a'); - link.classList.remove('hide'); - link.setAttribute('href', getMoreItemsHref(item, type)); - } else { - element.querySelector('a').classList.add('hide'); - } - - listOptions.items = result.Items; - var itemsContainer = element.querySelector('.itemsContainer'); - - if (type == 'Audio') { - html = listView.getListViewHtml(listOptions); - itemsContainer.classList.remove('vertical-wrap'); - itemsContainer.classList.add('vertical-list'); - } else { - html = cardBuilder.getCardsHtml(listOptions); - itemsContainer.classList.add('vertical-wrap'); - itemsContainer.classList.remove('vertical-list'); - } - - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); + if (item.ArtistCount) { + sections.push({ + name: globalize.translate('Artists'), + type: 'MusicArtist' }); } - function getMoreItemsHref(item, type) { - if (item.Type == 'Genre') { - return 'list.html?type=' + type + '&genreId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'MusicGenre') { - return 'list.html?type=' + type + '&musicGenreId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'Studio') { - return 'list.html?type=' + type + '&studioId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'MusicArtist') { - return 'list.html?type=' + type + '&artistId=' + item.Id + '&serverId=' + item.ServerId; - } - - if (item.Type == 'Person') { - return 'list.html?type=' + type + '&personId=' + item.Id + '&serverId=' + item.ServerId; - } - - return 'list.html?type=' + type + '&parentId=' + item.Id + '&serverId=' + item.ServerId; + if (item.ProgramCount && item.Type === 'Person') { + sections.push({ + name: globalize.translate('HeaderUpcomingOnTV'), + type: 'Program' + }); } - function addCurrentItemToQuery(query, item) { - if (item.Type == 'Person') { - query.PersonIds = item.Id; - } else if (item.Type == 'Genre') { - query.Genres = item.Name; - } else if (item.Type == 'MusicGenre') { - query.Genres = item.Name; - } else if (item.Type == 'GameGenre') { - query.Genres = item.Name; - } else if (item.Type == 'Studio') { - query.StudioIds = item.Id; - } else if (item.Type == 'MusicArtist') { - query.AlbumArtistIds = item.Id; + if (item.MovieCount) { + sections.push({ + name: globalize.translate('TabMovies'), + type: 'Movie' + }); + } + + if (item.SeriesCount) { + sections.push({ + name: globalize.translate('TabShows'), + type: 'Series' + }); + } + + if (item.EpisodeCount) { + sections.push({ + name: globalize.translate('TabEpisodes'), + type: 'Episode' + }); + } + + if (item.TrailerCount) { + sections.push({ + name: globalize.translate('TabTrailers'), + type: 'Trailer' + }); + } + + if (item.AlbumCount) { + sections.push({ + name: globalize.translate('TabAlbums'), + type: 'MusicAlbum' + }); + } + + if (item.MusicVideoCount) { + sections.push({ + name: globalize.translate('TabMusicVideos'), + type: 'MusicVideo' + }); + } + + const elem = page.querySelector('#childrenContent'); + elem.innerHTML = sections.map(function (section) { + let html = ''; + let sectionClass = 'verticalSection'; + + if (section.type === 'Audio') { + sectionClass += ' verticalSection-extrabottompadding'; } + + html += '
    '; + html += '
    '; + html += '

    '; + html += section.name; + html += '

    '; + html += ''; + html += '
    '; + html += '
    '; + html += '
    '; + html += '
    '; + return html; + }).join(''); + const sectionElems = elem.querySelectorAll('.verticalSection'); + + for (let i = 0, length = sectionElems.length; i < length; i++) { + renderSection(page, item, sectionElems[i], sectionElems[i].getAttribute('data-type')); + } +} + +function renderSection(page, item, element, type) { + switch (type) { + case 'Program': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Program', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'StartDate' + }, { + shape: 'overflowBackdrop', + showTitle: true, + centerText: true, + overlayMoreButton: true, + preferThumb: true, + overlayText: false, + showAirTime: true, + showAirDateTime: true, + showChannelName: true + }); + break; + + case 'Movie': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Movie', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayMoreButton: true, + overlayText: false, + showYear: true + }); + break; + + case 'MusicVideo': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicVideo', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Trailer': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Trailer', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Series': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Series', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 10, + SortBy: 'SortName' + }, { + shape: 'overflowPortrait', + showTitle: true, + centerText: true, + overlayMoreButton: true + }); + break; + + case 'MusicAlbum': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicAlbum', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortOrder: 'Descending', + SortBy: 'ProductionYear,Sortname' + }, { + shape: 'overflowSquare', + playFromHere: true, + showTitle: true, + showYear: true, + coverImage: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'MusicArtist': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'MusicArtist', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 8, + SortBy: 'SortName' + }, { + shape: 'overflowSquare', + playFromHere: true, + showTitle: true, + showParentTitle: true, + coverImage: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Episode': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Episode', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + Limit: 6, + SortBy: 'SortName' + }, { + shape: 'overflowBackdrop', + showTitle: true, + showParentTitle: true, + centerText: true, + overlayPlayButton: true + }); + break; + + case 'Audio': + loadItems(element, item, type, { + MediaTypes: '', + IncludeItemTypes: 'Audio', + PersonTypes: '', + ArtistIds: '', + AlbumArtistIds: '', + SortBy: 'AlbumArtist,Album,SortName' + }, { + playFromHere: true, + action: 'playallfromhere', + smallIcon: true, + artist: true + }); + } +} + +function loadItems(element, item, type, query, listOptions) { + query = getQuery(query, item); + getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) { + let html = ''; + + if (query.Limit && result.TotalRecordCount > query.Limit) { + const link = element.querySelector('a'); + link.classList.remove('hide'); + link.setAttribute('href', getMoreItemsHref(item, type)); + } else { + element.querySelector('a').classList.add('hide'); + } + + listOptions.items = result.Items; + const itemsContainer = element.querySelector('.itemsContainer'); + + if (type === 'Audio') { + html = listView.getListViewHtml(listOptions); + itemsContainer.classList.remove('vertical-wrap'); + itemsContainer.classList.add('vertical-list'); + } else { + html = cardBuilder.getCardsHtml(listOptions); + itemsContainer.classList.add('vertical-wrap'); + itemsContainer.classList.remove('vertical-list'); + } + + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + }); +} + +function getMoreItemsHref(item, type) { + if (item.Type === 'Genre') { + return 'list.html?type=' + type + '&genreId=' + item.Id + '&serverId=' + item.ServerId; } - function getQuery(options, item) { - var query = { - SortOrder: 'Ascending', - IncludeItemTypes: '', - Recursive: true, - Fields: 'AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo', - Limit: 100, - StartIndex: 0, - CollapseBoxSetItems: false - }; - query = Object.assign(query, options || {}); - addCurrentItemToQuery(query, item); - return query; + if (item.Type === 'MusicGenre') { + return 'list.html?type=' + type + '&musicGenreId=' + item.Id + '&serverId=' + item.ServerId; } - function getItemsFunction(options, item) { - var query = getQuery(options, item); - return function (index, limit, fields) { - query.StartIndex = index; - query.Limit = limit; - - if (fields) { - query.Fields += ',' + fields; - } - - var apiClient = connectionManager.getApiClient(item.ServerId); - - if (query.IncludeItemTypes === 'MusicArtist') { - query.IncludeItemTypes = null; - return apiClient.getAlbumArtists(apiClient.getCurrentUserId(), query); - } - - return apiClient.getItems(apiClient.getCurrentUserId(), query); - }; + if (item.Type === 'Studio') { + return 'list.html?type=' + type + '&studioId=' + item.Id + '&serverId=' + item.ServerId; } - window.ItemsByName = { - renderItems: renderItems + if (item.Type === 'MusicArtist') { + return 'list.html?type=' + type + '&artistId=' + item.Id + '&serverId=' + item.ServerId; + } + + if (item.Type === 'Person') { + return 'list.html?type=' + type + '&personId=' + item.Id + '&serverId=' + item.ServerId; + } + + return 'list.html?type=' + type + '&parentId=' + item.Id + '&serverId=' + item.ServerId; +} + +function addCurrentItemToQuery(query, item) { + if (item.Type === 'Person') { + query.PersonIds = item.Id; + } else if (item.Type === 'Genre') { + query.Genres = item.Name; + } else if (item.Type === 'MusicGenre') { + query.Genres = item.Name; + } else if (item.Type === 'GameGenre') { + query.Genres = item.Name; + } else if (item.Type === 'Studio') { + query.StudioIds = item.Id; + } else if (item.Type === 'MusicArtist') { + query.AlbumArtistIds = item.Id; + } +} + +function getQuery(options, item) { + let query = { + SortOrder: 'Ascending', + IncludeItemTypes: '', + Recursive: true, + Fields: 'AudioInfo,SeriesInfo,ParentId,PrimaryImageAspectRatio,BasicSyncInfo', + Limit: 100, + StartIndex: 0, + CollapseBoxSetItems: false }; -}); + query = Object.assign(query, options || {}); + addCurrentItemToQuery(query, item); + return query; +} + +function getItemsFunction(options, item) { + const query = getQuery(options, item); + return function (index, limit, fields) { + query.StartIndex = index; + query.Limit = limit; + + if (fields) { + query.Fields += ',' + fields; + } + + const apiClient = connectionManager.getApiClient(item.ServerId); + + if (query.IncludeItemTypes === 'MusicArtist') { + query.IncludeItemTypes = null; + return apiClient.getAlbumArtists(apiClient.getCurrentUserId(), query); + } + + return apiClient.getItems(apiClient.getCurrentUserId(), query); + }; +} + +window.ItemsByName = { + renderItems: renderItems +}; diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index 46cda51e55..83d683a690 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -119,7 +119,10 @@ export function getQueryPagingHtml (options) { } export function showSortMenu (options) { - require(['dialogHelper', 'emby-radio'], function (dialogHelper) { + Promise.all([ + import('dialogHelper'), + import('emby-radio') + ]).then(([{default: dialogHelper}]) => { function onSortByChange() { var newValue = this.value; diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 376b19f70d..543aa6f3d0 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -1,12 +1,26 @@ -define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncPlayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncPlayManager, groupSelectionMenu, browser, globalize, imageHelper) { - 'use strict'; +import dom from 'dom'; +import layoutManager from 'layoutManager'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import events from 'events'; +import viewManager from 'viewManager'; +import appRouter from 'appRouter'; +import appHost from 'apphost'; +import playbackManager from 'playbackManager'; +import syncPlayManager from 'syncPlayManager'; +import groupSelectionMenu from 'groupSelectionMenu'; +import browser from 'browser'; +import globalize from 'globalize'; +import imageHelper from 'scripts/imagehelper'; +import 'paper-icon-button-light'; +import 'material-icons'; +import 'scrollStyles'; +import 'flexStyles'; - viewManager = viewManager.default || viewManager; - playbackManager = playbackManager.default || playbackManager; - browser = browser.default || browser; +/* eslint-disable indent */ function renderHeader() { - var html = ''; + let html = ''; html += '
    '; html += '
    '; html += ''; @@ -19,7 +33,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' html += ``; html += ``; html += ``; - html += ``; + html += ``; html += ''; html += '
    '; html += '
    '; @@ -50,7 +64,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function lazyLoadViewMenuBarImages() { - require(['imageLoader'], function (imageLoader) { + import('imageLoader').then(({default: imageLoader}) => { imageLoader.lazyChildren(skinHeader); }); } @@ -60,11 +74,11 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateUserInHeader(user) { - var hasImage; + let hasImage; if (user && user.name) { if (user.imageUrl) { - var url = user.imageUrl; + const url = user.imageUrl; updateHeaderUserButton(url); hasImage = true; } @@ -91,9 +105,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' headerCastButton.classList.remove('hide'); } - var policy = user.Policy ? user.Policy : user.localUser.Policy; + const policy = user.Policy ? user.Policy : user.localUser.Policy; - var apiClient = getCurrentApiClient(); + const apiClient = getCurrentApiClient(); if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) { headerSyncButton.classList.remove('hide'); } @@ -143,7 +157,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' mainDrawerButton.addEventListener('click', toggleMainDrawer); } - var headerBackButton = skinHeader.querySelector('.headerBackButton'); + const headerBackButton = skinHeader.querySelector('.headerBackButton'); if (headerBackButton) { headerBackButton.addEventListener('click', onBackClick); @@ -185,20 +199,20 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onCastButtonClicked() { - var btn = this; + const btn = this; - require(['playerSelectionMenu'], function (playerSelectionMenu) { + import('playerSelectionMenu').then(({default: playerSelectionMenu}) => { playerSelectionMenu.show(btn); }); } function onSyncButtonClicked() { - var btn = this; + const btn = this; groupSelectionMenu.show(btn); } function onSyncPlayEnabled(event, enabled) { - var icon = headerSyncButton.querySelector('span'); + const icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (enabled) { icon.classList.add('sync'); @@ -208,7 +222,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onSyncPlaySyncing(event, is_syncing, syncMethod) { - var icon = headerSyncButton.querySelector('span'); + const icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (is_syncing) { icon.classList.add('sync_problem'); @@ -254,7 +268,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function refreshLibraryInfoInDrawer(user, drawer) { - var html = ''; + let html = ''; html += '
    '; html += '' + globalize.translate('ButtonHome') + ''; @@ -290,12 +304,12 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' // add buttons to navigation drawer navDrawerScrollContainer.innerHTML = html; - var btnSettings = navDrawerScrollContainer.querySelector('.btnSettings'); + const btnSettings = navDrawerScrollContainer.querySelector('.btnSettings'); if (btnSettings) { btnSettings.addEventListener('click', onSettingsClick); } - var btnLogout = navDrawerScrollContainer.querySelector('.btnLogout'); + const btnLogout = navDrawerScrollContainer.querySelector('.btnLogout'); if (btnLogout) { btnLogout.addEventListener('click', onLogoutClick); } @@ -317,20 +331,20 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateDashboardMenuSelectedItem() { - var links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); - var currentViewId = viewManager.currentView().id; + const links = navDrawerScrollContainer.querySelectorAll('.navMenuOption'); + const currentViewId = viewManager.currentView().id; - for (var i = 0, length = links.length; i < length; i++) { - var link = links[i]; - var selected = false; - var pageIds = link.getAttribute('data-pageids'); + for (let i = 0, length = links.length; i < length; i++) { + let link = links[i]; + let selected = false; + let pageIds = link.getAttribute('data-pageids'); if (pageIds) { pageIds = pageIds.split('|'); selected = pageIds.indexOf(currentViewId) != -1; } - var pageUrls = link.getAttribute('data-pageurls'); + let pageUrls = link.getAttribute('data-pageurls'); if (pageUrls) { pageUrls = pageUrls.split('|'); @@ -339,7 +353,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' if (selected) { link.classList.add('navMenuOption-selected'); - var title = ''; + let title = ''; link = link.querySelector('.navMenuOptionText') || link; title += (link.innerText || link.textContent).trim(); LibraryMenu.setTitle(title); @@ -350,7 +364,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function createToolsMenuList(pluginItems) { - var links = [{ + const links = [{ name: globalize.translate('TabServer') }, { name: globalize.translate('TabDashboard'), @@ -373,7 +387,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' pageIds: ['mediaLibraryPage', 'librarySettingsPage', 'libraryDisplayPage', 'metadataImagesConfigurationPage', 'metadataNfoPage'], icon: 'folder' }, { - name: globalize.translate('TabPlayback'), + name: globalize.translate('TitlePlayback'), icon: 'play_arrow', href: 'encodingsettings.html', pageIds: ['encodingSettingsPage', 'playbackConfigurationPage', 'streamingSettingsPage'] @@ -462,8 +476,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function addPluginPagesToMainMenu(links, pluginItems, section) { - for (var i = 0, length = pluginItems.length; i < length; i++) { - var pluginItem = pluginItems[i]; + for (let i = 0, length = pluginItems.length; i < length; i++) { + const pluginItem = pluginItems[i]; if (pluginItem.EnableInMainMenu && pluginItem.MenuSection === section) { links.push({ @@ -483,10 +497,10 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function getToolsLinkHtml(item) { - var menuHtml = ''; - var pageIds = item.pageIds ? item.pageIds.join('|') : ''; + let menuHtml = ''; + let pageIds = item.pageIds ? item.pageIds.join('|') : ''; pageIds = pageIds ? ' data-pageids="' + pageIds + '"' : ''; - var pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; + let pageUrls = item.pageUrls ? item.pageUrls.join('|') : ''; pageUrls = pageUrls ? ' data-pageurls="' + pageUrls + '"' : ''; menuHtml += ''; @@ -502,11 +516,11 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function getToolsMenuHtml(apiClient) { return getToolsMenuLinks(apiClient).then(function (items) { - var item; - var menuHtml = ''; + let item; + let menuHtml = ''; menuHtml += '
    '; - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { item = items[i]; if (item.href) { @@ -524,7 +538,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function createDashboardMenu(apiClient) { return getToolsMenuHtml(apiClient).then(function (toolsMenuHtml) { - var html = ''; + let html = ''; html += ''; @@ -535,24 +549,24 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function onSidebarLinkClick() { - var section = this.getElementsByClassName('sectionName')[0]; - var text = section ? section.innerHTML : this.innerHTML; + const section = this.getElementsByClassName('sectionName')[0]; + const text = section ? section.innerHTML : this.innerHTML; LibraryMenu.setTitle(text); } function getUserViews(apiClient, userId) { return apiClient.getUserViews({}, userId).then(function (result) { - var items = result.Items; - var list = []; + const items = result.Items; + const list = []; - for (var i = 0, length = items.length; i < length; i++) { - var view = items[i]; + for (let i = 0, length = items.length; i < length; i++) { + const view = items[i]; list.push(view); if (view.CollectionType == 'livetv') { view.ImageTags = {}; view.icon = 'live_tv'; - var guideView = Object.assign({}, view); + const guideView = Object.assign({}, view); guideView.Name = globalize.translate('ButtonGuide'); guideView.ImageTags = {}; guideView.icon = 'dvr'; @@ -566,7 +580,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function showBySelector(selector, show) { - var elem = document.querySelector(selector); + const elem = document.querySelector(selector); if (elem) { if (show) { @@ -596,17 +610,17 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' showBySelector('.libraryMenuDownloads', false); } - var userId = Dashboard.getCurrentUserId(); - var apiClient = getCurrentApiClient(); - var libraryMenuOptions = document.querySelector('.libraryMenuOptions'); + const userId = Dashboard.getCurrentUserId(); + const apiClient = getCurrentApiClient(); + const libraryMenuOptions = document.querySelector('.libraryMenuOptions'); if (libraryMenuOptions) { getUserViews(apiClient, userId).then(function (result) { - var items = result; - var html = `

    ${globalize.translate('HeaderMedia')}

    `; + const items = result; + let html = `

    ${globalize.translate('HeaderMedia')}

    `; html += items.map(function (i) { - var icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); - var itemId = i.Id; + const icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); + const itemId = i.Id; return ` @@ -614,8 +628,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' `; }).join(''); libraryMenuOptions.innerHTML = html; - var elem = libraryMenuOptions; - var sidebarLinks = elem.querySelectorAll('.navMenuOption'); + const elem = libraryMenuOptions; + const sidebarLinks = elem.querySelectorAll('.navMenuOption'); for (const sidebarLink of sidebarLinks) { sidebarLink.removeEventListener('click', onSidebarLinkClick); @@ -644,9 +658,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateCastIcon() { - var context = document; - var info = playbackManager.getPlayerInfo(); - var icon = headerCastButton.querySelector('.material-icons'); + const context = document; + const info = playbackManager.getPlayerInfo(); + const icon = headerCastButton.querySelector('.material-icons'); icon.classList.remove('cast_connected', 'cast'); @@ -662,18 +676,16 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateLibraryNavLinks(page) { - var i; - var length; - var isLiveTvPage = page.classList.contains('liveTvPage'); - var isChannelsPage = page.classList.contains('channelsPage'); - var isEditorPage = page.classList.contains('metadataEditorPage'); - var isMySyncPage = page.classList.contains('mySyncPage'); - var id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains('allLibraryPage') ? '' : getTopParentId() || ''; - var elems = document.getElementsByClassName('lnkMediaFolder'); + const isLiveTvPage = page.classList.contains('liveTvPage'); + const isChannelsPage = page.classList.contains('channelsPage'); + const isEditorPage = page.classList.contains('metadataEditorPage'); + const isMySyncPage = page.classList.contains('mySyncPage'); + const id = isLiveTvPage || isChannelsPage || isEditorPage || isMySyncPage || page.classList.contains('allLibraryPage') ? '' : getTopParentId() || ''; + const elems = document.getElementsByClassName('lnkMediaFolder'); - for (var i = 0, length = elems.length; i < length; i++) { - var lnkMediaFolder = elems[i]; - var itemId = lnkMediaFolder.getAttribute('data-itemid'); + for (let i = 0, length = elems.length; i < length; i++) { + const lnkMediaFolder = elems[i]; + const itemId = lnkMediaFolder.getAttribute('data-itemid'); if (isChannelsPage && itemId === 'channels') { lnkMediaFolder.classList.add('navMenuOption-selected'); @@ -694,7 +706,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateMenuForPageType(isDashboardPage, isLibraryPage) { - var newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; + const newPageType = isDashboardPage ? 2 : isLibraryPage ? 1 : 3; if (currentPageType !== newPageType) { currentPageType = newPageType; @@ -705,7 +717,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' skinHeader.classList.remove('headroomDisabled'); } - var bodyClassList = document.body.classList; + const bodyClassList = document.body.classList; if (isLibraryPage) { bodyClassList.add('libraryDocument'); @@ -742,7 +754,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function updateTitle(page) { - var title = page.getAttribute('data-title'); + const title = page.getAttribute('data-title'); if (title) { LibraryMenu.setTitle(title); @@ -766,8 +778,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function initHeadRoom(elem) { - require(['headroom'], function (Headroom) { - var headroom = new Headroom(elem); + import('headroom').then(({default: Headroom}) => { + const headroom = new Headroom(elem); headroom.init(); }); } @@ -787,7 +799,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } function getNavDrawerOptions() { - var drawerWidth = screen.availWidth - 50; + let drawerWidth = screen.availWidth - 50; drawerWidth = Math.max(drawerWidth, 240); drawerWidth = Math.min(drawerWidth, 320); return { @@ -806,9 +818,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' navDrawerScrollContainer = navDrawerElement.querySelector('.scrollContainer'); navDrawerScrollContainer.addEventListener('click', onMainDrawerClick); return new Promise(function (resolve, reject) { - require(['navdrawer'], function (navdrawer) { - navdrawer = navdrawer.default || navdrawer; - + import('navdrawer').then(({default: navdrawer}) => { navDrawerInstance = new navdrawer(getNavDrawerOptions()); if (!layoutManager.tv) { @@ -820,98 +830,98 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' }); } - var navDrawerElement; - var navDrawerScrollContainer; - var navDrawerInstance; - var mainDrawerButton; - var headerHomeButton; - var currentDrawerType; - var pageTitleElement; - var headerBackButton; - var headerUserButton; - var currentUser; - var headerCastButton; - var headerSearchButton; - var headerAudioPlayerButton; - var headerSyncButton; - var enableLibraryNavDrawer = layoutManager.desktop; - var enableLibraryNavDrawerHome = !layoutManager.tv; - var skinHeader = document.querySelector('.skinHeader'); - var requiresUserRefresh = true; - window.LibraryMenu = { - getTopParentId: getTopParentId, - onHardwareMenuButtonClick: function () { - toggleMainDrawer(); - }, - setTabs: function (type, selectedIndex, builder) { - require(['mainTabsManager'], function (mainTabsManager) { - if (type) { - mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { - return []; - }); - } else { - mainTabsManager.setTabs(null); - } - }); - }, - setDefaultTitle: function () { - if (!pageTitleElement) { - pageTitleElement = document.querySelector('.pageTitle'); - } + let navDrawerElement; + let navDrawerScrollContainer; + let navDrawerInstance; + let mainDrawerButton; + let headerHomeButton; + let currentDrawerType; + let pageTitleElement; + let headerBackButton; + let headerUserButton; + let currentUser; + let headerCastButton; + let headerSearchButton; + let headerAudioPlayerButton; + let headerSyncButton; + const enableLibraryNavDrawer = layoutManager.desktop; + const enableLibraryNavDrawerHome = !layoutManager.tv; + const skinHeader = document.querySelector('.skinHeader'); + let requiresUserRefresh = true; - if (pageTitleElement) { - pageTitleElement.classList.add('pageTitleWithLogo'); - pageTitleElement.classList.add('pageTitleWithDefaultLogo'); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = ''; - } - - document.title = 'Jellyfin'; - }, - setTitle: function (title) { - if (title == null) { - return void LibraryMenu.setDefaultTitle(); - } - - if (title === '-') { - title = ''; - } - - var html = title; - - if (!pageTitleElement) { - pageTitleElement = document.querySelector('.pageTitle'); - } - - if (pageTitleElement) { - pageTitleElement.classList.remove('pageTitleWithLogo'); - pageTitleElement.classList.remove('pageTitleWithDefaultLogo'); - pageTitleElement.style.backgroundImage = null; - pageTitleElement.innerHTML = html || ''; - } - - document.title = title || 'Jellyfin'; - }, - setTransparentMenu: function (transparent) { - if (transparent) { - skinHeader.classList.add('semiTransparent'); + function setTabs (type, selectedIndex, builder) { + import('mainTabsManager').then((mainTabsManager) => { + if (type) { + mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { + return []; + }); } else { - skinHeader.classList.remove('semiTransparent'); + mainTabsManager.setTabs(null); } + }); + } + + function setDefaultTitle () { + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); } - }; - var currentPageType; + + if (pageTitleElement) { + pageTitleElement.classList.add('pageTitleWithLogo'); + pageTitleElement.classList.add('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = ''; + } + + document.title = 'Jellyfin'; + } + + function setTitle (title) { + if (title == null) { + return void LibraryMenu.setDefaultTitle(); + } + + if (title === '-') { + title = ''; + } + + const html = title; + + if (!pageTitleElement) { + pageTitleElement = document.querySelector('.pageTitle'); + } + + if (pageTitleElement) { + pageTitleElement.classList.remove('pageTitleWithLogo'); + pageTitleElement.classList.remove('pageTitleWithDefaultLogo'); + pageTitleElement.style.backgroundImage = null; + pageTitleElement.innerHTML = html || ''; + } + + document.title = title || 'Jellyfin'; + } + + function setTransparentMenu (transparent) { + if (transparent) { + skinHeader.classList.add('semiTransparent'); + } else { + skinHeader.classList.remove('semiTransparent'); + } + } + + let currentPageType; pageClassOn('pagebeforeshow', 'page', function (e) { if (!this.classList.contains('withTabs')) { LibraryMenu.setTabs(null); } }); + pageClassOn('pageshow', 'page', function (e) { - var page = this; - var isDashboardPage = page.classList.contains('type-interior'); - var isHomePage = page.classList.contains('homePage'); - var isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); - var apiClient = getCurrentApiClient(); + const page = this; + const isDashboardPage = page.classList.contains('type-interior'); + const isHomePage = page.classList.contains('homePage'); + const isLibraryPage = !isDashboardPage && page.classList.contains('libraryPage'); + const apiClient = getCurrentApiClient(); if (isDashboardPage) { if (mainDrawerButton) { @@ -948,7 +958,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' renderHeader(); events.on(connectionManager, 'localusersignedin', function (e, user) { - var currentApiClient = connectionManager.getApiClient(user.ServerId); + const currentApiClient = connectionManager.getApiClient(user.ServerId); currentDrawerType = null; currentUser = { @@ -962,15 +972,32 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' updateUserInHeader(user); }); }); + events.on(connectionManager, 'localusersignedout', function () { currentUser = {}; updateUserInHeader(); }); + events.on(playbackManager, 'playerchange', updateCastIcon); events.on(syncPlayManager, 'enabled', onSyncPlayEnabled); events.on(syncPlayManager, 'syncing', onSyncPlaySyncing); loadNavDrawer(); - return LibraryMenu; -}); + + const LibraryMenu = { + getTopParentId: getTopParentId, + onHardwareMenuButtonClick: function () { + toggleMainDrawer(); + }, + setTabs: setTabs, + setDefaultTitle: setDefaultTitle, + setTitle: setTitle, + setTransparentMenu: setTransparentMenu + }; + + window.LibraryMenu = LibraryMenu; + +export default LibraryMenu; + +/* eslint-enable indent */ diff --git a/src/scripts/livetvcomponents.js b/src/scripts/livetvcomponents.js index fd1b48d0eb..46fb714763 100644 --- a/src/scripts/livetvcomponents.js +++ b/src/scripts/livetvcomponents.js @@ -1,115 +1,109 @@ -define(['layoutManager', 'datetime', 'cardBuilder', 'apphost'], function (layoutManager, datetime, cardBuilder, appHost) { - 'use strict'; +import layoutManager from 'layoutManager'; +import datetime from 'datetime'; +import cardBuilder from 'cardBuilder'; - function enableScrollX() { - return !layoutManager.desktop; +function enableScrollX() { + return !layoutManager.desktop; +} + +function getBackdropShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getTimersHtml(timers, options) { + options = options || {}; + + const items = timers.map(function (t) { + t.Type = 'Timer'; + return t; + }); + + const groups = []; + let currentGroupName = ''; + let currentGroup = []; + + for (const item of items) { + let dateText = ''; + + if (options.indexByDate !== false && item.StartDate) { + try { + const premiereDate = datetime.parseISO8601Date(item.StartDate, true); + dateText = datetime.toLocaleDateString(premiereDate, { + weekday: 'long', + month: 'short', + day: 'numeric' + }); + } catch (err) { + console.error('error parsing premiereDate:' + item.StartDate + '; error: ' + err); + } + } + + if (dateText != currentGroupName) { + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup + }); + } + + currentGroupName = dateText; + currentGroup = [item]; + } else { + currentGroup.push(item); + } } - function getBackdropShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getTimersHtml(timers, options) { - options = options || {}; - var i; - var length; - var items = timers.map(function (t) { - t.Type = 'Timer'; - return t; + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup }); - var groups = []; - var currentGroupName = ''; - var currentGroup = []; - - for (i = 0, length = items.length; i < length; i++) { - var item = items[i]; - var dateText = ''; - - if (options.indexByDate !== false && item.StartDate) { - try { - var premiereDate = datetime.parseISO8601Date(item.StartDate, true); - dateText = datetime.toLocaleDateString(premiereDate, { - weekday: 'long', - month: 'short', - day: 'numeric' - }); - } catch (err) { - console.error('error parsing premiereDate:' + item.StartDate + '; error: ' + err); - } - } - - if (dateText != currentGroupName) { - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - currentGroupName = dateText; - currentGroup = [item]; - } else { - currentGroup.push(item); - } - } - - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - var html = ''; - - for (i = 0, length = groups.length; i < length; i++) { - var group = groups[i]; - - if (group.name) { - html += '
    '; - html += '

    ' + group.name + '

    '; - } - if (enableScrollX()) { - var scrollXClass = 'scrollX hiddenScrollX'; - - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
    '; - } else { - html += '
    '; - } - - html += cardBuilder.getCardsHtml({ - items: group.items, - shape: getBackdropShape(), - showParentTitleOrTitle: true, - showAirTime: true, - showAirEndTime: true, - showChannelName: false, - cardLayout: true, - centerText: false, - action: 'edit', - cardFooterAside: 'none', - preferThumb: true, - defaultShape: null, - coverImage: true, - allowBottomPadding: false, - overlayText: false, - showChannelLogo: true - }); - html += '
    '; - - if (group.name) { - html += '
    '; - } - } - - return Promise.resolve(html); } + let html = ''; + for (const group of groups) { + if (group.name) { + html += '
    '; + html += '

    ' + group.name + '

    '; + } - window.LiveTvHelpers = { - getTimersHtml: getTimersHtml - }; -}); + if (enableScrollX()) { + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; + } + html += '
    '; + } else { + html += '
    '; + } + + html += cardBuilder.getCardsHtml({ + items: group.items, + shape: getBackdropShape(), + showParentTitleOrTitle: true, + showAirTime: true, + showAirEndTime: true, + showChannelName: false, + cardLayout: true, + centerText: false, + action: 'edit', + cardFooterAside: 'none', + preferThumb: true, + defaultShape: null, + coverImage: true, + allowBottomPadding: false, + overlayText: false, + showChannelLogo: true + }); + + html += '
    '; + + if (group.name) { + html += '
    '; + } + } + return Promise.resolve(html); +} + +window.LiveTvHelpers = { + getTimersHtml: getTimersHtml +}; diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 4094a2552f..4bb3eb25d9 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -112,54 +112,69 @@ import 'detailtablecss'; }); defineRoute({ - path: '/dashboard.html', + alias: '/dashboard.html', + path: '/controllers/dashboard/dashboard.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dashboard' }); defineRoute({ - path: '/dashboardgeneral.html', + alias: '/dashboardgeneral.html', + path: '/controllers/dashboard/general.html', controller: 'dashboard/general', autoFocus: false, roles: 'admin' }); defineRoute({ - path: '/networking.html', + alias: '/networking.html', + path: '/controllers/dashboard/networking.html', autoFocus: false, roles: 'admin', controller: 'dashboard/networking' }); defineRoute({ - path: '/devices.html', + alias: '/devices.html', + path: '/controllers/dashboard/devices/devices.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/devices' }); defineRoute({ - path: '/device.html', + alias: '/device.html', + path: '/controllers/dashboard/devices/device.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/device' }); defineRoute({ - path: '/dlnaprofile.html', + alias: '/dlnaprofile.html', + path: '/controllers/dashboard/dlna/profile.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profile' }); defineRoute({ - path: '/dlnaprofiles.html', + alias: '/dlnaprofiles.html', + path: '/controllers/dashboard/dlna/profiles.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profiles' }); + defineRoute({ + alias: '/dlnasettings.html', + path: '/controllers/dashboard/dlna/settings.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/dlna/settings' + }); + defineRoute({ alias: '/addplugin.html', path: '/controllers/dashboard/plugins/add/index.html', @@ -169,54 +184,54 @@ import 'detailtablecss'; }); defineRoute({ - path: '/library.html', + alias: '/library.html', + path: '/controllers/dashboard/library.html', autoFocus: false, roles: 'admin', - controller: 'dashboard/mediaLibrary' + controller: 'dashboard/library' }); defineRoute({ - path: '/librarydisplay.html', + alias: '/librarydisplay.html', + path: '/controllers/dashboard/librarydisplay.html', autoFocus: false, roles: 'admin', controller: 'dashboard/librarydisplay' }); defineRoute({ - path: '/dlnasettings.html', - autoFocus: false, - roles: 'admin', - controller: 'dashboard/dlna/settings' - }); - - defineRoute({ - path: '/edititemmetadata.html', + alias: '/edititemmetadata.html', + path: '/controllers/edititemmetadata.html', controller: 'edititemmetadata', autoFocus: false }); defineRoute({ - path: '/encodingsettings.html', + alias: '/encodingsettings.html', + path: '/controllers/dashboard/encodingsettings.html', autoFocus: false, roles: 'admin', controller: 'dashboard/encodingsettings' }); defineRoute({ - path: '/log.html', + alias: '/log.html', + path: '/controllers/dashboard/logs.html', roles: 'admin', controller: 'dashboard/logs' }); defineRoute({ - path: '/metadataimages.html', + alias: '/metadataimages.html', + path: '/controllers/dashboard/metadataimages.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadataImages' }); defineRoute({ - path: '/metadatanfo.html', + alias: '/metadatanfo.html', + path: '/controllers/dashboard/metadatanfo.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadatanfo' @@ -239,7 +254,8 @@ import 'detailtablecss'; }); defineRoute({ - path: '/playbackconfiguration.html', + alias: '/playbackconfiguration.html', + path: '/controllers/dashboard/playback.html', autoFocus: false, roles: 'admin', controller: 'dashboard/playback' @@ -262,19 +278,22 @@ import 'detailtablecss'; }); defineRoute({ - path: '/home.html', + alias: '/home.html', + path: '/controllers/home.html', autoFocus: false, controller: 'home', type: 'home' }); defineRoute({ - path: '/search.html', + alias: '/search.html', + path: '/controllers/search.html', controller: 'searchpage' }); defineRoute({ - path: '/list.html', + alias: '/list.html', + path: '/controllers/list.html', autoFocus: false, controller: 'list' }); @@ -287,46 +306,53 @@ import 'detailtablecss'; }); defineRoute({ - path: '/livetv.html', + alias: '/livetv.html', + path: '/controllers/livetv.html', controller: 'livetv/livetvsuggested', autoFocus: false }); defineRoute({ - path: '/livetvguideprovider.html', + alias: '/livetvguideprovider.html', + path: '/controllers/livetvguideprovider.html', autoFocus: false, roles: 'admin', controller: 'livetvguideprovider' }); defineRoute({ - path: '/livetvsettings.html', + alias: '/livetvsettings.html', + path: '/controllers/livetvsettings.html', autoFocus: false, controller: 'livetvsettings' }); defineRoute({ - path: '/livetvstatus.html', + alias: '/livetvstatus.html', + path: '/controllers/livetvstatus.html', autoFocus: false, roles: 'admin', controller: 'livetvstatus' }); defineRoute({ - path: '/livetvtuner.html', + alias: '/livetvtuner.html', + path: '/controllers/livetvtuner.html', autoFocus: false, roles: 'admin', controller: 'livetvtuner' }); defineRoute({ - path: '/movies.html', + alias: '/movies.html', + path: '/controllers/movies/movies.html', autoFocus: false, controller: 'movies/moviesrecommended' }); defineRoute({ - path: '/music.html', + alias: '/music.html', + path: '/controllers/music/music.html', controller: 'music/musicrecommended', autoFocus: false }); @@ -340,82 +366,94 @@ import 'detailtablecss'; }); defineRoute({ - path: '/scheduledtask.html', + alias: '/scheduledtask.html', + path: '/controllers/dashboard/scheduledtasks/scheduledtask.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtask' }); defineRoute({ - path: '/scheduledtasks.html', + alias: '/scheduledtasks.html', + path: '/controllers/dashboard/scheduledtasks/scheduledtasks.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtasks' }); defineRoute({ - path: '/serveractivity.html', + alias: '/serveractivity.html', + path: '/controllers/dashboard/serveractivity.html', autoFocus: false, roles: 'admin', controller: 'dashboard/serveractivity' }); defineRoute({ - path: '/apikeys.html', + alias: '/apikeys.html', + path: '/controllers/dashboard/apikeys.html', autoFocus: false, roles: 'admin', controller: 'dashboard/apikeys' }); defineRoute({ - path: '/streamingsettings.html', + alias: '/streamingsettings.html', + path: '/controllers/dashboard/streaming.html', autoFocus: false, roles: 'admin', controller: 'dashboard/streaming' }); defineRoute({ - path: '/tv.html', + alias: '/tv.html', + path: '/controllers/shows/tvrecommended.html', autoFocus: false, controller: 'shows/tvrecommended' }); defineRoute({ - path: '/useredit.html', + alias: '/useredit.html', + path: '/controllers/dashboard/users/useredit.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/useredit' }); defineRoute({ - path: '/userlibraryaccess.html', + alias: '/userlibraryaccess.html', + path: '/controllers/dashboard/users/userlibraryaccess.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userlibraryaccess' }); defineRoute({ - path: '/usernew.html', + alias: '/usernew.html', + path: '/controllers/dashboard/users/usernew.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/usernew' }); defineRoute({ - path: '/userparentalcontrol.html', + alias: '/userparentalcontrol.html', + path: '/controllers/dashboard/users/userparentalcontrol.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userparentalcontrol' }); defineRoute({ - path: '/userpassword.html', + alias: '/userpassword.html', + path: '/controllers/dashboard/users/userpassword.html', autoFocus: false, controller: 'dashboard/users/userpasswordpage' }); defineRoute({ - path: '/userprofiles.html', + alias: '/userprofiles.html', + path: '/controllers/dashboard/users/userprofiles.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userprofilespage' @@ -438,10 +476,11 @@ import 'detailtablecss'; }); defineRoute({ - path: '/wizardlibrary.html', + alias: '/wizardlibrary.html', + path: '/controllers/wizard/library.html', autoFocus: false, anonymous: true, - controller: 'dashboard/mediaLibrary' + controller: 'dashboard/library' }); defineRoute({ diff --git a/src/scripts/settings/webSettings.js b/src/scripts/settings/webSettings.js index 800b56ec7c..2ffe290d88 100644 --- a/src/scripts/settings/webSettings.js +++ b/src/scripts/settings/webSettings.js @@ -1,21 +1,40 @@ let data; -function getConfig() { +async function getConfig() { if (data) return Promise.resolve(data); - return fetch('config.json?nocache=' + new Date().getUTCMilliseconds()).then(response => { - data = response.json(); + try { + const response = await fetch('config.json', { + cache: 'no-cache' + }); + + if (!response.ok) { + throw new Error('network response was not ok'); + } + + data = await response.json(); + return data; - }).catch(error => { - console.warn('web config file is missing so the template will be used'); + } catch (error) { + console.warn('failed to fetch the web config file:', error); return getDefaultConfig(); - }); + } } -function getDefaultConfig() { - return fetch('config.template.json').then(function (response) { - data = response.json(); +async function getDefaultConfig() { + try { + const response = await fetch('config.template.json', { + cache: 'no-cache' + }); + + if (!response.ok) { + throw new Error('network response was not ok'); + } + + data = await response.json(); return data; - }); + } catch (error) { + console.error('failed to fetch the default web config file:', error); + } } export function getMultiServer() { diff --git a/src/scripts/site.js b/src/scripts/site.js index aa74411af4..8c990077f3 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -271,17 +271,16 @@ function initClient() { } function createConnectionManager() { - return require(['connectionManagerFactory', 'apphost', 'credentialprovider', 'events', 'userSettings'], function (ConnectionManager, apphost, credentialProvider, events, userSettings) { + return require(['connectionManagerFactory', 'apphost', 'credentialprovider', 'events', 'userSettings'], function (ConnectionManager, appHost, credentialProvider, events, userSettings) { + appHost = appHost.default || appHost; + var credentialProviderInstance = new credentialProvider(); - var promises = [apphost.getSyncProfile(), apphost.init()]; + var promises = [appHost.init()]; return Promise.all(promises).then(function (responses) { - var deviceProfile = responses[0]; - var capabilities = Dashboard.capabilities(apphost); + var capabilities = Dashboard.capabilities(appHost); - capabilities.DeviceProfile = deviceProfile; - - var connectionManager = new ConnectionManager(credentialProviderInstance, apphost.appName(), apphost.appVersion(), apphost.deviceName(), apphost.deviceId(), capabilities); + var connectionManager = new ConnectionManager(credentialProviderInstance, appHost.appName(), appHost.appVersion(), appHost.deviceName(), appHost.deviceId(), capabilities); defineConnectionManager(connectionManager); bindConnectionManagerEvents(connectionManager, events, userSettings); @@ -292,7 +291,7 @@ function initClient() { return require(['apiclient'], function (apiClientFactory) { console.debug('creating ApiClient singleton'); - var apiClient = new apiClientFactory(Dashboard.serverAddress(), apphost.appName(), apphost.appVersion(), apphost.deviceName(), apphost.deviceId()); + var apiClient = new apiClientFactory(Dashboard.serverAddress(), appHost.appName(), appHost.appVersion(), appHost.deviceName(), appHost.deviceId()); apiClient.enableAutomaticNetworking = false; apiClient.manualAddressOnly = true; @@ -350,6 +349,8 @@ function initClient() { } function getLayoutManager(layoutManager, appHost) { + layoutManager = layoutManager.default || layoutManager; + appHost = appHost.default || appHost; if (appHost.getDefaultLayout) { layoutManager.defaultLayout = appHost.getDefaultLayout(); } @@ -469,6 +470,8 @@ function initClient() { } require(['apphost', 'css!assets/css/librarybrowser'], function (appHost) { + appHost = appHost.default || appHost; + loadPlugins(appHost, browser).then(function () { onAppReady(browser); }); @@ -517,6 +520,8 @@ function initClient() { // ensure that appHost is loaded in this point require(['apphost', 'appRouter'], function (appHost, appRouter) { + appHost = appHost.default || appHost; + window.Emby = {}; console.debug('onAppReady: loading dependencies'); diff --git a/src/strings/af.json b/src/strings/af.json index 3b82f0363c..8e34e87d60 100644 --- a/src/strings/af.json +++ b/src/strings/af.json @@ -47,7 +47,6 @@ "TabGenres": "Genres", "TabFavorites": "Gunstellinge", "TabEpisodes": "Episodes", - "TabDisplay": "Vertoon", "TabDirectPlay": "Speel Direk", "TabDevices": "Toestelle", "TabDashboard": "Paneelbord", @@ -131,9 +130,7 @@ "TellUsAboutYourself": "Vertel ons van jouself", "TabUsers": "Gebruikers", "TabUpcoming": "Komende", - "TabTranscoding": "Transkodering", "TabTrailers": "Voorprente", - "TabSuggestions": "Voorstelle", "TabStreaming": "Stroom", "TabSongs": "Liedjies", "TabShows": "Programme", @@ -147,7 +144,6 @@ "TabProfiles": "Profiele", "TabProfile": "Profiel", "TabPlaylists": "Speel lyste", - "TabPlayback": "Terugspeel", "TabPassword": "Wagwoord", "TabParentalControl": "Ouer Beheer", "TabOther": "Ander", @@ -158,7 +154,6 @@ "TabMusicVideos": "Musiek Videos", "TabMusic": "Musiek", "TabMovies": "Rolprente", - "TabMetadata": "Meta Inligting", "TabLogs": "Logs", "TabLiveTV": "Lewendige TV" } diff --git a/src/strings/ar.json b/src/strings/ar.json index eb7d6f4249..862c985c85 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -11,7 +11,6 @@ "BirthPlaceValue": "مكان الميلاد: {0}", "Browse": "تصفح", "MessageBrowsePluginCatalog": "تصفح قائمتنا للملحق لترى المتوفر من الملاحق.", - "ButtonAdd": "إضافة", "ButtonAddMediaLibrary": "إضافة مكتبة وسائط", "ButtonAddScheduledTaskTrigger": "إضافة زناد", "ButtonAddServer": "إضافة خادم", @@ -36,7 +35,6 @@ "ButtonForgotPassword": "نسيت كلمة السر", "ButtonFullscreen": "ملء الشاشة", "ButtonGuide": "الدليل", - "ButtonHelp": "المساعدة", "ButtonHome": "الرئيسية", "ButtonInfo": "معلومات", "ButtonLibraryAccess": "صلاحيات المكتبة", @@ -58,14 +56,11 @@ "ButtonRefreshGuideData": "إعادة تنشيط بيانات الدليل", "ButtonRemove": "إزالة", "ButtonRename": "إعادة التسمية", - "ButtonRepeat": "كرر", "ButtonResetEasyPassword": "إعادة تهيئة الرمز الشخصي الميسر", "ButtonResetPassword": "إعادة تهيئة كلمة السر", "ButtonRestart": "إعادة التشغيل", "ButtonResume": "استأنف", "ButtonRevoke": "أرفض", - "ButtonSave": "حفظ", - "ButtonSearch": "بحث", "ButtonSelectDirectory": "إختر الدليلة", "ButtonSelectServer": "إختر الخادم", "ButtonSelectView": "إختر طريقة عرض", @@ -176,14 +171,11 @@ "HeaderDevices": "الأجهزة", "HeaderDirectPlayProfile": "عريضة التشغيل المباشر", "HeaderDirectPlayProfileHelp": "أضف مباشرةً عريضة تشغيل للإشارة لأي صيغة يتمكن الجهاز من التعامل معه بتلقائية.", - "HeaderDisplay": "إظهار", "HeaderEasyPinCode": "الرمز الشخصي الميسر", "HeaderEpisodes": "الحلقات", "HeaderError": "حدث خطأ", "HeaderFeatureAccess": "صلاحية الخاصية", - "HeaderFeatures": "المواصقات", "HeaderFetchImages": "إطهار الصور:", - "HeaderFilters": "مرشحات", "HeaderForKids": "للأطفال", "HeaderForgotPassword": "نسيت كلمة السر", "HeaderFrequentlyPlayed": "تم تشغيله مراراً", @@ -270,7 +262,6 @@ "HeaderSubtitleProfiles": "عرائض الترجمة", "HeaderSubtitleProfilesHelp": "عرائض الترجمة تصف صيغ الترجمة المدعومة على الأجهزة المختلفة.", "HeaderSystemDlnaProfiles": "عرائض النظام", - "HeaderTags": "البطاقات", "HeaderTaskTriggers": "زنادات المهام", "HeaderThisUserIsCurrentlyDisabled": "هذا المستخدم موقف حالياً", "HeaderTracks": "المقاطع الصوتية", @@ -307,7 +298,6 @@ "LabelAlbumArtMaxWidthHelp": "الدقة القصوى لرسومات الألبوم المظهّرة عبر سمة upnp:albumArtURI.", "LabelAlbumArtPN": "رسومات الألبوم PN:", "LabelAlbumArtists": "فنانو الألبومات:", - "LabelAll": "الجميع", "LabelAllowHWTranscoding": "السماح بالتشفير البيني بعتاد الحاسب", "LabelAppName": "اسم التطبيق", "LabelAppNameExample": "مثال: Sickbeard، Sonarr", @@ -796,7 +786,6 @@ "TabAdvanced": "متقدم", "TabAlbumArtists": "فنانو الألبومات", "TabAlbums": "الألبومات", - "TabArtists": "الفنانون", "TabCatalog": "الكتالوج", "TabChannels": "القنوات", "TabCodecs": "الكودكات", @@ -805,7 +794,6 @@ "TabDashboard": "لوحة العدادات", "TabDevices": "الأجهزة", "TabDirectPlay": "تشغيل مباشر", - "TabDisplay": "إظهار", "TabEpisodes": "الحلقات", "TabFavorites": "المفضلة", "TabGenres": "أنواع الأفلام", @@ -814,7 +802,6 @@ "TabLatest": "الاخير", "TabLiveTV": "التلفاز المباشر", "TabLogs": "الكشوفات", - "TabMetadata": "واصفات البيانات", "TabMovies": "الفيلم", "TabMusic": "الموسيقى", "TabMusicVideos": "الفيديوهات الموسيقية", @@ -825,7 +812,6 @@ "TabOther": "أخرى", "TabParentalControl": "التحكم الأبوي", "TabPassword": "كلمة السر", - "TabPlayback": "تشغيل", "TabPlaylists": "قوائم التشغيل", "TabPlugins": "الملحقات", "TabProfile": "عريضة", @@ -840,9 +826,7 @@ "TabShows": "المسلسلات", "TabSongs": "الاغانى", "TabStreaming": "التشغيل التدفقي", - "TabSuggestions": "مقترحات", "TabTrailers": "العروض الإعلانية", - "TabTranscoding": "التشفير البيني", "TabUpcoming": "القادم", "TabUsers": "المستخدمون", "TellUsAboutYourself": "اخبرنا عن نفسك", @@ -1057,7 +1041,6 @@ "ClientSettings": "إعدادات التطبيق", "ButtonTogglePlaylist": "قائمة التشغيل", "BoxSet": "طقم", - "ButtonToggleContextMenu": "المزيد", "ButtonSplit": "تقسيم", "AllowFfmpegThrottlingHelp": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل.", "InstallingPackage": "تثبيت {0} (الإصدار {1})", diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index 6eee5e403b..20337a876a 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -20,7 +20,6 @@ "BirthPlaceValue": "Родно място: {0}", "Books": "Книги", "Browse": "Разглеждане", - "ButtonAdd": "Добавяне", "ButtonAddMediaLibrary": "Добавяне на библиотека", "ButtonAddScheduledTaskTrigger": "Добавяне на спусък", "ButtonAddServer": "Добавяне на сървър", @@ -41,7 +40,6 @@ "ButtonForgotPassword": "Забравена парола", "ButtonGotIt": "Добре", "ButtonGuide": "Справочник", - "ButtonHelp": "Помощ", "ButtonHome": "Начало", "ButtonInfo": "Сведения", "ButtonLibraryAccess": "Достъп до библиотеката", @@ -65,9 +63,7 @@ "ButtonResetPassword": "Зануляване на паролата", "ButtonRestart": "Повторно пускане", "ButtonResume": "Продължаване", - "ButtonSave": "Запазване", "ButtonScanAllLibraries": "Сканиране на всички библиотеки", - "ButtonSearch": "Търсене", "ButtonSelectDirectory": "Изберете папка", "ButtonSend": "Изпращане", "ButtonSettings": "Настройки", @@ -171,16 +167,13 @@ "HeaderDeviceAccess": "Достъп на устройствата", "HeaderDevices": "Устройства", "HeaderDirectPlayProfile": "Direct Play профил", - "HeaderDisplay": "Показване", "HeaderDownloadSync": "Изтегляне и синхронизиране", "HeaderEasyPinCode": "Лесен ПИН код", "HeaderEditImages": "Редактиране на изображенията", "HeaderEnabledFields": "Включени полета", "HeaderError": "Грешка", "HeaderFeatureAccess": "Достъп до функции", - "HeaderFeatures": "Функции", "HeaderFetchImages": "Свали изображения:", - "HeaderFilters": "Филтри", "HeaderForKids": "Детски", "HeaderForgotPassword": "Забравена парола", "HeaderFrequentlyPlayed": "Често пускани", @@ -253,7 +246,6 @@ "HeaderStatus": "Състояние", "HeaderSubtitleAppearance": "Облик на субтитрите", "HeaderSystemDlnaProfiles": "Системни профили", - "HeaderTags": "Етикети", "HeaderTaskTriggers": "Спусъци на задачи", "HeaderTracks": "Песни", "HeaderTranscodingProfile": "Профил на транскодинг", @@ -677,7 +669,6 @@ "TabAdvanced": "Допълнителни", "TabAlbumArtists": "Изпълнители на албуми", "TabAlbums": "Албуми", - "TabArtists": "Изпълнители", "TabCatalog": "Каталог", "TabChannels": "Канали", "TabCodecs": "Кодеци", @@ -686,7 +677,6 @@ "TabDashboard": "Табло", "TabDevices": "Устройства", "TabDirectPlay": "Директно пускане", - "TabDisplay": "Показване", "TabEpisodes": "Епизоди", "TabFavorites": "Любими", "TabGenres": "Жанрове", @@ -695,7 +685,6 @@ "TabLatest": "Последни", "TabLiveTV": "Телевизия на живо", "TabLogs": "Журнали", - "TabMetadata": "Метаданни", "TabMovies": "Филми", "TabMusic": "Музика", "TabMusicVideos": "Музикални клипове", @@ -706,7 +695,6 @@ "TabOther": "Други", "TabParentalControl": "Родителски контрол", "TabPassword": "Парола", - "TabPlayback": "Възпроизвеждане", "TabPlaylists": "Списъци", "TabPlugins": "Приставки", "TabProfile": "Профил", @@ -721,9 +709,7 @@ "TabShows": "Предавания", "TabSongs": "Песни", "TabStreaming": "Излъчване", - "TabSuggestions": "Предложения", "TabTrailers": "Трейлъри", - "TabTranscoding": "Прекодиране", "TabUpcoming": "Предстоящи", "TabUsers": "Потребители", "Tags": "Етикети", @@ -813,7 +799,6 @@ "ButtonStart": "Пускане", "ButtonSelectView": "Изберете изглед", "ButtonSelectServer": "Изберете сървър", - "ButtonRepeat": "Повтаряне", "ButtonNetwork": "Мрежа", "ButtonFullscreen": "На цял екран", "ButtonDown": "Надолу", @@ -888,7 +873,6 @@ "DeleteImageConfirmation": "Сигурнили сте че искате да премахнете това Изображение?", "DeleteImage": "Премахване на Исображение", "ButtonTogglePlaylist": "Списък с изпълнения", - "ButtonToggleContextMenu": "Повече", "ErrorSavingTvProvider": "Има проблем със запазването на ТВ доставчика.Убедете се ,че е достъпен и опитайте отново.", "ErrorPleaseSelectLineup": "Моля изберете списък и опитайте отново.Ако няма налични списъци се убедете ,че името,паролата и пощенския код са точни.", "ErrorStartHourGreaterThanEnd": "Времето за край трябва да бъде по-голямо от началното време.", @@ -1071,7 +1055,6 @@ "LabelAllowedRemoteAddressesMode": "Режим на филтъра за външни ИП адреси:", "LabelAllowedRemoteAddresses": "Филтър за външни ИП адреси:", "LabelAllowHWTranscoding": "Разреши хардуерно транскодиране", - "LabelAll": "Всички", "LabelAlbumArtMaxWidthHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.", "LabelAlbumArtMaxHeightHelp": "Максимална резолюция на обложките за албуми показани чрез upnp:albumArtURI.", "LabelAlbumArtMaxHeight": "Максимална височина на обложките за албуми:", diff --git a/src/strings/ca.json b/src/strings/ca.json index 7250a6cb31..4033ba56c4 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -15,7 +15,6 @@ "AttributeNew": "Nou", "Audio": "Àudio", "MessageBrowsePluginCatalog": "Consulta el nostre catàleg per veure els complements disponibles.", - "ButtonAdd": "Afegeix", "ButtonAddMediaLibrary": "Afegir Biblioteca Multimèdia", "ButtonAddScheduledTaskTrigger": "Afegir Disparador", "ButtonAddServer": "Afegeix Servidor", @@ -37,7 +36,6 @@ "ButtonForgotPassword": "He oblidat la contrasenya", "ButtonGotIt": "Entesos", "ButtonGuide": "Guia", - "ButtonHelp": "Ajuda", "ButtonHome": "Inici", "ButtonLibraryAccess": "Accés a la biblioteca", "ButtonManualLogin": "Inici de sessió manual", @@ -59,8 +57,6 @@ "ButtonResetPassword": "Reiniciar Contrasenya", "ButtonRestart": "Reiniciar", "ButtonResume": "Reprèn", - "ButtonSave": "Desa", - "ButtonSearch": "Cercar", "ButtonSelectDirectory": "Selecciona Directori", "ButtonSelectServer": "Seleccionar servidor", "ButtonSend": "Envia", @@ -159,15 +155,12 @@ "HeaderDeviceAccess": "Accés de Dispositiu", "HeaderDevices": "Dispositius", "HeaderDirectPlayProfile": "Perfil de Reproducció Directa", - "HeaderDisplay": "Visualització", "HeaderEasyPinCode": "Codi Pin Senzill", "HeaderEditImages": "Edita Imatges", "HeaderEnabledFields": "Camps Habilitats", "HeaderExternalIds": "Identificadors externs:", "HeaderFeatureAccess": "Accés a Funcions", - "HeaderFeatures": "Característiques", "HeaderFetchImages": "Obtingues Imatges:", - "HeaderFilters": "Filtres", "HeaderForgotPassword": "He oblidat la contrasenya", "HeaderFrequentlyPlayed": "Reproduït Freqüentment", "HeaderGenres": "Gèneres", @@ -641,7 +634,6 @@ "TabAccess": "Accés", "TabAdvanced": "Avançat", "TabAlbums": "Àlbums", - "TabArtists": "Artistes:", "TabCatalog": "Catàleg", "TabChannels": "Canals", "TabCodecs": "Còdecs", @@ -650,7 +642,6 @@ "TabDashboard": "Tauler de Control", "TabDevices": "Dispositius", "TabDirectPlay": "Reproducció Directa", - "TabDisplay": "Visualització", "TabEpisodes": "Episodis", "TabFavorites": "Preferits", "TabGenres": "Gèneres", @@ -658,7 +649,6 @@ "TabInfo": "Informació", "TabLatest": "Novetats", "TabLiveTV": "TV en Directe", - "TabMetadata": "Metadades", "TabMovies": "Pel·lícules", "TabMusic": "Música", "TabMusicVideos": "Vídeos musicals", @@ -669,7 +659,6 @@ "TabOther": "Altres", "TabParentalControl": "Control Parental", "TabPassword": "Contrasenya", - "TabPlayback": "Reproducció", "TabPlaylists": "Llistes de reproducció", "TabPlugins": "Complements", "TabProfile": "Perfil", @@ -682,9 +671,7 @@ "TabSettings": "Preferències", "TabShows": "Programes", "TabSongs": "Cançons", - "TabSuggestions": "Suggerències", "TabTrailers": "Tràilers", - "TabTranscoding": "Transcodificació", "TabUpcoming": "Properament", "TabUsers": "Usuaris", "Tags": "Etiquetes", @@ -742,7 +729,6 @@ "ButtonSelectView": "Selecciona la vista", "ButtonScanAllLibraries": "Escanejar totes les biblioteques", "ButtonRevoke": "Revocar", - "ButtonRepeat": "Repetir", "ButtonRename": "Canviar el nom", "ButtonNetwork": "Xarxa", "ButtonInfo": "Informació", @@ -777,7 +763,7 @@ "Aired": "Transmès", "AirDate": "Data d'emissió", "AdditionalNotificationServices": "Examineu el catàleg de complements per instal·lar serveis de notificació addicionals.", - "AddedOnValue": "Afegit {0}", + "AddedOnValue": "Afegit {0}", "Actor": "Actor", "Absolute": "Absolut", "ClientSettings": "Configuració del client", @@ -785,14 +771,13 @@ "ChannelNameOnly": "Número de canal", "ChangingMetadataImageSettingsNewContent": "Els canvis als paràmetres de descàrrega de metadades o d'obra d'art només s'apliquen al contingut nou afegit a la biblioteca. Per aplicar els canvis als títols existents, haureu de refrescar les metadades manualment.", "ButtonTogglePlaylist": "Llista de reproducció", - "ButtonToggleContextMenu": "més", "ButtonOff": "Apagar", - "BurnSubtitlesHelp": "Determina si el servidor hauria de gravar-se en els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VOBSUB, PGS, SUB, IDX) i certs subtítols ASS o SSA.", + "BurnSubtitlesHelp": "Determina si el servidor hauria de gravar els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VOBSUB, PGS, SUB, IDX) i certs subtítols ASS o SSA.", "Browse": "Navega", "BoxRear": "Caixa (posterior)", "BoxSet": "conjunt de caixes", "Box": "Caixa", - "BookLibraryHelp": "Els àudio i llibres de text són compatibles. Reviseu la {0} guia de denominació de llibres {1}.", + "BookLibraryHelp": "L'àudio i els llibres de text són compatibles. Reviseu la {0} guia de denominació de llibres {1}.", "Backdrops": "Fons", "Backdrop": "Fons", "Artist": "Artista", @@ -802,5 +787,22 @@ "AllowOnTheFlySubtitleExtractionHelp": "Els subtítols incrustats es poden extreure de vídeos i entregar-los a clients en text senzill per tal d'evitar la transcodificació de vídeo. En alguns sistemes, això pot trigar molt i fer que la reproducció de vídeo s’aturi durant el procés d’extracció. Desactiveu-ho per tenir subtítols incrustats incrustats amb la transcodificació de vídeo quan no són compatibles amb el dispositiu client de forma nativa.", "AlbumArtist": "Album artista", "Album": "Album", - "ButtonSyncPlay": "SyncPlay" + "ButtonSyncPlay": "SyncPlay", + "CriticRating": "Ràting de la crítica", + "CopyStreamURLSuccess": "L'URL s'ha copiat correctament.", + "CopyStreamURL": "Copiar l'URL de reproducció", + "ContinueWatching": "Continuar mirant", + "ConfirmEndPlayerSession": "Vols tancar Jellyfin a {0}?", + "ConfirmDeleteItems": "L'esborrat d'aquests elements els eliminarà del sistema de fitxers i de la biblioteca multimèdia. Estàs segur que vols continuar?", + "ConfirmDeleteItem": "L'esborrat d'aquest element l'eliminarà del sistema de fitxers i de la biblioteca multimèdia. Estàs segur que vols continuar?", + "ConfigureDateAdded": "Configura com es determina la data d'afegit en el quadre de comandament dins les Preferències de la biblioteca", + "CommunityRating": "Ràting comunitari", + "ColorTransfer": "Transferència de color", + "ColorSpace": "Espai de color", + "ColorPrimaries": "Colors primaris", + "DefaultMetadataLangaugeDescription": "Aquests són els teus valors per defecte i poden ser personalitats per cada biblioteca.", + "Default": "Per defecte", + "DatePlayed": "Data reproduït", + "DateAdded": "Data d'afegit", + "CustomDlnaProfilesHelp": "Crear un perfil personalitzat per a un nou dispositiu o substitueix un perfil de sistema." } diff --git a/src/strings/cs.json b/src/strings/cs.json index 80415c24af..85d4b175f2 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -34,7 +34,6 @@ "Box": "Pouzdro", "BoxRear": "Zadní část pouzdra", "MessageBrowsePluginCatalog": "Prohlédněte si náš katalog, kde najdete dostupné zásuvné moduly.", - "ButtonAdd": "Přidat", "ButtonAddMediaLibrary": "Přidat knihovnu médií", "ButtonAddScheduledTaskTrigger": "Přidat Spouštěč", "ButtonAddServer": "Přidat server", @@ -60,7 +59,6 @@ "ButtonFullscreen": "Celá obrazovka", "ButtonGotIt": "Mám to", "ButtonGuide": "Programový průvodce", - "ButtonHelp": "Nápověda", "ButtonHome": "Domů", "ButtonLibraryAccess": "Přístup ke knihovně", "ButtonManualLogin": "Manuální přihlášení", @@ -80,13 +78,10 @@ "ButtonRefreshGuideData": "Obnovit data programového průvodce", "ButtonRemove": "Odstranit", "ButtonRename": "Přejmenovat", - "ButtonRepeat": "Opakovat", "ButtonResetEasyPassword": "Obnovit easy pin kód", "ButtonResetPassword": "Obnovit heslo", "ButtonResume": "Pokračovat", "ButtonRevoke": "Odvolat", - "ButtonSave": "Uložit", - "ButtonSearch": "Hledání", "ButtonSelectDirectory": "Vybrat složku", "ButtonSelectServer": "Výběr serveru", "ButtonSelectView": "Výběr zobrazení", @@ -114,7 +109,7 @@ "Collections": "Kolekce", "CommunityRating": "Hodnocení komunity", "Composer": "Skladatel", - "ConfigureDateAdded": "Konfigurace přidání data je definována v nastavení knihovny na nástěnce serveru Jellyfin", + "ConfigureDateAdded": "Konfigurace přidání data je definována v nastavení knihovny na nástěnce", "ConfirmDeleteImage": "Odstranit obrázek?", "ConfirmDeleteItem": "Smazáním položky odstraníte soubor jak z knihovny médií tak ze souborového systému. Jste si jisti, že chcete pokračovat?", "ConfirmDeleteItems": "Odstraněním těchto položek odstraníte vaše média jak z knihovny médií, tak i ze souborového systému. Jste si jisti, že chcete pokračovat?", @@ -177,10 +172,10 @@ "EndsAtValue": "Končí v {0}", "Episodes": "Epizody", "ErrorAddingListingsToSchedulesDirect": "Došlo k chybě při přidání sestavy do účtu vašeho Direct plánovače. Direct plánovač umožňuje pouze omezený počet sestav na účet. Možná se budete muset přihlásit do webových stránek Direct plánovače a před pokračováním odstranit ostatní výpisy ze svého účtu.", - "ErrorAddingMediaPathToVirtualFolder": "Při přidávání cesty k médiím došlo k chybě. Zkontrolujte zda je zadaná složka platná a zda má server Jellyfin k této složce přístup.", + "ErrorAddingMediaPathToVirtualFolder": "Při přidávání cesty k médiím došlo k chybě. Zkontrolujte zda je zadaná složka platná a zda má Jellyfin k této složce přístup.", "ErrorAddingTunerDevice": "Došlo k chybě při přidání zařízení tuneru. Prosím, ujistěte se, že je přístupný a zkuste to znovu.", "ErrorAddingXmlTvFile": "Nastala chyba při přístupu k XMLTV souboru. Ujistěte se, že soubor existuje a zkuste jej znovu otevřít.", - "ErrorDeletingItem": "Při odstranění položky ze serveru Jellyfin došlo k chybě. Zkontrolujte prosím, zda má server Jellyfin oprávnění k zápisu do složky médií, a zkuste to prosím znovu.", + "ErrorDeletingItem": "Při odstranění položky ze serveru došlo k chybě. Zkontrolujte prosím, zda má Jellyfin oprávnění k zápisu do složky médií, a zkuste to prosím znovu.", "ErrorGettingTvLineups": "Došlo k chybě při stahování TV sestav. Ujistěte se prosím, že zadané informace jsou správné a zkuste to znovu.", "ErrorStartHourGreaterThanEnd": "Čas ukončení musí být větší než čas startu.", "ErrorPleaseSelectLineup": "Vyberte prosím sestavu a zkuste to znovu. Pokud nejsou k dispozici žádné sestavy, zkontrolujte, zda je vaše uživatelské jméno, heslo a poštovní směrovací číslo správné.", @@ -268,7 +263,6 @@ "HeaderDevices": "Zařízení", "HeaderDirectPlayProfile": "Profil Direct Play", "HeaderDirectPlayProfileHelp": "Přidat profil přímého přehrání pro definici formátů, které přístroj zvládne přehrávat nativně.", - "HeaderDisplay": "Zobrazení", "HeaderEasyPinCode": "Jednoduchý pin kód", "HeaderEditImages": "Editovat obrázky", "HeaderEnabledFields": "Povolené pole", @@ -276,9 +270,7 @@ "HeaderEpisodes": "Epizody", "HeaderError": "Chyba", "HeaderFeatureAccess": "Přístup k funkcím", - "HeaderFeatures": "Funkce", "HeaderFetchImages": "Načíst obrázky:", - "HeaderFilters": "Filtry", "HeaderForKids": "Pro děti", "HeaderForgotPassword": "Zapomenuté heslo", "HeaderFrequentlyPlayed": "Nejčastěji přehráváno", @@ -379,7 +371,6 @@ "HeaderSubtitleProfiles": "Profily titulků", "HeaderSubtitleProfilesHelp": "Profily titulků popisují formáty titulků, které daná zařízení podporují.", "HeaderSystemDlnaProfiles": "Systémové profily", - "HeaderTags": "Tagy", "HeaderTaskTriggers": "Spouštěče úloh", "HeaderThisUserIsCurrentlyDisabled": "Tento uživatel je aktuálně zakázán", "HeaderTracks": "Stopy", @@ -430,7 +421,6 @@ "LabelAlbumArtMaxWidthHelp": "Maximální rozlišení alb nabízených prostřednictvím upnp:albumArtURI.", "LabelAlbumArtPN": "Alba PN:", "LabelAlbumArtists": "Alba umělce:", - "LabelAll": "Vše", "LabelAllowHWTranscoding": "Povolit hardwarové překódování", "LabelAppName": "Název aplikace", "LabelAppNameExample": "Příklad: Sickbeard, Sonarr", @@ -439,7 +429,7 @@ "LabelAudio": "Zvuk", "LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:", "LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:", - "LabelBindToLocalNetworkAddressHelp": "Změní místní IP adresu serveru HTTP. Pokud je ponecháno prázdné, server bude svázán se všemi dostupnými adresami. Změna této hodnoty vyžaduje restartování serveru Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Změní místní IP adresu serveru HTTP. Pokud je ponecháno prázdné, server bude svázán se všemi dostupnými adresami. Změna této hodnoty vyžaduje restartování.", "LabelBirthDate": "Datum narození:", "LabelBirthYear": "Rok narození:", "LabelBlastMessageInterval": "Doba zobrazení zprávy", @@ -489,7 +479,7 @@ "LabelEnableBlastAliveMessages": "Vytroubit zprávu do světa", "LabelEnableBlastAliveMessagesHelp": "Tuto možnost povolte, pokud není server zjistitelný jinými UPnP zařízeními v síti.", "LabelEnableDlnaClientDiscoveryInterval": "Interval pro vyhledání klienta", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi vyhledáváním SSDP, které Jellyfin provádí.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi vyhledáváním SSDP.", "LabelEnableDlnaDebugLogging": "Povolit DLNA protokolování (pro ladění)", "LabelEnableDlnaDebugLoggingHelp": "Vytváří velké soubory se záznamy a doporučuje se používat pouze pro potřeby odstraňování problémů.", "LabelEnableDlnaPlayTo": "Povolit DLNA přehrávání", @@ -754,13 +744,13 @@ "MessageConfirmProfileDeletion": "Jste si jisti, že chcete smazat tento profil?", "MessageConfirmRecordingCancellation": "Zrušit nahrávání?", "MessageConfirmRemoveMediaLocation": "Jste si jist, že chcete odstranit toto umístění?", - "MessageConfirmRestart": "Opravdu chcete restartovat server Jellyfin?", - "MessageConfirmRevokeApiKey": "Opravdu chcete zrušit tento klíč k API? Připojení dané aplikace k serveru Jellyfin bude náhle ukončeno.", + "MessageConfirmRestart": "Opravdu chcete restartovat Jellyfin?", + "MessageConfirmRevokeApiKey": "Opravdu chcete zrušit tento klíč k API? Připojení dané aplikace k tomuto serveru bude náhle ukončeno.", "MessageConfirmShutdown": "Jste si jisti, že chcete server vypnout?", "MessageContactAdminToResetPassword": "Kontaktujte, prosím, vašeho systémového administrátora k obnovení vašeho hesla.", "MessageCreateAccountAt": "Vytvořit účet v {0}", "MessageDeleteTaskTrigger": "Opravdu si přejete odebrat spouštění úlohy?", - "MessageDirectoryPickerBSDInstruction": "V operačním systému FreeBSD či FreeNAS může být nutné nakonfigurovat úložiště přímo pomocí izolační funkce jail, aby k němu měl Jellyfin přístup.", + "MessageDirectoryPickerBSDInstruction": "U BSD může být potřeba nakonfigurovat úložiště přímo pomocí izolační funkce jail v FreeNAS, aby měl Jellyfin přístup k vašim médiím.", "MessageDirectoryPickerLinuxInstruction": "Pro systémy Linux jako Arch Linux, CentOS, Debian, Fedora, OpenSUSE nebo Ubuntu musíte udělit uživateli služby oprávnění alespoň pro čtení.", "MessageDownloadQueued": "Stažení zařazeno.", "MessageFileReadError": "Došlo k chybě při čtení souboru. Prosím zkuste to znovu.", @@ -780,7 +770,7 @@ "MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.", "MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.", "MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.", - "MessagePluginInstallDisclaimer": "Zásuvné moduly vytvořené členy komunity Jellyfin jsou skvělým způsobem, jak si zlepšit prožitek z používání projektu Jellyfin. Před instalací se prosím seznamte se všemi dopady, které mohou doplňky mít na server Jellyfin, např.: pomalejší skenování knihovny, další zpracování na pozadí nebo snížení stability systému.", + "MessagePluginInstallDisclaimer": "Zásuvné moduly vytvořené členy komunity jsou skvělým způsobem, jak si zlepšit prožitek pomocí dalších funkcí. Před instalací se prosím seznamte se všemi dopady, které mohou doplňky na server mít, např.: pomalejší skenování knihovny, delší zpracování na pozadí nebo snížená stabilita systému.", "MessageReenableUser": "Viz níže pro znovuzapnutí", "MessageSettingsSaved": "Nastavení uloženo.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií:", @@ -930,7 +920,7 @@ "OptionRuntime": "Délka", "OptionSaturday": "Sobota", "OptionSaveMetadataAsHidden": "Ukládat metadata a obrázky jako skryté soubory", - "OptionSaveMetadataAsHiddenHelp": "Změna se projeví u všech nově uložených metadat. Existující soubory metadat se aktualizují při příštím uložení serverem Jellyfin.", + "OptionSaveMetadataAsHiddenHelp": "Změna se projeví u všech nově uložených metadat. Existující soubory metadat se aktualizují při příštím uložení serverem.", "OptionSpecialEpisode": "Speciální", "OptionSubstring": "subřetězec", "OptionSunday": "Neděle", @@ -975,7 +965,7 @@ "PleaseAddAtLeastOneFolder": "Přidejte prosím nejméně jednu složku do této knihovny pomocí tlačítka Přidat.", "PleaseConfirmPluginInstallation": "Pro potvrzení, že jste si přečetli text výše a chcete pokračovat v instalaci zásuvných modulů, klikněte na tlačítko OK.", "PleaseEnterNameOrId": "Prosím, zadejte název nebo externí Id.", - "PleaseRestartServerName": "Prosím restartuje server Jellyfin - {0}.", + "PleaseRestartServerName": "Prosím restartuje Jellyfin na serveru {0}.", "PleaseSelectTwoItems": "Vyberte nejméně dvě položky prosím.", "Premiere": "Premiéra", "Premieres": "Premiéry", @@ -1031,9 +1021,9 @@ "SeriesRecordingScheduled": "Plán nahrávání seriálu.", "SeriesSettings": "Nastavení seriálu", "SeriesYearToPresent": "{0} - Současnost", - "ServerNameIsRestarting": "Server Jellyfin - {0} se restartuje.", - "ServerNameIsShuttingDown": "Server Jellyfin - {0} se vypíná.", - "ServerUpdateNeeded": "Tento server Jellyfin je nutné aktualizovat. Chcete-li stáhnout nejnovější verzi, navštivte prosím {0}", + "ServerNameIsRestarting": "Server {0} se restartuje.", + "ServerNameIsShuttingDown": "Server {0} se vypíná.", + "ServerUpdateNeeded": "Tento server je nutné aktualizovat. Chcete-li stáhnout nejnovější verzi, navštivte prosím {0}", "Settings": "Nastavení", "SettingsSaved": "Nastavení uloženo.", "SettingsWarning": "Změna těchto hodnot může způsobit nestabilitu nebo selhání připojení. Pokud narazíte na nějaké problémy, doporučujeme jej změnit zpět na výchozí hodnotu.", @@ -1062,7 +1052,6 @@ "TabAdvanced": "Pokročilé", "TabAlbumArtists": "Umělci alba", "TabAlbums": "Alba", - "TabArtists": "Umělec", "TabCatalog": "Katalog", "TabChannels": "Kanály", "TabCodecs": "Kodeky", @@ -1070,7 +1059,6 @@ "TabContainers": "Obaly", "TabDashboard": "Nástěnka", "TabDevices": "Zařízení", - "TabDisplay": "Zobrazení", "TabEpisodes": "Epizody", "TabFavorites": "Oblíbené", "TabGenres": "Žánry", @@ -1087,7 +1075,6 @@ "TabOther": "Další", "TabParentalControl": "Rodičovská kontrola", "TabPassword": "Heslo", - "TabPlayback": "Přehrávání", "TabPlaylists": "Playlisty", "TabPlugins": "Zásuvné moduly", "TabProfile": "Profil", @@ -1101,9 +1088,7 @@ "TabShows": "Seriály", "TabSongs": "Skladby", "TabStreaming": "Streamování", - "TabSuggestions": "Návrhy", "TabTrailers": "Upoutávky", - "TabTranscoding": "Překódování", "TabUpcoming": "Nadcházející", "TabUsers": "Uživatelé", "Tags": "Tagy", @@ -1169,7 +1154,7 @@ "AllowMediaConversionHelp": "Povolit nebo zakázat přístup k funkci konverze médií.", "AllowOnTheFlySubtitleExtraction": "Povolit extrahování titulků za běhu", "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky je možné vytáhnout z videa a dodat klientům v textové podobě, aby nebylo nutné video překódovat. Na některých systémech to může trvat dlouho a způsobit zasekávání videa. Pokud tuto funkci vypnete a klientské zařízení vložené titulky nepodporuje, při překódování budou vypáleny přímo do obrazu.", - "AllowRemoteAccess": "Povolit vzdálené připojení k tomuto serveru Jellyfin.", + "AllowRemoteAccess": "Povolit vzdálené připojení k tomuto serveru.", "AllowRemoteAccessHelp": "Pokud není zapnuto, všechna vzdálená připojení budou blokována.", "AllowedRemoteAddressesHelp": "Seznam IP adres nebo síťových masek oddělených čárkou pro sítě, ze kterých se lze vzdáleně připojit. Pokud necháte prázdné, všechny adresy budou povoleny.", "AnyLanguage": "Jakýkoli jazyk", @@ -1262,7 +1247,7 @@ "LabelLanNetworks": "Sítě LAN:", "LabelMaxStreamingBitrate": "Maximální kvalita streamování:", "LabelMetadata": "Metadata:", - "LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, zadání cesty ke sdílené složce umožní aplikacím Jellyfin na jiných zařízeních přímý přístup k souborům s médii. Například {0} nebo {1}.", + "LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, zadání cesty ke sdílené složce umožní klientům na jiných zařízeních přímý přístup k souborům s médii. Například {0} nebo {1}.", "LabelPersonRole": "Úloha:", "LabelPlaylist": "Playlist:", "LabelReasonForTranscoding": "Důvod pro překódování:", @@ -1342,7 +1327,7 @@ "OptionProtocolHttp": "HTTP", "OptionRequirePerfectSubtitleMatchHelp": "Vyžadování dokonalé shody filtruje titulky tak, aby obsahovaly pouze ty, které byly testovány a ověřeny s vaším přesným videosouborem. Zrušení zaškrtnutí tohoto políčka zvýší pravděpodobnost stahování titulků, ale zvýší pravděpodobnost chybného nebo nesprávného textu titulků.", "PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit při žádosti tohoto uživatele o resetování hesla.", - "MessagePluginInstalled": "Zásuvný modul byl úspěšně nainstalován. Server Jellyfin bude nutné restartovat, aby se změny projevily.", + "MessagePluginInstalled": "Zásuvný modul byl úspěšně nainstalován. Server bude nutné restartovat, aby se změny projevily.", "PreferEmbeddedTitlesOverFileNames": "Preferovat vložené názvy nad názvy souborů", "PreferEmbeddedTitlesOverFileNamesHelp": "Toto určuje výchozí název zobrazení, pokud nejsou k dispozici žádná metadata z internetu nebo místní metadata.", "Raised": "Vystupující", @@ -1351,7 +1336,7 @@ "SaveSubtitlesIntoMediaFoldersHelp": "Ukládání titulků vedle video souborů umožní jejich snadnější správu.", "ScanLibrary": "Skenovat knihovnu", "SeriesDisplayOrderHelp": "Seřadit epizody podle data vysílání, pořadí DVD nebo absolutního číslování.", - "ServerRestartNeededAfterPluginInstall": "Server Jellyfin bude nutné po instalaci zásuvného modulu restartovat.", + "ServerRestartNeededAfterPluginInstall": "Jellyfin bude nutné po instalaci zásuvného modulu restartovat.", "ShowAdvancedSettings": "Zobrazit rozšířená nastavení", "ShowTitle": "Zobrazit název", "ShowYear": "Zobrazit rok", @@ -1366,7 +1351,6 @@ "TabDirectPlay": "Přímé přehrávání", "TabInfo": "Info", "TabLiveTV": "Televize", - "TabMetadata": "Metadata", "TabServer": "Server", "TagsValue": "Tagy: {0}", "ThemeSongs": "Tematická hudba", @@ -1456,7 +1440,7 @@ "DailyAt": "Denně v {0}", "PersonRole": "{0}", "ListPaging": "{0}-{1} ze {2}", - "WriteAccessRequired": "Server Jellyfin vyžaduje oprávnění pro zápis do této složky. Zkontrolujte oprávnění a zkuste to znovu.", + "WriteAccessRequired": "Jellyfin vyžaduje oprávnění pro zápis do této složky. Zkontrolujte oprávnění a zkuste to znovu.", "PathNotFound": "Cesta nebyla nalezena. Zkontrolujte, zda je platná a zkuste to znovu.", "WeeklyAt": "V {0} v {1}", "LastSeen": "Naposledy zobrazené {0}", @@ -1470,7 +1454,6 @@ "Filter": "Filtr", "New": "Nové", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Více", "LabelStable": "Stabilní", "LabelChromecastVersion": "Verze Chromecastu", "ApiKeysCaption": "Seznam povolených API klíčů", @@ -1542,5 +1525,10 @@ "ViewAlbumArtist": "Zobrazit interpreta alba", "PreviousTrack": "Předchozí", "NextTrack": "Další", - "LabelUnstable": "Nestabilní" + "LabelUnstable": "Nestabilní", + "Preview": "Náhled", + "SubtitleVerticalPositionHelp": "Číslo řádku, na kterém se zobrazí text. Kladná čísla znamenají směr shora dolů. Záporná čísla zdola nahoru.", + "LabelSubtitleVerticalPosition": "Svislé umístění:", + "MessageGetInstalledPluginsError": "Při načítání seznamu nainstalovaných zásuvných modulů došlo k chybě.", + "MessagePluginInstallError": "Při instalaci zásuvného modulu došlo k chybě." } diff --git a/src/strings/da.json b/src/strings/da.json index 89bdd61808..7e4721a42b 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -31,7 +31,6 @@ "BookLibraryHelp": "Lyd- og tekstbøger er understøttet. Se {0}guiden til navngivning af bøger{1}.", "Browse": "Gennemse", "MessageBrowsePluginCatalog": "Gennemse vores plugin-katalog for at se tilgængelige plugins.", - "ButtonAdd": "Tilføj", "ButtonAddMediaLibrary": "Tilføj Mediebibliotek", "ButtonAddScheduledTaskTrigger": "Tilføj udløser", "ButtonAddServer": "Tilføj Server", @@ -55,7 +54,6 @@ "ButtonForgotPassword": "Glemt Adgangskode", "ButtonFullscreen": "Fuld skærm", "ButtonGotIt": "Forstået", - "ButtonHelp": "Hjælp", "ButtonHome": "Hjem", "ButtonLibraryAccess": "Biblioteksadgang", "ButtonManualLogin": "Manuel Login", @@ -74,15 +72,12 @@ "ButtonRefreshGuideData": "Opdater Guide data", "ButtonRemove": "Fjern", "ButtonRename": "Omdøb", - "ButtonRepeat": "Gentag", "ButtonResetEasyPassword": "Nulstil pinkode", "ButtonResetPassword": "Nulstil adgangskode", "ButtonRestart": "Genstart", "ButtonResume": "Genoptag", "ButtonRevoke": "Invalider", - "ButtonSave": "Gem", "ButtonScanAllLibraries": "Skan Alle Biblioteker", - "ButtonSearch": "Søg", "ButtonSelectDirectory": "Vælg mappe", "ButtonSelectServer": "Vælg server", "ButtonSelectView": "Vælg visning", @@ -232,7 +227,6 @@ "HeaderDevices": "Enheder", "HeaderDirectPlayProfile": "Profil for direkte afspilning", "HeaderDirectPlayProfileHelp": "Tilføj profiler for direkte afspilning for at angive hvilke formater enheden selv kan håndtere.", - "HeaderDisplay": "Visning", "HeaderEasyPinCode": "Nem pinkode", "HeaderEditImages": "Rediger billeder", "HeaderEnabledFields": "Aktivér Felter", @@ -241,10 +235,8 @@ "HeaderError": "Fejl", "HeaderExternalIds": "Eksterne ID'er:", "HeaderFeatureAccess": "Adgang til funktioner", - "HeaderFeatures": "Egenskaber", "HeaderFetchImages": "Hent billeder:", "HeaderFetcherSettings": "Henter indstillinger", - "HeaderFilters": "Filtre", "HeaderForKids": "For Børn", "HeaderForgotPassword": "Glemt adgangskode", "HeaderFrequentlyPlayed": "Ofte afspillet", @@ -389,7 +381,6 @@ "LabelAlbumArtMaxWidthHelp": "Maksimumopløsningen på album billede der bliver vist med upnp:albumArtURI.", "LabelAlbumArtPN": "Album billede PN:", "LabelAlbumArtists": "Albumartister:", - "LabelAll": "Alle", "LabelAllowHWTranscoding": "Tillad hardware-omkodning", "LabelAllowedRemoteAddresses": "Fjernadgang IP adresse filter:", "LabelAllowedRemoteAddressesMode": "Fjernadgang IP adresse filter mode:", @@ -974,7 +965,6 @@ "TabAccess": "Adgang", "TabAdvanced": "Avanceret", "TabAlbumArtists": "Album-artister", - "TabArtists": "Kunstnere", "TabCatalog": "Katalog", "TabChannels": "Kanaler", "TabCollections": "Samlinger", @@ -982,7 +972,6 @@ "TabDashboard": "Betjeningspanel", "TabDevices": "Enheder", "TabDirectPlay": "Direkte afspilning", - "TabDisplay": "Visning", "TabEpisodes": "Episoder", "TabFavorites": "Favoritter", "TabGenres": "Genre", @@ -997,7 +986,6 @@ "TabOther": "Andet", "TabParentalControl": "Forældrekontrol", "TabPassword": "Adgangskode", - "TabPlayback": "Afspilning", "TabPlaylists": "Afspilningslister", "TabPlugins": "Tilføjelser", "TabProfile": "Profil", @@ -1010,9 +998,7 @@ "TabSettings": "Indstillinger", "TabShows": "Serier", "TabSongs": "Sange", - "TabSuggestions": "Forslag", "TabTrailers": "Trailere", - "TabTranscoding": "Transkodning", "TabUpcoming": "Kommende", "TabUsers": "Brugere", "TellUsAboutYourself": "Fortæl os lidt om dig selv", @@ -1174,7 +1160,6 @@ "HeaderStatus": "Status", "HeaderStopRecording": "Stop Optagelse", "HeaderSubtitleAppearance": "Undertekst Udseende", - "HeaderTags": "Mærker", "HeaderVideoQuality": "Video Kvalitet", "HeaderVideoType": "Video Type", "Hide": "Skjul", @@ -1331,7 +1316,6 @@ "TabInfo": "Information", "TabLiveTV": "Live TV", "TabLogs": "Log", - "TabMetadata": "Metadata", "TabServer": "Server", "TabStreaming": "Streamer", "Tags": "Mærker", @@ -1470,6 +1454,5 @@ "Filter": "Filtrer", "New": "Nye", "ButtonTogglePlaylist": "Spilleliste", - "ButtonToggleContextMenu": "Mere", "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/de.json b/src/strings/de.json index 1c87a95a95..40035d6a45 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -22,7 +22,7 @@ "AllowMediaConversionHelp": "Erlaube oder unterbinde Zugriff auf die Medienkonvertierung.", "AllowOnTheFlySubtitleExtraction": "Erlaube Untertitelextraktion \"on-the-fly\"", "AllowOnTheFlySubtitleExtractionHelp": "Eingebettete Untertitel können aus Videos extrahiert und in Reintext an Clients gesendet werden, um eine Videotranskodierung zu vermeiden. Auf manchen Systemen kann dieser Vorgang eine lange Zeit in Anspruch nehmen und deswegen währenddessen die Videowiedergabe stoppen. Deaktiviere diese Option, um eingebettete Untertitel während des Videotranskodierens einbrennen zu lassen, wenn sie nicht nativ vom Client unterstützt werden.", - "AllowRemoteAccess": "Erlaube externe Verbindungen zu diesem Jellyfin Server.", + "AllowRemoteAccess": "Erlaube externe Verbindungen zu diesem Server.", "AllowRemoteAccessHelp": "Wenn deaktiviert werden alle externen Verbindungen blockiert.", "AllowedRemoteAddressesHelp": "Kommagetrennte Liste von IP Adressen oder IP/Netzmasken für Netzwerke, für die externe Verbindungen erlaubt sind. Wenn leer, sind alle Adressen erlaubt.", "AlwaysPlaySubtitles": "Immer anzeigen", @@ -46,7 +46,6 @@ "Browse": "Blättern", "MessageBrowsePluginCatalog": "Durchsuche unsere Bibliothek, um alle verfügbaren Plugins anzuzeigen.", "BurnSubtitlesHelp": "Legt fest, ob der Server die Untertitel während der Videotranskodierung einbrennen soll. Deaktivieren verbessert die Serverperformance immens. Wähle Auto, um bildbasierte Formate (z.B. VOBSUB, PGS, SUB, IDX, ...) sowie bestimmte ASS- oder SSA-Untertitel einbrennen zu lassen.", - "ButtonAdd": "Hinzufügen", "ButtonAddMediaLibrary": "Füge Medienbibliothek hinzu", "ButtonAddScheduledTaskTrigger": "Auslöser hinzufügen", "ButtonAddServer": "Server hinzufügen", @@ -70,7 +69,6 @@ "ButtonFullscreen": "Vollbild", "ButtonGotIt": "Verstanden", "ButtonGuide": "TV Guide", - "ButtonHelp": "Hilfe", "ButtonLibraryAccess": "Bibliothekszugang", "ButtonManualLogin": "Manuelle Anmeldung", "ButtonMore": "Mehr", @@ -88,15 +86,12 @@ "ButtonRefreshGuideData": "Aktualisiere TV-Programmdaten", "ButtonRemove": "Entfernen", "ButtonRename": "Umbenennen", - "ButtonRepeat": "Wiederholung", "ButtonResetEasyPassword": "Einfachen PIN zurücksetzen", "ButtonResetPassword": "Passwort zurücksetzten", "ButtonRestart": "Neustart", "ButtonResume": "Fortsetzen", "ButtonRevoke": "Zurücknehmen", - "ButtonSave": "Speichern", "ButtonScanAllLibraries": "Scanne alle Bibliotheken", - "ButtonSearch": "Suche", "ButtonSelectDirectory": "Wähle Verzeichnis", "ButtonSelectServer": "Wähle Server", "ButtonSelectView": "Ansicht wählen", @@ -124,7 +119,7 @@ "ColorSpace": "Farbraum", "CommunityRating": "Community Bewertung", "Composer": "Komponist", - "ConfigureDateAdded": "Bestimme in den Bibliotheks-Einstellungen des Jellyfin Server Dashboards, wie das Feld \"Hinzugefügt am\" interpretiert werden soll", + "ConfigureDateAdded": "Bestimme in den Bibliotheks-Einstellungen des Dashboards, wie das Feld \"Hinzugefügt am\" interpretiert werden soll", "ConfirmDeleteImage": "Bild löschen?", "ConfirmDeleteItem": "Löschen dieses Eintrages bedeutet das Löschen der Datei und das Entfernen aus der Medien-Bibliothek. Möchtest du wirklich fortfahren?", "ConfirmDeleteItems": "Das Löschen dieser Objekte löscht die Dateien vom Laufwerk und in deiner Medienbibliothek. Bist du wirklich sicher?", @@ -201,10 +196,10 @@ "EndsAtValue": "Endet um {0}", "Episodes": "Episoden", "ErrorAddingListingsToSchedulesDirect": "Ein Fehler trat beim Hinzufügen Ihrer Zusammenstellung zu Ihrem Schedules Direct Konto auf. Schedules Direct erlaubt nur eine begrenzte Anzahl von Zusammenstellungen je Account. Sie sollten sich auf der Website in Ihrem Schedules-Direct Konto einloggen und ein paar Zusammenstellungen von Ihrem Konto löschen bevor Sie fortfahren.", - "ErrorAddingMediaPathToVirtualFolder": "Ein Fehler trat beim Hinzufügen eines Medienverzeichnisses auf. Bitte stellen Sie sicher, dass der Pfad gültig ist und der Jellyfin Server Prozess die notwendigen Zugriffsrechte besitzt.", + "ErrorAddingMediaPathToVirtualFolder": "Ein Fehler trat beim Hinzufügen eines Medienverzeichnisses auf. Bitte stellen Sie sicher, dass der Pfad gültig ist und Jellyfin die notwendigen Zugriffsrechte besitzt.", "ErrorAddingTunerDevice": "Es trat ein Fehler beim hinzufügen eines Tuners auf. Bitte stellen Sie sicher das dieser erreichbar ist und versuchen Sie es erneut.", "ErrorAddingXmlTvFile": "Fehler beim Zugriff auf die XMLTV Datei. Stelle bitte sicher, dass die Datei existiert und versuche es nochmal.", - "ErrorDeletingItem": "Fehler beim Löschen des Mediums vom Jellyfin Server. Bitte stelle sicher dass der Jellyfin Server Schreibzugriff auf den Dateiordner hat und versuche es erneut.", + "ErrorDeletingItem": "Fehler beim Löschen des Mediums vom Server. Bitte stelle sicher, dass Jellyfin Schreibzugriff auf den Dateiordner hat und versuche es erneut.", "ErrorGettingTvLineups": "Ein Fehler trat beim Herunterladen des Fernsehprogramms auf. Bitte stellen Sie sicher, dass Ihre Informationen korrekt sind und versuchen Sie es erneut.", "ErrorStartHourGreaterThanEnd": "Die Endzeit muss größer als die Startzeit sein.", "ErrorPleaseSelectLineup": "Bitte wählen Sie ein TV Programm und versuchen Sie es erneut. Wenn keine Programme verfügbar sind prüfen Sie bitte Benutzername, Passwort und Ihre Postleitzahl.", @@ -299,7 +294,6 @@ "HeaderDevices": "Geräte", "HeaderDirectPlayProfile": "Direktwiedergabe Profil", "HeaderDirectPlayProfileHelp": "Füge Direct-Play Profile hinzu um die nativen Abspielmöglichkeiten von Geräten festzulegen.", - "HeaderDisplay": "Anzeige", "HeaderDownloadSync": "Herunterladen & Synchronisieren", "HeaderEasyPinCode": "Einfacher PIN Code", "HeaderEditImages": "Bilder bearbeiten", @@ -309,10 +303,8 @@ "HeaderError": "Fehler", "HeaderExternalIds": "Externe IDs:", "HeaderFeatureAccess": "Funktionszugriff", - "HeaderFeatures": "Funktionen", "HeaderFetchImages": "Bilder abrufen:", "HeaderFetcherSettings": "Fetcher Einstellungen", - "HeaderFilters": "Filter", "HeaderForKids": "Für Kinder", "HeaderForgotPassword": "Passwort vergessen", "HeaderFrequentlyPlayed": "Oft gesehen", @@ -471,7 +463,6 @@ "LabelAlbumArtMaxWidthHelp": "Maximale Auflösung für durch UPnP übermittelte Album Art:albumArtURI.", "LabelAlbumArtPN": "Alben-Cover PN:", "LabelAlbumArtists": "Alben Interpreten:", - "LabelAll": "Alle", "LabelAllowHWTranscoding": "Erlaube Hardware Transkodierung", "LabelAllowedRemoteAddresses": "Remote-IP Adressen Filter:", "LabelAllowedRemoteAddressesMode": "Remote IP Adressen Filtermodus:", @@ -482,7 +473,7 @@ "LabelAudioLanguagePreference": "Bevorzugte Audiosprache:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Aktualisiere Metadaten automatisch aus dem Internet:", "LabelBindToLocalNetworkAddress": "Binde an lokale Netzwerkadresse:", - "LabelBindToLocalNetworkAddressHelp": "Überschreibt die lokale IP Adresse für den HTTP Server. Wenn leer, wird der Server an alle verfügbaren Adressen gebunden. Änderungen benötigen einen Neustart des Jellyfin Servers.", + "LabelBindToLocalNetworkAddressHelp": "Überschreibt die lokale IP-Adresse für den HTTP Server. Wenn leer, wird der Server an alle verfügbaren Adressen gebunden. Änderungen benötigen einen Neustart.", "LabelBirthDate": "Geburtsdatum:", "LabelBirthYear": "Geburtsjahr:", "LabelBlastMessageInterval": "Alive Meldungsintervall", @@ -540,7 +531,7 @@ "LabelEnableBlastAliveMessages": "Erzeuge Alive Meldungen", "LabelEnableBlastAliveMessagesHelp": "Aktiviere dies, wenn der Server nicht zuverlässig von anderen UPnP Geräten in ihrem Netzwerk erkannt wird.", "LabelEnableDlnaClientDiscoveryInterval": "Client-Entdeckungs Intervall", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Ermittelt die Zeit in Sekunden zwischen SSDP Suchanfragen die durch Jellyfin ausgeführt wurden.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Ermittelt die Zeit in Sekunden zwischen SSDP Suchanfragen.", "LabelEnableDlnaDebugLogging": "Aktiviere DLNA Debug Logging", "LabelEnableDlnaDebugLoggingHelp": "Erzeugt große Logdateien und sollte nur zur Fehlerbehebung benutzt werden.", "LabelEnableDlnaPlayTo": "Aktiviere DLNA Play To", @@ -655,7 +646,7 @@ "LabelNumberOfGuideDays": "Anzahl von Tagen für die Programminformationen geladen werden sollen:", "LabelNumberOfGuideDaysHelp": "Das laden von zusätzlichen Programmdaten bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Aber es wird länger dauern alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", "LabelOptionalNetworkPath": "Geteilter Netzwerkordner:", - "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Jellyfin Apps auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen. Beispielsweise {0} oder {1}.", + "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Clients auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen. Beispielsweise {0} oder {1}.", "LabelOriginalAspectRatio": "Original Seitenverhältnis:", "LabelOriginalTitle": "Original Titel:", "LabelOverview": "Übersicht:", @@ -816,13 +807,13 @@ "MessageConfirmProfileDeletion": "Bist du dir sicher, dass du dieses Profil löschen möchtest?", "MessageConfirmRecordingCancellation": "Aufnahme abbrechen?", "MessageConfirmRemoveMediaLocation": "Bist du dir sicher diese Medienquelle entfernen zu wollen?", - "MessageConfirmRestart": "Möchten Sie Jellyfin Server wirklich neu starten?", - "MessageConfirmRevokeApiKey": "Möchten Sie diesen API Schlüssel wirklich löschen? Die Verbindung der Anwendung zum Jellyfin Server wird sofort unterbrochen.", + "MessageConfirmRestart": "Möchten Sie Jellyfin wirklich neu starten?", + "MessageConfirmRevokeApiKey": "Möchten Sie diesen API Schlüssel wirklich löschen? Die Verbindung der Anwendung zum Server wird sofort unterbrochen.", "MessageConfirmShutdown": "Möchten Sie den Server wirklich herunterfahren?", "MessageContactAdminToResetPassword": "Bitte kontaktiere deinen Systemadministrator, um dein Passwort zurücksetzen zu lassen.", "MessageCreateAccountAt": "Erstellen Sie ein Konto bei {0}", "MessageDeleteTaskTrigger": "Bist du dir sicher, dass du diesen Aufgabenauslöser entfernen möchtest?", - "MessageDirectoryPickerBSDInstruction": "Für BSD müssen Sie ggf. Speicherplatz auf Ihrem FreeNAS Jail für Empby freigeben.", + "MessageDirectoryPickerBSDInstruction": "Für BSD müssen Sie ggf. Speicherplatz in deinem FreeNAS Jail konfigurieren, damit Jellyfin auf deine Medien zugreifen kann.", "MessageDirectoryPickerLinuxInstruction": "Für Linux auf Arch Linux, CentOS, Debian, Fedora, openSUSE oder Ubuntu muss der Service Benutzer mindestens lesenden Zugriff auf die Speicherorte der Medien besitzen.", "MessageDownloadQueued": "Download eingereiht.", "MessageEnablingOptionLongerScans": "Die Aktivierung dieser Option kann erheblich längere Bibliotheks-Scans verursachen.", @@ -844,7 +835,7 @@ "MessagePleaseEnsureInternetMetadata": "Bitte sicherstellen, dass das Herunterladen von Internet Metadaten aktiviert ist.", "MessagePleaseWait": "Bitte warten, dies kann eine Minute dauern.", "MessagePluginConfigurationRequiresLocalAccess": "Melde dich bitte direkt an deinem lokalen Server an, um dieses Plugin konfigurieren zu können.", - "MessagePluginInstallDisclaimer": "Plugins aus der Jellyfin Community sind eine gute Möglichkeit um Jellyfin mit weiteren Funktionen und Vorteilen aufzuwerten. Bevor Sie diese jedoch installieren, seien Sie sich den daraus resultierenden möglichen Umständen für Jellyfin bewusst. Dies können z.B. längere Bibliotheken Scans, weiterführende Verarbeitung von Daten im Hintergrund sowie Systeminstabilität sein.", + "MessagePluginInstallDisclaimer": "Plugins aus der Community sind eine gute Möglichkeit um dein Erlebnis mit weiteren Funktionen und Vorteilen aufzuwerten. Bevor du diese installierst, sei dir den daraus resultierenden möglichen Umständen für deinen Server bewusst. Dies können z.B. längere Bibliotheken Scans, weiterführende Verarbeitung von Daten im Hintergrund sowie Systeminstabilität sein.", "MessageReenableUser": "Für Reaktivierung schauen Sie unten", "MessageSettingsSaved": "Einstellungen gespeichert.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Die folgenden Medienverzeichnisse werden aus der Bibliothek entfernt:", @@ -994,7 +985,7 @@ "OptionRuntime": "Dauer", "OptionSaturday": "Samstag", "OptionSaveMetadataAsHidden": "Speichere Metadaten und Bilder als versteckte Dateien", - "OptionSaveMetadataAsHiddenHelp": "Änderungen werden sich auf neue Metadaten angewendet. Bereits existierende Metadaten werden bei der nächsten Speicherung des Jellyfin Servers auf den neusten Stand gebracht.", + "OptionSaveMetadataAsHiddenHelp": "Änderungen werden sich auf neue Metadaten angewendet. Bereits existierende Metadaten werden bei der nächsten Speicherung des Servers auf den neusten Stand gebracht.", "OptionSunday": "Sonntag", "OptionThursday": "Donnerstag", "OptionTuesday": "Dienstag", @@ -1035,9 +1026,9 @@ "PleaseAddAtLeastOneFolder": "Bitte fügen Sie mindestens ein Verzeichniss zur Bibliothek durch Klicken der \"Hinzufügen\"-Schaltfläche hinzu.", "PleaseConfirmPluginInstallation": "Bitte bestätigen Sie mit OK, dass Sie den oben stehenden Text gelesen haben und die Installation des Plugins fortführen möchten.", "PleaseEnterNameOrId": "Bitte gib einen Namen oder eine externe ID an.", - "PleaseRestartServerName": "Bitte starte Jellyfin Server - {0} neu.", + "PleaseRestartServerName": "Bitte starte Jellyfin an {0} neu.", "PleaseSelectTwoItems": "Bitte wähle mindestens zwei Optionen aus.", - "MessagePluginInstalled": "Das Plugin wurde erfolgreich installiert. Der Jellyfin-Server muss neu gestartet werden, um die Änderungen zu übernehmen.", + "MessagePluginInstalled": "Das Plugin wurde erfolgreich installiert. Der Server muss neu gestartet werden, um die Änderungen zu übernehmen.", "PreferEmbeddedTitlesOverFileNames": "Bevorzuge eingebettete Titel vor Dateinamen", "PreferEmbeddedTitlesOverFileNamesHelp": "Das bestimmt den Standard Displaytitel wenn keine lokale oder Internetmetadaten verfügbar sind.", "PreferEmbeddedEpisodeInfosOverFileNames": "Bevorzuge eingebettete Episodeninformationen vor Dateinamen", @@ -1100,10 +1091,10 @@ "SeriesRecordingScheduled": "Serien-Aufnahme geplant.", "SeriesSettings": "Serieneinstellungen", "SeriesYearToPresent": "{0}-Heute", - "ServerNameIsRestarting": "Jellyfin Server - {0} startet neu.", - "ServerNameIsShuttingDown": "Jellyfin Server - {0} fährt herunter.", - "ServerRestartNeededAfterPluginInstall": "Der Jellyfin-Server muss nach der Installation eines Plugins neu gestartet werden.", - "ServerUpdateNeeded": "Dieser Jellyfin Server muss aktualisiert werden. Um die neueste Version herunterzuladen, besuche bitte {0}", + "ServerNameIsRestarting": "Jellyfin Server an {0} startet neu.", + "ServerNameIsShuttingDown": "Der Server an {0} fährt herunter.", + "ServerRestartNeededAfterPluginInstall": "Jellyfin muss nach der Installation eines Plugins neu gestartet werden.", + "ServerUpdateNeeded": "Dieser Server muss aktualisiert werden. Um die neueste Version herunterzuladen, besuche bitte {0}", "Settings": "Einstellungen", "SettingsSaved": "Einstellungen gespeichert.", "SettingsWarning": "Das Verändern dieser Werte kann Instabilität und Verbindungsprobleme hervorrufen. Wenn Probleme auftreten sollten empfehlen wir diese Einstellungen auf die Standardwerte zurück zu stellen.", @@ -1139,7 +1130,6 @@ "TabAdvanced": "Erweitert", "TabAlbumArtists": "Album-Interpreten", "TabAlbums": "Alben", - "TabArtists": "Interpreten", "TabCatalog": "Katalog", "TabChannels": "Kanäle", "TabCollections": "Sammlungen", @@ -1147,13 +1137,11 @@ "TabDashboard": "Übersicht", "TabDevices": "Geräte", "TabDirectPlay": "Direktwiedergabe", - "TabDisplay": "Anzeige", "TabEpisodes": "Episoden", "TabFavorites": "Favoriten", "TabGuide": "Programm", "TabLatest": "Neueste", "TabLiveTV": "Live-TV", - "TabMetadata": "Metadaten", "TabMovies": "Filme", "TabMusic": "Musik", "TabMusicVideos": "Musikvideos", @@ -1164,7 +1152,6 @@ "TabOther": "Andere", "TabParentalControl": "Kindersicherung", "TabPassword": "Passwort", - "TabPlayback": "Wiedergabe", "TabPlaylists": "Wiedergabelisten", "TabProfile": "Profil", "TabProfiles": "Profile", @@ -1175,9 +1162,7 @@ "TabSeries": "Serie", "TabSettings": "Einstellungen", "TabShows": "Serien", - "TabSuggestions": "Empfehlungen", "TabTrailers": "Trailer", - "TabTranscoding": "Transkodierung", "TabUpcoming": "Bevorstehend", "TabUsers": "Benutzer", "TellUsAboutYourself": "Sagen Sie uns etwas über sich selbst", @@ -1265,7 +1250,6 @@ "HeaderHttpHeaders": "HTTP-Header", "HeaderPluginInstallation": "Plugininstallation", "HeaderStatus": "Status", - "HeaderTags": "Tags", "HeaderVideos": "Videos", "Home": "Startseite", "Horizontal": "Horizontal", @@ -1448,7 +1432,7 @@ "LastSeen": "Zuletzt gesehen {0}", "PersonRole": "als {0}", "ListPaging": "{0}-{1} von {2}", - "WriteAccessRequired": "Jellyfin Server benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.", + "WriteAccessRequired": "Jellyfin benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.", "PathNotFound": "Der Pfad konnte nicht gefunden werden. Bitte versichere dich dass der Pfad korrekt ist und versuche es erneut.", "Track": "Track", "Season": "Staffel", @@ -1470,7 +1454,6 @@ "New": "Neu", "HeaderFavoritePlaylists": "Lieblings-Wiedergabeliste", "ButtonTogglePlaylist": "Wiedergabeliste", - "ButtonToggleContextMenu": "Mehr", "ApiKeysCaption": "Liste der aktuell aktivierten API-Schlüssel", "LabelStable": "Stable", "LabelChromecastVersion": "Chromecast Version", @@ -1542,5 +1525,10 @@ "ViewAlbumArtist": "Zeige Albumkünstler", "PreviousTrack": "Zum Vorherigen springen", "NextTrack": "Zum Nächsten springen", - "LabelUnstable": "Instabil" + "LabelUnstable": "Instabil", + "SubtitleVerticalPositionHelp": "Zeilennummer, in der der Text angezeigt wird. Positive Zahlen geben die Zeile von oben an. Negative Zahlen geben die Zeile von unten an.", + "Preview": "Vorschau", + "LabelSubtitleVerticalPosition": "Vertikale Position:", + "MessageGetInstalledPluginsError": "Beim Abrufen der Liste der derzeit installierten Plugins ist ein Fehler aufgetreten.", + "MessagePluginInstallError": "Bei der Installation des Plugins ist ein Fehler aufgetreten." } diff --git a/src/strings/el.json b/src/strings/el.json index c390f7bde4..d3176ae83c 100644 --- a/src/strings/el.json +++ b/src/strings/el.json @@ -44,7 +44,6 @@ "Browse": "Αναζήτηση", "MessageBrowsePluginCatalog": "Πλοηγηθείτε στον κατάλογο plugin μας για να δείτε τα διαθέσιμα plugins.", "BurnSubtitlesHelp": "Καθορίζει αν ο διακομιστής πρέπει να εγγράψει τους υπότιτλους κατά τη μετατροπή βίντεο ανάλογα με τη μορφή των υπότιτλων. Η αποφυγή της εγγραφής στους υπότιτλους θα βελτιώσει την απόδοση του διακομιστή. Επιλέξτε Αυτόματα για να εγγράψετε μορφές βασισμένες σε εικόνες (VOBSUB, PGS, SUB / IDX κ.λπ.) και ορισμένους υπότιτλους ASS / SSA.", - "ButtonAdd": "Πρόσθεσε", "ButtonAddMediaLibrary": "Προσθήκη βιβλιοθήκης πολυμέσων", "ButtonAddScheduledTaskTrigger": "Προσθήκη διακόπτη", "ButtonAddServer": "Προσθήκη διακομιστή", @@ -69,7 +68,6 @@ "ButtonFullscreen": "Πλήρης οθόνη", "ButtonGotIt": "Το κατάλαβα", "ButtonGuide": "Οδηγός", - "ButtonHelp": "Βοήθεια", "ButtonHome": "Αρχική", "ButtonInfo": "Πληροφορία", "ButtonLibraryAccess": "Πρόσβαση στη βιβλιοθήκη", @@ -88,15 +86,12 @@ "ButtonRefreshGuideData": "Ανανέωση Δεδομένων Οδηγού", "ButtonRemove": "Κατάργηση", "ButtonRename": "Μετονομασία", - "ButtonRepeat": "Επανάληψη", "ButtonResetEasyPassword": "Επαναφορά εύκολου κωδικού PIN", "ButtonResetPassword": "Επαναφορά του κωδικού πρόσβασης", "ButtonRestart": "Επανεκκίνηση", "ButtonResume": "Συνέχιση", "ButtonRevoke": "Ανακαλώ", - "ButtonSave": "Αποθήκευση", "ButtonScanAllLibraries": "Σάρωση όλων των βιβλιοθηκών", - "ButtonSearch": "Αναζήτηση", "ButtonSelectDirectory": "Επιλογή Φακέλου", "ButtonSelectServer": "Επιλογή Διακομιστή", "ButtonSelectView": "Επιλέξτε Προβολή", @@ -285,7 +280,6 @@ "HeaderDevices": "Συσκευές", "HeaderDirectPlayProfile": "Προφίλ Άμεσης Αναπαραγωγής", "HeaderDirectPlayProfileHelp": "Προσθέστε προφίλ άμεσης αναπαραγωγής για να δείξετε ποιες μορφές μπορεί να χειριστεί εγγενώς η συσκευή.", - "HeaderDisplay": "Εμφάνιση", "HeaderEasyPinCode": "Κωδικός PIN", "HeaderEditImages": "Επεξεργασία εικόνων", "HeaderEnabledFields": "Ενεργά Πεδία", @@ -294,9 +288,7 @@ "HeaderError": "Σφάλμα", "HeaderExternalIds": "Εξωτερικά ID:", "HeaderFeatureAccess": "Πρόσβαση χαρακτηριστικών", - "HeaderFeatures": "Χαρακτηριστικά", "HeaderFetchImages": "Λήψη εικόνων:", - "HeaderFilters": "Φίλτρα", "HeaderForKids": "Για τα Παιδιά", "HeaderForgotPassword": "Ξέχασα τον κωδικό", "HeaderFrequentlyPlayed": "Συχνά έπαιξε", @@ -392,7 +384,6 @@ "HeaderSubtitleProfiles": "Προφίλ Υπότιτλων", "HeaderSubtitleProfilesHelp": "Τα προφίλ υποτίτλων περιγράφουν τις μορφές υπότιτλων που υποστηρίζει η συσκευή.", "HeaderSystemDlnaProfiles": "Προφίλ Συστήματος", - "HeaderTags": "Ετικέτες", "HeaderTaskTriggers": "Έναυσμα εργασιών", "HeaderThisUserIsCurrentlyDisabled": "Ο χρήστης είναι απενεργοποιημένος", "HeaderTranscodingProfileHelp": "Προσθέστε προφίλ αποκωδικοποίησης για να δηλώσετε ποιες μορφές θα πρέπει να χρησιμοποιηθούν όταν απαιτείται μετασύνδεση.", @@ -438,7 +429,6 @@ "LabelAlbumArtMaxWidthHelp": "Μέγιστη ανάλυση του άλμπουμ art που εκτίθεται μέσω του upnp: albumArtURI.", "LabelAlbumArtPN": "PN άλμπουμ art:", "LabelAlbumArtists": "Καλλιτέχνες του 'Αλμπουμ:", - "LabelAll": "Όλα", "LabelAppName": "Όνομα App", "LabelAppNameExample": "Παράδειγμα: Sickbeard, NzbDrone", "LabelArtists": "Καλλιτέχνες:", @@ -1029,21 +1019,18 @@ "TabAdvanced": "Για προχωρημένους", "TabAlbumArtists": "Άλμπουμ Καλλιτέχνες", "TabAlbums": "Άλμπουμ", - "TabArtists": "Καλλιτέχνες", "TabCatalog": "Κατάλογος", "TabChannels": "Κανάλια", "TabCollections": "Συλλογές", "TabDashboard": "Πίνακας Ελέγχου", "TabDevices": "Συσκευές", "TabDirectPlay": "Άμεση Αναπαραγωγή", - "TabDisplay": "Εμφάνιση", "TabEpisodes": "Επεισόδια", "TabFavorites": "Αγαπημένα", "TabGenres": "Είδη", "TabGuide": "Οδηγός", "TabInfo": "Πληροφορία", "TabLatest": "Τελευταία", - "TabMetadata": "Μεταδεδομένα", "TabMovies": "Ταινίες", "TabMusic": "Μουσική", "TabMusicVideos": "Μουσικά βίντεο", @@ -1054,7 +1041,6 @@ "TabOther": "Άλλα", "TabParentalControl": "Γονικός έλεγχος", "TabPassword": "Κωδικός", - "TabPlayback": "Αναπαραγωγή", "TabPlaylists": "Λίστες αναπαραγωγής", "TabPlugins": "Πρόσθετα", "TabProfile": "Προφίλ", @@ -1068,9 +1054,7 @@ "TabShows": "Επεισόδια", "TabSongs": "Τραγούδια", "TabStreaming": "Ροή", - "TabSuggestions": "Προτάσεις", "TabTrailers": "Τρέιλερς", - "TabTranscoding": "Κωδικοποίηση", "TabUpcoming": "Επερχόμενα", "TabUsers": "Χρήστες", "Tags": "Ετικέτες", @@ -1212,7 +1196,6 @@ "AllowedRemoteAddressesHelp": "Λίστα διαχωρισμένων διευθύνσεων IP ή καταχωρίσεων IP / netmask για δίκτυα που θα επιτρέπεται η σύνδεση εξ αποστάσεως. Εάν αφεθεί κενό, όλες οι απομακρυσμένες διευθύνσεις θα επιτρέπονται.", "AllowFfmpegThrottlingHelp": "Όταν ένας διακωδικοποιητής ή remux φτάσει αρκετά μπροστά από την τρέχουσα θέση αναπαραγωγής, διακόψτε τη διαδικασία ώστε να καταναλώσει λιγότερους πόρους. Αυτό είναι πιο χρήσιμο όταν παρακολουθείτε χωρίς να αναζητάτε συχνά. Απενεργοποιήστε το εάν αντιμετωπίζετε προβλήματα αναπαραγωγής.", "ButtonTogglePlaylist": "Λίστα αναπαραγωγής", - "ButtonToggleContextMenu": "Περισσότερα", "ButtonSplit": "Διαχωρισμός", "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index b3ec646101..f2fc6e7764 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -99,7 +99,6 @@ "BoxRear": "Box (rear)", "Browse": "Browse", "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.", - "ButtonAdd": "Add", "ButtonAddMediaLibrary": "Add Media Library", "ButtonAddScheduledTaskTrigger": "Add Trigger", "ButtonAddServer": "Add Server", @@ -125,7 +124,6 @@ "ButtonFullscreen": "Fullscreen", "ButtonGotIt": "Got It", "ButtonGuide": "Guide", - "ButtonHelp": "Help", "ButtonHome": "Home", "ButtonInfo": "Info", "ButtonLibraryAccess": "Library access", @@ -147,15 +145,12 @@ "ButtonRefreshGuideData": "Refresh Guide Data", "ButtonRemove": "Remove", "ButtonRename": "Rename", - "ButtonRepeat": "Repeat", "ButtonResetEasyPassword": "Reset easy pin code", "ButtonResetPassword": "Reset Password", "ButtonRestart": "Restart", "ButtonResume": "Resume", "ButtonRevoke": "Revoke", - "ButtonSave": "Save", "ButtonScanAllLibraries": "Scan All Libraries", - "ButtonSearch": "Search", "ButtonSelectDirectory": "Select Directory", "ButtonSelectServer": "Select Server", "ButtonSelectView": "Select view", @@ -357,7 +352,6 @@ "HeaderDevices": "Devices", "HeaderDirectPlayProfile": "Direct Play Profile", "HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.", - "HeaderDisplay": "Display", "HeaderDownloadSync": "Download & Sync", "HeaderEasyPinCode": "Easy Pin Code", "HeaderEditImages": "Edit Images", @@ -376,10 +370,8 @@ "HeaderFavoriteVideos": "Favourite Videos", "HeaderFavoritePlaylists": "Favourite Playlists", "HeaderFeatureAccess": "Feature Access", - "HeaderFeatures": "Features", "HeaderFetchImages": "Fetch Images:", "HeaderFetcherSettings": "Fetcher Settings", - "HeaderFilters": "Filters", "HeaderForKids": "For Kids", "HeaderForgotPassword": "Forgot Password", "HeaderFrequentlyPlayed": "Frequently Played", @@ -478,7 +470,6 @@ "TabResumeSettings": "Resume", "TabResponses": "Responses", "TabRecordings": "Recordings", - "TabPlayback": "Playback", "TabOther": "Other", "TabNotifications": "Notifications", "TabNetworks": "Networks", @@ -486,13 +477,10 @@ "TabMusicVideos": "Music Videos", "TabMusic": "Music", "TabMovies": "Movies", - "TabMetadata": "Metadata", "TabLogs": "Logs", - "TabDisplay": "Display", "TabDirectPlay": "Direct Play", "TabDevices": "Devices", "TabChannels": "Channels", - "TabArtists": "Artists", "TabAlbums": "Albums", "TabAlbumArtists": "Album Artists", "TabAdvanced": "Advanced", @@ -924,9 +912,7 @@ "Tags": "Tags", "TabUsers": "Users", "TabUpcoming": "Upcoming", - "TabTranscoding": "Transcoding", "TabTrailers": "Trailers", - "TabSuggestions": "Suggestions", "LabelDisplayMode": "Display mode:", "LabelDisplayLanguageHelp": "Translating Jellyfin is an ongoing project.", "LabelDisplayLanguage": "Display language:", @@ -1148,7 +1134,6 @@ "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode:", "LabelAllowedRemoteAddresses": "Remote IP address filter:", "LabelAllowHWTranscoding": "Allow hardware transcoding", - "LabelAll": "All", "LabelAlbumArtists": "Album artists:", "LabelAlbumArtPN": "Album art PN:", "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", @@ -1192,7 +1177,6 @@ "HeaderTracks": "Tracks", "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", "HeaderTaskTriggers": "Task Triggers", - "HeaderTags": "Tags", "HeaderSystemDlnaProfiles": "System Profiles", "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", "HeaderSubtitleProfiles": "Subtitle Profiles", @@ -1472,7 +1456,6 @@ "Album": "Album", "UnsupportedPlayback": "Jellyfin cannot decrypt content protected by DRM but all content will be attempted regardless, including protected titles. Some files may appear completely black due to encryption or other unsupported features, such as interactive titles.", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "More", "HeaderDVR": "DVR", "ApiKeysCaption": "List of the currently enabled API keys", "ButtonCast": "Cast", diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 4dffc49618..2e437b148b 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -27,7 +27,7 @@ "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", "AllowFfmpegThrottling": "Throttle Transcodes", "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", - "AllowRemoteAccess": "Allow remote connections to this Jellyfin Server.", + "AllowRemoteAccess": "Allow remote connections to this server.", "AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.", "AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.", "AlwaysPlaySubtitles": "Always Play", @@ -61,7 +61,6 @@ "Browse": "Browse", "MessageBrowsePluginCatalog": "Browse our plugin catalog to view available plugins.", "BurnSubtitlesHelp": "Determines if the server should burn in subtitles when transcoding videos. Avoiding this will greatly improve performance. Select Auto to burn image based formats (VOBSUB, PGS, SUB, IDX, …) and certain ASS or SSA subtitles.", - "ButtonAdd": "Add", "ButtonAddImage": "Add Image", "ButtonAddMediaLibrary": "Add Media Library", "ButtonAddScheduledTaskTrigger": "Add Trigger", @@ -88,7 +87,6 @@ "ButtonFullscreen": "Fullscreen", "ButtonGotIt": "Got It", "ButtonGuide": "Guide", - "ButtonHelp": "Help", "ButtonHome": "Home", "ButtonInfo": "Info", "ButtonLibraryAccess": "Library access", @@ -110,15 +108,12 @@ "ButtonRefreshGuideData": "Refresh Guide Data", "ButtonRemove": "Remove", "ButtonRename": "Rename", - "ButtonRepeat": "Repeat", "ButtonResetEasyPassword": "Reset easy pin code", "ButtonResetPassword": "Reset Password", "ButtonRestart": "Restart", "ButtonResume": "Resume", "ButtonRevoke": "Revoke", - "ButtonSave": "Save", "ButtonScanAllLibraries": "Scan All Libraries", - "ButtonSearch": "Search", "ButtonSelectDirectory": "Select Directory", "ButtonSelectServer": "Select Server", "ButtonSelectView": "Select view", @@ -134,7 +129,6 @@ "ButtonSplit": "Split", "ButtonSubmit": "Submit", "ButtonSubtitles": "Subtitles", - "ButtonToggleContextMenu": "More", "ButtonTogglePlaylist": "Playlist", "ButtonTrailer": "Trailer", "ButtonUninstall": "Uninstall", @@ -156,7 +150,7 @@ "ColorTransfer": "Color transfer", "CommunityRating": "Community rating", "Composer": "Composer", - "ConfigureDateAdded": "Configure how date added is determined in the Jellyfin Server dashboard under Library settings", + "ConfigureDateAdded": "Configure how date added is determined in the dashboard under the library settings", "ConfirmDeleteImage": "Delete image?", "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?", "ConfirmDeleteItems": "Deleting these items will delete them from both the file system and your media library. Are you sure you wish to continue?", @@ -243,10 +237,10 @@ "Episode": "Episode", "Episodes": "Episodes", "ErrorAddingListingsToSchedulesDirect": "There was an error adding the lineup to your Schedules Direct account. Schedules Direct only allows a limited number of lineups per account. You may need to log into the Schedules Direct website and remove others listings from your account before proceeding.", - "ErrorAddingMediaPathToVirtualFolder": "There was an error adding the media path. Please ensure the path is valid and the Jellyfin Server process has access to that location.", + "ErrorAddingMediaPathToVirtualFolder": "There was an error adding the media path. Please ensure the path is valid and Jellyfin has access to that location.", "ErrorAddingTunerDevice": "There was an error adding the tuner device. Please ensure it is accessible and try again.", "ErrorAddingXmlTvFile": "There was an error accessing the XMLTV file. Please ensure the file exists and try again.", - "ErrorDeletingItem": "There was an error deleting the item from Jellyfin Server. Please check that Jellyfin Server has write access to the media folder and try again.", + "ErrorDeletingItem": "There was an error deleting the item from the server. Please check that Jellyfin has write access to the media folder and try again.", "ErrorGettingTvLineups": "There was an error downloading TV lineups. Please ensure your information is correct and try again.", "ErrorStartHourGreaterThanEnd": "End time must be greater than the start time.", "ErrorPleaseSelectLineup": "Please select a lineup and try again. If no lineups are available, then please check that your username, password, and postal code is correct.", @@ -350,7 +344,6 @@ "HeaderDevices": "Devices", "HeaderDirectPlayProfile": "Direct Play Profile", "HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.", - "HeaderDisplay": "Display", "HeaderDownloadSync": "Download & Sync", "HeaderDVR": "DVR", "HeaderEasyPinCode": "Easy Pin Code", @@ -371,10 +364,8 @@ "HeaderFavoriteVideos": "Favorite Videos", "HeaderFavoritePlaylists": "Favorite Playlists", "HeaderFeatureAccess": "Feature Access", - "HeaderFeatures": "Features", "HeaderFetchImages": "Fetch Images:", "HeaderFetcherSettings": "Fetcher Settings", - "HeaderFilters": "Filters", "HeaderForKids": "For Kids", "HeaderForgotPassword": "Forgot Password", "HeaderFrequentlyPlayed": "Frequently Played", @@ -494,7 +485,6 @@ "HeaderSyncPlaySelectGroup": "Join a group", "HeaderSyncPlayEnabled": "SyncPlay enabled", "HeaderSystemDlnaProfiles": "System Profiles", - "HeaderTags": "Tags", "HeaderTaskTriggers": "Task Triggers", "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", "HeaderTracks": "Tracks", @@ -550,7 +540,6 @@ "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", "LabelAlbumArtPN": "Album art PN:", "LabelAlbumArtists": "Album artists:", - "LabelAll": "All", "LabelAllowHWTranscoding": "Allow hardware transcoding", "LabelAllowedRemoteAddresses": "Remote IP address filter:", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode:", @@ -568,7 +557,7 @@ "LabelAuthProvider": "Authentication Provider:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Automatically refresh metadata from the internet:", "LabelBindToLocalNetworkAddress": "Bind to local network address:", - "LabelBindToLocalNetworkAddressHelp": "Override the local IP address for the HTTP server. If left empty, the server will bind to all availabile addresses. Changing this value requires restarting Jellyfin Server.", + "LabelBindToLocalNetworkAddressHelp": "Override the local IP address for the HTTP server. If left empty, the server will bind to all availabile addresses. Changing this value requires a restart.", "LabelBirthDate": "Birth date:", "LabelBirthYear": "Birth year:", "LabelBitrate": "Bitrate:", @@ -632,7 +621,7 @@ "LabelEnableBlastAliveMessages": "Blast alive messages", "LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.", "LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Jellyfin.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches.", "LabelEnableDlnaDebugLogging": "Enable DLNA debug logging", "LabelEnableDlnaDebugLoggingHelp": "Create large log files and should only be used as needed for troubleshooting purposes.", "LabelEnableDlnaPlayTo": "Enable DLNA Play To", @@ -761,7 +750,7 @@ "LabelNumberOfGuideDays": "Number of days of guide data to download:", "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", "LabelOptionalNetworkPath": "Shared network folder:", - "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly. For example, {0} or {1}.", + "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow clients on other devices to access media files directly. For example, {0} or {1}.", "LabelOriginalAspectRatio": "Original aspect ratio:", "LabelOriginalTitle": "Original title:", "LabelOverview": "Overview:", @@ -984,13 +973,13 @@ "MessageConfirmProfileDeletion": "Are you sure you wish to delete this profile?", "MessageConfirmRecordingCancellation": "Cancel recording?", "MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?", - "MessageConfirmRestart": "Are you sure you wish to restart Jellyfin Server?", - "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Jellyfin Server will be abruptly terminated.", + "MessageConfirmRestart": "Are you sure you wish to restart Jellyfin?", + "MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this API key? The application's connection to this server will be abruptly terminated.", "MessageConfirmShutdown": "Are you sure you wish to shutdown the server?", "MessageContactAdminToResetPassword": "Please contact your system administrator to reset your password.", "MessageCreateAccountAt": "Create an account at {0}", "MessageDeleteTaskTrigger": "Are you sure you wish to delete this task trigger?", - "MessageDirectoryPickerBSDInstruction": "For BSD, you may need to configure storage within your FreeNAS Jail in order to allow Jellyfin to access it.", + "MessageDirectoryPickerBSDInstruction": "For BSD, you may need to configure storage within your FreeNAS Jail so Jellyfin can access your media.", "MessageDirectoryPickerLinuxInstruction": "For Linux on Arch Linux, CentOS, Debian, Fedora, openSUSE, or Ubuntu, you must grant the service user at least read access to your storage locations.", "MessageDownloadQueued": "Download queued.", "MessageEnablingOptionLongerScans": "Enabling this option may result in significantly longer library scans.", @@ -1024,7 +1013,7 @@ "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "MessagePleaseWait": "Please wait. This may take a minute.", "MessagePluginConfigurationRequiresLocalAccess": "To configure this plugin please sign in to your local server directly.", - "MessagePluginInstallDisclaimer": "Plugins built by Jellyfin community members are a great way to enhance your Jellyfin experience with additional features and benefits. Before installing, please be aware of the effects they may have on your Jellyfin Server, such as longer library scans, additional background processing, and decreased system stability.", + "MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.", "MessageReenableUser": "See below to reenable", "MessageSettingsSaved": "Settings saved.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "The following media locations will be removed from your library:", @@ -1229,7 +1218,7 @@ "OptionRuntime": "Runtime", "OptionSaturday": "Saturday", "OptionSaveMetadataAsHidden": "Save metadata and images as hidden files", - "OptionSaveMetadataAsHiddenHelp": "Changing this will apply to new metadata saved going forward. Existing metadata files will be updated the next time they are saved by Jellyfin Server.", + "OptionSaveMetadataAsHiddenHelp": "Changing this will apply to new metadata saved going forward. Existing metadata files will be updated the next time they are saved by the server.", "OptionSpecialEpisode": "Specials", "OptionSubstring": "Substring", "OptionSunday": "Sunday", @@ -1279,9 +1268,11 @@ "PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the Add button.", "PleaseConfirmPluginInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin installation.", "PleaseEnterNameOrId": "Please enter a name or an external ID.", - "PleaseRestartServerName": "Please restart Jellyfin Server - {0}.", + "PleaseRestartServerName": "Please restart Jellyfin on {0}.", "PleaseSelectTwoItems": "Please select at least two items.", - "MessagePluginInstalled": "The plugin has been successfully installed. Jellyfin Server will need to be restarted for changes to take effect.", + "MessagePluginInstalled": "The plugin has been successfully installed. The server will need to be restarted for changes to take effect.", + "MessagePluginInstallError": "An error occured while installing the plugin.", + "MessageGetInstalledPluginsError": "An error occured while getting the list of currently installed plugins.", "PreferEmbeddedTitlesOverFileNames": "Prefer embedded titles over filenames", "PreferEmbeddedTitlesOverFileNamesHelp": "This determines the default display title when no internet metadata or local metadata is available.", "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "This uses the episode information from the embedded metadata if available.", @@ -1350,10 +1341,10 @@ "SeriesRecordingScheduled": "Series recording scheduled.", "SeriesSettings": "Series settings", "SeriesYearToPresent": "{0} - Present", - "ServerNameIsRestarting": "Jellyfin Server - {0} is restarting.", - "ServerNameIsShuttingDown": "Jellyfin Server - {0} is shutting down.", - "ServerRestartNeededAfterPluginInstall": "Jellyfin Server will need to be restarted after installing a plugin.", - "ServerUpdateNeeded": "This Jellyfin Server needs to be updated. To download the latest version, please visit {0}", + "ServerNameIsRestarting": "The server at {0} is restarting.", + "ServerNameIsShuttingDown": "The server at {0} is shutting down.", + "ServerRestartNeededAfterPluginInstall": "Jellyfin will need to be restarted after installing a plugin.", + "ServerUpdateNeeded": "This server needs to be updated. To download the latest version, please visit {0}", "Settings": "Settings", "SettingsSaved": "Settings saved.", "SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.", @@ -1399,7 +1390,6 @@ "TabAdvanced": "Advanced", "TabAlbumArtists": "Album Artists", "TabAlbums": "Albums", - "TabArtists": "Artists", "TabCatalog": "Catalog", "TabRepositories": "Repositories", "TabChannels": "Channels", @@ -1409,7 +1399,6 @@ "TabDashboard": "Dashboard", "TabDevices": "Devices", "TabDirectPlay": "Direct Play", - "TabDisplay": "Display", "TabDVR": "DVR", "TabEpisodes": "Episodes", "TabFavorites": "Favorites", @@ -1419,7 +1408,6 @@ "TabLatest": "Latest", "TabLiveTV": "Live TV", "TabLogs": "Logs", - "TabMetadata": "Metadata", "TabMovies": "Movies", "TabMusic": "Music", "TabMusicVideos": "Music Videos", @@ -1431,7 +1419,6 @@ "TabOther": "Other", "TabParentalControl": "Parental Control", "TabPassword": "Password", - "TabPlayback": "Playback", "TabPlaylists": "Playlists", "TabPlugins": "Plugins", "TabProfile": "Profile", @@ -1446,9 +1433,7 @@ "TabShows": "Shows", "TabSongs": "Songs", "TabStreaming": "Streaming", - "TabSuggestions": "Suggestions", "TabTrailers": "Trailers", - "TabTranscoding": "Transcoding", "TabUpcoming": "Upcoming", "TabUsers": "Users", "Tags": "Tags", @@ -1524,7 +1509,7 @@ "Yes": "Yes", "Yesterday": "Yesterday", "PathNotFound": "The path could not be found. Please ensure the path is valid and try again.", - "WriteAccessRequired": "Jellyfin Server requires write access to this folder. Please ensure write access and try again.", + "WriteAccessRequired": "Jellyfin requires write access to this folder. Please ensure write access and try again.", "ListPaging": "{0}-{1} of {2}", "PersonRole": "as {0}", "LastSeen": "Last seen {0}", diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index c8a1dbb87c..6fb6907284 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -7,7 +7,6 @@ "HeaderLatestEpisodes": "Últimos capítulos", "HeaderLiveTV": "TV en vivo", "HeaderSeries": "Series", - "LabelAll": "Todo", "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar capítulos no disponibles en temporadas", "LabelFinish": "Terminar", "LabelNext": "Siguiente", @@ -96,7 +95,6 @@ "Browse": "Explorar", "MessageBrowsePluginCatalog": "Explore nuestro catálogo de complementos para ver los complementos disponibles.", "BurnSubtitlesHelp": "Determina si el servidor debe grabarse en subtítulos al transcodificar videos. Evitar esto mejorará en gran medida el rendimiento. Seleccione Automático para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX, ...) y ciertos subtítulos ASS o SSA.", - "ButtonAdd": "Agregar", "ButtonAddMediaLibrary": "Agregar biblioteca de medios", "ButtonAddScheduledTaskTrigger": "Agregar desencadenador", "ButtonAddServer": "Agregar servidor", @@ -122,7 +120,6 @@ "ButtonFullscreen": "Pantalla completa", "ButtonGotIt": "Lo entendí", "ButtonGuide": "Guía", - "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", "ButtonInfo": "Información", "ButtonLibraryAccess": "Acceso a la biblioteca", @@ -143,15 +140,12 @@ "ButtonRefreshGuideData": "Actualizar datos de la guía", "ButtonRemove": "Quitar", "ButtonRename": "Renombrar", - "ButtonRepeat": "Repetir", "ButtonResetEasyPassword": "Restablecer código PIN", "ButtonResetPassword": "Restablecer contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Resumir", "ButtonRevoke": "Revocar", - "ButtonSave": "Guardar", "ButtonScanAllLibraries": "Escanear todas las bibliotecas", - "ButtonSearch": "Buscar", "ButtonSelectDirectory": "Seleccionar directorio", "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectView": "Seleccionar vista", @@ -380,7 +374,6 @@ "HeaderEditImages": "Editar imágenes", "HeaderEasyPinCode": "Código PIN fácil", "HeaderDownloadSync": "Descargar y sincronizar", - "HeaderDisplay": "Pantalla", "HeaderDirectPlayProfile": "Perfil Direct Play", "HeaderDevices": "Dispositivos", "HeaderDeviceAccess": "Acceso al dispositivo", @@ -425,14 +418,11 @@ "HeaderFrequentlyPlayed": "Reproducido con frecuencia", "HeaderForgotPassword": "Olvidé la contraseña", "HeaderForKids": "Para niños", - "HeaderFilters": "Filtros", "HeaderFetcherSettings": "Configuración del recuperador", "HeaderFetchImages": "Obtener imágenes:", - "HeaderFeatures": "Caracteristicas", "HeaderFeatureAccess": "Acceso a características", "HeaderFavoritePlaylists": "Listas de reproducción favoritas", "ButtonTogglePlaylist": "Lista de reproducción", - "ButtonToggleContextMenu": "Más", "HeaderPlaybackError": "Error de reproducción", "HeaderPlayback": "Reproducción de medios", "HeaderPlayOn": "Reproducir en", @@ -493,7 +483,6 @@ "HeaderTracks": "Pistas", "HeaderThisUserIsCurrentlyDisabled": "Este usuario está actualmente deshabilitado", "HeaderTaskTriggers": "Desencadenantes de tareas", - "HeaderTags": "Etiquetas", "HeaderSystemDlnaProfiles": "Perfiles del sistema", "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulos describen los formatos de subtítulos compatibles con el dispositivo.", "HeaderSubtitleProfiles": "Perfiles de subtítulos", @@ -1409,13 +1398,11 @@ "Tags": "Etiquetas", "TabUsers": "Usuarios", "TabUpcoming": "Próximamente", - "TabTranscoding": "Transcodificación", "MarkUnplayed": "Marcar no reproducido", "MarkPlayed": "Marcar reproducido", "LabelSkipForwardLength": "Saltar hacia adelante longitud:", "Trailers": "Avances", "TabTrailers": "Avances", - "TabSuggestions": "Sugerencias", "TabStreaming": "Transmisión", "TabSongs": "Canciones", "TabShows": "Programas", @@ -1430,7 +1417,6 @@ "TabProfile": "Perfil", "TabPlugins": "Complementos", "TabPlaylists": "Listas de reproducción", - "TabPlayback": "Reproducción", "TabPassword": "Contraseña", "TabParentalControl": "Control parental", "TabOther": "Otro", @@ -1442,7 +1428,6 @@ "TabMusicVideos": "Videos musicales", "TabMusic": "Música", "TabMovies": "Películas", - "TabMetadata": "Metadatos", "TabLogs": "Registros", "TabLiveTV": "TV en vivo", "TabLatest": "Último", @@ -1451,7 +1436,6 @@ "TabGenres": "Géneros", "TabFavorites": "Favoritos", "TabDVR": "DVR", - "TabDisplay": "Pantalla", "TabDirectPlay": "Reproducción directa", "TabDevices": "Dispositivos", "TabDashboard": "Tablero", @@ -1461,7 +1445,6 @@ "TabChannels": "Canales", "TabRepositories": "Repositorios", "TabCatalog": "Catálogo", - "TabArtists": "Artistas", "TabAlbums": "Álbumes", "TabAlbumArtists": "Artistas del álbum", "TabAdvanced": "Avanzado", diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index b0923c0228..643758b660 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -50,7 +50,6 @@ "Browse": "Explorar", "MessageBrowsePluginCatalog": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", - "ButtonAdd": "Agregar", "ButtonAddMediaLibrary": "Agregar biblioteca de medios", "ButtonAddScheduledTaskTrigger": "Agregar disparador", "ButtonAddServer": "Agregar servidor", @@ -76,7 +75,6 @@ "ButtonFullscreen": "Pantalla completa", "ButtonGotIt": "Hecho", "ButtonGuide": "Guía", - "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", "ButtonLibraryAccess": "Acceso a biblioteca(s)", "ButtonManualLogin": "Inicio de sesión manual", @@ -96,15 +94,12 @@ "ButtonRefreshGuideData": "Actualizar datos de la guía", "ButtonRemove": "Remover", "ButtonRename": "Renombrar", - "ButtonRepeat": "Repetir", "ButtonResetEasyPassword": "Restablecer código PIN sencillo", "ButtonResetPassword": "Restablecer contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Continuar", "ButtonRevoke": "Revocar", - "ButtonSave": "Guardar", "ButtonScanAllLibraries": "Escanear todas las bibliotecas", - "ButtonSearch": "Búsqueda", "ButtonSelectDirectory": "Seleccionar directorio", "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectView": "Seleccionar vista", @@ -316,7 +311,6 @@ "HeaderDevices": "Dispositivos", "HeaderDirectPlayProfile": "Perfil de reproducción directa", "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", - "HeaderDisplay": "Pantalla", "HeaderDownloadSync": "Descargar y sincronizar", "HeaderEasyPinCode": "Código PIN sencillo", "HeaderEditImages": "Editar imágenes", @@ -325,10 +319,8 @@ "HeaderEpisodes": "Episodios", "HeaderExternalIds": "IDs externos:", "HeaderFeatureAccess": "Acceso a características", - "HeaderFeatures": "Características", "HeaderFetchImages": "Obtener imágenes:", "HeaderFetcherSettings": "Configuración del recolector", - "HeaderFilters": "Filtros", "HeaderForKids": "Para niños", "HeaderForgotPassword": "Olvidé mi contraseña", "HeaderFrequentlyPlayed": "Reproducido frecuentemente", @@ -441,7 +433,6 @@ "HeaderSubtitleProfiles": "Perfiles de subtítulo", "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulo describen los formatos de subtítulo soportados por el dispositivo.", "HeaderSystemDlnaProfiles": "Perfiles del sistema", - "HeaderTags": "Etiquetas", "HeaderTaskTriggers": "Disparadores de tarea", "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", "HeaderTracks": "Pistas", @@ -495,7 +486,6 @@ "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", "LabelAlbumArtPN": "PN del arte del álbum:", "LabelAlbumArtists": "Artistas del álbum:", - "LabelAll": "Todos", "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", @@ -1200,7 +1190,6 @@ "TabAdvanced": "Avanzado", "TabAlbumArtists": "Artistas del álbum", "TabAlbums": "Álbumes", - "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canales", "TabCollections": "Colecciones", @@ -1208,7 +1197,6 @@ "TabDashboard": "Panel de control", "TabDevices": "Dispositivos", "TabDirectPlay": "Reproducción directa", - "TabDisplay": "Mostrar", "TabEpisodes": "Episodios", "TabFavorites": "Favoritos", "TabGenres": "Géneros", @@ -1216,7 +1204,6 @@ "TabLatest": "Recientes", "TabLiveTV": "TV en vivo", "TabLogs": "Registros", - "TabMetadata": "Metadatos", "TabMovies": "Películas", "TabMusic": "Música", "TabMusicVideos": "Videos musicales", @@ -1227,7 +1214,6 @@ "TabOther": "Otros", "TabParentalControl": "Control parental", "TabPassword": "Contraseña", - "TabPlayback": "Reproducción", "TabPlaylists": "Listas de reproducción", "TabPlugins": "Complementos", "TabProfile": "Perfil", @@ -1241,9 +1227,7 @@ "TabShows": "Programas", "TabSongs": "Canciones", "TabStreaming": "Transmisión", - "TabSuggestions": "Sugerencias", "TabTrailers": "Trailers", - "TabTranscoding": "Transcodificación", "TabUpcoming": "Próximamente", "TabUsers": "Usuarios", "Tags": "Etiquetas", @@ -1469,7 +1453,6 @@ "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", "HeaderFavoritePlaylists": "Listas de reproducción favoritas", "ButtonTogglePlaylist": "Lista de reproducción", - "ButtonToggleContextMenu": "Más", "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM de todas formas todo el contenido será intentado, incluyendo los títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos interactivos.", "TabDVR": "DVR", "SaveChanges": "Guardar cambios", diff --git a/src/strings/es.json b/src/strings/es.json index d720b515f4..3ee716038c 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -40,7 +40,6 @@ "BoxRear": "Caja (trasera)", "Browse": "Explorar", "MessageBrowsePluginCatalog": "Explore el catálogo de extensiones para ver las extensiones disponibles.", - "ButtonAdd": "Añadir", "ButtonAddMediaLibrary": "Añadir biblioteca de medios", "ButtonAddScheduledTaskTrigger": "Agregar Activador", "ButtonAddServer": "Añadir servidor", @@ -66,7 +65,6 @@ "ButtonFullscreen": "Pantalla completa", "ButtonGotIt": "Entendido", "ButtonGuide": "Guía", - "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", "ButtonLibraryAccess": "Acceso a la biblioteca", "ButtonManualLogin": "Acceder manualmente", @@ -87,15 +85,12 @@ "ButtonRefreshGuideData": "Actualizar datos de la guía", "ButtonRemove": "Quitar", "ButtonRename": "Renombrar", - "ButtonRepeat": "Repetir", "ButtonResetEasyPassword": "Restablecer código PIN", "ButtonResetPassword": "Reiniciar Contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Continuar", "ButtonRevoke": "Revocar", - "ButtonSave": "Guardar", "ButtonScanAllLibraries": "Escanear todas las bibliotecas", - "ButtonSearch": "Buscar", "ButtonSelectDirectory": "Seleccionar directorio", "ButtonSelectServer": "Elegir servidor", "ButtonSelectView": "Seleccionar vista", @@ -231,7 +226,7 @@ "HeaderAllowMediaDeletionFrom": "Permitir borrar contenido desde", "HeaderApiKey": "Clave API", "HeaderApiKeys": "Claves API", - "HeaderApiKeysHelp": "Las aplicaciones externas requieren de una clave API para comunicarse con el servidor Jellyfin. Las claves se facilitan iniciando sesión con una cuenta de Jellyfin, u otorgando manualmente una clave a la aplicación.", + "HeaderApiKeysHelp": "Las aplicaciones externas requieren de una clave API para comunicarse con el servidor. Las claves se facilitan iniciando sesión con una cuenta de usuario en Jellyfin, u otorgando manualmente una clave a la aplicación.", "HeaderAudioBooks": "Audiolibros", "HeaderAudioSettings": "Ajustes de audio", "HeaderBlockItemsWithNoRating": "Bloquear artículos sin valoraciones o si son desconocidas:", @@ -269,7 +264,6 @@ "HeaderDevices": "Dispositivos", "HeaderDirectPlayProfile": "Perfil de reproducción directa", "HeaderDirectPlayProfileHelp": "Añadir perfiles de reproducción directa para indicar qué formatos puede utilizar el dispositivo de forma nativa.", - "HeaderDisplay": "Mostrar", "HeaderDownloadSync": "Descargar y sincronizar", "HeaderEasyPinCode": "Código PIN", "HeaderEditImages": "Editar imágenes", @@ -278,10 +272,8 @@ "HeaderEpisodes": "Episodios", "HeaderExternalIds": "IDs externos:", "HeaderFeatureAccess": "Permisos de acceso", - "HeaderFeatures": "Características", "HeaderFetchImages": "Buscar imágenes:", "HeaderFetcherSettings": "Ajustes del capturador", - "HeaderFilters": "Filtros", "HeaderForKids": "Para niños", "HeaderForgotPassword": "Contraseña olvidada", "HeaderFrequentlyPlayed": "Reproducido frecuentemente", @@ -346,7 +338,7 @@ "HeaderPreferredMetadataLanguage": "Idioma preferido para las etiquetas", "HeaderProfile": "Perfil", "HeaderProfileInformation": "Información del perfil", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presenta al dispositivo.", + "HeaderProfileServerSettingsHelp": "Estos valores controlan cómo el servidor será presentado a los clientes.", "HeaderRecentlyPlayed": "Reproducido recientemente", "HeaderRecordingOptions": "Ajustes de grabación", "HeaderRecordingPostProcessing": "Grabación post procesamiento", @@ -370,7 +362,7 @@ "HeaderSelectServerCachePath": "Seleccione la ruta para el caché del servidor", "HeaderSelectServerCachePathHelp": "Navega o introduce la ruta para alojar los archivos caché del servidor. Tienes que tener permisos de escritura en esa carpeta.", "HeaderSelectTranscodingPath": "Ruta para los archivos temporales de las conversiones", - "HeaderSelectTranscodingPathHelp": "Busca o escribe la ruta que se utilizará para guardar los archivos temporales que se generarán mientras se convierten los archivos. Jellyfin debe tener permisos de escritura en la carpeta.", + "HeaderSelectTranscodingPathHelp": "Busca o escribe la ruta que se utilizará para guardar los archivos que se generarán mientras se convierten los archivos. Jellyfin debe tener permisos de escritura en la carpeta.", "HeaderSendMessage": "Enviar mensaje", "HeaderSeries": "Series", "HeaderSeriesOptions": "Opciones de series", @@ -390,7 +382,6 @@ "HeaderSubtitleProfiles": "Perfil de los subtítulos", "HeaderSubtitleProfilesHelp": "El perfil de los subtítulos describe el formato soportado por el dispositivo.", "HeaderSystemDlnaProfiles": "Perfiles del sistema", - "HeaderTags": "Etiquetas", "HeaderTaskTriggers": "Tareas de activación", "HeaderThisUserIsCurrentlyDisabled": "Este usuario está desactivado", "HeaderTranscodingProfile": "Parámetros de conversión", @@ -417,8 +408,8 @@ "HttpsRequiresCert": "Para activar la conexión segura, necesitas un certificado SSL de confianza, como Let's Encrypt. De lo contrario, desactive las conexiones seguras.", "Identify": "Identificar", "Images": "Imágenes", - "ImportFavoriteChannelsHelp": "Si está activado, sólo los canales guardados como favoritos en el sintonizador se importarán.", - "ImportMissingEpisodesHelp": "Si está activada, la información sobre los episodios que faltan se importará en su base de datos Jellyfin y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", + "ImportFavoriteChannelsHelp": "Sólo los canales guardados como favoritos en el sintonizador se importarán.", + "ImportMissingEpisodesHelp": "La información sobre los episodios que faltan se importará en su base de datos y se mostrará en temporadas y series. Esto puede causar exploraciones de bibliotecas significativamente más largas.", "InstallingPackage": "Instalando {0} (versión {1})", "InstantMix": "Mix instantáneo", "ItemCount": "Elementos {0}", @@ -442,18 +433,17 @@ "LabelAlbumArtMaxWidthHelp": "Resolución máxima de la carátula del álbum expuesta a través de upnp: albumArtURI.", "LabelAlbumArtPN": "Carátula del album PN:", "LabelAlbumArtists": "Artistas de los álbumes:", - "LabelAll": "Todo", "LabelAllowHWTranscoding": "Activar la conversión acelerada por hardware", "LabelAllowedRemoteAddresses": "Filtro de dirección IP remota:", "LabelAllowedRemoteAddressesMode": "Modo de filtro de dirección IP remota:", "LabelAppName": "Nombre de la aplicación", "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelArtists": "Artistas:", - "LabelArtistsHelp": "Separar múltiples artistas usando ;", + "LabelArtistsHelp": "Separar múltiples artistas utilizando punto y coma.", "LabelAudioLanguagePreference": "Idioma de audio preferido:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar las etiquetas automáticamente desde Internet:", "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Anule la dirección IP local para enlazar el servidor HTTP. Si se deja vacío, el servidor se enlazará a todas las direcciones disponibles. Para cambiar este valor, debe reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Anule la dirección IP local para enlazar el servidor HTTP. Si se deja vacío, el servidor se enlazará a todas las direcciones disponibles. Para cambiar este valor, debe reiniciar el servidor Jellyfin.", "LabelBirthDate": "Fecha de nacimiento:", "LabelBirthYear": "Año de nacimiento:", "LabelBlastMessageInterval": "Intervalo para mensajes en vivo (segundos)", @@ -1101,7 +1091,6 @@ "TabAdvanced": "Avanzado", "TabAlbumArtists": "Artistas de los álbumes", "TabAlbums": "Álbumes", - "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canales", "TabCodecs": "Códecs", @@ -1110,14 +1099,12 @@ "TabDashboard": "Panel de control", "TabDevices": "Dispositivos", "TabDirectPlay": "Reproducción directa", - "TabDisplay": "Pantalla", "TabEpisodes": "Episodios", "TabFavorites": "Favoritos", "TabGenres": "Géneros", "TabGuide": "Guía", "TabLatest": "Novedades", "TabLiveTV": "Televisión en directo", - "TabMetadata": "Etiquetas", "TabMovies": "Películas", "TabMusic": "Música", "TabMusicVideos": "Videos musicales", @@ -1128,7 +1115,6 @@ "TabOther": "Otros", "TabParentalControl": "Control parental", "TabPassword": "Contraseña", - "TabPlayback": "Reproducción", "TabPlaylists": "Listas de reproducción", "TabProfile": "Perfil", "TabProfiles": "Perfiles", @@ -1141,8 +1127,6 @@ "TabShows": "Programas", "TabSongs": "Canciones", "TabStreaming": "Transmisión", - "TabSuggestions": "Sugerencias", - "TabTranscoding": "Conversión", "TabUpcoming": "Próximos", "TabUsers": "Usuarios", "Tags": "Etiquetas", @@ -1228,7 +1212,7 @@ "DatePlayed": "Reproducido el", "Descending": "Descendiente", "DirectStreamHelp1": "El tipo de archivo (H.264, AC3, etc.) y la resolución son compatibles con el dispositivo, pero no el contenedor (mkv, avi, wmv, etc.). El vídeo será re-empaquetado al vuelo antes de transmitirlo al dispositivo.", - "DirectStreamHelp2": "La transmisión directa del archivo usa muy poco procesamiento sin ninguna pérdida de calidad en el vídeo.", + "DirectStreamHelp2": "La transmisión directa del archivo usa muy poco procesamiento sin mínima pérdida de calidad en el vídeo.", "Director": "Dirección de", "Directors": "Directores", "Display": "Mostrar", @@ -1470,7 +1454,6 @@ "YadifBob": "YADIF Bob", "Yadif": "YADIF", "ButtonTogglePlaylist": "Lista de reproducción", - "ButtonToggleContextMenu": "Más", "Filter": "Filtro", "New": "Nuevo", "HeaderFavoritePlaylists": "Lista reproducción favorita", @@ -1539,5 +1522,11 @@ "MessageNoRepositories": "Sin repositorios.", "Writers": "Escritores", "StopPlayback": "Detener la reproducción", - "ClearQueue": "Borrar la cola" + "ClearQueue": "Borrar la cola", + "LabelSubtitleVerticalPosition": "Posición vertical:", + "PreviousTrack": "Saltar al anterior", + "MessageGetInstalledPluginsError": "Ha ocurrido un error al recuperar la lista de plugins instalados.", + "MessagePluginInstallError": "Ha ocurrido un error al instalar este plugin.", + "NextTrack": "Saltar al siguiente", + "LabelUnstable": "Inestable" } diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 54dd68e9ed..7f1702c910 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -132,8 +132,6 @@ "Tags": "Etiquetas", "TabUsers": "Usuarios", "TabUpcoming": "Próximamente", - "TabTranscoding": "Transcodificación", - "TabSuggestions": "Sugerencias", "TabStreaming": "Transmisión", "TabSongs": "Canciones", "TabShows": "Programas", @@ -148,7 +146,6 @@ "TabProfile": "Perfil", "TabPlugins": "Complementos", "TabPlaylists": "Listas de reproducción", - "TabPlayback": "Reproducción", "TabPassword": "Contraseña", "TabParentalControl": "Control parental", "TabOther": "Otros", @@ -160,7 +157,6 @@ "TabMusicVideos": "Videos musicales", "TabMusic": "Música", "TabMovies": "Películas", - "TabMetadata": "Metadatos", "TabLogs": "Registros", "TabLiveTV": "TV en vivo", "TabInfo": "Información", @@ -169,7 +165,6 @@ "TabFavorites": "Favoritos", "TabEpisodes": "Episodios", "TabDVR": "DVR", - "TabDisplay": "Mostrar", "TabDirectPlay": "Reproducción directa", "TabDevices": "Dispositivos", "TabDashboard": "Panel de control", @@ -181,9 +176,9 @@ "OptionPoster": "Póster", "OptionPlayed": "Reproducido", "OptionPlayCount": "Contador de reproducciones", - "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlainVideoItemsHelp": "Todos los videos serán representados en DIDL como «object.item.videoItem» en vez de un tipo más específico, como «object.item.videoItem.movie».", "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", - "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", + "OptionPlainStorageFoldersHelp": "Todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", "OptionParentalRating": "Clasificación parental", "OptionOnInterval": "En un intervalo", @@ -201,7 +196,7 @@ "OptionIsSD": "SD", "OptionIsHD": "HD", "OptionImdbRating": "Calificación de IMDb", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Estas solicitudes serán consideradas pero se ignorará el encabezado de rango de bytes.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", "OptionHomeVideos": "Fotos", "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", @@ -234,7 +229,7 @@ "OptionDownloadPrimaryImage": "Principal", "OptionDownloadMenuImage": "Menú", "OptionDownloadLogoImage": "Logo", - "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes se descargan cuando son solicitadas por un cliente. Habilita esta opción para descargarlas todas por por adelantado a medida que se agreguen nuevos medios. Esto podría causar que los escaneos de bibliotecas sean significativamente más largos.", "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", "OptionDownloadDiscImage": "Disco", "OptionDownloadBoxImage": "Caja", @@ -244,7 +239,7 @@ "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", "OptionDislikes": "No me gusta", - "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUserHelp": "El servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", "OptionDisableUser": "Desactivar este usuario", "OptionDescending": "Descendente", "OptionDatePlayed": "Fecha de reproducción", @@ -263,7 +258,7 @@ "OptionBlockChannelContent": "Contenido de canales de Internet", "OptionBlockBooks": "Libros", "OptionBanner": "Banner", - "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", + "OptionAutomaticallyGroupSeriesHelp": "Series que estén repartidas en múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", "OptionAutomatic": "Automático", "OptionAuto": "Automático", @@ -276,7 +271,7 @@ "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", - "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en reproducción en las aplicaciones debido a formatos de medios no soportados.", "OptionAllowMediaPlayback": "Permitir reproducción de medios", "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", @@ -333,7 +328,7 @@ "Mobile": "Móvil", "MinutesBefore": "minutos antes", "MinutesAfter": "minutos después", - "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o haz actualizaciones masivas usando el administrador de metadatos.", "MetadataManager": "Administrador de metadatos", "Metadata": "Metadatos", "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", @@ -392,7 +387,7 @@ "LatestFromLibrary": "Últimas - {0}", "Large": "Grande", "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", - "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LabelffmpegPathHelp": "La ruta hacia el archivo ejecutable ffmpeg, o la carpeta que contenga ffmpeg.", "LabelffmpegPath": "Ruta del FFmpeg:", "LabelZipCode": "Código postal:", "LabelYoureDone": "¡Has terminado!", @@ -563,7 +558,7 @@ "ReleaseDate": "Fecha de estreno", "RefreshQueued": "Actualización puesta en la cola.", "RefreshMetadata": "Actualizar metadatos", - "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", + "RefreshDialogHelp": "Los metadatos se actualizan según las configuraciones y servicios de internet que se habilitan en el panel de control.", "Refresh": "Actualizar", "Recordings": "Grabaciones", "RecordingScheduled": "Grabación programada.", @@ -664,7 +659,7 @@ "LabelServerName": "Nombre del servidor:", "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", "LabelServerHost": "Servidor:", - "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series:", "LabelSerialNumber": "Número de serie", "LabelSendNotificationToUsers": "Enviar la notificación a:", "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", @@ -676,7 +671,7 @@ "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", - "LabelRuntimeMinutes": "Duración (minutos):", + "LabelRuntimeMinutes": "Duración:", "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", "LabelRequireHttps": "Requerir HTTPS", "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", @@ -728,7 +723,7 @@ "LabelOriginalTitle": "Título original:", "LabelOriginalAspectRatio": "Relación de aspecto original:", "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", - "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", + "LabelOptionalNetworkPath": "Carpeta de red compartida:", "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", "LabelNumber": "Número:", @@ -741,9 +736,9 @@ "LabelStable": "Estable", "LabelChromecastVersion": "Versión de Chromecast", "LabelName": "Nombre:", - "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la máxima velocidad de bits al transmitir música.", "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", - "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas:", "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", "LabelMoviePrefix": "Prefijo de la película:", "LabelMovieCategories": "Categorías de películas:", @@ -870,7 +865,6 @@ "LabelChannels": "Canales:", "LabelCertificatePasswordHelp": "Si tu certificado requiere una contraseña, por favor, introdúcela aquí.", "LabelCertificatePassword": "Contraseña del certificado:", - "TabArtists": "Artistas", "TabAlbums": "Álbumes", "TabAlbumArtists": "Artistas del álbum", "TabAdvanced": "Avanzado", @@ -941,11 +935,11 @@ "LabelBurnSubtitles": "Quemar subtítulos:", "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", - "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", + "LabelBlastMessageInterval": "Intervalo de mensajes de vida", "LabelBitrate": "Velocidad de bits:", "LabelBirthYear": "Año de nacimiento:", "LabelBirthDate": "Fecha de nacimiento:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Sobrescribe la dirección IP local del servidor HTTP. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Para cambiar este valor se necesita reiniciar el servidor.", "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", "LabelAuthProvider": "Proveedor de autenticación:", @@ -956,14 +950,13 @@ "LabelAudioBitrate": "Velocidad de bits de audio:", "LabelAudioBitDepth": "Profundidad de bits de audio:", "LabelAudio": "Audio", - "LabelArtistsHelp": "Separar múltiples empleando ;", + "LabelArtistsHelp": "Separar múltiples artistas por punto y coma.", "LabelArtists": "Artistas:", "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelAppName": "Nombre de la aplicación", "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", - "LabelAll": "Todos", "LabelAlbumArtists": "Artistas del álbum:", "LabelAlbumArtPN": "PN del arte del álbum:", "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", @@ -987,8 +980,8 @@ "ItemCount": "{0} elementos", "InstantMix": "Mix instantáneo", "InstallingPackage": "Instalando {0} (versión {1})", - "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", - "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", + "ImportMissingEpisodesHelp": "La información sobre los episodios faltantes se importará a la base de datos y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", + "ImportFavoriteChannelsHelp": "Solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", "Images": "Imágenes", "Identify": "Identificar", "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", @@ -1017,7 +1010,6 @@ "HeaderTracks": "Pistas", "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", "HeaderTaskTriggers": "Disparadores de tarea", - "HeaderTags": "Etiquetas", "HeaderSystemDlnaProfiles": "Perfiles del sistema", "HeaderSyncPlayEnabled": "SyncPlay habilitado", "HeaderSyncPlaySelectGroup": "Unirse a un grupo", @@ -1041,7 +1033,7 @@ "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", "HeaderSelectServer": "Seleccionar servidor", "HeaderSelectPath": "Seleccionar ruta", - "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectMetadataPathHelp": "Explora o escribe la ruta donde deseas guardar los metadatos. Se tienen que tener permisos de escritura en esa carpeta.", "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", "HeaderSecondsValue": "{0} segundos", @@ -1060,7 +1052,7 @@ "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", "HeaderRecordingOptions": "Opciones de grabación", "HeaderRecentlyPlayed": "Reproducido recientemente", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderProfileServerSettingsHelp": "Estos valores controlan cómo el servidor se presentará a los clientes.", "HeaderProfileInformation": "Información del perfil", "HeaderProfile": "Perfil", "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", @@ -1108,7 +1100,7 @@ "HeaderLatestMovies": "Últimas películas", "HeaderLatestMedia": "Últimos medios", "HeaderLatestEpisodes": "Últimos episodios", - "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edita una biblioteca y ubica la sección de grabadores de metadatos.", "HeaderKeepSeries": "Conservar serie", "HeaderKeepRecording": "Conservar grabación", "HeaderItems": "Elementos", @@ -1128,10 +1120,8 @@ "HeaderFrequentlyPlayed": "Reproducido frecuentemente", "HeaderForgotPassword": "Olvidé mi contraseña", "HeaderForKids": "Para niños", - "HeaderFilters": "Filtros", "HeaderFetcherSettings": "Configuración del recolector", "HeaderFetchImages": "Obtener imágenes:", - "HeaderFeatures": "Características", "HeaderFeatureAccess": "Acceso a características", "HeaderFavoritePlaylists": "Listas de reproducción favoritas", "HeaderFavoriteVideos": "Videos favoritos", @@ -1185,7 +1175,6 @@ "HeaderEasyPinCode": "Código PIN sencillo", "HeaderDVR": "DVR", "HeaderDownloadSync": "Descargar y sincronizar", - "HeaderDisplay": "Pantalla", "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", "HeaderDirectPlayProfile": "Perfil de reproducción directa", "HeaderDevices": "Dispositivos", @@ -1226,7 +1215,7 @@ "HeaderAppearsOn": "Aparece en", "HeaderApp": "Aplicación", "ApiKeysCaption": "Lista de claves API actualmente habilitadas", - "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor. Las claves se emiten al iniciar sesión con una cuenta de usuario, o al otorgar manualmente una clave a la aplicación.", "HeaderApiKeys": "Claves API", "HeaderApiKey": "Clave API", "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", @@ -1372,7 +1361,7 @@ "HeaderSeriesOptions": "Opciones de serie", "HeaderSeries": "Series", "HeaderSendMessage": "Enviar mensaje", - "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPathHelp": "Explora o escribe la ruta para los archivos de transcodificación. Se tienen que tener permisos de escritura en esa carpeta.", "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", @@ -1397,7 +1386,6 @@ "ButtonUninstall": "Desinstalar", "ButtonTrailer": "Trailer", "ButtonTogglePlaylist": "Lista de reproducción", - "ButtonToggleContextMenu": "Más", "ButtonSubtitles": "Subtítulos", "ButtonSubmit": "Enviar", "ButtonSplit": "Dividir", @@ -1413,15 +1401,12 @@ "ButtonSelectView": "Seleccionar vista", "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectDirectory": "Seleccionar directorio", - "ButtonSearch": "Búsqueda", "ButtonScanAllLibraries": "Escanear todas las bibliotecas", - "ButtonSave": "Guardar", "ButtonRevoke": "Revocar", "ButtonResume": "Continuar", "ButtonRestart": "Reiniciar", "ButtonResetPassword": "Restablecer contraseña", "ButtonResetEasyPassword": "Restablecer código PIN sencillo", - "ButtonRepeat": "Repetir", "ButtonRename": "Renombrar", "ButtonRemove": "Remover", "ButtonRefreshGuideData": "Actualizar datos de la guía", @@ -1443,7 +1428,6 @@ "ButtonLibraryAccess": "Acceso a biblioteca(s)", "ButtonInfo": "Info", "ButtonHome": "Inicio", - "ButtonHelp": "Ayuda", "ButtonGuide": "Guía", "ButtonGotIt": "Hecho", "ButtonFullscreen": "Pantalla completa", @@ -1470,7 +1454,6 @@ "ButtonAddScheduledTaskTrigger": "Agregar disparador", "ButtonAddMediaLibrary": "Agregar biblioteca de medios", "ButtonAddImage": "Agregar imagen", - "ButtonAdd": "Agregar", "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", "MessageBrowsePluginCatalog": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", "Browse": "Explorar", @@ -1485,7 +1468,7 @@ "Backdrops": "Imágenes de fondo", "Backdrop": "Imagen de fondo", "Auto": "Auto", - "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "AuthProviderHelp": "Selecciona el proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "Audio": "Audio", "AttributeNew": "Nuevo", "AspectRatio": "Relación de aspecto", @@ -1501,7 +1484,7 @@ "AlwaysPlaySubtitles": "Siempre reproducir", "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", - "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor.", "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", "AllowFfmpegThrottling": "Regular transcodificaciones", "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", @@ -1539,5 +1522,13 @@ "ButtonCast": "Emitir", "Writers": "Escritores", "ViewAlbumArtist": "Ver Álbum de Artista", - "TabRepositories": "Repositorios" + "TabRepositories": "Repositorios", + "NextTrack": "Saltar al siguiente", + "LabelUnstable": "Inestable", + "Preview": "Vista previa", + "SubtitleVerticalPositionHelp": "Número de línea donde aparece el texto. Números positivos representan de arriba hacia abajo. Números negativos representan de abajo hacia arriba.", + "LabelSubtitleVerticalPosition": "Posición Vertical:", + "PreviousTrack": "Saltar al anterior", + "MessageGetInstalledPluginsError": "Ocurrió un error buscando la lista de plugins instalados.", + "MessagePluginInstallError": "Ocurrió un error instalando el plugin." } diff --git a/src/strings/fa.json b/src/strings/fa.json index 7d27860aaa..5db3a657ca 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -14,7 +14,6 @@ "ButtonPlay": "پخش", "ButtonQuickStartGuide": "راهنمای شروع سریع", "ButtonResetPassword": "تنظیم مجدد رمز", - "ButtonSave": "ذخیره", "ButtonSignOut": "Sign out", "ButtonSort": "مرتب سازی", "DeleteMedia": "حذف رسانه", @@ -30,7 +29,6 @@ "HeaderDeviceAccess": "دسترسی دستگاه", "HeaderEasyPinCode": "پین کد آسان", "HeaderFetcherSettings": "تنظیمات ورودی", - "HeaderFilters": "فیلتر ها", "HeaderImageOptions": "گزینه های تصویر", "HeaderInstantMix": "درهم کردن فوری", "HeaderKodiMetadataHelp": "برای فعال یا غیرفعال سازی ابرداده‌های Nfo ، یک کتابخانه را در صفحه تنظیم کتابخانه Jellyfin ویرایش کرده و قسمت سرورهای ابرداده را مسیردهی کنید.", @@ -89,11 +87,9 @@ "TabAdvanced": "پیشرفته", "TabAlbumArtists": "هنرمندان آلبوم", "TabAlbums": "آلبوم ها", - "TabArtists": "هنرمندان", "TabEpisodes": "قسمت ها", "TabGenres": "ژانرها", "TabLatest": "جدیدترین‌ها", - "TabMetadata": "فراداده", "TabMusicVideos": "موزیک ویدیوها", "TabNetworks": "شبکه ها", "TabNotifications": "اعلان ها", @@ -102,7 +98,6 @@ "TabProfiles": "پروفایل ها", "TabShows": "سریال ها", "TabSongs": "آهنگ ها", - "TabSuggestions": "پیشنهادها", "TabUpcoming": "بزودی", "TellUsAboutYourself": "در مورد خودتان به ما بگویید", "ThisWizardWillGuideYou": "این عمل برای انجام تنظیمات به شما کمک می‌کند. برای شروع، لطفا زبان مورد نظر خود را انتخاب کنید.", @@ -144,13 +139,11 @@ "ButtonSend": "ارسال", "ButtonSelectView": "انتخاب نما", "ButtonSelectServer": "انتخاب سرور", - "ButtonSearch": "جستجو", "ButtonScanAllLibraries": "اسکن تمام کتابخانه‌ها", "ButtonRevoke": "ابطال", "ButtonResume": "ادامه", "ButtonRestart": "راه اندازی مجدد", "ButtonResetEasyPassword": "بازنشانی کد پین آسان", - "ButtonRepeat": "تکرار", "ButtonRename": "تغییر نام", "ButtonRemove": "حذف", "ButtonRefreshGuideData": "به‌روز‌رسانی داده‌ی راهنما", @@ -168,7 +161,6 @@ "ButtonLibraryAccess": "دسترسی به کتابخانه", "ButtonInfo": "اطلاعات", "ButtonHome": "خانه", - "ButtonHelp": "کمک", "ButtonGuide": "راهنما", "ButtonGotIt": "متوجه شدم", "ButtonFullscreen": "تمام صفحه", @@ -189,7 +181,6 @@ "ButtonAddScheduledTaskTrigger": "افزودن راه انداز", "ButtonAddMediaLibrary": "افزودن کتابخانه رسانه", "ButtonAddImage": "افزودن تصویر", - "ButtonAdd": "افزودن", "BoxRear": "جعبه (پشت)", "Box": "جعبه", "Blacklist": "لیست سیاه", @@ -370,7 +361,6 @@ "HeaderForgotPassword": "فراموشی گذرواژه", "HeaderForKids": "برای کودکان", "HeaderFetchImages": "دریافت عکس‌ها:", - "HeaderFeatures": "برجسته‌ها", "HeaderFeatureAccess": "دسترسی‌های برجسته", "HeaderFavoriteVideos": "ویدیو‌های مورد علاقه", "HeaderFavoritePeople": "افراد مورد علاقه", @@ -383,7 +373,6 @@ "HeaderEnabledFields": "فیلد‌های فعال شده", "HeaderEditImages": "ویرایش عکس‌ها", "HeaderDownloadSync": "بارگیری و همگام‌سازی", - "HeaderDisplay": "نمایش", "HeaderDirectPlayProfileHelp": "نمایه‌ی پخش مستقیم را اضافه کنید تا مشخص کنید با چه فرمی دستگاه می‌تواند محلی برخورد کند.", "HeaderDirectPlayProfile": "نمایه‌ی پخش مستقیم", "HeaderDevices": "دستگاه‌ها", @@ -630,7 +619,6 @@ "TabInfo": "اطلاعات", "TabGuide": "راهنما", "TabFavorites": "مورد علاقه‌ها", - "TabDisplay": "نمایش", "TabDirectPlay": "پخش مستقیم", "TabDevices": "دستگاه‌ها", "TabDashboard": "داشبورد", @@ -640,7 +628,6 @@ "TabCatalog": "فهرست", "TV": "تلویزیون", "Sunday": "یکشنبه", - "TabTranscoding": "کدگذاری", "TabTrailers": "تریلرها", "Suggestions": "پیشنهادها", "Subtitles": "زیرنویس‌ها", @@ -655,7 +642,6 @@ "Smaller": "کوچکتر", "Small": "کوچک", "ButtonTogglePlaylist": "لیست پخش", - "ButtonToggleContextMenu": "بیشتر", "TheseSettingsAffectSubtitlesOnThisDevice": "این تنظیمات روی زیرنویس‌ها در این دستگاه تأثیر می‌گذارد", "TabStreaming": "در حال پخش", "TabSettings": "تنظیمات", @@ -667,7 +653,6 @@ "TabRecordings": "ضبط‌ها", "TabPlugins": "افزونه‌ها", "TabPlaylists": "لیست‌های پخش", - "TabPlayback": "پخش", "TabParentalControl": "رتبه بندی والدین", "TabOther": "سایر", "TabNfoSettings": "تنظیمات NFO", @@ -1067,7 +1052,6 @@ "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", "HeaderSyncPlaySelectGroup": "Join a group", "HeaderSyncPlayEnabled": "SyncPlay enabled", - "HeaderTags": "Tags", "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", "HeaderTracks": "Tracks", "HeaderTranscodingProfile": "Transcoding Profile", @@ -1120,7 +1104,6 @@ "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", "LabelAlbumArtPN": "Album art PN:", "LabelAlbumArtists": "Album artists:", - "LabelAll": "All", "LabelAllowHWTranscoding": "Allow hardware transcoding", "LabelAllowedRemoteAddresses": "Remote IP address filter:", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode:", diff --git a/src/strings/fi.json b/src/strings/fi.json index 73f3c608ab..fd514ddb77 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -4,7 +4,6 @@ "ButtonCancel": "Peruuta", "ButtonDeleteImage": "Poista Kuva", "ButtonResetPassword": "Nollaa salasana", - "ButtonSave": "Tallenna", "ButtonSignOut": "Sign out", "Delete": "Poista", "DeleteImage": "Poista Kuva", @@ -100,7 +99,6 @@ "Box": "Laatikko", "BoxRear": "Laatikko (takaa)", "Browse": "Selaa", - "ButtonAdd": "Lisää", "ButtonAddMediaLibrary": "Lisää Mediakirjasto", "ButtonAddScheduledTaskTrigger": "Lisää Liipaisin", "ButtonAddServer": "Lisää Palvelin", @@ -123,7 +121,6 @@ "ButtonFullscreen": "Kokonäyttötila", "ButtonGotIt": "Selvä", "ButtonGuide": "Opas", - "ButtonHelp": "Apua", "ButtonHome": "Koti", "ButtonInfo": "Tiedot", "ButtonLibraryAccess": "Kiraston pääsy", @@ -146,13 +143,11 @@ "ButtonRefreshGuideData": "Päivitä oppaan tiedot", "ButtonRemove": "Poista", "ButtonRename": "Nimeä uudelleen", - "ButtonRepeat": "Uudelleentoisto", "ButtonResetEasyPassword": "Nollaa helppo PIN-koodi", "ButtonRestart": "Käynnistä uudelleen", "ButtonResume": "Jatka", "ButtonRevoke": "Peruuta", "ButtonScanAllLibraries": "Skannaa kaikki kirjastot", - "ButtonSearch": "Haku", "ButtonSelectDirectory": "Valitse hakemisto", "ButtonSelectServer": "Valitse palvelin", "ButtonSelectView": "Valitse näkymä", @@ -461,7 +456,6 @@ "HeaderLoginFailure": "Kirjautumisvirhe", "HeaderIdentifyItemHelp": "Anna yksi tai useampi hakukriteeri. Poista kriteerejä lisätäksesi hakutuloksia.", "HeaderIdentificationCriteriaHelp": "Lisää ainakin yksi tunnistuskriteeri.", - "HeaderFeatures": "Ominaisuudet", "HeaderFavoriteVideos": "Suosikkivideot", "HeaderFavoritePeople": "Suosikki-ihmiset", "HeaderFavoriteMovies": "Suosikkielokuvat", @@ -524,7 +518,6 @@ "HeaderFrequentlyPlayed": "Usein toistetut", "HeaderFetcherSettings": "Hakijan asetukset", "HeaderFetchImages": "Hae kuvia:", - "HeaderFilters": "Suodattimet", "OptionBlockBooks": "Kirjat", "Filters": "Suodattimet", "FastForward": "Hyppää eteenpäin", @@ -670,8 +663,6 @@ "Tags": "Tunnisteet", "TabUsers": "Käyttäjät", "TabUpcoming": "Tulevat", - "TabTranscoding": "Transkoodaus", - "TabSuggestions": "Ehdotukset", "TabSongs": "Kappaleet", "TabSettings": "Asetukset", "TabServer": "Palvelin", @@ -682,14 +673,12 @@ "TabRecordings": "Tallennukset", "TabPlugins": "Liitännäiset", "TabPlaylists": "Soittolistat", - "TabPlayback": "Toistaminen", "TabNfoSettings": "NFO-asetukset", "TabNetworks": "Verkot", "TabMyPlugins": "Omat liittännäiseni", "TabMusicVideos": "Musiikkivideot", "TabMusic": "Musiikki", "TabMovies": "Elokuvat", - "TabMetadata": "Metadata", "TabLogs": "Lokit", "TabLiveTV": "Live-TV", "TabLatest": "Uusimmat", @@ -697,14 +686,12 @@ "TabGenres": "Tyylilajit", "TabFavorites": "Suosikit", "TabEpisodes": "Jaksot", - "TabDisplay": "Näyttö", "TabDirectPlay": "Suoratoisto", "TabDevices": "Laitteet", "TabDashboard": "Päänäkymä", "TabCollections": "Kokoelmat", "TabChannels": "Kanavat", "TabCatalog": "Luettelo", - "TabArtists": "Artistit", "TabAlbums": "Albumit", "TabAlbumArtists": "Albumin artistit", "TabAdvanced": "Edistynyt", @@ -889,7 +876,6 @@ "LabelBirthYear": "Syntymävuosi:", "LabelBirthDate": "Syntymäaika:", "LabelArtists": "Artistit:", - "LabelAll": "Kaikki", "LabelAlbum": "Albumi:", "LabelAirTime": "Lähetysaika:", "LabelAccessDay": "Viikonpäivä:", @@ -903,7 +889,6 @@ "HeaderVideoQuality": "Kuvanlaatu", "HeaderUsers": "Käyttäjät", "HeaderUser": "Käyttäjä", - "HeaderTags": "Tunnisteet", "HeaderSubtitleAppearance": "Tekstityksen ulkonäkö", "HeaderStatus": "Tila", "HeaderShutdown": "Sammuta", @@ -928,7 +913,6 @@ "HeaderError": "Virhe", "HeaderEpisodes": "Jaksot", "HeaderEditImages": "Muokkaa kuvia", - "HeaderDisplay": "Näyttö", "HeaderDevices": "Laitteet", "HeaderDeleteItems": "Poista valitut", "HeaderDeleteItem": "Poista valittu", @@ -1011,7 +995,6 @@ "ExitFullscreen": "Poistu kokonäyttötilasta", "Episode": "Jakso", "ButtonTogglePlaylist": "Soittolista", - "ButtonToggleContextMenu": "Lisää", "Artist": "Artisti", "RefreshQueued": "Päivitys odottamassa.", "SeriesCancelled": "Sarja peruttu.", diff --git a/src/strings/fr-ca.json b/src/strings/fr-ca.json index 9229aabf56..d5782ea3fe 100644 --- a/src/strings/fr-ca.json +++ b/src/strings/fr-ca.json @@ -138,7 +138,6 @@ "MessageBrowsePluginCatalog": "Explorer notre catalogue des plugins pour voir les plugins disponibles.", "AllowHWTranscodingHelp": "Permets au syntonisateur de transcoder les flux à la volée. Cela peut aider à réduire le transcodage requis par le serveur.", "BurnSubtitlesHelp": "Détermine si le serveur doit graver les sous-titres lors du transcodage vidéo. Éviter ceci améliorera les performances du serveur. Sélectionnez Auto pour graver les formats basés sur l'image (par exemple, VOBSUB, PGS, SUB/IDX etc) ainsi que certains sous-titres ASS/SSA.", - "ButtonAdd": "Ajouter", "ButtonAddMediaLibrary": "Ajouter une médiathèque", "ButtonAddScheduledTaskTrigger": "Ajouter un déclencheur", "ButtonAddServer": "Ajouter un serveur", @@ -162,7 +161,6 @@ "ButtonForgotPassword": "Mot de passe oublié", "ButtonFullscreen": "Plein écran", "ButtonGuide": "Guide", - "ButtonHelp": "Aide", "ButtonHome": "Accueil", "ButtonInfo": "Informations", "ButtonLibraryAccess": "Accès à la médiathèque", @@ -207,7 +205,6 @@ "ButtonUp": "Vers le haut", "ButtonUninstall": "Désinstaller", "ButtonTogglePlaylist": "Liste de lecture", - "ButtonToggleContextMenu": "Plus", "ButtonSubtitles": "Sous-titres", "ButtonSubmit": "Soumettre", "ButtonStop": "Arrêt", @@ -220,14 +217,11 @@ "ButtonSend": "Envoyer", "ButtonSelectServer": "Sélectionner le serveur", "ButtonSelectDirectory": "Sélectionner le répertoire", - "ButtonSearch": "Rechercher", "ButtonScanAllLibraries": "Analyser toutes les médiathèques", - "ButtonSave": "Sauvegarder", "ButtonRevoke": "Révoquer", "ButtonResume": "Reprendre la lecture", "ButtonResetPassword": "Réinitialiser le mot de passe", "ButtonResetEasyPassword": "Remettre à nouveau le code NIP facile", - "ButtonRepeat": "Répéter", "ButtonRename": "Renommer", "ButtonRemove": "Enlever", "ButtonRefreshGuideData": "Rafraîchir les données de guide", diff --git a/src/strings/fr.json b/src/strings/fr.json index 6d8fb68ecf..8e13275e39 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -22,7 +22,7 @@ "AllowMediaConversionHelp": "Autoriser ou refuser l'accès à la fonctionnalité de conversion des médias.", "AllowOnTheFlySubtitleExtraction": "Autoriser l'extraction des sous-titres à la volée", "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et envoyés vers les clients au format texte afin d'éviter le transcodage vidéo. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour conserver les sous-titres pendant le transcodage si l'appareil client ne les prend pas en charge nativement.", - "AllowRemoteAccess": "Autoriser les connexions distantes à ce serveur Jellyfin.", + "AllowRemoteAccess": "Autoriser les connexions distantes à ce serveur.", "AllowRemoteAccessHelp": "Si l'option est désactivée, toutes les connexions distantes seront bloquées.", "AllowedRemoteAddressesHelp": "Liste d'adresses IP ou d'IP/masque de sous-réseau séparées par des virgules qui seront autorisées à se connecter à distance. Si la liste est vide, toutes les adresses distantes seront autorisées.", "AlwaysPlaySubtitles": "Toujours afficher les sous-titres", @@ -49,7 +49,6 @@ "Browse": "Parcourir", "MessageBrowsePluginCatalog": "Explorer notre catalogue des plugins pour voir les plugins disponibles.", "BurnSubtitlesHelp": "Détermine si le serveur doit incruster les sous-titres lors du transcodage de la vidéo. Les performances seront grandement améliorées sans incrustation. Sélectionnez Auto pour incruster par image les formats (VOBSUB, PGS, SUB, IDX etc) et certains sous-titres ASS ou SSA.", - "ButtonAdd": "Ajouter", "ButtonAddMediaLibrary": "Ajouter une médiathèque", "ButtonAddScheduledTaskTrigger": "Ajouter un déclencheur", "ButtonAddServer": "Ajouter un serveur", @@ -74,7 +73,6 @@ "ButtonForgotPassword": "Mot de passe oublié", "ButtonFullscreen": "Plein écran", "ButtonGotIt": "Compris", - "ButtonHelp": "Aide", "ButtonHome": "Accueil", "ButtonInfo": "Informations", "ButtonLibraryAccess": "Accès à la médiathèque", @@ -95,15 +93,12 @@ "ButtonRefreshGuideData": "Actualiser les données du guide", "ButtonRemove": "Supprimer", "ButtonRename": "Renommer", - "ButtonRepeat": "Répéter", "ButtonResetEasyPassword": "Réinitialiser le code easy PIN", "ButtonResetPassword": "Réinitialiser le mot de passe", "ButtonRestart": "Redémarrer", "ButtonResume": "Reprendre", "ButtonRevoke": "Révoquer", - "ButtonSave": "Enregistrer", "ButtonScanAllLibraries": "Actualiser toutes les médiathèques", - "ButtonSearch": "Recherche", "ButtonSelectDirectory": "Sélectionner le répertoire", "ButtonSelectServer": "Sélectionner le serveur", "ButtonSelectView": "Sélectionnez une vue", @@ -136,7 +131,7 @@ "ColorTransfer": "Transfert de couleur", "CommunityRating": "Note de la communauté", "Composer": "Compositeur(trice)", - "ConfigureDateAdded": "Configurez comment la date d'ajout est déterminée dans le tableau de bord du serveur Jellyfin, dans les paramètres de médiathèque", + "ConfigureDateAdded": "Configurez comment la date d'ajout est déterminée dans le tableau de bord sous paramètres de médiathèque", "ConfirmDeleteImage": "Supprimer l'image ?", "ConfirmDeleteItem": "Supprimer cet élément l'effacera à la fois du système de fichiers et de votre médiathèque. Voulez-vous vraiment continuer ?", "ConfirmDeleteItems": "Supprimer ces éléments les effacera à la fois du système de fichiers et de votre médiathèque. Voulez-vous vraiment continuer ?", @@ -215,10 +210,10 @@ "EndsAtValue": "Se termine à {0}", "Episodes": "Épisodes", "ErrorAddingListingsToSchedulesDirect": "Une erreur est survenue pendant l'ajout de la programmation avec votre compte Schedules Direct. Schedules Direct autorise uniquement un nombre limité de programmations par compte. Vous devez vous connecter au site Schedules Direct et supprimer d'autres programmations depuis votre compte avant de pouvoir réessayer.", - "ErrorAddingMediaPathToVirtualFolder": "Une erreur est survenue pendant l'ajout du chemin des médias. Veuillez vérifier que le chemin est valide et que le processus du serveur Jellyfin peut y accéder.", + "ErrorAddingMediaPathToVirtualFolder": "Une erreur est survenue pendant l'ajout du chemin des médias. Veuillez vérifier que le chemin est valide et que Jellyfin peut y accéder.", "ErrorAddingTunerDevice": "Une erreur est survenue lors de l'ajout du tuner. Assurez-vous qu'il est accessible et réessayez.", "ErrorAddingXmlTvFile": "Une erreur est survenue lors de l'accès au fichier XMLTV. Assurez-vous que le fichier existe et réessayez.", - "ErrorDeletingItem": "Une erreur s'est produite lors de la suppression de l'élément du serveur Jellyfin. Vérifiez que le serveur Jellyfin a un accès en écriture au dossier multimédia et réessayez.", + "ErrorDeletingItem": "Une erreur s'est produite lors de la suppression de l'élément du serveur. Vérifiez que Jellyfin a un accès en écriture au dossier multimédia et réessayez.", "ErrorGettingTvLineups": "Une erreur est survenue pendant le téléchargement des programmes TV. Assurez-vous que vos informations sont correctes et réessayez.", "ErrorStartHourGreaterThanEnd": "La date de fin doit être postérieure à la date de début.", "ErrorPleaseSelectLineup": "Veuillez sélectionner une programmation et réessayer. Si aucune programmation n'est disponible, veuillez vérifier que vos identifiant, mot de passe et code postal sont corrects.", @@ -314,7 +309,6 @@ "HeaderDevices": "Appareils", "HeaderDirectPlayProfile": "Profil de lecture directe :", "HeaderDirectPlayProfileHelp": "Ajoutez des profils de lecture directe pour indiquer quels formats l'appareil peut lire de façon native.", - "HeaderDisplay": "Affichage", "HeaderDownloadSync": "Télécharger et synchroniser", "HeaderEasyPinCode": "Code Easy PIN", "HeaderEditImages": "Modifier les images", @@ -324,10 +318,8 @@ "HeaderError": "Erreur", "HeaderExternalIds": "Identifiants externes :", "HeaderFeatureAccess": "Accès aux fonctionnalités", - "HeaderFeatures": "Fonctionnalités", "HeaderFetchImages": "Télécharger les images :", "HeaderFetcherSettings": "Paramètres du récupérateur", - "HeaderFilters": "Filtres", "HeaderForKids": "Jeunesse", "HeaderForgotPassword": "Mot de passe oublié", "HeaderFrequentlyPlayed": "Fréquemment lus", @@ -438,7 +430,6 @@ "HeaderSubtitleProfiles": "Profils de sous-titre", "HeaderSubtitleProfilesHelp": "Les profils de sous-titre décrivent les formats de sous-titre supportés par l'appareil.", "HeaderSystemDlnaProfiles": "Profils système", - "HeaderTags": "Étiquettes", "HeaderTaskTriggers": "Déclencheurs de tâches", "HeaderThisUserIsCurrentlyDisabled": "Cet utilisateur est actuellement désactivé", "HeaderTracks": "Pistes", @@ -491,7 +482,6 @@ "LabelAlbumArtMaxWidthHelp": "Résolution maximum des images d'album exposée par upnp:albumArtURI.", "LabelAlbumArtPN": "PN d'images d'album :", "LabelAlbumArtists": "Artistes de l'album :", - "LabelAll": "Tout", "LabelAllowHWTranscoding": "Autoriser le transcodage matériel", "LabelAllowedRemoteAddresses": "Filtre d'adresse IP distante :", "LabelAllowedRemoteAddressesMode": "Type de filtre des adresses IP distantes :", @@ -502,7 +492,7 @@ "LabelAudioLanguagePreference": "Langue audio préférée :", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualiser automatiquement les métadonnées depuis internet :", "LabelBindToLocalNetworkAddress": "Lier à l'adresse de réseau local :", - "LabelBindToLocalNetworkAddressHelp": "Remplace l'adresse IP locale du serveur HTTP. Sans paramètre, le serveur va se lier à toutes les adresses disponibles. La modification de cette valeur nécessite le redémarrage du serveur Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Remplace l'adresse IP locale du serveur HTTP. Sans paramètre, le serveur va se lier à toutes les adresses disponibles. La modification de cette valeur nécessite un redémarrage.", "LabelBirthDate": "Date de naissance :", "LabelBirthYear": "Année de naissance :", "LabelBlastMessageInterval": "Intervalle des messages de présence", @@ -562,7 +552,7 @@ "LabelEnableBlastAliveMessages": "Diffuser des message de présence", "LabelEnableBlastAliveMessagesHelp": "Activer cette option si le serveur n'est pas détecté de manière fiable par les autres appareils UPnP sur votre réseau.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalle de découverte des clients", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Détermine la durée en secondes entre les recherches SSDP exécutées par Jellyfin.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Détermine la durée en secondes entre les recherches SSDP.", "LabelEnableDlnaDebugLogging": "Activer le débogage DLNA dans le journal d'événements", "LabelEnableDlnaDebugLoggingHelp": "Génère de gros fichiers de journal d'événements et ne devrait être utilisé que pour des diagnostics d'erreur.", "LabelEnableDlnaPlayTo": "Activer la lecture en DLNA", @@ -681,7 +671,7 @@ "LabelNumberOfGuideDays": "Nombre de jours de données du guide à télécharger :", "LabelNumberOfGuideDaysHelp": "Télécharger plus de journées du guide permet de programmer des enregistrements plus longtemps à l'avance et de visualiser plus de contenus, mais prendra également plus de temps. Automatique permettra une sélection automatique basée sur le nombre de chaînes.", "LabelOptionalNetworkPath": "Dossier réseau partagé :", - "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner le chemin d'accès au dossier réseau peut permettre aux applications Jellyfin sur d'autres appareils d'avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", + "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner le chemin d'accès au dossier réseau peut permettre aux clients sur d'autres appareils d'avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", "LabelOriginalAspectRatio": "Ratio d'aspect original :", "LabelOriginalTitle": "Titre original :", "LabelOverview": "Synopsis :", @@ -857,13 +847,13 @@ "MessageConfirmProfileDeletion": "Voulez-vous vraiment supprimer ce profil ?", "MessageConfirmRecordingCancellation": "Annuler l'enregistrement ?", "MessageConfirmRemoveMediaLocation": "Voulez-vous vraiment supprimer cet emplacement ?", - "MessageConfirmRestart": "Voulez-vous vraiment redémarrer le serveur Jellyfin ?", - "MessageConfirmRevokeApiKey": "Voulez-vous vraiment révoquer cette clé API ? La connexion de l'application au serveur Jellyfin sera brutalement interrompue.", + "MessageConfirmRestart": "Voulez-vous vraiment redémarrer Jellyfin ?", + "MessageConfirmRevokeApiKey": "Voulez-vous vraiment révoquer cette clé API ? La connexion de l'application à ce serveur sera brutalement interrompue.", "MessageConfirmShutdown": "Voulez-vous vraiment éteindre le serveur ?", "MessageContactAdminToResetPassword": "Veuillez contacter votre administrateur système pour réinitialiser votre mot de passe.", "MessageCreateAccountAt": "Créer un compte sur {0}", "MessageDeleteTaskTrigger": "Voulez-vous vraiment supprimer ce déclencheur de tâche ?", - "MessageDirectoryPickerBSDInstruction": "Sur BSD, vous devrez peut-être configurer le stockage de votre jail FreeNAS pour autoriser Jellyfin à y accéder.", + "MessageDirectoryPickerBSDInstruction": "Sur BSD, vous devrez peut-être configurer le stockage de votre jail FreeNAS pour autoriser Jellyfin à accéder à vos médias.", "MessageDirectoryPickerLinuxInstruction": "Pour Linux sur Arch Linux, CentOS, Debian, Fedora, openSUSE ou Ubuntu, vous devez au moins autoriser l'accès en lecture à vos répertoires de stockage pour l'utilisateur de service .", "MessageDownloadQueued": "Téléchargement mis en file d'attente.", "MessageEnablingOptionLongerScans": "Activer cette option peut accroître la durée d'actualisation de la médiathèque.", @@ -885,7 +875,7 @@ "MessagePleaseEnsureInternetMetadata": "Veuillez vous assurer que le téléchargement des métadonnées depuis Internet est activé.", "MessagePleaseWait": "Veuillez patienter. Ceci peut prendre quelques minutes.", "MessagePluginConfigurationRequiresLocalAccess": "Pour configurer cette extension, veuillez vous connecter directement à votre serveur local.", - "MessagePluginInstallDisclaimer": "Les extensions développées par les membres de la communauté Jellyfin sont une excellente manière d'améliorer votre expérience Jellyfin avec de nouvelles fonctionnalités. Avant toute installation, veuillez prendre connaissance de l'impact qu'elles peuvent avoir sur le serveur Jellyfin, comme l'augmentation de la durée d'actualisation de la médiathèque, de nouvelles tâches de fond, ou un système moins stable.", + "MessagePluginInstallDisclaimer": "Les extensions développées par les membres de la communauté sont une excellente manière d'améliorer votre expérience avec de nouvelles fonctionnalités. Avant toute installation, veuillez prendre connaissance de l'impact qu'elles peuvent avoir sur le serveur, comme l'augmentation de la durée d'actualisation de la médiathèque, de nouvelles tâches de fond, ou un système moins stable.", "MessageReenableUser": "Voir ci-dessous pour le réactiver", "MessageSettingsSaved": "Paramètres enregistrés.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Ces emplacements de média vont être supprimés de votre médiathèque :", @@ -1045,7 +1035,7 @@ "OptionRuntime": "Durée", "OptionSaturday": "Samedi", "OptionSaveMetadataAsHidden": "Enregistrer les métadonnées et les images en tant que fichier cachés", - "OptionSaveMetadataAsHiddenHelp": "La modification s'appliquera aux nouvelles métadonnées enregistrées à l'avenir. Les fichiers de métadonnées existants seront mis à jour la prochaine fois qu'ils seront enregistrés par le serveur Jellyfin.", + "OptionSaveMetadataAsHiddenHelp": "La modification s'appliquera aux nouvelles métadonnées enregistrées à l'avenir. Les fichiers de métadonnées existants seront mis à jour la prochaine fois qu'ils seront enregistrés par le serveur.", "OptionSpecialEpisode": "Spéciaux", "OptionSubstring": "Sous-chaîne", "OptionSunday": "Dimanche", @@ -1088,9 +1078,9 @@ "PleaseAddAtLeastOneFolder": "Veuillez ajouter au moins un dossier à cette médiathèque en cliquant sur le bouton Ajouter.", "PleaseConfirmPluginInstallation": "Merci de cliquer sur OK pour confirmer que vous avez lu ce qui précède et que vous souhaitez poursuivre l'installation de l'extension.", "PleaseEnterNameOrId": "Veuillez saisir un nom ou un identifiant externe.", - "PleaseRestartServerName": "Veuillez redémarrer le serveur Jellyfin - {0}.", + "PleaseRestartServerName": "Veuillez redémarrer le serveur Jellyfin sur {0}.", "PleaseSelectTwoItems": "Veuillez sélectionner au moins deux éléments.", - "MessagePluginInstalled": "Cette extension a été installée avec succès. Le serveur Jellyfin doit être redémarré afin que les modifications soient prises en compte.", + "MessagePluginInstalled": "Cette extension a été installée avec succès. Le serveur doit être redémarré afin que les modifications soient prises en compte.", "PreferEmbeddedTitlesOverFileNames": "Préférer les titres intégrés aux médias aux noms des fichiers", "PreferEmbeddedTitlesOverFileNamesHelp": "Cela détermine le titre affiché par défaut quand il n'y a pas de métadonnées en ligne ou locales disponibles.", "Premieres": "Inédits", @@ -1152,10 +1142,10 @@ "SeriesRecordingScheduled": "Enregistrement de la série planifié.", "SeriesSettings": "Paramètres de la série", "SeriesYearToPresent": "{0} - Présent", - "ServerNameIsRestarting": "Serveur Jellyfin - {0} redémarre.", - "ServerNameIsShuttingDown": "Serveur Jellyfin - {0} s'arrête.", - "ServerRestartNeededAfterPluginInstall": "Le serveur Jellyfin devra être redémarré après l'installation d'une extension.", - "ServerUpdateNeeded": "Le serveur Jellyfin doit être mis à jour. Pour télécharger la dernière version, veuillez visiter {0}", + "ServerNameIsRestarting": "Le serveur sur {0} redémarre.", + "ServerNameIsShuttingDown": "Le serveur sur {0} s'arrête.", + "ServerRestartNeededAfterPluginInstall": "Jellyfin devra être redémarré après l'installation d'une extension.", + "ServerUpdateNeeded": "Ce serveur doit être mis à jour. Pour télécharger la dernière version, veuillez visiter {0}", "Settings": "Paramètres", "SettingsSaved": "Paramètres enregistrés.", "SettingsWarning": "La modification de ces valeurs peut provoquer des défaillances de stabilité ou de connectivité. Si vous rencontrez des problèmes, nous vous recommandons de les remettre aux valeurs par défaut.", @@ -1190,20 +1180,17 @@ "TabAccess": "Accès", "TabAdvanced": "Avancé", "TabAlbumArtists": "Artistes de l'album", - "TabArtists": "Artistes", "TabCatalog": "Catalogue", "TabChannels": "Chaînes", "TabContainers": "Conteneurs", "TabDashboard": "Tableau de bord", "TabDevices": "Appareils", "TabDirectPlay": "Lecture directe", - "TabDisplay": "Affichage", "TabEpisodes": "Épisodes", "TabFavorites": "Favoris", "TabLatest": "Derniers", "TabLiveTV": "TV en direct", "TabLogs": "Journaux", - "TabMetadata": "Métadonnées", "TabMovies": "Films", "TabMusic": "Musique", "TabMusicVideos": "Vidéos musicales", @@ -1213,7 +1200,6 @@ "TabOther": "Autre", "TabParentalControl": "Contrôle Parental", "TabPassword": "Mot de passe", - "TabPlayback": "Lecture", "TabPlaylists": "Listes de lecture", "TabProfile": "Profil", "TabProfiles": "Profils", @@ -1227,7 +1213,6 @@ "TabShows": "Séries", "TabSongs": "Chansons", "TabTrailers": "Bandes-annonces", - "TabTranscoding": "Transcodage", "TabUpcoming": "À venir", "TabUsers": "Utilisateurs", "Tags": "Étiquettes", @@ -1347,7 +1332,6 @@ "TabNotifications": "Notifications", "TabPlugins": "Extensions", "TabStreaming": "Streaming", - "TabSuggestions": "Suggestions", "ValueAlbumCount": "{0} albums", "ValueMinutes": "{0} min", "ValueOneAlbum": "1 album", @@ -1457,7 +1441,7 @@ "LastSeen": "Vu pour la dernière fois {0}", "PersonRole": "en tant que {0}", "ListPaging": "{0}-{1} de {2}", - "WriteAccessRequired": "Le serveur Jellyfin a besoin d'un accès en écriture à ce dossier. Merci de vérifier l’accès en écriture et réessayez.", + "WriteAccessRequired": "Jellyfin a besoin d'un accès en écriture à ce dossier. Merci de vérifier l’accès en écriture et réessayez.", "PathNotFound": "Le chemin d'accès n'a pas pu être trouvé. Merci de le vérifier et de réessayer.", "YadifBob": "YADIF Bob", "Yadif": "YADIF", @@ -1467,7 +1451,6 @@ "LabelLibraryPageSizeHelp": "Définit la quantité d'éléments à afficher sur une page de médiathèque. Définir à 0 afin de désactiver la pagination.", "UnsupportedPlayback": "Jellyfin ne peut pas décoder du contenu protégé par un système de gestion des droits numériques, mais une tentative de lecture sera effectuée sur tout le contenu, y compris les titres protégés. Certains fichiers peuvent apparaître complètement noir, du fait de protections ou de fonctionnalités non supportées, comme les titres interactifs.", "ButtonTogglePlaylist": "Liste de lecture", - "ButtonToggleContextMenu": "Plus", "Filter": "Filtre", "New": "Nouveau", "HeaderFavoritePlaylists": "Listes de lecture favorites", @@ -1542,5 +1525,10 @@ "ViewAlbumArtist": "Voir l'album de l'artiste", "PreviousTrack": "Revenir au précédent", "NextTrack": "Passer au prochain", - "LabelUnstable": "Instable" + "LabelUnstable": "Instable", + "Preview": "Aperçu", + "SubtitleVerticalPositionHelp": "Numéro de ligne où le texte apparaît. Un nombre positif compte les lignes de haut en bas. Un nombre négatif, de bas en haut.", + "LabelSubtitleVerticalPosition": "Position verticale :", + "MessageGetInstalledPluginsError": "Une erreur est survenue lors de la récupération de la liste des extensions installées.", + "MessagePluginInstallError": "Une erreur est survenue durant l'installation de l'extension." } diff --git a/src/strings/gsw.json b/src/strings/gsw.json index bf9852e512..70c773a137 100644 --- a/src/strings/gsw.json +++ b/src/strings/gsw.json @@ -6,7 +6,6 @@ "ButtonOk": "OK", "ButtonQuickStartGuide": "Schnellstart Instruktione", "ButtonResetPassword": "Passwort zrug setze", - "ButtonSave": "Speichere", "ButtonSignOut": "Uslogge", "ButtonSort": "Sortiere", "ChannelAccessHelp": "Wähl en Kanal us, um de mit dem User z'teile. Administratore werded immer d'Möglichkeit ha alli Kanäl mitm Metadate Manager z'bearbeite.", @@ -98,12 +97,10 @@ "TabAdvanced": "Erwiitert", "TabAlbumArtists": "Album-Artist", "TabAlbums": "Albene", - "TabArtists": "Artist", "TabCatalog": "Katalog", "TabEpisodes": "Episode", "TabGenres": "Genre", "TabLatest": "Letschti", - "TabMetadata": "Metadate", "TabMovies": "Film", "TabMusicVideos": "Musigvideos", "TabMyPlugins": "Miini Plugins", @@ -113,7 +110,6 @@ "TabProfile": "Profil", "TabProfiles": "Profil", "TabShows": "Serie", - "TabSuggestions": "Vorschläg", "TabUpcoming": "Usstehend", "TellUsAboutYourself": "Verzell was über dech selber", "ThisWizardWillGuideYou": "De Assistent hilft der dur de Installations Prozess. Zum afange, wähl bitte dini Sproch us.", diff --git a/src/strings/he.json b/src/strings/he.json index c266f587c2..f5084ace4c 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -16,7 +16,6 @@ "Backdrops": "תמונות רקע", "BirthLocation": "מיקום לידה", "MessageBrowsePluginCatalog": "עבור לקטלוג התוספים לראות אילו זמינים.", - "ButtonAdd": "הוסף", "ButtonAddUser": "הוסף משתמש", "ButtonCancel": "בטל", "ButtonDelete": "מחק", @@ -33,8 +32,6 @@ "ButtonRemove": "הסר", "ButtonResetPassword": "איפוס סיסמא", "ButtonRestart": "הפעל מחדש", - "ButtonSave": "שמור", - "ButtonSearch": "חיפוש", "ButtonSelectDirectory": "בחר תיקיות", "ButtonShutdown": "כבה", "ButtonSignIn": "היכנס", @@ -440,7 +437,6 @@ "TabAdvanced": "מתקדם", "TabAlbumArtists": "אמני אלבום", "TabAlbums": "אלבומים", - "TabArtists": "אמנים", "TabCatalog": "קטלוג", "TabChannels": "ערוצים", "TabCodecs": "מקודדים", @@ -467,9 +463,7 @@ "TabSettings": "הגדרות", "TabShows": "תוכניות", "TabSongs": "שירים", - "TabSuggestions": "המלצות", "TabTrailers": "טריילרים", - "TabTranscoding": "קידוד", "TabUpcoming": "בקרוב", "Tags": "מילות מפתח", "TellUsAboutYourself": "ספר לנו על עצמך", @@ -573,7 +567,6 @@ "ButtonMore": "עוד", "ButtonInfo": "מידע", "ButtonHome": "בית", - "ButtonHelp": "עזרה", "ButtonFullscreen": "מסך מלא", "ButtonEditImages": "ערוך תמונות", "ButtonConnect": "התחבר", @@ -588,7 +581,6 @@ "MessageConfirmRestart": "‫האם אתה בטוח שברצונך לאתחל את שרת ה-Jellyfin‏?", "HeaderThisUserIsCurrentlyDisabled": "משתמש זה אינו פעיל כרגע", "HeaderTaskTriggers": "טריגרים של המשימה", - "HeaderTags": "מילות מפתח", "HeaderStopRecording": "עצור הקלטה", "HeaderSortOrder": "סדר מיון", "HeaderSortBy": "מיין לפי", @@ -626,7 +618,6 @@ "HeaderHome": "בית", "HeaderGenres": "ז'אנרים", "HeaderForKids": "עבור ילדים", - "HeaderFilters": "מסננים", "HeaderFavoriteVideos": "סרטונים מועדפים", "HeaderFavoritePeople": "אנשים מועדפים", "HeaderFavoriteMovies": "סרטים מועדפים", @@ -702,7 +693,6 @@ "TabScheduledTasks": "משימות מתוזמנות", "TabResumeSettings": "המשך צפייה", "ButtonResume": "המשך", - "ButtonRepeat": "חזרה", "ButtonRefresh": "רענון", "ButtonProfile": "פרופיל", "ButtonOpen": "פתח", @@ -756,7 +746,6 @@ "LabelSpecialSeasonsDisplayName": "שם תצוגת \"עונה מיוחדת\":", "LabelSource": "מקור:", "ButtonTogglePlaylist": "רשימת ניגון", - "ButtonToggleContextMenu": "עוד", "ButtonSyncPlay": "SyncPlay", "ButtonPlayer": "נגן", "StopPlayback": "הפסק הפעלה", @@ -821,8 +810,6 @@ "MessagePlayAccessRestricted": "התוכן הזה לא ניתן לניגון כרגע. למידע נוסף, נא ליצור קשר עם מנהל המערכת שלך.", "MessageContactAdminToResetPassword": "נא ליצור קשר עם מנהל המערכת שלך על מנת לאפס את הסיסמה שלך.", "HeaderAdmin": "מנהל", - "TabDisplay": "תצוגה", - "HeaderDisplay": "תצוגה", "Suggestions": "המלצות", "MessageSyncPlayNoGroupsAvailable": "אין קבוצות זמינות. התחל לנגן משהו קודם.", "OptionHomeVideos": "תמונות", diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index e7e45dfa20..e7ac775adf 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -44,7 +44,6 @@ "ButtonLibraryAccess": "पुस्तकालय का उपयोग", "ButtonInfo": "जानकारी", "ButtonHome": "घर", - "ButtonHelp": "मदद", "ButtonGuide": "मार्गदर्शक", "ButtonGotIt": "समझ गया", "ButtonFullscreen": "पूर्ण स्क्रीन", @@ -71,7 +70,6 @@ "ButtonAddScheduledTaskTrigger": "ट्रिगर जोड़ें", "ButtonAddMediaLibrary": "मीडिया लाइब्रेरी जोड़ें", "ButtonAddImage": "छवि जोड़ें", - "ButtonAdd": "जोड़ें", "UnsupportedPlayback": "Jellyfin DRM द्वारा संरक्षित सामग्री को डिक्रिप्ट नहीं कर सकता है, लेकिन सभी सामग्री की परवाह किए बिना, संरक्षित शीर्षकों सहित प्रयास किया जाएगा। एन्क्रिप्शन या अन्य असमर्थित सुविधाओं जैसे इंटरेक्टिव शीर्षक के कारण कुछ फाइलें पूरी तरह से काली दिखाई दे सकती हैं।", "BoxRear": "बॉक्स (पीछे)", "Box": "बॉक्स", diff --git a/src/strings/hr.json b/src/strings/hr.json index 622df7775e..44c151be31 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -16,7 +16,6 @@ "BirthLocation": "Lokacija rođenja", "BirthPlaceValue": "Mjesto rođenja: {0}", "MessageBrowsePluginCatalog": "Pregledajte dostupne dodatke u našem katalogu.", - "ButtonAdd": "Dodaj", "ButtonAddMediaLibrary": "Dodaj medijsku bibilioteku", "ButtonAddScheduledTaskTrigger": "Dodaj okidač", "ButtonAddServer": "Dodaj Server", @@ -41,7 +40,6 @@ "ButtonFullscreen": "Puni zaslon", "ButtonGotIt": "Shvaćam", "ButtonGuide": "Vodič", - "ButtonHelp": "Pomoć", "ButtonHome": "Početna", "ButtonLibraryAccess": "Pristup biblioteci", "ButtonManualLogin": "Ručna prijava", @@ -62,14 +60,11 @@ "ButtonRefreshGuideData": "Osvježi TV vodič", "ButtonRemove": "Ukloni", "ButtonRename": "Preimenuj", - "ButtonRepeat": "Ponovi", "ButtonResetEasyPassword": "Poništi jednostavan PIN kod", "ButtonResetPassword": "Resetiraj lozinku", "ButtonRestart": "Ponovo pokreni", "ButtonResume": "Nastavi", "ButtonRevoke": "Opozvati", - "ButtonSave": "Snimi", - "ButtonSearch": "Traži", "ButtonSelectDirectory": "Odaberi mapu", "ButtonSelectServer": "Odaberi Server", "ButtonSelectView": "Odaberi pogled", @@ -202,7 +197,6 @@ "HeaderDevices": "Uređaji", "HeaderDirectPlayProfile": "Profil za direktnu reprodukciju", "HeaderDirectPlayProfileHelp": "Dodaj izravne profile reprodukcije za označavanje kojim formatima uređaj može rukovati prirodno.", - "HeaderDisplay": "Prikaz", "HeaderEasyPinCode": "Lagan PIN", "HeaderEditImages": "Uređivanje slika", "HeaderEnabledFields": "Omogući polja", @@ -210,9 +204,7 @@ "HeaderEpisodes": "Epizode", "HeaderError": "Greška", "HeaderFeatureAccess": "Pristup opcijama", - "HeaderFeatures": "Mogućnosti", "HeaderFetchImages": "Dohvati slike:", - "HeaderFilters": "Filtri", "HeaderForKids": "Za djecu", "HeaderForgotPassword": "Zaboravili ste lozinku", "HeaderFrequentlyPlayed": "Često izvođeno", @@ -297,7 +289,6 @@ "HeaderSubtitleProfiles": "Profili titlova prijevoda", "HeaderSubtitleProfilesHelp": "Profili titlova prijevoda opisuju format titlova koji podržava uređaj.", "HeaderSystemDlnaProfiles": "Sistemski profil", - "HeaderTags": "Oznake", "HeaderTaskTriggers": "Okidači zadataka", "HeaderThisUserIsCurrentlyDisabled": "Ovaj je korisnik trenutno onemogućen", "HeaderTranscodingProfile": "Profil transkodiranja", @@ -340,7 +331,6 @@ "LabelAlbumArtMaxWidthHelp": "Maksimalna rezolucija albuma izloženih putem UPnP:albumArtURI.", "LabelAlbumArtPN": "Grafika albuma PN:", "LabelAlbumArtists": "Izvođači albuma:", - "LabelAll": "Sve", "LabelAllowHWTranscoding": "Dopusti hardversko konvertiranje", "LabelAppName": "Ime aplikacije", "LabelAppNameExample": "Primjer: Sickbeard, Sonarr", @@ -896,7 +886,6 @@ "TabAdvanced": "Napredno", "TabAlbumArtists": "Albumi izvođača", "TabAlbums": "Albumi", - "TabArtists": "Izvođači", "TabCatalog": "Katalog", "TabChannels": "Kanali", "TabCodecs": "Kodek", @@ -905,7 +894,6 @@ "TabDashboard": "Nadzorna ploča", "TabDevices": "Uređaji", "TabDirectPlay": "Direktna reprodukcija", - "TabDisplay": "Prikaz", "TabEpisodes": "Epizode", "TabFavorites": "Omiljeni", "TabGenres": "Žanrovi", @@ -913,7 +901,6 @@ "TabLatest": "Zadnje", "TabLiveTV": "TV uživo", "TabLogs": "Dnevnici", - "TabMetadata": "Meta-podaci", "TabMovies": "Filmovi", "TabMusic": "Glazba", "TabMusicVideos": "Muzički spotovi", @@ -924,7 +911,6 @@ "TabOther": "Ostalo", "TabParentalControl": "Roditeljska kontrola", "TabPassword": "Lozinka", - "TabPlayback": "Reprodukcija", "TabPlaylists": "Popisi", "TabPlugins": "Dodaci", "TabProfile": "Profil", @@ -938,9 +924,7 @@ "TabShows": "Emisije", "TabSongs": "Pjesme", "TabStreaming": "Strujanje", - "TabSuggestions": "Prijedlozi", "TabTrailers": "Kratki filmovi", - "TabTranscoding": "Konvertiranje", "TabUpcoming": "Uskoro", "TabUsers": "Korisnici", "Tags": "Oznake", @@ -1048,7 +1032,6 @@ "Connect": "Spoji", "ClientSettings": "Postavke klijenta", "ButtonTogglePlaylist": "Lista izvođenja", - "ButtonToggleContextMenu": "Više", "ButtonSplit": "Odvoji", "ButtonStop": "Stop", "ButtonScanAllLibraries": "Skeniraj sve biblioteke", diff --git a/src/strings/hu.json b/src/strings/hu.json index a25e02882c..70089ff734 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -16,7 +16,6 @@ "BirthPlaceValue": "Születési hely: {0}", "Books": "Könyvek", "Browse": "Tallózás", - "ButtonAdd": "Hozzáadás", "ButtonAddMediaLibrary": "Médiakönyvtár hozzáadása", "ButtonAddServer": "Szerver Hozzáadása", "ButtonAddUser": "Új felhasználó", @@ -35,7 +34,6 @@ "ButtonFilter": "Szűrő", "ButtonForgotPassword": "Elfelejtett Jelszó", "ButtonGotIt": "Értettem", - "ButtonHelp": "Segítség", "ButtonHome": "Kezdőlap", "ButtonLibraryAccess": "Könyvtár hozzáférés", "ButtonManualLogin": "Manuális belépés", @@ -53,14 +51,11 @@ "ButtonRefresh": "Frissítés", "ButtonRemove": "Eltávolítás", "ButtonRename": "Átnevezés", - "ButtonRepeat": "Ismétlés", "ButtonResetEasyPassword": "Pin kód visszaállítása", "ButtonResetPassword": "Jelszó visszaállítás", "ButtonRestart": "Újraindítás", "ButtonResume": "Folytatás", - "ButtonSave": "Mentés", "ButtonScanAllLibraries": "Minden könyvtár beolvasása", - "ButtonSearch": "Keresés", "ButtonSelectDirectory": "Könyvtár választása", "ButtonSelectServer": "Szerver Kiválasztás", "ButtonSend": "Küldés", @@ -140,15 +135,12 @@ "HeaderDeveloperInfo": "Fejlesztői információk", "HeaderDeviceAccess": "Eszköz Hozzáférések", "HeaderDevices": "Eszközök", - "HeaderDisplay": "Megjelenítés", "HeaderDownloadSync": "Letöltés és szinkronizálás", "HeaderEasyPinCode": "Pin kód", "HeaderEditImages": "Képek szerkesztése", "HeaderEnabledFields": "Engedélyezett mezők", "HeaderEpisodes": "Epizódok", "HeaderExternalIds": "Külső id-k:", - "HeaderFeatures": "Jellemzők", - "HeaderFilters": "Szűrők", "HeaderForgotPassword": "Elfelejtett Jelszó", "HeaderFrequentlyPlayed": "Gyakran játszott", "HeaderGenres": "Műfajok", @@ -208,7 +200,6 @@ "HeaderStatus": "Állapot", "HeaderSubtitleDownloads": "Felirat letöltések", "HeaderSystemDlnaProfiles": "Rendszer profilok", - "HeaderTags": "Címkék", "HeaderTracks": "Sávok", "HeaderUploadImage": "Kép feltöltés", "HeaderUser": "Felhasználó", @@ -225,7 +216,6 @@ "InstallingPackage": "{0} ({1} verzió) telepítése", "Label3DFormat": "3D formátum:", "LabelAlbumArtists": "Album előadók:", - "LabelAll": "Összes", "LabelArtists": "Előadók:", "LabelAudio": "Audió", "LabelAudioLanguagePreference": "Audió nyelvének beállítása:", @@ -352,7 +342,7 @@ "MediaInfoResolution": "Felbontás", "MediaInfoSampleRate": "Mintavételi ráta", "MessageAlreadyInstalled": "Ez a verzió már telepítve van.", - "MessageConfirmRestart": "Biztosan újra szeretnéd indítani a Jellyfin Szervert?", + "MessageConfirmRestart": "Biztosan újra szeretnéd indítani a Jellyfint?", "MessageConfirmShutdown": "Biztosan le akarod állítani a Szervert?", "MessageItemsAdded": "Elem hozzáadva.", "MessageNoPluginsInstalled": "Nincs bővítmény telepítve.", @@ -445,7 +435,7 @@ "PlayAllFromHere": "Összes vetítése innen", "PlayCount": "Lejátszások száma", "Played": "Megnézett", - "PleaseRestartServerName": "Kérlek indítsd újra a Jellyfin Szerver-t - {0}.", + "PleaseRestartServerName": "Kérlek indítsd újra a Jellyfint itt: {0}.", "Quality": "Minőség", "RecommendationBecauseYouLike": "Mert tetszett a(z) {0}", "RecommendationBecauseYouWatched": "Amiért megnézted ezt: {0}", @@ -475,9 +465,9 @@ "SearchResults": "A keresés eredménye", "SendMessage": "Üzenet küldés", "SeriesYearToPresent": "{0} - Napjainkig", - "ServerNameIsRestarting": "Jellyfin Szerver - {0} újraindul.", - "ServerNameIsShuttingDown": "Jellyfin Server - {0} leáll.", - "ServerUpdateNeeded": "Ezt a Jellyfin Szervert frissíteni kell. A legújabb verzió letöltéséhez kérjük, látogass el ide {0}", + "ServerNameIsRestarting": "A szerver újraindul itt: {0}.", + "ServerNameIsShuttingDown": "A szerver leáll itt: {0}.", + "ServerUpdateNeeded": "A Jellyfint frissíteni kell. A legújabb verzió letöltéséhez kérjük, látogass el ide {0}", "Settings": "Beállítások", "SettingsSaved": "Beállítások mentve.", "Share": "Megosztás", @@ -495,7 +485,6 @@ "TabAdvanced": "Haladó", "TabAlbumArtists": "Album Előadók", "TabAlbums": "Albumok", - "TabArtists": "Előadók", "TabCatalog": "Katalógus", "TabChannels": "Csatornák", "TabCodecs": "Kódek", @@ -503,7 +492,6 @@ "TabContainers": "Tároló", "TabDashboard": "Vezérlőpult", "TabDevices": "Eszközök", - "TabDisplay": "Megjelenítés", "TabEpisodes": "Epizódok", "TabFavorites": "Kedvencek", "TabGenres": "Műfajok", @@ -511,7 +499,6 @@ "TabInfo": "Infó", "TabLatest": "Legújabb", "TabLogs": "Naplók", - "TabMetadata": "Metaadat", "TabMovies": "Filmek", "TabMusic": "Zene", "TabMusicVideos": "Zenei Videók", @@ -522,7 +509,6 @@ "TabOther": "Egyéb", "TabParentalControl": "Szülői Felügyelet", "TabPassword": "Jelszó", - "TabPlayback": "Lejátszás", "TabPlaylists": "Lejátszási listák", "TabPlugins": "Bővítmények", "TabProfile": "Profil", @@ -534,9 +520,7 @@ "TabSettings": "Beállítások", "TabShows": "Műsorok", "TabSongs": "Dalok", - "TabSuggestions": "Javaslatok", "TabTrailers": "Előzetesek", - "TabTranscoding": "Átkódolás", "TabUpcoming": "Hamarosan érkezik", "TabUsers": "Felhasználók", "Tags": "Címkék", @@ -579,7 +563,7 @@ "AllComplexFormats": "Minden összetett formátum (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllowMediaConversion": "Média konvertálás engedélyezése", "AllowMediaConversionHelp": "Add meg vagy tiltsd le a média konvertálás funkcióhoz való hozzáférést.", - "AllowRemoteAccess": "Engedélyezze a távoli kapcsolatokat a Jellyfin szerverhez.", + "AllowRemoteAccess": "Távoli kapcsolatok engedélyezése ehhez a szerverhez.", "AllowRemoteAccessHelp": "Ha nincs bekapcsolva, minden távoli kapcsolat blokkolva lesz.", "AlwaysPlaySubtitles": "Mindig jelenjen meg", "AnyLanguage": "Bármelyik nyelv", @@ -623,7 +607,7 @@ "ColorSpace": "Színtér", "ColorTransfer": "Színátvitel", "Composer": "Zeneszerző", - "ConfigureDateAdded": "Állítsd be a hozzáadott dátum meghatározását a Jellyfin Szerver Vezérlőpultjában a Könyvtár beállításai alatt", + "ConfigureDateAdded": "Állítsd be a hozzáadott dátum meghatározását a Vezérlőpultban a Könyvtár beállításai alatt", "ConfirmDeleteImage": "Kép törlése?", "ConfirmDeleteItem": "Az elem törlése mind a fájlrendszerből, mind a médiakönyvtárból törlődik. Biztosan folytatni akarod?", "ConfirmDeleteItems": "Az elem törlése mind a fájlrendszerből, mind a médiakönyvtárból törlődik. Biztosan folytatni akarod?", @@ -668,10 +652,10 @@ "EnablePhotos": "Fotók megjelenítése", "EnablePhotosHelp": "A fényképeket a médiafájlok mellett észleli és megjeleníti.", "Ended": "Befejeződött", - "ErrorAddingMediaPathToVirtualFolder": "Hiba történt a média elérésekor. Kérlek győződjön meg róla, hogy az elérési út érvényes és a Jellyfin szerver hozzáfér az adott helyhez.", + "ErrorAddingMediaPathToVirtualFolder": "Hiba történt a média elérésekor. Kérlek győződj meg róla, hogy az elérési út érvényes és a szerver hozzáfér a megadott helyhez.", "ErrorAddingTunerDevice": "Hiba történt a tuner eszköz hozzáadásakor. Kérlek győződj meg róla, hogy az eszköz elérhető és próbáld meg újra.", "ErrorAddingXmlTvFile": "Hiba történt az XMLTV fájl elérésekor. Győződj meg róla, hogy a fájl létezik és próbáld meg újra.", - "ErrorDeletingItem": "Hiba történt az elem törlése során a Jellyfin Szerverről. Ellenőrizd, hogy a Jellyfin Szerver rendelkezik-e írási jogosultsággal a média mappához és próbálja újra.", + "ErrorDeletingItem": "Hiba történt az elem szerverről való törlése során. Ellenőrizd, hogy a szerver rendelkezik-e írási jogosultsággal a média mappához és próbálja újra.", "ErrorStartHourGreaterThanEnd": "A befejezési időnek nagyobbnak kell lennie mint a kezdési idő.", "ErrorSavingTvProvider": "Hiba történt a TV szolgáltató mentésekor. Kérlek győződj meg róla, hogy elérhető és próbálkozz meg újra.", "EveryNDays": "Minden {0} nap", @@ -826,7 +810,7 @@ "LabelAppNameExample": "Például: Sickbeard, Sonarr", "LabelAutomaticallyRefreshInternetMetadataEvery": "A metaadatok automatikus frissítése az internetről:", "LabelBindToLocalNetworkAddress": "Kötés a helyi hálózati címhez:", - "LabelBindToLocalNetworkAddressHelp": "A helyi IP cím felülbírálása a HTTP szerverhez való csatlakozáshoz. Ha üres marad, a szerver minden elérhető címhez kötődik. Az érték megváltoztatásához a Jellyfin Szerver újraindítása szükséges.", + "LabelBindToLocalNetworkAddressHelp": "A helyi IP cím felülbírálása a HTTP szerverhez való csatlakozáshoz. Ha üres marad, a szerver minden elérhető címhez kötődik. Az érték megváltoztatásához a szerver újraindítása szükséges.", "LabelBirthDate": "Születési dátum:", "LabelBlastMessageInterval": "Élő üzenetintervallum", "LabelBlastMessageIntervalHelp": "Meghatározza másodpercben az üzenetek közötti időtartamot.", @@ -854,7 +838,7 @@ "LabelEnableAutomaticPortMapHelp": "A szerver az UPnP segítségével a routeren megpróbálja automatikusan átirányítani a nyilvános portot a helyi portra. Előfordulhat, hogy egyes router modellek, vagy hálózati konfigurációk esetén ez nem működik. A módosítások újraindítás után lépnek életbe.", "LabelEnableBlastAliveMessagesHelp": "Engedélyezd ezt ha a szerver nem észleli megbízhatóan a hálózat más UPnP-eszközeit.", "LabelEnableDlnaClientDiscoveryInterval": "Kliens felderítési intervallum", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "A Jellyfin által végrehajtott SSDP keresések időtartamát határozza meg másodpercben.", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "A szerver által végrehajtott SSDP keresések időtartamát határozza meg másodpercben.", "LabelEnableDlnaDebugLogging": "DLNA hibakeresési naplózás engedélyezése", "LabelEnableDlnaDebugLoggingHelp": "Ez nagy naplófájlokat hoz létre és csak hibaelhárítás céljából használható.", "LabelEnableDlnaPlayTo": "DLNA Play To engedélyezése", @@ -916,7 +900,7 @@ "LabelNewName": "Új név:", "LabelNewsCategories": "Hírek kategóriái:", "LabelNumber": "Szám:", - "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a Jellyfin alkalmazások más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0{ vagy {1}.", + "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a kliensek más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0{ vagy {1}.", "LabelPasswordConfirm": "Jelszó (megerősítés):", "LabelPlaceOfBirth": "Születési hely:", "LabelPostProcessor": "A feldolgozás utáni alkalmazás:", @@ -1028,7 +1012,7 @@ "LabelSpecialSeasonsDisplayName": "Speciális évad megjelenítési neve:", "LanNetworksHelp": "Vesszővel elválasztott lista az IP címekről vagy IP / netmask bejegyzésekről a hálózatokban, amelyeket a helyi hálózaton figyelembe kell venni a sávszélesség korlátozások végrehajtása során. Ha be van állítva, az összes többi IP cím külső hálózaton lesz, és a külső sávszélesség korlátozások szabálya alá tartozik. Ha üres marad, csak a szerver alhálózata tekinthető a helyi hálózatnak.", "LiveBroadcasts": "Élő adások", - "MessageConfirmRevokeApiKey": "Biztosan visszavonod ezt az API kulcsot? Az alkalmazás csatlakozása a Jellyfin Szerverhez hirtelen megszűnik.", + "MessageConfirmRevokeApiKey": "Biztosan visszavonod ezt az API kulcsot? Az alkalmazás csatlakozása a szerverhez hirtelen megszűnik.", "MessageDirectoryPickerLinuxInstruction": "Az Arch Linux, CentOS, Debian, Fedora, openSUSE vagy Ubuntu Linux operációs rendszereken a Jellyfin szolgáltatás felhasználójának legalább olvasási hozzáférést kell biztosítania a tárolóhelyekhez.", "MessageForgotPasswordInNetworkRequired": "Kérlek próbáld meg újra a jelszó visszaállítási folyamatot az otthoni hálózatban.", "MessageNoMovieSuggestionsAvailable": "Jelenleg nincsenek filmajánlatok. Kezdj el nézni és értékelni a filmeket, majd térj vissza, hogy megtekinthesd az ajánlásokat.", @@ -1036,7 +1020,7 @@ "MessagePlayAccessRestricted": "A tartalom lejátszása jelenleg korlátozott. További információért fordulj a Szerver üzemeltetőjéhez.", "MessagePleaseWait": "Kérlek várj. Ez eltarthat egy percet.", "MessagePluginConfigurationRequiresLocalAccess": "A bővítmény beállításához jelentkezz be közvetlenül a helyi szerverre.", - "MessagePluginInstallDisclaimer": "A Jellyfin közösség tagjai által készített bővítmények nagyszerű módot adnak a Jellyfin élményének, funkcióinak bővítéséhez. Telepítés előtt kérlek vedd figyelembe a Jellyfin szerverre gyakorolt hatásokat, mint például a hosszabb könyvtárvizsgálatokat, a további háttérfeldolgozást, vagy akár a rendszer stabilitásának csökkenését.", + "MessagePluginInstallDisclaimer": "A közösség tagjai által készített bővítmények nagyszerű módot adnak a felhasználói élmény bővítéséhez. Telepítés előtt kérlek vedd figyelembe a szerverre gyakorolt hatásokat, mint például a hosszabb könyvtárvizsgálatokat, a további háttérfeldolgozást, vagy akár a rendszer stabilitásának csökkenését.", "MessageReenableUser": "Az újra engedélyezéshez lásd lentebb", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "A következő médiahelyek eltávolításra kerülnek a könyvtáradból:", "MessageUnableToConnectToServer": "Jelenleg nem tudunk csatlakozni a kiválasztott szerverhez. Győződj meg róla, hogy fut és próbáld meg újra.", @@ -1131,7 +1115,7 @@ "OptionRequirePerfectSubtitleMatch": "Csak olyan feliratokat töltsön le, amelyek tökéletesen megfelelnek a videó fájljaimnak", "OptionRequirePerfectSubtitleMatchHelp": "A tökéletes egyezés megköveteli a feliratok szűrését, hogy csak azokat tartalmazzák amelyeket teszteltek és hitelesítettek a pontos videofájljával. Az opció kikapcsolása növeli a felirat találat valószínűségét, de növeli a hibás vagy helytelen felirat szövegének esélyeit is.", "OptionSaveMetadataAsHidden": "Mentse a metaadatokat és a képeket rejtett fájlként", - "OptionSaveMetadataAsHiddenHelp": "Ennek megváltoztatása az új metaadatokra vonatkozik, amelyeket ment. A már meglévő metaadatfájlok frissítése a Jellyfin Server következő mentésekor történik.", + "OptionSaveMetadataAsHiddenHelp": "Ennek a beállításnak a módosítása az ezután történő metaadatok mentését érinti. A meglévő metaadatok a szerver általi következő mentéskor lesznek frissítve.", "OptionTvdbRating": "TVDB értékelés", "OptionWakeFromSleep": "Ébredjen alvásból", "OptionWeekdays": "Hétköznapok", @@ -1153,7 +1137,7 @@ "PleaseConfirmPluginInstallation": "Kérlek kattints az OK gombra, hogy megerősítsd, hogy elolvastad a fentieket és folytatni kívánod a bővítmény telepítését.", "PleaseEnterNameOrId": "Kérlek adj meg egy nevet vagy egy külső ID-t.", "PleaseSelectTwoItems": "Kérlek válassz legalább két elemet.", - "MessagePluginInstalled": "A bővítmény sikeresen telepítve lett. A módosítások életbelépéséhez újra kell indítani a Jellyfin Szerver programot.", + "MessagePluginInstalled": "A bővítmény sikeresen telepítve lett. A módosítások életbelépéséhez újra kell indítani a Jellyfint.", "PreferEmbeddedTitlesOverFileNames": "A fájlnevek helyett előnyben részesíti a beépített címeket", "PreferEmbeddedTitlesOverFileNamesHelp": "Ez határozza meg az alapértelmezett megjelenítési címet, ha nem áll rendelkezésre internetes metaadat vagy helyi metaadat.", "Premiere": "Premier", @@ -1186,7 +1170,7 @@ "SeriesCancelled": "Sorozat törölve.", "SeriesRecordingScheduled": "A sorozatfelvétel ütemezett.", "SeriesSettings": "Sorozat beállítások", - "ServerRestartNeededAfterPluginInstall": "A bővítmény telepítése után újra kell indítani a Jellyfin Szerver-t.", + "ServerRestartNeededAfterPluginInstall": "A bővítmény telepítése után újra kell indítani a Jellyfint.", "SettingsWarning": "Ezen értékek módosítása instabilitást vagy csatlakozási hibákat okozhat. Ha bármilyen probléma merül fel javasoljuk, hogy állítsd vissza őket az alapértelmezettre.", "ShowIndicatorsFor": "Indikátor megjelenítése a következőhöz:", "ShowYear": "Év megjelenítése", @@ -1405,7 +1389,7 @@ "LabelXDlnaDoc": "X-DLNA doc:", "LabelXDlnaCap": "X-DLNA cap:", "MapChannels": "Csatornák feltérképezése", - "PasswordResetProviderHelp": "Válassz egy jelszó-visszaállítási szolgáltatót, amelyet akkor kell használni, amikor a felhasználó jelszó-visszaállítást kér", + "PasswordResetProviderHelp": "Válassz egy jelszó-visszaállítási szolgáltatót, amelyet akkor kell használni, amikor a felhasználó jelszó-visszaállítást kér.", "OptionResElement": "res elem", "OptionReportByteRangeSeekingWhenTranscodingHelp": "Erre olyan készülékek esetében van szükség, amelyek időigénye nem nagyon jó.", "OptionPlainVideoItemsHelp": "Az összes videót a DIDL-ben \"object.item.videoItem\" -ként ábrázolja, nem pedig egy specifikusabb típusként, például \"object.item.videoItem.movie\" .", @@ -1414,7 +1398,7 @@ "OptionEquals": "Egyenlő", "OptionForceRemoteSourceTranscoding": "A távoli médiaforrások (például az élő TV) átkódolásának kényszerítése", "NoCreatedLibraries": "Úgy tűnik még nem hoztál létre egy könyvtárat sem. {0}Szeretnél létrehozni egyet most?{1}", - "MessageDirectoryPickerBSDInstruction": "A BSD esetében valószínűleg konfigurálni kell a FreeNAS Jailben lévő tárolót, hogy a Jellyfin hozzáférhest kapjon.", + "MessageDirectoryPickerBSDInstruction": "A BSD esetében valószínűleg konfigurálni kell a FreeNAS Jailben lévő tárolót, hogy a Jellyfin hozzáférhessen a médiádhoz.", "LabelXDlnaDocHelp": "Meghatározza az X_DLNADOC elem tartalmát az urn: schemas-dlna-org: device-1-0 névtérben.", "LabelXDlnaCapHelp": "Meghatározza az X_DLNACAP elem tartalmát az urn: schemas-dlna-org: eszköz-1-0 névtérben.", "LabelVaapiDeviceHelp": "Ez a render csomópont, amelyet a hardveres gyorsításhoz használunk.", @@ -1447,7 +1431,7 @@ "LastSeen": "Utoljára elérhető {0}", "PersonRole": "mint {0}", "ListPaging": "{0}-{1} / {2}", - "WriteAccessRequired": "A Jellyfin Szerver írási jogosultságot igényel ehhez a könyvtárhoz. Kérjük, ellenőrizd, hogy van-e jogod írni ide, majd próbáld újra.", + "WriteAccessRequired": "A Jellyfin írási jogosultságot igényel ehhez a könyvtárhoz. Kérjük, ellenőrizd, hogy van-e jogod írni ide, majd próbáld újra.", "PathNotFound": "Az elérési út nem található. Kérjük, ellenőrizd, hogy az elérési út megfelelő-e, majd próbáld újra.", "Track": "Szám", "Season": "Évad", @@ -1467,7 +1451,6 @@ "YadifBob": "YADIF Bob", "Yadif": "YADIF", "ButtonTogglePlaylist": "Lejátszási listák", - "ButtonToggleContextMenu": "Továbbiak", "Filter": "Szűrés", "New": "Új", "HeaderFavoritePlaylists": "Kedvenc lejátszási listák", @@ -1542,5 +1525,10 @@ "ButtonPlayer": "Lejátszó", "PreviousTrack": "Ugrás az előzőhöz", "NextTrack": "Ugrás a következőre", - "LabelUnstable": "Instabil" + "LabelUnstable": "Instabil", + "SubtitleVerticalPositionHelp": "Annak a sornak a száma, ahol a szöveg megjelenik. A pozitív számok fentről lefelé mutatnak. A negatív számok alulról felfelé mutatnak.", + "Preview": "Előnézet", + "LabelSubtitleVerticalPosition": "Függőleges pozíció:", + "MessageGetInstalledPluginsError": "Hiba történt a jelenleg telepített bővítmények lekérdezése során.", + "MessagePluginInstallError": "Hiba történt a bővítmény telepítése során." } diff --git a/src/strings/id.json b/src/strings/id.json index cb840a4989..bcb8c6a441 100644 --- a/src/strings/id.json +++ b/src/strings/id.json @@ -93,13 +93,10 @@ "ButtonSelectView": "Pilih tampilan", "ButtonSelectServer": "Pilih Peladen", "ButtonSelectDirectory": "Pilih Direktori", - "ButtonSearch": "Cari", "ButtonScanAllLibraries": "Pindai Semua Pustaka", - "ButtonSave": "Simpan", "ButtonResume": "Lanjutkan", "ButtonResetPassword": "Atur ulang Kata sandi", "ButtonResetEasyPassword": "Atur ulang kode pin mudah", - "ButtonRepeat": "Ulangi", "ButtonRename": "Ubah nama", "ButtonRemove": "Hapus", "ButtonRefreshGuideData": "Muat ulang Data Panduan", @@ -117,7 +114,6 @@ "ButtonLibraryAccess": "Akses pustaka", "ButtonInfo": "Info", "ButtonHome": "Beranda", - "ButtonHelp": "Bantuan", "ButtonGuide": "Panduan", "ButtonGotIt": "Paham", "ButtonFullscreen": "Layar penuh", @@ -144,7 +140,6 @@ "ButtonAddScheduledTaskTrigger": "Tambah Pemicu", "ButtonAddMediaLibrary": "Tambah Pustaka Media", "ButtonAddImage": "Tamba gambar", - "ButtonAdd": "Tambah", "MessageBrowsePluginCatalog": "Jelajahi katalog plugin kamu untuk melihat plugin yang tersedia.", "Browse": "Jelajah", "BoxRear": "Kotak (belakang)", diff --git a/src/strings/is-is.json b/src/strings/is-is.json index 66ea452a64..a820f82921 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -108,7 +108,6 @@ "Blacklist": "Bannlisti", "Box": "Kassi", "BoxRear": "Box (að aftan)", - "ButtonAdd": "Bæta við", "ButtonAddMediaLibrary": "Bæta við myndasafni", "ButtonAddScheduledTaskTrigger": "Bæta við orsakavald (trigger)", "Books": "Bækur", @@ -220,8 +219,6 @@ "ButtonSend": "Senda", "ButtonSelectServer": "Velja netþjón", "ButtonSelectDirectory": "Velja möppu", - "ButtonSearch": "Leita", - "ButtonSave": "Vista", "ButtonRestart": "Endurræsa", "ButtonResetPassword": "Endurstilla lykilorð", "ButtonOpen": "Opna", @@ -236,7 +233,6 @@ "ButtonRename": "Endurnefna", "Sync": "Samstilla", "ButtonRevoke": "Afturkalla", - "ButtonRepeat": "Endurtaka", "Monday": "Mánudagur", "ButtonRefresh": "Endurhlaða", "ButtonParentalControl": "Foreldraeftirlit", @@ -248,7 +244,6 @@ "ButtonLibraryAccess": "Aðgangur að safni", "ButtonInfo": "Upplýsingar", "ButtonHome": "Heim", - "ButtonHelp": "Hjálp", "ButtonGuide": "Sjónvarpsvísir", "ButtonGotIt": "Skilið", "ButtonFullscreen": "Fylla upp í skjá", @@ -309,7 +304,6 @@ "HeaderDetectMyDevices": "Finna tækin mín", "HeaderFavoritePeople": "Uppáhalds Fólk", "HeaderFavoritePlaylists": "Uppáhalds spilunarlistar", - "HeaderFilters": "Síur", "HeaderForgotPassword": "Gleymt lykilorð", "HeaderForKids": "Fyrir Krakka", "HeaderFrequentlyPlayed": "Oft Spilað", @@ -387,7 +381,6 @@ "Composer": "Tónskáld", "ClientSettings": "Stillingar biðlara", "ButtonTogglePlaylist": "Spilunarlisti", - "ButtonToggleContextMenu": "Meira", "ButtonSplit": "Skipta", "ButtonStop": "Stöðva", "ButtonResetEasyPassword": "Endurstilla Easy PIN númer", @@ -435,9 +428,7 @@ "TellUsAboutYourself": "Segðu okkur frá sjálfum þér", "TabUsers": "Notendur", "TabUpcoming": "Væntanlegt", - "TabTranscoding": "Umkóðun", "TabTrailers": "Sýnishorn", - "TabSuggestions": "Tillögur", "TabSongs": "Lög", "TabResumeSettings": "Halda áfram", "TabProfile": "Prófíll", @@ -482,7 +473,6 @@ "LabelAudio": "Hljóð", "LabelArtists": "Listamenn:", "LabelAppNameExample": "Dæmi: Sickbeard, Sonarr", - "LabelAll": "Allt", "LabelAccessDay": "Vikudagur:", "Kids": "Krakkar", "Hide": "Fela", @@ -493,7 +483,6 @@ "HeaderVideoQuality": "Myndgæði", "HeaderUsers": "Notendur", "HeaderUser": "Notandi", - "TabMetadata": "Lýsigögn", "TabGenres": "Flokkar", "TabFavorites": "Eftirlæti", "TabEpisodes": "Þættir", diff --git a/src/strings/it.json b/src/strings/it.json index 15e32d84bf..2dd958fb49 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -44,7 +44,6 @@ "Browse": "Esplora", "MessageBrowsePluginCatalog": "Sfoglia il catalogo dei Plugins.", "BurnSubtitlesHelp": "Determina se il server deve imprimere i sottotitoli quando i video vengono convertiti. Evitare ciò migliorerà di molto le prestazioni. Selezionare Auto per imprimere formati basati sull'immagine (VOBSUB, PGS, SUB, IDX, ...) e alcuni sottotitoli ASS o SSA.", - "ButtonAdd": "Aggiungi", "ButtonAddMediaLibrary": "Aggiungi raccolta multimediale", "ButtonAddScheduledTaskTrigger": "Aggiungi operazione", "ButtonAddServer": "Aggiungi server", @@ -69,7 +68,6 @@ "ButtonFullscreen": "Schermo Intero", "ButtonGotIt": "Ho capito", "ButtonGuide": "Guida", - "ButtonHelp": "Aiuto", "ButtonLibraryAccess": "Accesso biblioteca", "ButtonManualLogin": "Accesso Manuale", "ButtonMore": "Altro", @@ -88,15 +86,12 @@ "ButtonRefreshGuideData": "Aggiorna la guida", "ButtonRemove": "Rimuovi", "ButtonRename": "Rinomina", - "ButtonRepeat": "Ripeti", "ButtonResetEasyPassword": "Resetta codice PIN", "ButtonResetPassword": "Reset Password", "ButtonRestart": "Riavvia", "ButtonResume": "Riprendi", "ButtonRevoke": "Revocare", - "ButtonSave": "Salva", "ButtonScanAllLibraries": "Scansiona Tutte le Librerie", - "ButtonSearch": "Cerca", "ButtonSelectDirectory": "Seleziona cartella", "ButtonSelectServer": "Scegli Server", "ButtonSelectView": "Seleziona vista", @@ -302,7 +297,6 @@ "HeaderDevices": "Dispositivi", "HeaderDirectPlayProfile": "Profilo Direct Play", "HeaderDirectPlayProfileHelp": "Aggiungere \"profili riproduzione diretta\" per indicare i formati che il dispositivo è in grado di gestire in modo nativo.", - "HeaderDisplay": "Schermo", "HeaderDownloadSync": "Scarica & Sincronizza", "HeaderEasyPinCode": "Codice pin semplificato", "HeaderEditImages": "Modifica Immagini", @@ -312,9 +306,7 @@ "HeaderError": "Errore", "HeaderExternalIds": "ID esterni:", "HeaderFeatureAccess": "Accesso alle funzionalità", - "HeaderFeatures": "Caratteristiche", "HeaderFetchImages": "Identifica Immagini:", - "HeaderFilters": "Filtri", "HeaderForKids": "Per Bambini", "HeaderForgotPassword": "Password dimenticata", "HeaderFrequentlyPlayed": "Visti di frequente", @@ -423,7 +415,6 @@ "HeaderSubtitleProfiles": "Profili sottotitoli", "HeaderSubtitleProfilesHelp": "Profili sottotitoli descrivono i formati di sottotitoli supportati dal dispositivo.", "HeaderSystemDlnaProfiles": "Profili di sistema", - "HeaderTags": "Tag", "HeaderTaskTriggers": "Operazioni Pianificate", "HeaderThisUserIsCurrentlyDisabled": "Questo utente è al momento disabilitato", "HeaderTracks": "Traccia", @@ -476,7 +467,6 @@ "LabelAlbumArtMaxWidthHelp": "Risoluzione massima copertina album inviata tramite upnp:albumArtURI.", "LabelAlbumArtPN": "Copertine Album PN:", "LabelAlbumArtists": "Artisti album:", - "LabelAll": "Tutti", "LabelAllowHWTranscoding": "Consenti transcodifica hardware", "LabelAllowedRemoteAddresses": "Filtro indirizzo IP Remoto:", "LabelAllowedRemoteAddressesMode": "Modalità filtro indirizzo IP remoto:", @@ -1149,7 +1139,6 @@ "TabAdvanced": "Avanzato", "TabAlbumArtists": "Artisti degli Album", "TabAlbums": "Album", - "TabArtists": "Artisti", "TabCatalog": "Catalogo", "TabChannels": "Canali", "TabCodecs": "Codec", @@ -1158,14 +1147,12 @@ "TabDashboard": "Pannello Controllo", "TabDevices": "Dispositivi", "TabDirectPlay": "Riproduzione Diretta", - "TabDisplay": "Schermo", "TabEpisodes": "Episodi", "TabFavorites": "Preferiti", "TabGenres": "Generi", "TabGuide": "Guida", "TabLatest": "Novità", "TabLiveTV": "Tv in Diretta", - "TabMetadata": "Metadati", "TabMovies": "Film", "TabMusic": "Musica", "TabMusicVideos": "Video Musicali", @@ -1175,7 +1162,6 @@ "TabNotifications": "Notifiche", "TabOther": "Altro", "TabParentalControl": "Controllo Genitore", - "TabPlayback": "Riproduzione", "TabPlaylists": "Playlist", "TabProfile": "Profilo", "TabProfiles": "Profili", @@ -1187,9 +1173,7 @@ "TabSettings": "Impostazioni", "TabShows": "Spettacoli", "TabSongs": "Brani", - "TabSuggestions": "Suggerimenti", "TabTrailers": "Trailer", - "TabTranscoding": "Trascodifica", "TabUpcoming": "In Arrivo", "TabUsers": "Utenti", "Tags": "Tag", @@ -1467,7 +1451,6 @@ "AlbumArtist": "Artisti dell'Album", "UnsupportedPlayback": "Jellyfin non è in grado di decriptare i contenuti protetti da DRM ma tutti i contenuti verranno tentati a prescindere, compresi quelli protetti. Alcuni file potrebbero apparire completamente neri a causa della crittografia o di altre funzionalità non supportate, come i titoli interattivi.", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Altro", "HeaderFavoritePlaylists": "Playlist Favorite", "Filter": "Filtro", "New": "Nuovo", diff --git a/src/strings/ja.json b/src/strings/ja.json index a6738d9517..f57a1ea672 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -53,7 +53,6 @@ "Browse": "ブラウズ", "MessageBrowsePluginCatalog": "利用可能なプラグインを表示するには、プラグインカタログを参照してください。", "BurnSubtitlesHelp": "ビデオのトランスコード時にサーバーが字幕を焼付けるかどうかを決定します。 この字幕焼付けを避けると、サーバーのパフォーマンスが非常に向上します。 画像ベースの形式 (VOBSUB, PGS, SUB, IDX など) と特定の ASS または SSA 字幕でだけ焼付けを行うには、自動を選んでください。", - "ButtonAdd": "追加", "ButtonAddMediaLibrary": "メディアライブラリを追加", "ButtonAddScheduledTaskTrigger": "トリガーを追加", "ButtonAddServer": "サーバーを追加", @@ -79,7 +78,6 @@ "ButtonFullscreen": "フルスクリーン", "ButtonGotIt": "了解", "ButtonGuide": "ガイド", - "ButtonHelp": "ヘルプ", "ButtonHome": "ホーム", "ButtonInfo": "情報", "ButtonLibraryAccess": "ライブラリへアクセス", @@ -99,15 +97,12 @@ "ButtonRefreshGuideData": "ガイドデータの更新", "ButtonRemove": "削除", "ButtonRename": "名前の変更", - "ButtonRepeat": "リピート", "ButtonResetEasyPassword": "easy pin code をリセット", "ButtonResetPassword": "パスワードをリセット", "ButtonRestart": "再起動", "ButtonResume": "レジューム", "ButtonRevoke": "取り消す", - "ButtonSave": "保存", "ButtonScanAllLibraries": "すべてのライブラリをスキャン", - "ButtonSearch": "検索", "ButtonSelectDirectory": "ディレクトリの選択", "ButtonSelectServer": "サーバーの選択", "ButtonSelectView": "ビューを選択", @@ -332,7 +327,6 @@ "HeaderDevices": "デバイス", "HeaderDirectPlayProfile": "ダイレクトプレイ プロファイル", "HeaderDirectPlayProfileHelp": "ダイレクトプレイプロファイルを追加して、デバイスがどのフォーマットをネイティブに処理できるかを示します。", - "HeaderDisplay": "ディスプレイ", "HeaderDownloadSync": "ダウンロード&同期", "HeaderEasyPinCode": "簡単なPINコード", "HeaderEditImages": "イメージの編集", @@ -349,10 +343,8 @@ "HeaderFavoriteSongs": "お気に入りの曲", "HeaderFavoriteVideos": "お気に入りのビデオ", "HeaderFeatureAccess": "機能へのアクセス", - "HeaderFeatures": "機能", "HeaderFetchImages": "画像を取得する:", "HeaderFetcherSettings": "フェッチャー設定", - "HeaderFilters": "フィルタ", "HeaderForKids": "子供向け", "HeaderForgotPassword": "パスワードを忘れました", "HeaderFrequentlyPlayed": "よく再生する", @@ -464,7 +456,6 @@ "HeaderSubtitleProfiles": "字幕のプロファイル", "HeaderSubtitleProfilesHelp": "字幕プロファイルは、デバイスでサポートされている字幕フォーマットを表します。", "HeaderSystemDlnaProfiles": "システムプロファイル", - "HeaderTags": "タグ", "HeaderTaskTriggers": "タスクトリガー", "HeaderThisUserIsCurrentlyDisabled": "このユーザーは現在無効になっています", "HeaderTracks": "トラック", @@ -603,7 +594,6 @@ "Sunday": "日曜日", "CopyStreamURLSuccess": "URLのコピーが成功しました。", "TabDirectPlay": "直接再生", - "TabDisplay": "表示", "TabEpisodes": "エピソード", "TabFavorites": "お気に入り", "TabGenres": "ジャンル", @@ -640,7 +630,6 @@ "SeriesYearToPresent": "{0} - 現在", "SubtitleOffset": "字幕オフセット", "TabPassword": "パスワード", - "TabPlayback": "プレイバック", "ThemeSongs": "テーマ曲", "ThemeVideos": "テーマビデオ", "ValueMusicVideoCount": "{0} ミュージックビデオ", @@ -770,7 +759,6 @@ "TabLatest": "最新", "TabLiveTV": "ライブTV", "TabLogs": "ログ", - "TabMetadata": "メタデータ", "TabMovies": "ムービー", "TabMusic": "ミュージック", "TabMusicVideos": "ミュージックビデオ", @@ -838,7 +826,6 @@ "LabelMusicStreamingTranscodingBitrate": "ミュージックトランスコーディングビットレート:", "LabelName": "名前:", "LabelProtocolInfo": "プロトコル情報:", - "LabelAll": "すべて", "LabelAlbumArtPN": "アルバムアートPN:", "LabelAlbumArtists": "アルバムアーティスト:", "LabelAllowHWTranscoding": "ハードウェアトランスコーディングを許可", @@ -1065,9 +1052,7 @@ "TabShows": "表示", "TabSongs": "曲", "TabStreaming": "ストリーミング", - "TabSuggestions": "おすすめ", "TabTrailers": "トレーラー", - "TabTranscoding": "トランスコーディング", "MessageContactAdminToResetPassword": "パスワードをリセットするためにシステムの管理者に連絡してください。", "TabUsers": "ユーザー", "TagsValue": "タグ: {0}", @@ -1085,7 +1070,6 @@ "SearchForSubtitles": "字幕を検索", "ServerNameIsShuttingDown": "Jellyfinサーバー - {0} は停止中です。", "SettingsSaved": "設定を保存しました。", - "TabArtists": "アーティスト", "ValueSeconds": "{0} 秒", "ValueTimeLimitSingleHour": "タイムリミット: 1 時間", "Wednesday": "水曜日", @@ -1126,7 +1110,6 @@ "ButtonSyncPlay": "SyncPlay", "DeinterlaceMethodHelp": "インターレースコンテンツをトランスコードする際に使用するデインターレース方式を選びます。", "ButtonTogglePlaylist": "プレイリスト", - "ButtonToggleContextMenu": "さらに表示", "BoxSet": "ボックスセット", "AllowFfmpegThrottlingHelp": "トランスコードや remux が現在の再生位置から十分に先に進んだ場合、処理を一時停止してリソースの消費を抑えます。これは、あまり早送り・早戻しをしないで視聴する場合に便利です。再生に問題が発生した場合は、この機能をオフにしてください。", "LabelDisplaySpecialsWithinSeasons": "放送されたシーズン内のスペシャルを表示", diff --git a/src/strings/kk.json b/src/strings/kk.json index 909af11da3..2f0d0c5927 100644 --- a/src/strings/kk.json +++ b/src/strings/kk.json @@ -53,7 +53,6 @@ "Browse": "Sharlaý", "MessageBrowsePluginCatalog": "Qoljetimdi plagındermen tanysý úshin plagın tizimdemesin sholyńyz.", "BurnSubtitlesHelp": "Beıneni qaıta kodtaǵan kezde server sýbtıtrlerdi jazyýyn anyqtaıdy. Onan qashqaqtaý serverdiń ónimdiligin biraz jaqsartady. Sýretke negizdelgen pishimderdi (VOBSUB, PGS, SUB, IDX, j.t.b.) jáne keıbir ASS nemese SSA sýbtıtrlerin jazý úshin Avtomattyny tańdańyz.", - "ButtonAdd": "Ústeý", "ButtonAddMediaLibrary": "Tasyǵyshhana ústeý", "ButtonAddScheduledTaskTrigger": "Trıger ústeý", "ButtonAddServer": "Server ústeý", @@ -79,7 +78,6 @@ "ButtonFullscreen": "Tolyq ekran", "ButtonGotIt": "Túsinikti", "ButtonGuide": "Telegıd", - "ButtonHelp": "Anyqtama", "ButtonHome": "Basqyǵa", "ButtonInfo": "Aqparatqa", "ButtonLibraryAccess": "Tasyǵyshhanǵa qatynaý", @@ -101,15 +99,12 @@ "ButtonRefreshGuideData": "Telegıd derekterin jańǵyrtý", "ButtonRemove": "Alastaý", "ButtonRename": "Qaıta ataý", - "ButtonRepeat": "Qaıtalaý", "ButtonResetEasyPassword": "Ońaıtylǵan PIN-kodty ysyrý", "ButtonResetPassword": "Paróldi ysyrý", "ButtonRestart": "Qaıta iske qosý", "ButtonResume": "Jalǵastyrý", "ButtonRevoke": "Bas tartý", - "ButtonSave": "Saqtaý", "ButtonScanAllLibraries": "Barlyq tasyǵyshhanalardy skanerleý", - "ButtonSearch": "Izdeý", "ButtonSelectDirectory": "Qatalogty tańdaý", "ButtonSelectServer": "Serverdi tańdaý", "ButtonSelectView": "Kórinisti tańdaý", @@ -326,7 +321,6 @@ "HeaderDevices": "Qurylǵylar", "HeaderDirectPlayProfile": "Tikeleı oınatý profaıly", "HeaderDirectPlayProfileHelp": "Qurylǵynyń qandaı pishimderdi ádepki óńdetetin múmkindigin kórsetý ushin tikeleı oınatý profaıldar ústeý.", - "HeaderDisplay": "Beıneleý", "HeaderDownloadSync": "Júkteý men úndestirý", "HeaderEasyPinCode": "Ońaıtylǵan PIN-kod", "HeaderEditImages": "Sýretterdi óńdeý", @@ -336,10 +330,8 @@ "HeaderError": "Qate", "HeaderExternalIds": "Syrtqy sáıkestendirgishter:", "HeaderFeatureAccess": "Erekshelikterge qatynaý", - "HeaderFeatures": "Erekshelikter", "HeaderFetchImages": "Sýretterdi irikteý:", "HeaderFetcherSettings": "Irikteýshi parametrleri", - "HeaderFilters": "Súzgiler", "HeaderForKids": "Balalyq", "HeaderForgotPassword": "Paróldi umytyńyz ba", "HeaderFrequentlyPlayed": "Jıi oınatylǵandar", @@ -452,7 +444,6 @@ "HeaderSubtitleProfiles": "Sýbtıtrler profaıldary", "HeaderSubtitleProfilesHelp": "Sýbtıtrler profaıly osy qurylǵyda qoldaýy bar sýbtıtrler pishimderin sıpattaıdy.", "HeaderSystemDlnaProfiles": "Júıelik profaıldar", - "HeaderTags": "Tegter", "HeaderTaskTriggers": "Tapsyrma trıggerleri", "HeaderThisUserIsCurrentlyDisabled": "Osy paıdalanýshy aǵymda ajyratylǵan", "HeaderTracks": "Jolshyqtar", @@ -508,7 +499,6 @@ "LabelAlbumArtMaxWidthHelp": "upnp:albumArtURI arqyly kórsetetin álbom sýretiniń eń joǵary ajyratylymdyǵy.", "LabelAlbumArtPN": "Álbom sýreti PN:", "LabelAlbumArtists": "Álbom oryndaýshylary:", - "LabelAll": "Barlyq", "LabelAllowHWTranscoding": "Apparattyq qaıta kodtaýǵa ruqsat etý", "LabelAllowedRemoteAddresses": "Qashyqtaǵy IP-mekenjaı súzgisi:", "LabelAllowedRemoteAddressesMode": "Qashyqtaǵy IP-mekenjaı súzgisiniń rejimi:", @@ -1226,7 +1216,6 @@ "TabAdvanced": "Keńeıtilgen", "TabAlbumArtists": "Álbom oryndaýshylary", "TabAlbums": "Álbomdar", - "TabArtists": "Oryndaýshylar", "TabCatalog": "Tizimdeme", "TabChannels": "Arnalar", "TabCodecs": "Kodekter", @@ -1235,7 +1224,6 @@ "TabDashboard": "Taqta", "TabDevices": "Qurylǵylar", "TabDirectPlay": "Tikeleı oınatý", - "TabDisplay": "Beıneleý", "TabEpisodes": "TD-bólimder", "TabFavorites": "Tańdaýlylar", "TabGenres": "Janrlar", @@ -1244,7 +1232,6 @@ "TabLatest": "Eń keıingi", "TabLiveTV": "Efırlik TD", "TabLogs": "Jurnaldar", - "TabMetadata": "Metaderekter", "TabMovies": "Fılmder", "TabMusic": "Mýzyka", "TabMusicVideos": "Mýzykalyq beıneler", @@ -1255,7 +1242,6 @@ "TabOther": "Basqalar", "TabParentalControl": "Mazmundy basqarý", "TabPassword": "Paról", - "TabPlayback": "Oınatý", "TabPlaylists": "Oınatý tizimderi", "TabPlugins": "Plagınder", "TabProfile": "Profaıl", @@ -1270,9 +1256,7 @@ "TabShows": "Kórsetimder", "TabSongs": "Áýender", "TabStreaming": "Tasymaldaný", - "TabSuggestions": "Usynystar", "TabTrailers": "Treılerler", - "TabTranscoding": "Qaıta kodtaý", "TabUpcoming": "Kútilgen", "TabUsers": "Paıdalanýshylar", "Tags": "Tegter", @@ -1458,7 +1442,6 @@ "Episode": "Bólim", "ClientSettings": "Týtynýshy parametrleri", "ButtonTogglePlaylist": "Oýnatý tizimi", - "ButtonToggleContextMenu": "Kúbirek", "BoxSet": "Jıyntyq", "Artist": "Ornatýshy", "AlbumArtist": "Álbom ornatýshysy", diff --git a/src/strings/ko.json b/src/strings/ko.json index e18dc5f537..6a6dadc120 100644 --- a/src/strings/ko.json +++ b/src/strings/ko.json @@ -14,7 +14,6 @@ "BirthDateValue": "출생: {0}", "BirthPlaceValue": "출생지: {0}", "MessageBrowsePluginCatalog": "사용 가능한 플러그인을 보려면 플러그인 카탈로그를 참고하십시오.", - "ButtonAdd": "추가", "ButtonAddScheduledTaskTrigger": "트리거 추가", "ButtonAddServer": "서버 추가", "ButtonAddUser": "사용자 추가", @@ -35,7 +34,6 @@ "ButtonForgotPassword": "비밀번호 분실", "ButtonGotIt": "알겠습니다", "ButtonGuide": "가이드", - "ButtonHelp": "도움말", "ButtonHome": "홈", "ButtonInfo": "정보", "ButtonManualLogin": "수동 로그인", @@ -54,13 +52,10 @@ "ButtonRefreshGuideData": "가이드 데이터 새로 고침", "ButtonRemove": "제거", "ButtonRename": "이름 변경", - "ButtonRepeat": "반복", "ButtonResetEasyPassword": "간편 PIN 코드 재설정", "ButtonResetPassword": "비밀번호 재설정", "ButtonRestart": "다시 시작", "ButtonResume": "이어서 재생", - "ButtonSave": "저장", - "ButtonSearch": "검색", "ButtonSelectDirectory": "디렉터리 선택", "ButtonSelectServer": "서버 선택", "ButtonSelectView": "보기 선택", @@ -155,9 +150,7 @@ "HeaderEasyPinCode": "간편 PIN 코드", "HeaderError": "오류", "HeaderFeatureAccess": "기능 접근", - "HeaderFeatures": "특징", "HeaderFetchImages": "이미지 가져오기:", - "HeaderFilters": "필터", "HeaderForgotPassword": "비밀번호 분실", "HeaderFrequentlyPlayed": "자주 재생함", "HeaderGenres": "장르", @@ -232,7 +225,6 @@ "HeaderSubtitleProfile": "자막 프로필", "HeaderSubtitleProfilesHelp": "자막 프로필은 장치에서 지원하는 자막 형식을 나타냅니다.", "HeaderSystemDlnaProfiles": "시스템 프로필", - "HeaderTags": "태그", "HeaderTaskTriggers": "작업 트리거", "HeaderThisUserIsCurrentlyDisabled": "이 사용자는 현재 비활성화되었습니다", "HeaderTracks": "트랙", @@ -270,7 +262,6 @@ "LabelAlbumArtMaxWidthHelp": "upnp:albumArtURI를 통해 노출된 앨범 아트의 최대 해상도.", "LabelAlbumArtPN": "앨범 아트 PN:", "LabelAlbumArtists": "앨범 아티스트:", - "LabelAll": "모두", "LabelAppName": "앱 이름", "LabelArtists": "아티스트:", "LabelArtistsHelp": "; 를 사용하여 여러 개 분리", @@ -686,7 +677,6 @@ "TabAdvanced": "고급", "TabAlbumArtists": "앨범 아티스트", "TabAlbums": "앨범", - "TabArtists": "아티스트", "TabCatalog": "카탈로그", "TabChannels": "채널", "TabCodecs": "코덱", @@ -695,7 +685,6 @@ "TabDashboard": "대시보드", "TabDevices": "장치", "TabDirectPlay": "다이렉트 재생", - "TabDisplay": "화면", "TabEpisodes": "에피소드", "TabFavorites": "즐겨찾기", "TabGenres": "장르", @@ -704,7 +693,6 @@ "TabLatest": "최근", "TabLiveTV": "실시간 TV", "TabLogs": "로그", - "TabMetadata": "메타데이터", "TabMovies": "영화", "TabMusic": "음악", "TabMusicVideos": "뮤직비디오", @@ -714,7 +702,6 @@ "TabOther": "기타", "TabParentalControl": "자녀 보호", "TabPassword": "비밀번호", - "TabPlayback": "재생", "TabPlaylists": "재생목록", "TabPlugins": "플러그인", "TabProfile": "프로필", @@ -728,9 +715,7 @@ "TabShows": "쇼", "TabSongs": "노래", "TabStreaming": "스트리밍", - "TabSuggestions": "추천", "TabTrailers": "예고편", - "TabTranscoding": "트랜스코딩", "TabUpcoming": "방송 예정", "TabUsers": "사용자", "Tags": "태그", @@ -1183,7 +1168,6 @@ "HeaderKodiMetadataHelp": "NFO 메타데이터를 활성화/비활성화하려면, Jellyfin 라이브러리 설정에서 라이브러리를 편집하고 메타데이터 보호기 섹션을 찾으십시오.", "HeaderKeepRecording": "녹화 유지", "HeaderForKids": "아이들용", - "HeaderDisplay": "디스플레이", "HeaderDetectMyDevices": "내 장치 탐색", "HeaderDeleteItems": "항목 제거", "HeaderConfirmRevokeApiKey": "API 키 해지", @@ -1398,7 +1382,6 @@ "MovieLibraryHelp": "{0}영화 이름 지정 규칙{1}을 확인하십시오.", "HeaderFavoritePlaylists": "즐겨찾는 플레이리스트", "ButtonTogglePlaylist": "플레이리스트", - "ButtonToggleContextMenu": "더보기", "Rate": "평", "PerfectMatch": "정확히 일치", "ButtonSyncPlay": "SyncPlay", diff --git a/src/strings/lt-lt.json b/src/strings/lt-lt.json index 4a259afc8e..e02a183c97 100644 --- a/src/strings/lt-lt.json +++ b/src/strings/lt-lt.json @@ -12,7 +12,6 @@ "AttributeNew": "Naujas", "Backdrops": "Fonai", "BirthLocation": "Gimimo vieta", - "ButtonAdd": "Pridėti", "ButtonAddScheduledTaskTrigger": "Pridėti jungiklį", "ButtonAddUser": "Pridėt vartotoją", "ButtonArrowDown": "Žemyn", @@ -26,7 +25,6 @@ "ButtonEdit": "Redaguoti", "ButtonFilter": "Filtras", "ButtonGotIt": "Supratau", - "ButtonHelp": "Pagalba", "ButtonHome": "Pradinis", "ButtonManualLogin": "Rankinis prisijungimas", "ButtonNew": "Naujas", @@ -41,8 +39,6 @@ "ButtonRemove": "Pašalinti", "ButtonResetPassword": "Atstatyti slaptažodį", "ButtonRestart": "Iš naujo", - "ButtonSave": "Saugoti", - "ButtonSearch": "Paieška", "ButtonSelectDirectory": "Rinktis direktoriją", "ButtonSettings": "Nustatymai", "ButtonSignIn": "Prisijungti", @@ -114,7 +110,6 @@ "HeaderEnabledFieldsHelp": "Nuimkite varnelę nuo lauko kad jį užrakinti ir neleisti keisti jo duomenų.", "HeaderFeatureAccess": "Prieiga prie funkcijų", "HeaderFetchImages": "Gauti vaizdus:", - "HeaderFilters": "Filtrai", "HeaderFrequentlyPlayed": "Dažnai leista", "HeaderIdentificationCriteriaHelp": "Įveskite bent vieną identifikavimo kriterijų.", "HeaderIdentifyItemHelp": "Įveskite vieną ar daugiau paieškos kriterijų. Pašalinkite kriterijų jei norite gauti daugiau paieškos rezultatų.", @@ -466,7 +461,6 @@ "TabAdvanced": "Sudėtingiau", "TabAlbumArtists": "Albumo atlikėjai", "TabAlbums": "Albumai", - "TabArtists": "Atlikėjai", "TabCatalog": "Katalogas", "TabChannels": "Kanalai", "TabCollections": "Kolekcijos", @@ -475,7 +469,6 @@ "TabGenres": "Žanrai", "TabGuide": "Gidas", "TabLatest": "Vėliausi", - "TabMetadata": "Metaduomenys", "TabMovies": "Filmai", "TabMusic": "Muzika", "TabMusicVideos": "Muzikos klipai", @@ -492,9 +485,7 @@ "TabSettings": "Nustatymai", "TabShows": "Laidos", "TabSongs": "Dainos", - "TabSuggestions": "Pasiūlymai", "TabTrailers": "Anonsai", - "TabTranscoding": "Transkodavimas", "TabUpcoming": "Būsimi", "Tags": "Žymės", "TellUsAboutYourself": "Papasakokite apie save", @@ -563,7 +554,6 @@ "ButtonParentalControl": "Tėvų kontrolė", "ButtonProfile": "Profilis", "ButtonRename": "Pervadinti", - "ButtonRepeat": "Kartoti", "ButtonResume": "Tęsti", "ButtonRevoke": "Panaikinti", "ButtonScanAllLibraries": "Nuskaityti Visas Bibliotekas", @@ -732,7 +722,6 @@ "HeaderDateIssued": "Išdavimo data", "HeaderDefaultRecordingSettings": "Numatytieji įrašymo nustatymai", "HeaderDetectMyDevices": "Rasti mano prietaisus", - "HeaderDisplay": "Ekranas", "HeaderFavoriteBooks": "Mėgstamiausios knygos", "HeaderFavoriteMovies": "Mėgstamiausi filmai", "HeaderFavoriteShows": "Mėgstamiausi serialai", @@ -770,7 +759,6 @@ "LabelAllowedRemoteAddressesMode": "Nuotolinio IP adresų filtro režimas:", "HeaderLoginFailure": "Prisijungimo klaida", "Hide": "Paslėpti", - "LabelAll": "Visi", "LabelAudio": "Garsas", "LabelCancelled": "Atšaukta", "LabelCertificatePassword": "Sertifikato slaptažodis:", @@ -782,7 +770,6 @@ "EnableBackdropsHelp": "Rodyti fono dekoracijas naršant bibliotekoje.", "FolderTypeUnset": "Įvairus turinys", "HeaderAddUpdateImage": "Pridėti/Įkelti atvaizdą", - "HeaderTags": "Žymės", "LabelAlbumArtPN": "Albumo iliustracijos PN:", "HeaderSettings": "Nustatymai", "LabelMatchType": "Atitikties tipas:", @@ -821,7 +808,6 @@ "HeaderAccessScheduleHelp": "Sukurkite prieigos tvarkaraštį, kad apribotumėte prieigą tam tikromis valandomis.", "HeaderContainerProfile": "Konteinerio profilis", "ErrorStartHourGreaterThanEnd": "Pabaigos laikas turi būti didesnis nei pradžios laikas.", - "HeaderFeatures": "Medžiagos", "ErrorSavingTvProvider": "Išsaugant TV teikėją įvyko klaida. Įsitikinkite, kad jis prieinamas, ir bandykite dar kartą.", "HeaderKodiMetadataHelp": "Norėdami įjungti arba išjungti NFO metaduomenis, redaguokite biblioteką Jellyfin bibliotekos nustatymuose, metaduomenų išsaugojimo skyriuje.", "EveryNDays": "Kas {0} dienas", @@ -995,7 +981,6 @@ "Episode": "Episodas", "ClientSettings": "Kliento Nustatymai", "ButtonTogglePlaylist": "Grojaraštis", - "ButtonToggleContextMenu": "Daugiau", "ButtonSplit": "Skirstyti", "AskAdminToCreateLibrary": "Prašyti administratoriaus, kad sukurtų mediateka.", "Album": "Albumas", diff --git a/src/strings/lv.json b/src/strings/lv.json index 9346b653f4..094b3a4c74 100644 --- a/src/strings/lv.json +++ b/src/strings/lv.json @@ -224,7 +224,6 @@ "LabelArtists": "Izpildītājs:", "LabelAppNameExample": "Piemēram: Sickbeard, Sonarr", "LabelAppName": "Lietotnes nosaukums", - "LabelAll": "Viss", "LabelAlbumArtists": "Albuma izpildītāji:", "LabelAlbum": "Albums:", "LabelAirTime": "Tiešraides laiks:", @@ -325,7 +324,6 @@ "HeaderFrequentlyPlayed": "Bieži Atskaņots", "HeaderForgotPassword": "Aizmirst PAroli", "HeaderForKids": "Priekš Bērniem", - "HeaderFilters": "Filtri", "HeaderFavoriteVideos": "Video Favorīti", "MediaInfoPath": "Ceļš", "MediaInfoLevel": "Līmenis", @@ -362,7 +360,6 @@ "HeaderEpisodes": "Epizodes", "HeaderEditImages": "Rediģēt Attēlus", "HeaderDownloadSync": "Lejupielādēt & Sinhronizēt", - "HeaderDisplay": "Displejs", "HeaderDevices": "Ierīces", "HeaderDeviceAccess": "Ierīču Piekļuve", "HeaderDeveloperInfo": "Izstrādātāju Info", @@ -498,15 +495,12 @@ "ButtonSelectView": "Izvēlies Skatu", "ButtonSelectServer": "Izvēlies Serveri", "ButtonSelectDirectory": "Izvēlies Mapi", - "ButtonSearch": "Meklēt", "ButtonScanAllLibraries": "Skanēt Visas Bibliotēkas", - "ButtonSave": "Saglabāt", "ButtonRevoke": "Atsaukt", "ButtonResume": "Turpināt", "ButtonRestart": "Restartēt", "ButtonResetPassword": "Nomainīt Paroli", "ButtonResetEasyPassword": "Nomainīt vieglo pin kodu", - "ButtonRepeat": "Atkārtot", "ButtonRename": "Pārsaukt", "ButtonRemove": "Noņemt", "ButtonRefreshGuideData": "Atjaunot Gida Datus", @@ -526,7 +520,6 @@ "ButtonLibraryAccess": "Bibliotēku piekļuve", "ButtonInfo": "Info", "ButtonHome": "Mājas", - "ButtonHelp": "Palīdzība", "ButtonGuide": "Gids", "ButtonGotIt": "Sapratu", "ButtonFullscreen": "Pilnekrāna", @@ -546,7 +539,6 @@ "ButtonAddServer": "Pievienot Serveri", "ButtonAddMediaLibrary": "Pievienot Multimēdiju Bibliotēku", "ButtonAddImage": "Pievienot attēlu", - "ButtonAdd": "Pievienot", "MessageBrowsePluginCatalog": "Pārlūko mūsu paplašinājumu katalogu, lai redzētu pieejamos paplašinājumus.", "Browse": "Pārlūkot", "BoxRear": "Kaste (aizmugure)", @@ -573,7 +565,6 @@ "HeaderTranscodingProfile": "Transkodēšanas Profils", "HeaderTracks": "Celiņi", "HeaderThisUserIsCurrentlyDisabled": "Šis lietotājs pašlaik ir atspējots", - "HeaderTags": "Tagi", "HeaderSystemDlnaProfiles": "Sistēmas Profili", "HeaderSubtitleProfiles": "Subtitru Profili", "HeaderSubtitleProfile": "Subtitru Profils", @@ -697,9 +688,7 @@ "TagsValue": "Tagi: {0}", "Tags": "Tagi", "TabUsers": "Lietotāji", - "TabTranscoding": "Trans-kodēšana", "TabTrailers": "Treileri", - "TabSuggestions": "Ieteikumi", "TabStreaming": "Straumēšana", "TabSongs": "Dziesmas", "TabShows": "Raidījumi", @@ -713,7 +702,6 @@ "TabProfile": "Profils", "TabPlugins": "Paplašinājumi", "TabPlaylists": "Atskaņošanas Saraksti", - "TabPlayback": "Atskaņošana", "TabPassword": "Parole", "TabParentalControl": "Vecāku Pārvaldība", "TabOther": "Cits", @@ -725,7 +713,6 @@ "TabMusicVideos": "Mūzikas Video", "TabMusic": "Mūzika", "TabMovies": "Filmas", - "TabMetadata": "Metadati", "TabLogs": "Logs", "TabLiveTV": "Tiešraides TV", "TabLatest": "Jaunākais", @@ -734,7 +721,6 @@ "TabGenres": "Žanri", "TabFavorites": "Favorīti", "TabEpisodes": "Epizodes", - "TabDisplay": "Displejs", "TabDirectPlay": "Tiešā Atskaņošana", "TabDevices": "Ierīces", "TabContainers": "Konteineri", @@ -742,7 +728,6 @@ "TabCodecs": "Kodeksi", "TabChannels": "Kanāli", "TabCatalog": "Katalogs", - "TabArtists": "Izpildītāji", "TabAlbums": "Albumi", "ValueSpecialEpisodeName": "Speciālais - {0}", "Sync": "Sinhronizācija", @@ -918,7 +903,6 @@ "HeaderKodiMetadataHelp": "Lai iespējotu vai atspējotu NFO metadatus, rediģē bibliotēku Jellyfin bibliotēku iestatījumu metadata glabātāju sadaļā.", "HeaderIdentifyItemHelp": "Ievadi vienu vai vairākus meklēšanas kritērijus. Noņem kritērijus lai palielinātu meklēšanas rezultātus.", "HeaderFetchImages": "Ielādēt Attēlus:", - "HeaderFeatures": "Funkcijas", "HeaderFeatureAccess": "Funkciju Piekļuve", "HeaderEnabledFieldsHelp": "Atķeksē lauku lai to slēgtu un aizliegt tā satura mainīšanu.", "HeaderEnabledFields": "Iespējotie Lauki", @@ -1107,7 +1091,6 @@ "ColorTransfer": "Krāsu pārsūtīšana", "ClientSettings": "Klientu Iestatījumi", "ButtonTogglePlaylist": "Atskaņošanas Saraksts", - "ButtonToggleContextMenu": "Vairāk", "BurnSubtitlesHelp": "Nosaka, vai serverim ir jāiededzina subtitri video trans-kodēšanas laikā. To nedarot tiks stipri palielināta veiktspēja. Izvēlies Auto lai iededzinātu uz attēliem bāzētus formātus (VOBSUB, PGS, SUB, IDX, …) un noteiktus ASS vai SSA subtitrus.", "Artist": "Izpildītājs", "AllowOnTheFlySubtitleExtractionHelp": "Iegultie subtitri var tikt izvilkto no video un nogādāti klientiem kā parasts teksts, lai nevajadzētu veikt lieku video trans kodēšanu. Uz dažām sistēmām tas var aizņemt ilgu laiku un likt video atskaņošanai uzkārties izvilkšanas procesa laikā. Atspējo šo lai iegultos subtitrus iededzinātu video trans kodēšanas veidā, kad tos noklusēti neatbalsta klienta ierīce.", diff --git a/src/strings/mr.json b/src/strings/mr.json index 9ad45d4765..80708c700d 100644 --- a/src/strings/mr.json +++ b/src/strings/mr.json @@ -7,7 +7,6 @@ "ButtonNetwork": "नेटवर्क", "ButtonMore": "अजून", "ButtonInfo": "माहिती", - "ButtonHelp": "मदत", "ButtonGuide": "गाईड", "ButtonGotIt": "समजले", "ButtonForgotPassword": "पासवर्ड विसरलो", @@ -27,7 +26,6 @@ "ButtonArrowDown": "खाली", "ButtonAddUser": "प्रयोक्ता जोडा", "ButtonAddServer": "सर्व्हर जोडा", - "ButtonAdd": "जोडा", "Books": "पुस्तकं", "Blacklist": "ब्लॅकलिस्ट", "BirthPlaceValue": "जन्म ठिकाण: {0}", @@ -89,7 +87,6 @@ "ButtonSelectView": "दृष्य निवडा", "ButtonSelectServer": "सर्व्हर निवडा", "ButtonSelectDirectory": "डिरेक्टरी निवडा", - "ButtonSearch": "शोधा", "ButtonScanAllLibraries": "सर्व संग्रहालय स्कॅन करा", "ButtonRename": "नाव बदला", "ButtonRemove": "काढून टाका", diff --git a/src/strings/ms.json b/src/strings/ms.json index a8a5bba2ee..e7b5be39ee 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -57,7 +57,6 @@ "Books": "Buku-buku", "Box": "Kotak", "Browse": "Semak imbas", - "ButtonAdd": "Tambah", "ButtonAddMediaLibrary": "Tambah Pustaka Media", "ButtonAddServer": "Tambah pelayan", "ButtonAddUser": "Tambah pengguna", @@ -73,7 +72,6 @@ "ButtonLibraryAccess": "Akses pustaka", "ButtonInfo": "Info", "ButtonHome": "Mula", - "ButtonHelp": "Pertolongan", "ButtonGuide": "Panduan", "ButtonGotIt": "Terima", "ButtonFullscreen": "Paparan skrin penuh", diff --git a/src/strings/nb.json b/src/strings/nb.json index ee7f9e9ea5..c427b92486 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -33,7 +33,6 @@ "BookLibraryHelp": "Lyd- og tekstbøker støttes. Se igjennom {0} navneguiden for bøker {1}.", "Books": "Bøker", "MessageBrowsePluginCatalog": "Bla i tilleggskatalogen vår for å se tilgjengelige applikasjonstillegg.", - "ButtonAdd": "Legg til", "ButtonAddMediaLibrary": "Legg til bibliotek", "ButtonAddScheduledTaskTrigger": "Legg til utløser", "ButtonAddServer": "Legg til server", @@ -57,7 +56,6 @@ "ButtonForgotPassword": "Glemt passord", "ButtonFullscreen": "Fullskjerm", "ButtonGotIt": "Skjønner", - "ButtonHelp": "Hjelp", "ButtonHome": "Hjem", "ButtonLibraryAccess": "Bibliotektilgang", "ButtonManualLogin": "Manuell Login", @@ -77,15 +75,12 @@ "ButtonRefreshGuideData": "Oppdater TV-guidedata", "ButtonRemove": "Fjern", "ButtonRename": "Endre navn", - "ButtonRepeat": "Gjenta", "ButtonResetEasyPassword": "Tilbakestill PIN-kode", "ButtonResetPassword": "Tilbakestill passord", "ButtonRestart": "Omstart", "ButtonResume": "Fortsett", "ButtonRevoke": "Kall tilbake", - "ButtonSave": "Lagre", "ButtonScanAllLibraries": "Skann alle biblioteker", - "ButtonSearch": "Søk", "ButtonSelectDirectory": "Velg katalog", "ButtonSelectServer": "Velg server", "ButtonSelectView": "Velg visning", @@ -241,7 +236,6 @@ "HeaderDevices": "Enheter", "HeaderDirectPlayProfile": "Direkteavspillingsprofil", "HeaderDirectPlayProfileHelp": "Legg direkteavspillingsprofiler for å indikere hvilke formater enheten støtter direkte avspilling av.", - "HeaderDisplay": "Vis", "HeaderDownloadSync": "Last ned og synkroniser", "HeaderEasyPinCode": "Enkel PIN-kode", "HeaderEditImages": "Endre bilder", @@ -250,9 +244,7 @@ "HeaderEpisodes": "Episoder", "HeaderError": "Feil", "HeaderFeatureAccess": "Funksjonstilgang", - "HeaderFeatures": "Funksjoner", "HeaderFetchImages": "Hent bilder:", - "HeaderFilters": "Filtre", "HeaderForKids": "For barn", "HeaderForgotPassword": "Glemt passord", "HeaderFrequentlyPlayed": "Ofte avspilt", @@ -350,7 +342,6 @@ "HeaderSubtitleProfiles": "Undertekstprofiler", "HeaderSubtitleProfilesHelp": "Undertekstprofiler beskriver tekstingsformater som støttes av enheten.", "HeaderSystemDlnaProfiles": "Systemprofiler", - "HeaderTags": "Tagger", "HeaderTaskTriggers": "Oppgaveutløsere", "HeaderThisUserIsCurrentlyDisabled": "Denne brukeren er deaktivert", "HeaderTracks": "Spor", @@ -396,7 +387,6 @@ "LabelAlbumArtMaxWidth": "Maks bredde for albumomslag:", "LabelAlbumArtMaxWidthHelp": "Maksoppløsning av albumomslag er eksponert via upnp:albumArtURI.", "LabelAlbumArtists": "Albumartister:", - "LabelAll": "Alle", "LabelAllowHWTranscoding": "Tillat maskinvare-omkoding", "LabelAppName": "Applikasjonsnavn", "LabelAppNameExample": "Eksempel: Sickbeard, Sonarr", @@ -987,7 +977,6 @@ "TabAdvanced": "Avansert", "TabAlbumArtists": "Albumartister", "TabAlbums": "Album", - "TabArtists": "Artister", "TabCatalog": "Katalog", "TabChannels": "Kanaler", "TabCodecs": "Kodeker", @@ -996,7 +985,6 @@ "TabDashboard": "Dashbord", "TabDevices": "Enheter", "TabDirectPlay": "Direkteavspilling", - "TabDisplay": "Skjerm", "TabEpisodes": "Episoder", "TabFavorites": "Favoritter", "TabGenres": "Sjangre", @@ -1012,7 +1000,6 @@ "TabOther": "Annet", "TabParentalControl": "Foreldrekontroll", "TabPassword": "Passord", - "TabPlayback": "Avspilling", "TabPlaylists": "Spillelister", "TabPlugins": "Programtillegg", "TabProfile": "Profil", @@ -1025,7 +1012,6 @@ "TabSettings": "Innstillinger", "TabShows": "Programmer", "TabSongs": "Sanger", - "TabSuggestions": "Forslag", "TabTrailers": "Trailere", "TabUpcoming": "Kommende", "TabUsers": "Brukere", @@ -1170,7 +1156,6 @@ "HeaderFetcherSettings": "Henteinnstillinger", "TabServer": "Server", "TabStreaming": "Strømming", - "TabTranscoding": "Omkoding", "TagsValue": "Tagger: {0}", "ThemeSongs": "Temamusikk", "ThemeVideos": "Temavideoer", @@ -1399,7 +1384,6 @@ "TV": "TV", "TabInfo": "Informasjon", "TabLiveTV": "Direkte-TV", - "TabMetadata": "Metadata", "TabNetworking": "Nettverk", "Trailers": "Trailere", "Transcoding": "Omkoding", @@ -1447,7 +1431,6 @@ "HeaderFavoritePlaylists": "Favorittspillelister", "DeinterlaceMethodHelp": "Velg deinterlacing metoden som skal bli brukt når man transkoder interlaced innhold.", "ButtonTogglePlaylist": "Spilleliste", - "ButtonToggleContextMenu": "Mer", "EnableBlurHashHelp": "Bilder som fortsatt lastes inn vil vises med en tåkete plassholder", "EnableBlurHash": "Aktiver tåkete plassholdere for bilder", "UnsupportedPlayback": "Jellyfin kan ikke dekryptere innhold beskyttet med DRM, men alt innhold vil bli forsøkt uansett, inkludert beskyttede titler. Noen filer kan fremstå helt svarte grunnet kryptering eller andre ikke støttede funksjoner, som interaktive titler.", diff --git a/src/strings/nl.json b/src/strings/nl.json index 860783b083..0401a2f9bf 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -48,7 +48,6 @@ "Browse": "Bladeren", "MessageBrowsePluginCatalog": "Bekijk de Plugin catalogus voor beschikbare Plug-ins.", "BurnSubtitlesHelp": "Bepaalt of de server ondertitels moet branden bij het transcoderen van video's. Als u dit vermijd, worden de prestaties aanzienlijk verbeterd. Selecteer Auto om op afbeeldingen gebaseerde formaten (VOBSUB, PGS, SUB/IDX etc.) en bepaalde ASS/SSA ondertitels te branden.", - "ButtonAdd": "Toevoegen", "ButtonAddMediaLibrary": "Voeg Media Bibliotheek toe", "ButtonAddScheduledTaskTrigger": "Trigger Toevoegen", "ButtonAddServer": "Voeg server toe", @@ -73,7 +72,6 @@ "ButtonFullscreen": "Volledig scherm", "ButtonGotIt": "Begrepen", "ButtonGuide": "Gids", - "ButtonHelp": "Hulp", "ButtonHome": "Start", "ButtonLibraryAccess": "Bibliotheek toegang", "ButtonManualLogin": "Handmatige Aanmelding", @@ -93,15 +91,12 @@ "ButtonRefreshGuideData": "Gidsgegevens Vernieuwen", "ButtonRemove": "Verwijderen", "ButtonRename": "Naam wijzigen", - "ButtonRepeat": "Herhaling", "ButtonResetEasyPassword": "Reset eenvoudige pincode", "ButtonResetPassword": "Wachtwoord resetten", "ButtonRestart": "Herstart", "ButtonResume": "Hervatten", "ButtonRevoke": "Herroepen", - "ButtonSave": "Opslaan", "ButtonScanAllLibraries": "Scan alle bibliotheken", - "ButtonSearch": "Zoeken", "ButtonSelectDirectory": "Selecteer map", "ButtonSelectServer": "Server Selecteren", "ButtonSelectView": "Selecteer weergave", @@ -160,8 +155,8 @@ "DetectingDevices": "Apparaten detecteren", "DeviceAccessHelp": "Dit geldt alleen voor apparaten die uniek geïdentificeerd kunnen worden en voorkomen niet toegang via een webbrowser. Filteren van apparaat toegang voor gebruikers voorkomt dat zij nieuwe apparaten gebruiken totdat deze hier zijn goedgekeurd.", "DirectPlaying": "Direct afspelen", - "DirectStreamHelp1": "De resolutie en codec (bijv. H.264, AC3, etc.) wordt ondersteund door het apparaat, maar het medium is in een niet-ondersteunde bestandscontainer (bijv. mkv, avi, wmv). De video zal tijdens het afspelen opnieuw verpakt worden naar een andere bestandscontainer.", - "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processor kracht zonder verlies van beeldkwaliteit.", + "DirectStreamHelp1": "De resolutie en codec (H.264, AC3, etc.) wordt ondersteund door het apparaat, maar het medium is in een niet-ondersteunde bestandscontainer (mkv, avi, wmv, etc.). De video zal tijdens het afspelen opnieuw verpakt worden naar een andere bestandscontainer.", + "DirectStreamHelp2": "Direct streamen van een bestand gebruikt weinig processorkracht zonder verlies van beeldkwaliteit.", "DirectStreaming": "Direct streamen", "Director": "Regiseur", "Directors": "Regisseurs", @@ -267,7 +262,7 @@ "HeaderAllowMediaDeletionFrom": "Wissen van media toestaan van", "HeaderApiKey": "API Sleutel", "HeaderApiKeys": "API Sleutels", - "HeaderApiKeysHelp": "Externe applicaties zijn verplicht om een API sleutel te hebben om te communiceren met Jellyfin Server. Sleutels worden uitgegeven door in te loggen met een Jellyfin account, of door het handmatig verlenen van een sleutel voor de toepassing.", + "HeaderApiKeysHelp": "Externe applicaties zijn verplicht om een API sleutel te hebben om te communiceren met de server. Sleutels kunnen verkregen worden door in te loggen met een Jellyfin account, of door er een handmatig te verlenen.", "HeaderApp": "Applicatie", "HeaderAppearsOn": "Verschijnt op", "HeaderAudioBooks": "Luisterboeken", @@ -306,7 +301,6 @@ "HeaderDevices": "Apparaten", "HeaderDirectPlayProfile": "Direct Afspelen Profiel", "HeaderDirectPlayProfileHelp": "Voeg direct afspelen profielen toe om aan te geven welke formaten het apparaat standaard aankan.", - "HeaderDisplay": "Weergave", "HeaderDownloadSync": "Download & Synchronisatie", "HeaderEasyPinCode": "Eenvoudige Pincode", "HeaderEditImages": "Afbeeldingen bewerken", @@ -316,7 +310,6 @@ "HeaderError": "Fout", "HeaderExternalIds": "Externe ID's:", "HeaderFeatureAccess": "Functie toegang", - "HeaderFeatures": "Toevoegingen", "HeaderFetchImages": "Afbeeldingen ophalen:", "HeaderFetcherSettings": "Fetcher-instellingen", "HeaderForKids": "Voor Kinderen", @@ -332,7 +325,7 @@ "HeaderInstall": "Installeer", "HeaderKeepRecording": "Bewaar opname", "HeaderKeepSeries": "Series behouden", - "HeaderKodiMetadataHelp": "Om NFO-metadata in of uit te schakelen, gaat u naar de Jellyfin bibliotheekinstellingen en vervolgens naar de metadata-downloaders sectie.", + "HeaderKodiMetadataHelp": "Om NFO-metadata in of uit te schakelen, bewerk een bibliotheek en zoek in de metadata-downloaders sectie.", "HeaderLatestEpisodes": "Nieuwste Afleveringen", "HeaderLatestMedia": "Nieuwste Media", "HeaderLatestMovies": "Nieuwste Films", @@ -378,7 +371,7 @@ "HeaderPreferredMetadataLanguage": "Gewenste metadata taal", "HeaderProfile": "Profiel", "HeaderProfileInformation": "Profiel Informatie", - "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe Jellyfin Server zich zal presenteren aan het apparaat.", + "HeaderProfileServerSettingsHelp": "Deze waarden bepalen hoe de server zich zal presenteren aan het apparaat.", "HeaderRecentlyPlayed": "Recent afgespeeld", "HeaderRecordingOptions": "Opname instellingen", "HeaderRecordingPostProcessing": "Opname nabewerking", @@ -396,13 +389,13 @@ "HeaderSecondsValue": "{0} Seconden", "HeaderSelectCertificatePath": "Selecteer Certificaat Pad", "HeaderSelectMetadataPath": "Selecteer Metadata Pad", - "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet beschrijfbaar zijn.", + "HeaderSelectMetadataPathHelp": "Blader of voer het pad in dat u wilt gebruiken om metadata in op te slaan. De map moet schrijfbaar zijn.", "HeaderSelectPath": "Selecteer Pad", "HeaderSelectServer": "Selecteer server", "HeaderSelectServerCachePath": "Selecteer Server Cache Pad", "HeaderSelectServerCachePathHelp": "Bladeren of voer het pad in om te gebruiken voor server cache-bestanden. De map moet beschrijfbaar zijn.", "HeaderSelectTranscodingPath": "Selecteer Tijdelijke Transcodeer Pad", - "HeaderSelectTranscodingPathHelp": "Bladeren of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet beschrijfbaar zijn.", + "HeaderSelectTranscodingPathHelp": "Blader of voer het pad in om te gebruiken voor het transcoderen van tijdelijke bestanden. De map moet schrijfbaar zijn.", "HeaderSendMessage": "Stuur bericht", "HeaderSeries": "Series", "HeaderSeriesOptions": "Series Opties", @@ -423,7 +416,6 @@ "HeaderSubtitleProfiles": "Ondertitelingsprofielen", "HeaderSubtitleProfilesHelp": "Ondertitelingsprofielen beschrijven de ondertitelings formaten ondersteund door het apparaat.", "HeaderSystemDlnaProfiles": "Systeem Profielen", - "HeaderTags": "Labels", "HeaderTaskTriggers": "Taak Triggers", "HeaderThisUserIsCurrentlyDisabled": "Deze gebruiker is momenteel uitgesloten", "HeaderTranscodingProfile": "Direct Afspelen Profiel", @@ -452,7 +444,7 @@ "HttpsRequiresCert": "Om beveiligde verbindingen in te schakelen, is een vertrouwd SSL-certificaat vereist (zoals Let's Encrypt). Geef een certificaat op of schakel beveiligde verbindingen uit.", "Identify": "Identificeer", "Images": "Afbeeldingen", - "ImportFavoriteChannelsHelp": "Bij inschakelen zullen alleen kanalen geïmporteerd worden die op de tuner als favoriet aangemerkt zijn.", + "ImportFavoriteChannelsHelp": "Alleen kanalen die als favoriet aangemerkt zijn op de tuner zullen geïmporteerd worden.", "ImportMissingEpisodesHelp": "Indien ingeschakeld, wordt informatie over ontbrekende afleveringen in uw Jellyfin de database geïmporteerd en weergegeven in de seizoenen en series. Dit kan aanzienlijk langere bibliotheekscans veroorzaken.", "InstallingPackage": "Installeren van {0} (versie {1})", "Kids": "Kinderen", @@ -473,21 +465,20 @@ "LabelAlbumArtMaxWidthHelp": "Max. resolutie van albumhoezen weergegeven via upnp:albumArtURI.", "LabelAlbumArtPN": "Albumhoes PN:", "LabelAlbumArtists": "Album artiesten:", - "LabelAll": "Alles", "LabelAllowHWTranscoding": "Hardware transcoding toestaan", "LabelAllowedRemoteAddresses": "Externe IP-adressen filter:", "LabelAllowedRemoteAddressesMode": "Externe IP-adressen filter modus:", "LabelAppName": "Applicatie Naam", "LabelAppNameExample": "Voorbeeld: Sickbeard, Sonarr", "LabelArtists": "Artiest:", - "LabelArtistsHelp": "Scheidt meerdere met een ;", + "LabelArtistsHelp": "Scheidt artiesten met een ;", "LabelAudioLanguagePreference": "Voorkeurs audiotaal:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Vernieuw metagegevens automatisch van het internet:", "LabelBindToLocalNetworkAddress": "Binden aan het lokale netwerk adres:", "LabelBindToLocalNetworkAddressHelp": "Optioneel. Overrule het lokale IP-adres om aan de http-server te binden. Indien leeg gelaten, zal de server binden aan alle beschikbare adressen. Het veranderen van deze waarde vereist herstarten van Jellyfin Server.", "LabelBirthDate": "Geboortedatum:", "LabelBirthYear": "Geboorte jaar:", - "LabelBlastMessageInterval": "Alive bericht interval (seconden)", + "LabelBlastMessageInterval": "Alive bericht interval", "LabelBlastMessageIntervalHelp": "Bepaalt de duur in seconden tussen Blast Alive berichten.", "LabelBlockContentWithTags": "Blokkeer items met volgende tags:", "LabelBurnSubtitles": "Ondertitels inbranden:", @@ -512,7 +503,7 @@ "LabelCustomRating": "Aangepaste classificatie:", "LabelDateAdded": "Datum toegevoegd:", "LabelDateAddedBehavior": "Datum toegevoegd gedrag voor nieuwe content:", - "LabelDateAddedBehaviorHelp": "Als metadata gegevens aanwezig zijn hebben deze voorrang op deze opties.", + "LabelDateAddedBehaviorHelp": "Als metadata gegevens aanwezig is krijgt deze voorrang op deze opties.", "LabelDateTimeLocale": "Datum en tijd regio:", "LabelDay": "Dag:", "LabelDeathDate": "Overlijdens datum:", @@ -540,12 +531,12 @@ "LabelEnableAutomaticPortMapHelp": "Publieke poort automatisch doorsturen naar een lokale poort via UPnP. Dit werkt niet op alle routers en netwerk configuraties. De wijzigingen worden pas actief na een herstart van de server.", "LabelEnableBlastAliveMessages": "Alive berichten zenden", "LabelEnableBlastAliveMessagesHelp": "Zet dit aan als de server niet betrouwbaar door andere UPnP-apparaten op uw netwerk wordt gedetecteerd.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients (seconden)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Bepaalt de duur in seconden tussen SSDP zoekopdrachten uitgevoerd door Jellyfin.", "LabelEnableDlnaDebugLogging": "DLNA foutopsporings logboek inschakelen", "LabelEnableDlnaDebugLoggingHelp": "Genereer grote logboekbestanden en is alleen bedoeld voor het troubleshooting doeleinden.", "LabelEnableDlnaPlayTo": "DLNA \"Play To\" inschakelen", - "LabelEnableDlnaPlayToHelp": "Apparaten detecteren binnen uw netwerk en maak het mogelijk om ze op afstand te controleren.", + "LabelEnableDlnaPlayToHelp": "Apparaten detecteren binnen uw netwerk en maak het mogelijk om ze op afstand te gebruiken.", "LabelEnableDlnaServer": "DLNA server inschakelen", "LabelEnableDlnaServerHelp": "Sta UPnP apparaten op uw netwerk toe om door inhoud te bladeren en deze af te spelen.", "LabelEnableHardwareDecodingFor": "Activeer hardwaredecodering voor:", @@ -568,14 +559,14 @@ "LabelFriendlyName": "Gebruiksvriendelijke naam:", "LabelServerNameHelp": "Deze naam wordt gebruikt om de server te identificeren, standaard is deze de server zijn computer naam.", "LabelGroupMoviesIntoCollections": "Groepeer films in collecties", - "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films die tot een collectie behoren worden weergegeven als een gegroepeerd object.", + "LabelGroupMoviesIntoCollectionsHelp": "Bij de weergave van film lijsten, zullen films in een collectie worden weergegeven als een gegroepeerd object.", "LabelEncoderPreset": "H264 codering preset:", "LabelHardwareAccelerationType": "Hardware acceleratie:", "LabelHardwareAccelerationTypeHelp": "Hardwarematige versnelling vereist extra configuratie.", "LabelHomeNetworkQuality": "Thuisnetwerk kwaliteit:", "LabelHomeScreenSectionValue": "Beginscherm sectie {0}:", "LabelHttpsPort": "Lokale HTTPS poort nummer:", - "LabelHttpsPortHelp": "Het TCP poort nummer waar Jellyfin's HTTPS server aan moet verbinden.", + "LabelHttpsPortHelp": "Het TCP poort nummer voor de HTTPS server.", "LabelIconMaxHeight": "Pictogram maximum hoogte:", "LabelIconMaxHeightHelp": "Maximum resolutie van pictogrammen weergegeven via upnp:icon.", "LabelIconMaxWidth": "Pictogram maximum breedte:", @@ -602,7 +593,7 @@ "LabelLanNetworks": "LAN-netwerken:", "LabelLanguage": "Taal:", "LabelLocalHttpServerPortNumber": "Lokale HTTP poort nummer:", - "LabelLocalHttpServerPortNumberHelp": "Het TCP poort nummer waar Jellyfin's HTTP server aan moet verbinden.", + "LabelLocalHttpServerPortNumberHelp": "Het TCP poort nummer voor de HTTP server.", "LabelLockItemToPreventChanges": "Vergrendel dit item om toekomstige wijzigingen te voorkomen", "LabelLoginDisclaimer": "Aanmeld vrijwaring:", "LabelLoginDisclaimerHelp": "Een bericht dat weergeven zal worden onderaan op de login pagina.", @@ -642,7 +633,7 @@ "LabelMovieCategories": "Film categoriën:", "LabelMoviePrefix": "Film voorvoegsel:", "LabelMoviePrefixHelp": "Als een voorvoegsel wordt toegepast op filmtitels, typ deze dan eventueel hier zodat de server het goed kan verwerken.", - "LabelMovieRecordingPath": "Filmopname pad (optioneel):", + "LabelMovieRecordingPath": "Filmopname pad:", "LabelMusicStreamingTranscodingBitrate": "Muziek transcodering bitrate:", "LabelMusicStreamingTranscodingBitrateHelp": "Geef een maximum bitrate op voor het streamen van muziek.", "LabelName": "Naam:", @@ -655,7 +646,7 @@ "LabelNumber": "Nummer:", "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", - "LabelOptionalNetworkPath": "(Optioneel) Gedeelde netwerkmap:", + "LabelOptionalNetworkPath": "Gedeelde netwerkmap:", "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen. Bijvoorbeeld {0} or {1}.", "LabelOriginalAspectRatio": "Originele aspect ratio:", "LabelOriginalTitle": "Orginele titel:", @@ -696,7 +687,7 @@ "LabelReleaseDate": "Uitgave datum:", "LabelRemoteClientBitrateLimit": "Internet streaming bitrate limiet (Mbps):", "LabelRemoteClientBitrateLimitHelp": "Een optionele bitrate per stream limiet voor alle apparaten buiten het netwerk. Dit is handig om te voorkomen dat apparaten een hogere bitrate vragen dan je internetverbinding aan kan. Dit kan een verhoogde belasting van de CPU in je server veroorzaken om videos direct te transcoderen naar een lagere bitrate.", - "LabelRuntimeMinutes": "Speelduur (minuten):", + "LabelRuntimeMinutes": "Speelduur:", "LabelSaveLocalMetadata": "Afbeeldingen opslaan in mediamappen", "LabelSaveLocalMetadataHelp": "Door afbeeldingen op te slaan in de mediamappen kunnen ze makkelijker worden aangepast.", "LabelScheduledTaskLastRan": "Laatste keer {0}, duur {1}.", @@ -708,7 +699,7 @@ "LabelSelectVersionToInstall": "Selecteer de versie om te installeren:", "LabelSendNotificationToUsers": "Stuur de melding naar:", "LabelSerialNumber": "Serienummer", - "LabelSeriesRecordingPath": "Serieopname pad (optioneel):", + "LabelSeriesRecordingPath": "Serieopname pad:", "LabelServerHost": "Server:", "LabelServerHostHelp": "192.168.1.100:8096 of https://mijnserver.nl", "LabelSimultaneousConnectionLimit": "Gelijktijdige stream limiet:", @@ -891,7 +882,7 @@ "OptionAllowLinkSharingHelp": "Alleen webpagina's met media-informatie worden gedeeld. Media-bestanden worden nooit publiekelijk gedeeld. Gedeelde items zijn beperkt in tijd en verlopen na {0} dagen.", "OptionAllowManageLiveTv": "Live TV opname beheer toestaan", "OptionAllowMediaPlayback": "Media afspelen toestaan", - "OptionAllowMediaPlaybackTranscodingHelp": "Toegang tot transcodering beperken kan afspeelfouten in Jellyfin apps door niet ondersteunde madiaformaten veroorzaken.", + "OptionAllowMediaPlaybackTranscodingHelp": "Het beperken van toegang tot transcodering kan afspeelfouten in clients veroorzaken door niet ondersteunde madiaformaten.", "OptionAllowRemoteControlOthers": "Op afstand besturen van andere gebruikers toestaan", "OptionAllowRemoteSharedDevices": "Op afstand besturen van gedeelde apparaten toestaan", "OptionAllowRemoteSharedDevicesHelp": "DLNA apparaten worden als gedeeld apparaat gezien totdat een gebruiker deze gaat gebruiken.", @@ -903,7 +894,7 @@ "OptionAscending": "Oplopend", "OptionAutomatic": "Automatisch", "OptionAutomaticallyGroupSeries": "Automatisch samenvoegen serie die zijn verspreid over meerdere mappen", - "OptionAutomaticallyGroupSeriesHelp": "Indien ingeschakeld, zal serie die zijn verspreid over meerdere mappen binnen deze bibliotheek automatisch samengevoegd tot één serie.", + "OptionAutomaticallyGroupSeriesHelp": "Serie die verspreid zijn over meerdere mappen binnen deze bibliotheek worden automatisch samengevoegd tot één serie.", "OptionBlockBooks": "Boeken", "OptionBlockChannelContent": "Internet kanaal Inhoud", "OptionBlockLiveTvChannels": "Live TV Kanalen", @@ -922,7 +913,7 @@ "OptionDatePlayed": "Datum afgespeeld", "OptionDescending": "Aflopend", "OptionDisableUser": "Deze gebruiker uitschakelen", - "OptionDisableUserHelp": "Indien uitgeschakeld zal de server geen verbindingen van deze gebruiker toestaan. Bestaande verbindingen zullen abrupt worden beëindigd.", + "OptionDisableUserHelp": "De server staat geen verbindingen van deze gebruiker toe. Bestaande verbindingen zullen abrupt worden beëindigd.", "OptionDislikes": "Niet leuk", "OptionDisplayFolderView": "Toon een mappenweergave als u gewoon Mediamappen wilt weergeven", "OptionDisplayFolderViewHelp": "Geef folders weer naast uw andere media bibliotheken. Dit kan handig zijn als u een oppervlakkig folder aanzicht wilt hebben.", @@ -1134,19 +1125,16 @@ "TabAccess": "Toegang", "TabAdvanced": "Geavanceerd", "TabAlbumArtists": "Albumartiesten", - "TabArtists": "Artiesten", "TabCatalog": "Catalogus", "TabChannels": "Kanalen", "TabCollections": "Collecties", "TabDevices": "Apparaten", "TabDirectPlay": "Direct Afspelen", - "TabDisplay": "Weergave", "TabEpisodes": "Afleveringen", "TabFavorites": "Favorieten", "TabGuide": "Gids", "TabLatest": "Nieuw", "TabLogs": "Logboeken", - "TabMetadata": "Metagegevens", "TabMovies": "Films", "TabMusic": "Muziek", "TabMusicVideos": "Muziek Videos", @@ -1157,7 +1145,6 @@ "TabOther": "Overig", "TabParentalControl": "Ouderlijk toezicht", "TabPassword": "Wachtwoord", - "TabPlayback": "Afspelen", "TabPlaylists": "Afspeellijst", "TabProfile": "Profiel", "TabProfiles": "Profielen", @@ -1169,8 +1156,6 @@ "TabSettings": "Instellingen", "TabShows": "Series", "TabSongs": "Titels", - "TabSuggestions": "Suggesties", - "TabTranscoding": "Transcoderen", "TabUpcoming": "Binnenkort op TV", "TabUsers": "Gebruikers", "Tags": "Labels", @@ -1251,11 +1236,10 @@ "HeaderCastCrew": "Acteurs & medewerkers", "Art": "Afbeeldingen", "HeaderLiveTV": "Live TV", - "HeaderFilters": "Filters", "HeaderGenres": "Genres", "HeaderHttpHeaders": "HTTP Headers", "HeaderStatus": "Status", - "AuthProviderHelp": "Selecteer een Authenticatie Provider om het wachtwoord van deze gebruiker te verifiëren.", + "AuthProviderHelp": "Selecteer een authenticatie provider om het wachtwoord van deze gebruiker te verifiëren.", "HeaderFavoriteMovies": "Favoriete Films", "HeaderFavoriteShows": "Favoriete shows", "HeaderFavoriteEpisodes": "Favoriete afleveringen", @@ -1468,7 +1452,6 @@ "SelectAdminUsername": "Selecteer een gebruikersnaam voor het beheerder account.", "HeaderFavoritePlaylists": "Favoriete afspeellijsten", "ButtonTogglePlaylist": "Afspeellijst", - "ButtonToggleContextMenu": "Meer", "LabelRequireHttpsHelp": "Indien aangevinkt, zal de server alle verzoeken via HTTP automatisch omleiden naar HTTPS. Dit heeft geen effect als de server niet luistert op HTTPS.", "EnableDetailsBanner": "Details Banner", "MessageSyncPlayNoGroupsAvailable": "Geen groepen beschikbaar. Begin eerst iets te spelen.", @@ -1507,7 +1490,7 @@ "LabelRequireHttps": "HTTPS verplichten", "LabelStable": "Stabiel", "LabelChromecastVersion": "Chromecast versie", - "LabelEnableHttpsHelp": "Hiermee kan de server luisteren op de geconfigureerde HTTPS-poort. Om dit te laten werken moet ook een geldig certificaat worden geconfigureerd.", + "LabelEnableHttpsHelp": "Luisteren op de geconfigureerde HTTPS-poort. Om dit te laten werken moet ook een geldig certificaat worden ingesteld.", "LabelEnableHttps": "HTTPS inschakelen", "HeaderSyncPlayEnabled": "SyncPlay ingeschakeld", "HeaderSyncPlaySelectGroup": "Word lid van een groep", @@ -1537,5 +1520,11 @@ "LabelRepositoryUrlHelp": "De locatie van het repository manifest dat je wilt gebruiken.", "LabelRepositoryUrl": "Repository URL", "HeaderNewRepository": "Nieuwe repository", - "MessageNoRepositories": "Geen repositories." + "MessageNoRepositories": "Geen repositories.", + "LabelSubtitleVerticalPosition": "Verticale positie:", + "TabRepositories": "Repositories", + "MessageGetInstalledPluginsError": "Er is een fout opgetreden bij het ophalen van de lijst met geïnstalleerde plugins.", + "MessagePluginInstallError": "Er is een fout opgetreden tijdens het installeren van de plugin.", + "LabelUnstable": "Niet stabiel", + "NextTrack": "Ga naar volgende" } diff --git a/src/strings/pl.json b/src/strings/pl.json index b4612c14af..d97c4d633c 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -53,7 +53,6 @@ "Browse": "Przeglądaj", "MessageBrowsePluginCatalog": "Przejrzyj nasz katalog wtyczek żeby zobaczyć dostępne wtyczki.", "BurnSubtitlesHelp": "Określa czy serwer powinien wypalać napisy podczas konwersji wideo, w zależności od formatu napisów. Unikanie wypalania napisów znacząco poprawia wydajność serwera. Wybierz Automatycznie, w celu wypalania zarówno napisów w formatach graficznych (np. VOBSUB, PGS, SUB, IDX, ...), jak i pewnych napisów ASS lub SSA.", - "ButtonAdd": "Dodaj", "ButtonAddMediaLibrary": "Dodaj media do biblioteki", "ButtonAddScheduledTaskTrigger": "Dodaj wyzwalacz", "ButtonAddServer": "Dodaj Serwer", @@ -79,7 +78,6 @@ "ButtonFullscreen": "Pełny ekran", "ButtonGotIt": "Rozumiem", "ButtonGuide": "Przewodnik", - "ButtonHelp": "Pomoc", "ButtonHome": "Start", "ButtonInfo": "Informacje", "ButtonLibraryAccess": "Dostęp do biblioteki", @@ -100,15 +98,12 @@ "ButtonRefreshGuideData": "Odśwież dane przewodnika", "ButtonRemove": "Usuń", "ButtonRename": "Zmień nazwę", - "ButtonRepeat": "Powtórz", "ButtonResetEasyPassword": "Wyczyść kod PIN", "ButtonResetPassword": "Wyczyść hasło", "ButtonRestart": "Uruchom ponownie", "ButtonResume": "Wznów", "ButtonRevoke": "Odwołaj", - "ButtonSave": "Zapisz", "ButtonScanAllLibraries": "Skanuj biblioteki", - "ButtonSearch": "Szukaj", "ButtonSelectDirectory": "Wybierz folder", "ButtonSelectServer": "Serwery", "ButtonSelectView": "Wybierz widok", @@ -324,7 +319,6 @@ "HeaderDevices": "Urządzenia", "HeaderDirectPlayProfile": "Profil Odtwarzania Bezpośredniego", "HeaderDirectPlayProfileHelp": "Dodaj profil odtwarzania bezpośredniego aby wskazać które formaty mogą być obsługiwane natywnie.", - "HeaderDisplay": "Wyświetlanie", "HeaderDownloadSync": "Synchronizacja", "HeaderEasyPinCode": "Kod PIN", "HeaderEditImages": "Edytuj obrazy", @@ -334,10 +328,8 @@ "HeaderError": "Błąd", "HeaderExternalIds": "Identyfikatory zewnętrzne:", "HeaderFeatureAccess": "Dostęp do funkcji", - "HeaderFeatures": "Cechy", "HeaderFetchImages": "Pobieraj obrazy:", "HeaderFetcherSettings": "Ustawienia pobierania", - "HeaderFilters": "Filtry", "HeaderForKids": "Dla dzieci", "HeaderForgotPassword": "Zapomniałem hasła", "HeaderFrequentlyPlayed": "Często odtwarzane", @@ -450,7 +442,6 @@ "HeaderSubtitleProfiles": "Profile napisów", "HeaderSubtitleProfilesHelp": "Profile napisów określają formaty obsługiwane przez urządzenie.", "HeaderSystemDlnaProfiles": "Profile systemowe", - "HeaderTags": "Znaczniki", "HeaderTaskTriggers": "Wyzwalacze", "HeaderThisUserIsCurrentlyDisabled": "Ten użytkownik jest aktualnie zablokowany", "HeaderTracks": "Utwory", @@ -506,7 +497,6 @@ "LabelAlbumArtMaxWidthHelp": "Maksymalna rozdzielczość okładki albumu wystawiana przez upnp:albumArtURI.", "LabelAlbumArtPN": "PN okładki albumu:", "LabelAlbumArtists": "Wykonawcy albumów:", - "LabelAll": "Wszystkie", "LabelAllowHWTranscoding": "Zezwalaj na sprzętowe transkodowanie", "LabelAllowedRemoteAddresses": "Filtr adresów IP:", "LabelAllowedRemoteAddressesMode": "Tryb filtra adresów IP:", @@ -1217,7 +1207,6 @@ "TabAdvanced": "Zaawansowane", "TabAlbumArtists": "Wykonawcy albumów", "TabAlbums": "Albumy", - "TabArtists": "Wykonawcy", "TabCatalog": "Katalog", "TabChannels": "Kanały", "TabCodecs": "Kodeki", @@ -1226,7 +1215,6 @@ "TabDashboard": "Kokpit", "TabDevices": "Urządzenia", "TabDirectPlay": "Odtwarzanie Bezposrednie", - "TabDisplay": "Wyświetlanie", "TabEpisodes": "Odcinki", "TabFavorites": "Ulubione", "TabGenres": "Gatunki", @@ -1235,7 +1223,6 @@ "TabLatest": "Ostatnio dodane", "TabLiveTV": "Telewizja", "TabLogs": "Dziennik zdarzeń", - "TabMetadata": "Metadane", "TabMovies": "Filmy", "TabMusic": "Muzyka", "TabMusicVideos": "Teledyski", @@ -1246,7 +1233,6 @@ "TabOther": "Inne", "TabParentalControl": "Kontrola rodzicielska", "TabPassword": "Hasło", - "TabPlayback": "Odtwarzanie", "TabPlaylists": "Listy odtwarzania", "TabPlugins": "Wtyczki", "TabProfile": "Profil", @@ -1261,9 +1247,7 @@ "TabShows": "Seriale", "TabSongs": "Utwory", "TabStreaming": "Transmitowanie", - "TabSuggestions": "Polecane", "TabTrailers": "Zwiastuny", - "TabTranscoding": "Transkodowanie", "TabUpcoming": "Wkrótce", "TabUsers": "Użytkownicy", "Tags": "Znaczniki", @@ -1448,7 +1432,6 @@ "DeinterlaceMethodHelp": "Wybierz metodę usuwania przeplotu używaną podczas transkodowania.", "ClientSettings": "Ustawienia klienta", "ButtonTogglePlaylist": "Playlista", - "ButtonToggleContextMenu": "Więcej", "ButtonSyncPlay": "SyncPlay", "ClearQueue": "Wyczyść kolejkę", "StopPlayback": "Zatrzymaj odtwarzanie", diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index e398e47fa2..753b554da6 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -49,7 +49,6 @@ "Browse": "Navegar", "MessageBrowsePluginCatalog": "Navegue pelo nosso catálogo de plugins para ver os plugins disponíveis.", "BurnSubtitlesHelp": "Determina se o servidor deveria gravar as legendas no vídeo ao convertê-lo, dependendo do formato da legenda. Evitar a gravação da legenda irá melhorar a performance do servidor. Selecione Auto para gravar legendas baseados em imagem dos tipos (ex. VOBSUB, PGS, SUB/IDX, etc.) e algumas legendas ASS/SSA.", - "ButtonAdd": "Adicionar", "ButtonAddMediaLibrary": "Adicionar Biblioteca de Mídia", "ButtonAddScheduledTaskTrigger": "Adicionar Disparador", "ButtonAddServer": "Adicionar Servidor", @@ -74,7 +73,6 @@ "ButtonFullscreen": "Tela Cheia", "ButtonGotIt": "Feito", "ButtonGuide": "Guia", - "ButtonHelp": "Ajuda", "ButtonHome": "Início", "ButtonLibraryAccess": "Acesso à biblioteca", "ButtonManualLogin": "Login Manual", @@ -95,15 +93,12 @@ "ButtonRefreshGuideData": "Atualizar Dados do Guia", "ButtonRemove": "Remover", "ButtonRename": "Renomear", - "ButtonRepeat": "Repetir", "ButtonResetEasyPassword": "Redefinir código pin fácil", "ButtonResetPassword": "Redefinir Senha", "ButtonRestart": "Reiniciar", "ButtonResume": "Retomar", "ButtonRevoke": "Revogar", - "ButtonSave": "Salvar", "ButtonScanAllLibraries": "Rastrear Todas as Bibliotecas", - "ButtonSearch": "Busca", "ButtonSelectDirectory": "Selecionar Diretório", "ButtonSelectServer": "Selecionar Servidor", "ButtonSelectView": "Selecionar visualização", @@ -311,7 +306,6 @@ "HeaderDevices": "Dispositivos", "HeaderDirectPlayProfile": "Perfil de Reprodução Direta", "HeaderDirectPlayProfileHelp": "Adiciona perfis de reprodução direta que indiquem quais formatos o dispositivo pode suportar nativamente.", - "HeaderDisplay": "Exibição", "HeaderDownloadSync": "Download e Sincronização", "HeaderEasyPinCode": "Código Pin Fácil", "HeaderEditImages": "Editar Imagens", @@ -321,10 +315,8 @@ "HeaderError": "Erro", "HeaderExternalIds": "IDs Externos:", "HeaderFeatureAccess": "Acesso aos Recursos", - "HeaderFeatures": "Recursos", "HeaderFetchImages": "Buscar Imagens:", "HeaderFetcherSettings": "Configurações do Buscador", - "HeaderFilters": "Filtros", "HeaderForKids": "Para Crianças", "HeaderForgotPassword": "Esqueci a Senha", "HeaderFrequentlyPlayed": "Reproduzidos Frequentemente", @@ -489,7 +481,6 @@ "LabelAlbumArtMaxWidthHelp": "Resolução máxima da arte do álbum exposta via upnp:albumArtURI.", "LabelAlbumArtPN": "PN da arte do álbum:", "LabelAlbumArtists": "Artistas do álbum:", - "LabelAll": "Todos", "LabelAllowHWTranscoding": "Permitir a transcodificação de hardware", "LabelAllowedRemoteAddresses": "Filtro de endereço IP remoto:", "LabelAllowedRemoteAddressesMode": "Modo do filtro de endereço IP remoto:", @@ -1178,21 +1169,18 @@ "TabAdvanced": "Avançado", "TabAlbumArtists": "Artistas do Álbum", "TabAlbums": "Álbuns", - "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canais", "TabCollections": "Coletâneas", "TabDashboard": "Painel", "TabDevices": "Dispositivos", "TabDirectPlay": "Reprodução Direta", - "TabDisplay": "Exibição", "TabEpisodes": "Episódios", "TabFavorites": "Favoritos", "TabGenres": "Gêneros", "TabGuide": "Guia", "TabLatest": "Recentes", "TabLiveTV": "TV ao Vivo", - "TabMetadata": "Metadados", "TabMovies": "Filmes", "TabMusic": "Música", "TabMusicVideos": "Videoclipes", @@ -1203,7 +1191,6 @@ "TabOther": "Outros", "TabParentalControl": "Controle dos Pais", "TabPassword": "Senha", - "TabPlayback": "Reprodução", "TabPlaylists": "Listas de Reprodução", "TabProfile": "Perfil", "TabProfiles": "Perfis", @@ -1216,8 +1203,6 @@ "TabSettings": "Configurações", "TabShows": "Séries", "TabSongs": "Músicas", - "TabSuggestions": "Sugestões", - "TabTranscoding": "Transcodificação", "TabUpcoming": "A Seguir", "TabUsers": "Usuários", "TellUsAboutYourself": "Conte-nos sobre você", @@ -1296,7 +1281,6 @@ "HeaderAdmin": "Administrador", "HeaderApp": "Aplicativo", "HeaderStatus": "Status", - "HeaderTags": "Marcadores", "Horizontal": "Horizontal", "LabelAbortedByServerShutdown": "(Abortado devido ao desligamento do servidor)", "LabelCache": "Cache:", @@ -1467,7 +1451,6 @@ "Album": "Album", "UnsupportedPlayback": "O Jellyfin não pode descriptografar conteúdo protegido por DRM, porém mesmo assim fará uma tentativa para todo tipo de conteúdo, incluindo títulos protegidos. A imagem de alguns arquivos pode aparecer completamente preta devido a criptografia ou outros recursos não suportados, como títulos interativos.", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Mais", "Filter": "Filtro", "New": "Novo", "HeaderFavoritePlaylists": "Playlists Favoritas", diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index 56712f348a..3d780043aa 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -8,7 +8,6 @@ "Audio": "Áudio", "Backdrops": "Imagens de Fundo", "MessageBrowsePluginCatalog": "Procure extensões disponíveis no nosso catálogo.", - "ButtonAdd": "Adicionar", "ButtonAddMediaLibrary": "Adicionar Biblioteca de Multimédia", "ButtonAddScheduledTaskTrigger": "Adicionar tarefa agendada", "ButtonAddServer": "Adicionar Servidor", @@ -28,7 +27,6 @@ "ButtonFilter": "Filtro", "ButtonForgotPassword": "Esqueci-me da palavra-passe", "ButtonFullscreen": "Ecrã inteiro", - "ButtonHelp": "Ajuda", "ButtonHome": "Início", "ButtonInfo": "Informação", "ButtonManualLogin": "Início de Sessão Manual", @@ -45,15 +43,12 @@ "ButtonRefresh": "Atualizar", "ButtonRefreshGuideData": "Atualizar Programação de TV", "ButtonRemove": "Remover", - "ButtonRepeat": "Repetir", "ButtonResetEasyPassword": "Redefinir código PIN", "ButtonResetPassword": "Redefinir palavra-passe", "ButtonRestart": "Reiniciar", "ButtonResume": "Retomar", "ButtonRevoke": "Revogar", - "ButtonSave": "Guardar", "ButtonScanAllLibraries": "Analisar todas as Bibliotecas", - "ButtonSearch": "Procurar", "ButtonSelectDirectory": "Selecione a Pasta", "ButtonSelectView": "Selecionar visualização", "ButtonSend": "Enviar", @@ -144,15 +139,12 @@ "HeaderDevices": "Dispositivos", "HeaderDirectPlayProfile": "Perfil da Reprodução Direta", "HeaderDirectPlayProfileHelp": "Adicionar perfis de reprodução direta que indiquem que formatos o dispositivo pode suportar nativamente.", - "HeaderDisplay": "Visualização", "HeaderEasyPinCode": "Código PIN", "HeaderEnabledFields": "Campos Ativados", "HeaderEnabledFieldsHelp": "Desmarque um campo para bloqueá-lo e evitar que seus dados sejam alterados.", "HeaderError": "Erro", "HeaderFeatureAccess": "Acesso a Características", - "HeaderFeatures": "Recursos", "HeaderFetchImages": "Procurar Imagens:", - "HeaderFilters": "Filtros", "HeaderForgotPassword": "Esqueci-me da palavra-passe", "HeaderFrequentlyPlayed": "Reproduzido Frequentemente", "HeaderGenres": "Géneros", @@ -270,7 +262,6 @@ "LabelAlbumArtMaxWidthHelp": "Resolução máxima da capa do álbum exposta via upnp:albumArtURI.", "LabelAlbumArtPN": "PN da capa do álbum:", "LabelAlbumArtists": "Artistas do Álbum:", - "LabelAll": "Todos", "LabelAppName": "Nome da aplicação", "LabelAppNameExample": "Exemplo: Sickbeard, Sonarr", "LabelArtists": "Artistas:", @@ -677,7 +668,6 @@ "TabAdvanced": "Avançado", "TabAlbumArtists": "Artistas do Álbum", "TabAlbums": "Álbuns", - "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canais", "TabCollections": "Coleções", @@ -685,14 +675,12 @@ "TabDashboard": "Painel Principal", "TabDevices": "Dispositivos", "TabDirectPlay": "Reprodução Direta", - "TabDisplay": "Visualização", "TabEpisodes": "Episódios", "TabFavorites": "Favoritos", "TabGenres": "Géneros", "TabGuide": "Programação", "TabLatest": "Mais recente", "TabLiveTV": "TV em Direto", - "TabMetadata": "Metadados", "TabMovies": "Filmes", "TabMusic": "Música", "TabMusicVideos": "Videoclips", @@ -703,7 +691,6 @@ "TabOther": "Outro", "TabParentalControl": "Controlo Parental", "TabPassword": "Palavra-passe", - "TabPlayback": "Reprodução", "TabPlaylists": "Listas de Reprodução", "TabPlugins": "Extensões", "TabProfile": "Perfil", @@ -716,8 +703,6 @@ "TabSettings": "Configurações", "TabShows": "Séries", "TabSongs": "Músicas", - "TabSuggestions": "Sugestões", - "TabTranscoding": "Transcodificação", "TabUpcoming": "Próximos", "TabUsers": "Utilizadores", "TellUsAboutYourself": "Fale-nos sobre si", @@ -1162,7 +1147,6 @@ "CopyStreamURL": "Copiar URL da transmissão", "Disc": "Disco", "EnableBackdrops": "Imagens de Fundo", - "HeaderTags": "Etiquetas", "LabelLogs": "Registos:", "LabelSortTitle": "Título para ordenação:", "HeaderFavoritePeople": "Pessoas Favoritas", @@ -1467,7 +1451,6 @@ "DeinterlaceMethodHelp": "Selecionar um método de desentrelaçamento para converter conteúdo entrelaçado.", "ClientSettings": "Definições do Cliente", "ButtonTogglePlaylist": "Lista de Reprodução", - "ButtonToggleContextMenu": "Mais", "BoxSet": "Coleção", "Artist": "Artista", "AlbumArtist": "Artista do Álbum", diff --git a/src/strings/pt.json b/src/strings/pt.json index 45b8f0c182..9863623dc0 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -35,8 +35,6 @@ "TellUsAboutYourself": "Fale-nos sobre si", "TabUsers": "Utilizadores", "TabUpcoming": "Próximos", - "TabTranscoding": "Transcodificação", - "TabSuggestions": "Sugestões", "TabStreaming": "Transmissão", "TabSongs": "Músicas", "TabShows": "Séries", @@ -50,7 +48,6 @@ "TabProfile": "Perfil", "TabPlugins": "Extensões", "TabPlaylists": "Listas de Reprodução", - "TabPlayback": "Reprodução", "TabPassword": "Palavra-passe", "TabParentalControl": "Controlo Parental", "TabOther": "Outro", @@ -61,14 +58,12 @@ "TabMusicVideos": "Videoclips", "TabMusic": "Música", "TabMovies": "Filmes", - "TabMetadata": "Metadados", "TabLiveTV": "TV em Directo", "TabLatest": "Mais recente", "TabGuide": "Programação", "TabGenres": "Géneros", "TabFavorites": "Favoritos", "TabEpisodes": "Episódios", - "TabDisplay": "Visualização", "TabDirectPlay": "Reprodução Directa", "TabDevices": "Dispositivos", "TabDashboard": "Painel Principal", @@ -76,7 +71,6 @@ "TabCollections": "Colecções", "TabChannels": "Canais", "TabCatalog": "Catálogo", - "TabArtists": "Artistas", "TabAlbums": "Álbuns", "TabAlbumArtists": "Artistas do Álbum", "TabAdvanced": "Avançado", @@ -452,7 +446,6 @@ "LabelAllowedRemoteAddressesMode": "Tipo de filtro de IP remoto:", "LabelAllowedRemoteAddresses": "Filtro de IP remoto:", "LabelAllowHWTranscoding": "Permitir transcodificação por hardware", - "LabelAll": "Todos", "LabelAlbumArtists": "Artistas do Álbum:", "LabelAlbumArtPN": "PN da capa do álbum:", "LabelAlbumArtMaxWidthHelp": "Resolução máxima da capa do álbum exposta via upnp:albumArtURI.", @@ -617,9 +610,7 @@ "HeaderFrequentlyPlayed": "Reproduzido Frequentemente", "HeaderForgotPassword": "Esqueci-me da palavra-passe", "HeaderForKids": "Para Crianças", - "HeaderFilters": "Filtros", "HeaderFetchImages": "Procurar Imagens:", - "HeaderFeatures": "Recursos", "OptionDownloadThumbImage": "Miniatura", "OptionDownloadPrimaryImage": "Principal", "OptionDownloadImagesInAdvanceHelp": "Por omissão, a maioria das imagens são transferidas apenas quando uma aplicação do Jellyfin as solicita. Active esta opção para descarregar todas as imagens antecipadamente, assim que novos ficheiros multimédia sejam importados. Isto pode aumentar significativamente a duração da análise da biblioteca.", @@ -820,7 +811,6 @@ "HeaderEditImages": "Editar Imagens", "HeaderEasyPinCode": "Código PIN", "HeaderDownloadSync": "Transferência e Sincronização", - "HeaderDisplay": "Visualização", "HeaderDirectPlayProfileHelp": "Adicionar perfis de reprodução directa que indiquem que formatos o dispositivo pode suportar nativamente.", "HeaderDirectPlayProfile": "Perfil da Reprodução Directa", "HeaderDevices": "Dispositivos", @@ -962,15 +952,12 @@ "ButtonSelectView": "Seleccionar visualização", "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectDirectory": "Seleccione a Pasta", - "ButtonSearch": "Procurar", "ButtonScanAllLibraries": "Analisar todas as Bibliotecas", - "ButtonSave": "Guardar", "ButtonRevoke": "Revogar", "ButtonResume": "Retomar", "ButtonRestart": "Reiniciar", "ButtonResetPassword": "Redefinir palavra-passe", "ButtonResetEasyPassword": "Redefinir código PIN", - "ButtonRepeat": "Repetir", "ButtonRename": "Alterar o nome", "ButtonRemove": "Remover", "ButtonRefreshGuideData": "Actualizar Programação de TV", @@ -992,7 +979,6 @@ "ButtonLibraryAccess": "Acesso à biblioteca", "ButtonInfo": "Informação", "ButtonHome": "Início", - "ButtonHelp": "Ajuda", "ButtonFullscreen": "Ecrã inteiro", "ButtonForgotPassword": "Esqueci-me da palavra-passe", "ButtonFilter": "Filtro", @@ -1056,7 +1042,6 @@ "ButtonAddScheduledTaskTrigger": "Adicionar tarefa agendada", "ButtonAddMediaLibrary": "Adicionar Biblioteca de Multimédia", "ButtonAddImage": "Adicionar Imagem", - "ButtonAdd": "Adicionar", "BurnSubtitlesHelp": "Determina se o servidor deve integrar as legendas durante a conversão de vídeo, dependendo do formato da legenda. Evitar a integração de legendas melhora o desempenho do servidor. Selecione Automático para que legendas baseadas em imagem (VOBSUB, PGS, SUB/IDX) e certos formatos ASS/SSA sejam integrados.", "MessageBrowsePluginCatalog": "Explore as extensões disponíveis no nosso catálogo.", "Browse": "Explorar", @@ -1186,7 +1171,6 @@ "FetchingData": "Buscando Dados Adicionais", "EnableStreamLooping": "Habilitar loop do streaming", "Down": "Baixar", - "HeaderTags": "Tags", "HeaderNavigation": "Navegar", "ButtonSplit": "Dividir", "AskAdminToCreateLibrary": "Peça a um administrador para criar uma biblioteca.", @@ -1388,6 +1372,5 @@ "HeaderDVR": "DVR", "ApiKeysCaption": "Lista das chaves de API ativadas no momento", "ButtonTogglePlaylist": "Lista de leitura", - "ButtonToggleContextMenu": "Mais", "ButtonSyncPlay": "SyncPlay" } diff --git a/src/strings/ro.json b/src/strings/ro.json index f8aa986264..a627be87fb 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -1,6 +1,5 @@ { "All": "Toate", - "ButtonAdd": "Adaugă", "ButtonAddScheduledTaskTrigger": "Adaugă declanșator", "ButtonAddUser": "Adaugă Utilizator", "ButtonCancel": "Anulează", @@ -8,7 +7,6 @@ "ButtonDeleteImage": "Șterge Imaginea", "ButtonEdit": "Modifică", "ButtonFilter": "Filtru", - "ButtonHelp": "Ajutor", "ButtonManualLogin": "Conectare manuală", "ButtonNew": "Nou", "ButtonPlay": "Redă", @@ -17,7 +15,6 @@ "ButtonRefreshGuideData": "Reîmprospătează Ghidul", "ButtonRemove": "Elimină", "ButtonResetPassword": "Resetează parola", - "ButtonSave": "Salvează", "ButtonSelectDirectory": "Selectați Director", "ButtonSignIn": "Autentificare", "ButtonSignOut": "Delogare", @@ -44,7 +41,6 @@ "HeaderEasyPinCode": "Cod Pin Ușor", "HeaderFeatureAccess": "Acces Facilități", "HeaderFetchImages": "Preia imagini:", - "HeaderFilters": "Filtre", "HeaderFrequentlyPlayed": "Rulate Frecvent", "HeaderImageSettings": "Setari Imagini", "HeaderLatestEpisodes": "Cele Mai Noi Episoade", @@ -61,9 +57,9 @@ "HeaderTaskTriggers": "Declanșatori Sarcini", "HeaderUsers": "Utilizatori", "Help": "Ajutor", - "ImportMissingEpisodesHelp": "Dacă este activată, informația despre episoadele lipsă va fi importată in baza de date Jellyfin și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", + "ImportMissingEpisodesHelp": "Informația despre episoadele lipsă va fi importată în baza de date și va fi afișată în cadrul serialelor. Aceasta poate cauza un timp semnificativ mai îndelungat la scanarea bibliotecilor.", "LabelArtists": "Artisti:", - "LabelArtistsHelp": "Separare multiplă utilizând ;", + "LabelArtistsHelp": "Separară înșiruirea artiștilor utilizând ;", "LabelAudioLanguagePreference": "Preferințe de limbă pentru audio:", "LabelCachePath": "Cale pentru depozit:", "LabelCachePathHelp": "Specificați o locație specială pentru fișierele de tip depozit, precum imagini etc. Lasați gol pentru a folosi setarea implicită.", @@ -82,7 +78,7 @@ "LabelMetadataPath": "Cale pentru metadata:", "LabelMetadataPathHelp": "Specificați o locație specială pentru a descărca postere și metadata.", "LabelMinBackdropDownloadWidth": "Lățimea maximă pentru fundalurile descărcate:", - "LabelMovieRecordingPath": "Calea pentru înregistrări filme (opțional):", + "LabelMovieRecordingPath": "Calea pentru înregistrări filme:", "LabelName": "Nume:", "LabelNewPassword": "Parola nouă:", "LabelNewPasswordConfirm": "Confirmă parola nouă:", @@ -96,7 +92,7 @@ "LabelSaveLocalMetadata": "Salvează posterele si metadata în dosarele ce conțin fișierele media", "LabelSaveLocalMetadataHelp": "Salvând posterele și metadata direct in dosarele media, acestea vor fi mai accesibile pentru a fi modificate.", "LabelSelectUsers": "Selectare utilizatori:", - "LabelSeriesRecordingPath": "Calea pentru înregistrări seriale (opțional):", + "LabelSeriesRecordingPath": "Calea pentru înregistrări de seriale:", "LabelStopWhenPossible": "Oprește când este posibil:", "LabelTimeLimitHours": "Limită de timp(ore):", "LabelTranscodingTempPathHelp": "Specificați o cale specială pentru fișierele transcodate trimise clienților. Lasați gol pentru a folosi pe cea implicită în directorul de lucru al serverului.", @@ -133,7 +129,7 @@ "OptionDatePlayed": "Dată Rulare", "OptionDescending": "Descrescător", "OptionDisableUser": "Dezactivați acest utilizator", - "OptionDisableUserHelp": "Dacă este dezactivat, serverul nu va permite nicio conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", + "OptionDisableUserHelp": "Serverul nu va permite nici o conexiune de la acest utilizator. Conexiunile existente vor fi terminate brusc.", "OptionDislikes": "Dislike-uri", "OptionDownloadArtImage": "Fundal", "OptionDownloadBackImage": "Înapoi", @@ -182,7 +178,6 @@ "TabAdvanced": "Avansat", "TabAlbumArtists": "Albume Artiști", "TabAlbums": "Albume", - "TabArtists": "Artiști", "TabChannels": "Canale", "TabCollections": "Colecții", "TabEpisodes": "Episoade", @@ -205,9 +200,7 @@ "TabSettings": "Setări", "TabShows": "Seriale", "TabSongs": "Melodii", - "TabSuggestions": "Recomandări", "TabTrailers": "Trailere", - "TabTranscoding": "Conversie", "TabUpcoming": "Urmează să apară", "TellUsAboutYourself": "Spune-ne despre tine", "ThisWizardWillGuideYou": "Acest asistent vă va ghida prin procesul de configurare. Pentru a începe, vă rugăm să selectați limba preferată.", @@ -243,7 +236,7 @@ "ButtonStop": "Stop", "ButtonSubmit": "Trimite", "Collections": "Colecții", - "AllowRemoteAccess": "Permite conexiuni externe către acest server Jellyfin.", + "AllowRemoteAccess": "Permiteți conexiuni externe către acest server.", "AllowRemoteAccessHelp": "Dacă este neselectat, toate conexiunile externe vor fi blocate.", "AlwaysPlaySubtitles": "Întotdeauna arată", "AnyLanguage": "Orice Limbă", @@ -287,7 +280,7 @@ "ButtonSettings": "Setări", "ChangingMetadataImageSettingsNewContent": "Modificări ale metadatelor sau ale setărilor de descărcare a operelor de artă se va aplica doar conținutului nou adăugat în librăriile tale. Pentru a aplica modificările titlurilor deja existente va trebui reîmprospătată manual metadata lor.", "CinemaModeConfigurationHelp": "Mod cinema aduce experiența cinematografică în sufrageria dumneavoastră prin abilitatea de a rula trailere sau introuri personalizate înaintea titlului principal.", - "ConfigureDateAdded": "Configurează cum este determinată data adaugării în tabloul de bord al serverului Jellyfin în setările librariei", + "ConfigureDateAdded": "Configurează cum este determinată data adaugării în tabloul de bord din setările librariei", "DefaultSubtitlesHelp": "Subtitrările sunt încărcate în funcție de opțiunile implicite și forțate din metadatele încorporate. Preferințele de limbă sunt luate în considerare atunci când sunt disponibile mai multe opțiuni.", "DirectStreamHelp1": "Media este compatibilă cu dispozitivul în ceea ce privește rezoluția și tipul de media(H.264, AC3, etc), dar se află într-un container de fișiere incompatibil (mkv, avi, wmv, etc). Videoclipul va fi re-ambalat în timp real înainte de a-l transmite către dispozitiv.", "CopyStreamURLSuccess": "URL copiat cu succes.", @@ -339,12 +332,10 @@ "ErrorDefault": "A fost o eroare în procesarea cererii. Vă rugam încercați din nou mai târziu.", "DeleteImageConfirmation": "Sigur doriți să ștergeți această imagine?", "ButtonRename": "Redenumește", - "ButtonRepeat": "Repetă", "ButtonResetEasyPassword": "Resetează codul Easy PIN", "ButtonRestart": "Redemarează", "ButtonResume": "Continuă", "ButtonScanAllLibraries": "Scanează Toate Librariile", - "ButtonSearch": "Caută", "ButtonSelectServer": "Selectați Server", "ButtonSelectView": "Selectați perspectivă", "ButtonSend": "Trimite", @@ -409,7 +400,6 @@ "HeaderDeleteTaskTrigger": "Ștergeți Declanșarea Activității", "HeaderDetectMyDevices": "Detectează dispozitivele mele", "HeaderDeveloperInfo": "Informații pentru dezvoltatori", - "HeaderDisplay": "Afișare", "HeaderDownloadSync": "Descărcări și sincronizare", "HeaderEditImages": "Editează imagini", "HeaderEnabledFields": "Câmpuri activate", @@ -419,13 +409,12 @@ "HeaderExternalIds": "ID-uri Externe:", "HeaderFavoriteBooks": "Cărți Favorite", "HeaderBranding": "Marca", - "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu Jellyfin Server. Cheile sunt emise prin conectarea cu un cont Jellyfin sau prin acordarea manuală a unei chei aplicației.", + "HeaderApiKeysHelp": "Aplicațiile externe trebuie să aibă o cheie API pentru a comunica cu serverul. Cheile sunt emise prin conectarea cu un cont de utilizator sau prin acordarea manuală a unei chei aplicației.", "Sync": "Sincronizare", "ErrorAddingXmlTvFile": "A apărut o eroare la accesarea fișierului XMLTV. Vă rugăm să vă asigurați că fișierul există și încercați din nou.", "HeaderApiKey": "Cheie API", "HeaderFavoritePeople": "Persoane Favorite", "HeaderFavoriteVideos": "Video Favorite", - "HeaderFeatures": "Caracteristici", "HeaderFetcherSettings": "Setări Fetcher", "HeaderForKids": "Pentru Copii", "HeaderForgotPassword": "Am uitat parola", @@ -459,7 +448,7 @@ "HeaderMyMediaSmall": "Fișierele mele Media ( micșorat )", "HeaderNewApiKey": "Nouă cheie API", "HeaderNewDevices": "Dispozitive noi", - "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, în configurarea bibliotecii Jellyfin, și localizați secțiunea de salvare a metadatelor.", + "HeaderKodiMetadataHelp": "Pentru a activa sau dezactiva metadatele NFO, editați o bibliotecă, și localizați secțiunea de salvare a metadatelor.", "HeaderNextVideoPlayingInValue": "Următorul video se redă în {0}", "HeaderOnNow": "Pornit Acum", "HeaderOtherItems": "Alte Elemente", @@ -475,7 +464,7 @@ "HeaderPlaybackError": "Eroare la redare", "HeaderPluginInstallation": "Instalare Plugin", "HeaderProfileInformation": "Informații Profil", - "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care Jellyfin Server va fi reprezentat in dispozitiv.", + "HeaderProfileServerSettingsHelp": "Aceste valori controlează modul în care serverul va fi reprezentat in dispozitivele clientilor.", "HeaderRecordingOptions": "Opțiuni Înregistrare", "HeaderRecordingPostProcessing": "Post procesarea înregistrării", "HeaderRemoveMediaFolder": "Eliminați Dosarul Media", @@ -552,7 +541,7 @@ "ValueSpecialEpisodeName": "Special - {0}", "EnableStreamLoopingHelp": "Activați acestă opțiune dacă fluxurile live conțin doar câteva secunde de date și trebuie solicitate în mod continuu. Activarea acestei opțiuni atunci când nu este necesar poate provoca probleme.", "ErrorAddingListingsToSchedulesDirect": "A apărut o eroare la adăugarea liniei în contul dvs. Schedules Direct. Schedules Direct permite doar un număr limitat de linii pentru fiecare cont. Este posibil să fie nevoie să vă conectați la site-ul web Schedules Direct și să eliminați alte înregistrări din cont înainte de a continua.", - "ErrorAddingMediaPathToVirtualFolder": "A apărut o eroare la adăugarea căii de acces la fișierul media. Vă rugăm să vă asigurați că ruta este validă și procesul Jellyfin Server are acces la locația respectivă.", + "ErrorAddingMediaPathToVirtualFolder": "A apărut o eroare la adăugarea căii de acces la fișierul media. Vă rugăm să vă asigurați că ruta este validă și că Jellyfin are acces la locația respectivă.", "ErrorStartHourGreaterThanEnd": "Timpul de oprire trebuie să fie mai mare decât cel de pornire.", "ErrorPleaseSelectLineup": "Selectați o linie și încercați din nou. Dacă nu sunt disponibile linii, atunci vă rugăm să verificați dacă numele dvs. de utilizator, parola și codul poștal sunt corecte.", "ExitFullscreen": "Ieșiți din modul ecran complet", @@ -597,7 +586,7 @@ "EnableThemeVideos": "Videoclipuri tematice", "EnableThemeVideosHelp": "Redați videoclipuri tematice în fundal în timp ce navigați în bibliotecă.", "ErrorAddingTunerDevice": "A apărut o eroare la adăugarea tuner-ului. Vă rugăm să vă asigurați că este accesibil și încercați din nou.", - "ErrorDeletingItem": "A apărut o eroare la ștergerea elementului din Jellyfin Server. Vă rugăm să verificați dacă Jellyfin Server are acces de scriere la folderul media și încercați din nou.", + "ErrorDeletingItem": "A apărut o eroare la ștergerea elementului din server. Vă rugăm să verificați dacă Jellyfin are acces de scriere la folderul media și încercați din nou.", "ErrorGettingTvLineups": "A apărut o eroare la descărcarea liniilor TV. Vă rugăm să vă asigurați că informațiile dvs. sunt corecte și încercați din nou.", "ErrorSavingTvProvider": "A apărut o eroare la salvarea furnizorului de televiziune. Vă rugăm să vă asigurați că este accesibil și încercați din nou.", "ExtraLarge": "Foarte mare", @@ -614,7 +603,7 @@ "HeaderSelectServer": "Selectați Serverul", "HeaderSelectServerCachePath": "Selectați ruta pentru Server Cache", "HeaderSelectTranscodingPath": "Selectați ruta temporară pentru transcodare", - "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor temporare. Dosarul trebuie permisiuni de scriere.", + "HeaderSelectTranscodingPathHelp": "Căutați sau introduceți ruta dosarului de utilizat pentru transcodarea fișierelor. Dosarul trebuie permisiuni de scriere.", "HeaderSendMessage": "Trimite Mesaj", "HeaderSeriesOptions": "Opțiuni Seriale", "HeaderSeriesStatus": "Starea Serialelor", @@ -675,7 +664,7 @@ "LabelSeasonNumber": "Numărul sezonului:", "LabelScreensaver": "Protector de ecran:", "LabelScheduledTaskLastRan": "Ultima redare{0}, cu durata {1}.", - "LabelRuntimeMinutes": "Timp de redare (minute):", + "LabelRuntimeMinutes": "Timp de redare:", "LabelRemoteClientBitrateLimitHelp": "O limită de biți per-stream opțională pentru toate dispozitivele din rețea. Acest lucru este util pentru a împiedica dispozitivele să solicite un bitrate mai mare decât poate gestiona conexiunea dvs. de internet. Acest lucru poate duce la creșterea încărcării procesorului pe serverul dvs. pentru a transcoda videoclipurile din zbor la un bitrate mai mic.", "LabelRemoteClientBitrateLimit": "Limită de biți pentru streaming pe Internet (Mbps):", "LabelReleaseDate": "Data lansării:", @@ -719,8 +708,8 @@ "LabelOverview": "Prezentare generală:", "LabelOriginalTitle": "Titlu original:", "LabelOriginalAspectRatio": "Raport aspect original:", - "LabelOptionalNetworkPathHelp": "Dacă acest folder este partajat în rețeaua dvs., furnizarea căii de partajare a rețelei poate permite aplicațiilor Jellyfin de pe alte dispozitive să acceseze fișiere media direct.", - "LabelOptionalNetworkPath": "(Optional) Dosar partajat în rețea:", + "LabelOptionalNetworkPathHelp": "Dacă acest folder este partajat în rețeaua dvs., furnizarea căii de partajare a rețelei poate permite aplicațiilor client de pe alte dispozitive să acceseze fișiere media direct. De exemplu, {0} sau {1}.", + "LabelOptionalNetworkPath": "Dosar partajat în rețea:", "LabelNumber": "Număr:", "LabelNotificationEnabled": "Activează această notificare", "LabelNewsCategories": "Categoriile știrilor:", @@ -741,7 +730,7 @@ "LabelMinResumeDurationHelp": "Cea mai scurtă lungime video în secunde, care va salva locația de redare și vă va permite să reluați.", "LabelMinResumeDuration": "Durata minimă a reluării:", "LabelMethod": "Metoda:", - "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru a vă salva metadatele.", + "LabelMetadataSaversHelp": "Alegeți formatele de fișiere pentru salvarea metadatele.", "LabelMetadataSavers": "Salvări de metadate:", "LabelMetadataReadersHelp": "Clasificați sursele preferate de metadate locale în ordinea priorității. Primul fișier găsit va fi citit.", "LabelMetadataReaders": "Cititori de metadate:", @@ -761,7 +750,7 @@ "LabelLoginDisclaimerHelp": "Un mesaj care va fi afișat în partea de jos a paginii de conectare.", "LabelLoginDisclaimer": "Act de renunțare la autentificare:", "LabelLockItemToPreventChanges": "Blocați acest element pentru a preveni modificările viitoare", - "LabelLocalHttpServerPortNumberHelp": "Portul TCP pe care serverul HTTP Jellyfin ar trebui să îl utilizeze.", + "LabelLocalHttpServerPortNumberHelp": "Portul TCP pentru serverul HTTP.", "LabelLocalHttpServerPortNumber": "Portul local HTTP:", "LabelLineup": "Echipa:", "LabelLanNetworks": "Rețele LAN:", @@ -788,7 +777,7 @@ "LabelIconMaxWidth": "Lățimea maximă a pictogramei:", "LabelIconMaxHeightHelp": "Rezoluția maximă a pictogramelor expuse via upnp:icon.", "LabelIconMaxHeight": "Înălțimea maximă a pictogramei:", - "LabelHttpsPortHelp": "Portul TCP pe care serverul HTTPS Jellyfin ar trebui sa îl utilizeze.", + "LabelHttpsPortHelp": "Portul TCP pentru serverul HTTPS.", "LabelHttpsPort": "Portul local HTTPS:", "LabelHomeScreenSectionValue": "Secțiunea ecranului de pornire {0}:", "LabelHomeNetworkQuality": "Calitatea pe rețeaua de domiciliu:", @@ -816,7 +805,7 @@ "LabelEndDate": "Data de încheiere:", "LabelEnableSingleImageInDidlLimitHelp": "Unele dispozitive nu vor reda corect dacă mai multe imagini sunt încorporate în Didl.", "LabelEnableSingleImageInDidlLimit": "Limitați la o singură imagine încorporată", - "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat, pe sistemele de fișiere acceptate.", + "LabelEnableRealtimeMonitorHelp": "Modificările la fișiere vor fi procesate imediat pe sistemele de fișiere acceptate.", "LabelEnableRealtimeMonitor": "Activați monitorizarea în timp real", "LabelEnableHardwareDecodingFor": "Activați decodarea hardware pentru:", "LabelEnableDlnaServerHelp": "Permite dispozitivelor UPnP din rețeaua dvs. să răsfoiască și să redea conținut.", @@ -825,8 +814,8 @@ "LabelEnableDlnaPlayTo": "Activează DLNA Play To", "LabelEnableDlnaDebugLoggingHelp": "Creați fișiere de jurnal mari și trebuie utilizate numai în funcție de necesități pentru rezolvarea problemelor.", "LabelEnableDlnaDebugLogging": "Activați jurnalul de depanare DLNA", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determină durata în secunde între căutările SSDP efectuate de Jellyfin.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului (secunde)", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determină durata în secunde între căutările SSDP.", + "LabelEnableDlnaClientDiscoveryInterval": "Interval de descoperire a clientului", "LabelEnableBlastAliveMessagesHelp": "Activați acest lucru dacă serverul nu este detectat în mod fiabil de alte dispozitive UPnP din rețeaua dvs.", "LabelEnableBlastAliveMessages": "Trimitere mesaje de disponibilitate", "LabelEnableAutomaticPortMapHelp": "Încercați să mapați automat portul public către portul local prin UPnP. Este posibil să nu funcționeze cu unele modele de router. Modificările nu se vor aplica decât după repornirea serverului.", @@ -874,11 +863,11 @@ "LabelBurnSubtitles": "Imprimă subtitrările:", "LabelBlockContentWithTags": "Blochează articolele cu etichetele:", "LabelBlastMessageIntervalHelp": "Determină durata în secunde între transmiterea mesajele de viață.", - "LabelBlastMessageInterval": "Interval transmitere mesaj viu (secunde)", + "LabelBlastMessageInterval": "Interval transmitere mesaj viu", "LabelBitrate": "Rată de biți:", "LabelBirthYear": "Anul nașterii:", "LabelBirthDate": "Data nașterii:", - "LabelBindToLocalNetworkAddressHelp": "Opțional. Rescrie adresa IP locală pentru a o utiliza serverul http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită repornirea Jellyfin Server.", + "LabelBindToLocalNetworkAddressHelp": "Rescrie adresa IP locală a serverului http. Dacă este lăsat gol, serverul se va lega la toate adresele disponibile. Modificarea acestei valori necesită un restart.", "LabelBindToLocalNetworkAddress": "Utilizează adresa de rețea locală:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizați automat metadatele de pe internet:", "LabelAuthProvider": "Furnizor de autentificare:", @@ -893,7 +882,6 @@ "LabelAllowedRemoteAddressesMode": "Modul de filtrare a adresei IP de la distanță:", "LabelAllowedRemoteAddresses": "Filtrul de adrese IP de la distanță:", "LabelAllowHWTranscoding": "Permite transcodare hardware", - "LabelAll": "Tot", "LabelAlbumArtists": "Artiști album:", "LabelAlbumArtPN": "Artă album PN:", "LabelAlbumArtMaxWidthHelp": "Rezoluție maximă a artei albumului expusă via upnp:albumArtURI.", @@ -917,7 +905,7 @@ "ItemCount": "{0} articole", "InstantMix": "Mix instant", "InstallingPackage": "Instalare {0} (versiune {1})", - "ImportFavoriteChannelsHelp": "Dacă este activat, vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", + "ImportFavoriteChannelsHelp": "Vor fi importate numai canalele marcate ca preferate pe dispozitivul tuner.", "Images": "Imagini", "Identify": "Identifică", "HttpsRequiresCert": "Pentru a activa conexiunile securizate, va trebui să furnizați un certificat SSL de încredere, cum ar fi Let's Encrypt. Vă rugăm să furnizați un certificat sau să dezactivați conexiunile securizate.", @@ -945,7 +933,6 @@ "HeaderTranscodingProfile": "Profilul transcodării", "HeaderTracks": "Piese", "HeaderThisUserIsCurrentlyDisabled": "Acest utilizator este momentan dezactivat", - "HeaderTags": "Etichete", "HeaderSystemDlnaProfiles": "Profile de sistem", "HeaderSubtitleProfilesHelp": "Profilele subtitrării descriu formatul subtitrării compatibil cu dispozitivul.", "HeaderSubtitleProfiles": "Profilele subtitrării", @@ -969,14 +956,14 @@ "OptionBlockChannelContent": "Conținut canal Internet", "OptionBlockBooks": "Cărți", "OptionBanner": "Steag", - "OptionAutomaticallyGroupSeriesHelp": "Dacă este activat, seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", + "OptionAutomaticallyGroupSeriesHelp": "Seriile distribuite pe mai multe foldere din această bibliotecă vor fi comasate automat într-o singură serie.", "OptionAutomaticallyGroupSeries": "Fuzionează automat seriile care sunt răspândite pe mai multe foldere", "OptionAuto": "Auto", "OptionArtist": "Artist", "OptionAllowVideoPlaybackTranscoding": "Permiteți redarea video care necesită transcodare", "OptionAllowVideoPlaybackRemuxing": "Permiteți redarea video care necesită conversie fără re-codificare", "OptionAllowSyncTranscoding": "Permiteți descărcarea și sincronizarea media care necesită transcodare", - "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile Jellyfin din cauza formatelor media neacceptate.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restrângerea accesului la transcodare poate provoca defecțiuni de redare în aplicațiile client din cauza formatelor media neacceptate.", "OptionAllowContentDownloading": "Permiteți descărcarea și sincronizarea media", "OptionAllowAudioPlaybackTranscoding": "Permiteți redarea audio care necesită transcodare", "OptionAllUsers": "Toți utilizatorii", @@ -1028,7 +1015,7 @@ "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Următoarele locații media vor fi eliminate din biblioteca dvs.:", "MessageSettingsSaved": "Setări salvate.", "MessageReenableUser": "Consultați mai jos pentru a reactiva", - "MessagePluginInstallDisclaimer": "Pluginurile create de membrii comunității Jellyfin sunt o modalitate excelentă de a vă îmbunătăți experiența Jellyfin cu funcții și beneficii suplimentare. Înainte de instalare, vă rugăm să fiți conștienți de efectele pe care le pot avea asupra serverului dvs. Jellyfin, cum ar fi scanările de bibliotecă mai lungi, procesarea suplimentară a fundalului și scăderea stabilității sistemului.", + "MessagePluginInstallDisclaimer": "Pluginurile create de membrii comunității sunt o modalitate excelentă de a vă îmbunătăți experiența cu funcții și beneficii suplimentare. Înainte de instalare, vă rugăm să fiți conștienți de efectele pe care le pot avea asupra serverului dvs., cum ar fi scanările de bibliotecă mai îndelungate, procesare suplimentară în fundal și scăderea stabilității sistemului.", "MessagePluginConfigurationRequiresLocalAccess": "Pentru a configura acest plugin, vă rugăm să vă conectați direct la serverul dvs. local.", "MessagePleaseWait": "Te rog așteaptă. Poate dura un minut.", "MessagePlayAccessRestricted": "Redarea acestui conținut este în prezent restricționată. Vă rugăm să contactați administratorul serverului pentru mai multe informații.", @@ -1051,13 +1038,13 @@ "MessageFileReadError": "S-a întâmpinat o eroare în timpul citirii fișierului. Vă rugăm să încercați din nou.", "MessageDownloadQueued": "Descărcare adăugata în coadă.", "MessageDirectoryPickerLinuxInstruction": "Pentru Linux pe Arch Linux, CentOS, Debian, Fedora, openSUSE sau Ubuntu, trebuie să acordați utilizatorului serverului Jellyfin cel puțin permisiunea de citire la locațiile de stocare.", - "MessageDirectoryPickerBSDInstruction": "Pentru BSD, poate fi necesar să configurați stocarea în FreeNAS jail pentru a permite serverului Jellyfin să o acceseze.", + "MessageDirectoryPickerBSDInstruction": "Pentru BSD, poate fi necesar să configurați stocarea în FreeNAS Jail pentru ca Jellyfin să o acceseze.", "MessageDeleteTaskTrigger": "Sigur doriți să ștergeți acest declanșator de activitate?", "MessageCreateAccountAt": "Crează un cont la {0}", "MessageContactAdminToResetPassword": "Vă rugăm să contactați administratorul de sistem pentru a vă reseta parola.", "MessageConfirmShutdown": "Sigur doriți să opriți serverul?", - "MessageConfirmRevokeApiKey": "Sigur doriți să revocați această cheie API? Conexiunea aplicației la Jellyfin Server va fi terminată brusc.", - "MessageConfirmRestart": "Sigur doriți să redemarați serverul Jellyfin?", + "MessageConfirmRevokeApiKey": "Sigur doriți să revocați această cheie API? Conexiunea aplicației la acest server va fi terminată brusc.", + "MessageConfirmRestart": "Sigur doriți să restartați Jellyfin?", "MessageConfirmRemoveMediaLocation": "Sigur doriți să eliminați această locație?", "MessageConfirmRecordingCancellation": "Anulați înregistrarea?", "MessageConfirmProfileDeletion": "Sigur doriți să ștergeți acest profil?", @@ -1113,7 +1100,7 @@ "LatestFromLibrary": "Ultimele {0}", "Large": "Mare", "LanNetworksHelp": "Lista separată de virgule a adreselor IP sau a intrărilor de tip IP/mască de rețea pentru rețelele care vor fi luate în considerare în rețeaua locală atunci când se aplică restricțiile de lățime de bandă. Dacă este setat, toate celelalte adrese IP vor fi considerate a fi în rețeaua externă și vor fi supuse restricțiilor de lățime de bandă externe. Dacă este lăsat necompletat, numai subnetul serverului este considerat a fi în rețeaua locală.", - "LabelffmpegPathHelp": "Calea către executabilul ffmpeg, sau dosarul care conține ffmpeg.", + "LabelffmpegPathHelp": "Calea către executabilul ffmpeg sau dosarul care conține ffmpeg.", "LabelffmpegPath": "Calea către FFmpeg:", "LabelZipCode": "Cod poștal:", "LabelYear": "Anul:", @@ -1178,10 +1165,10 @@ "SettingsWarning": "Modificarea acestor valori poate provoca instabilități sau eșecuri de conectivitate. Dacă întâmpinați probleme, vă recomandăm să le schimbați înapoi cu cele din modul implicit.", "SettingsSaved": "Setări salvate.", "Settings": "Setări", - "ServerUpdateNeeded": "Acest Jellyfin Server trebuie actualizat. Pentru a descărca cea mai recentă versiune, accesați {0}", - "ServerRestartNeededAfterPluginInstall": "Jellyfin Server va trebui să fie repornit după instalarea unui plugin.", - "ServerNameIsShuttingDown": "Jellyfin Server - {0} se oprește.", - "ServerNameIsRestarting": "Jellyfin Server - {0} se repornește.", + "ServerUpdateNeeded": "Acest server trebuie actualizat. Pentru a descărca cea mai recentă versiune, accesați {0}", + "ServerRestartNeededAfterPluginInstall": "Jellyfin va trebui să fie repornit după instalarea unui plugin.", + "ServerNameIsShuttingDown": "Serverul de pe {0} se oprește.", + "ServerNameIsRestarting": "Serverul de pe {0} se repornește.", "SeriesYearToPresent": "{0} - Prezent", "SeriesSettings": "Setările serialului", "SeriesRecordingScheduled": "Înregistrarea serialului programată.", @@ -1217,7 +1204,7 @@ "ReleaseDate": "Data lansării", "RefreshQueued": "Actualizare adăugată în coadă.", "RefreshMetadata": "Actualizați metadatele", - "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord Jellyfin Server.", + "RefreshDialogHelp": "Metadatele sunt actualizate pe baza setărilor și a serviciilor de internet care sunt activate în tabloul de bord.", "Refresh": "Reîmprospătează", "Recordings": "Înregistrări", "RecordingScheduled": "Înregistrare programată.", @@ -1242,9 +1229,9 @@ "Premiere": "Premieră", "PreferEmbeddedTitlesOverFileNamesHelp": "Aceasta determină titlul afișat implicit atunci când nu sunt disponibile metadate din internet sau metadate locale.", "PreferEmbeddedTitlesOverFileNames": "Preferă titlurile incluse decât numele fișierelor", - "MessagePluginInstalled": "Pluginul a fost instalat cu succes. Jellyfin Server va trebui să fie repornit pentru ca modificările să intre în vigoare.", + "MessagePluginInstalled": "Pluginul a fost instalat cu succes. Serverul va trebui să fie repornit pentru ca modificările să intre în vigoare.", "PleaseSelectTwoItems": "Vă rugăm să selectați cel puțin două elemente.", - "PleaseRestartServerName": "Vă rugăm să reporniți Jellyfin Server - {0}.", + "PleaseRestartServerName": "Vă rugăm să reporniți Jellyfin pe {0}.", "PleaseEnterNameOrId": "Vă rugăm să introduceți un nume sau un ID extern.", "PleaseConfirmPluginInstallation": "Faceți clic pe OK pentru a confirma că ați citit mai sus și doriți să continuați cu instalarea pluginului.", "PleaseAddAtLeastOneFolder": "Vă rugăm să adăugați cel puțin un dosar la această bibliotecă făcând clic pe butonul Adăugare.", @@ -1263,7 +1250,7 @@ "PerfectMatch": "Potrivire perfectă", "People": "Oameni", "PasswordSaved": "Parolă salvată.", - "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei", + "PasswordResetProviderHelp": "Alegeți un furnizor de resetare a parolei pentru a fi utilizat atunci când acest utilizator solicită o resetare a parolei.", "HeaderResetPassword": "Resetează parola", "PasswordResetConfirmation": "Sigur doriți să resetați parola?", "PasswordResetComplete": "Parola a fost resetată.", @@ -1283,7 +1270,7 @@ "OptionThumb": "Miniatură", "OptionSubstring": "Subșir", "OptionSpecialEpisode": "Speciale", - "OptionSaveMetadataAsHiddenHelp": "Modificarea acestui lucru se va aplica la noi metadate salvate de acum înainte. Fișierele de metadate existente vor fi actualizate data viitoare când sunt salvate de Jellyfin Server.", + "OptionSaveMetadataAsHiddenHelp": "Modificarea acestui lucru se va aplica la noi metadate salvate de acum înainte. Fișierele de metadate existente vor fi actualizate data viitoare când sunt salvate de server.", "OptionSaveMetadataAsHidden": "Salvați metadata și imaginile ca fișiere ascunse", "OptionResElement": "element res", "OptionRequirePerfectSubtitleMatchHelp": "Cerând o potrivire perfectă va filtra subtitrările pentru a le include doar pe cele care au fost testate și verificate cu fișierul dvs. video exact. Debifând acest lucru, va crește probabilitatea descărcării subtitrărilor, dar va crește șansele de a avea decalaje sau de a greși textul de subtitrare.", @@ -1300,9 +1287,9 @@ "OptionProfileAudio": "Audio", "OptionPosterCard": "Carte de afiș", "OptionPoster": "Afiș", - "OptionPlainVideoItemsHelp": "Dacă este activat, toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", + "OptionPlainVideoItemsHelp": "Toate videoclipurile sunt reprezentate în DIDL ca „object.item.videoItem” în loc de un tip mai specific, cum ar fi „object.item.videoItem.movie”.", "OptionPlainVideoItems": "Afișați toate videoclipurile ca elemente video simple", - "OptionPlainStorageFoldersHelp": "Dacă este activat, toate folderele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", + "OptionPlainStorageFoldersHelp": "Toate dosarele sunt reprezentate în DIDL ca „object.container.storageFolder” în loc de un tip mai specific, cum ar fi „object.container.person.musicArtist”.", "OptionPlainStorageFolders": "Afișați toate dosarele ca dosare simple de stocare", "OptionOnInterval": "La un interval", "OptionOnAppStartup": "La pornirea aplicației", @@ -1315,7 +1302,7 @@ "OptionList": "Listă", "OptionIsSD": "SD", "OptionIsHD": "HD", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Dacă sunt activate, aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Aceste solicitări vor fi respectate, dar vor ignora antetul intervalului de octeți.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorați solicitările pentru transcodarea intervalului de octeți", "OptionHomeVideos": "Fotografii", "OptionHlsSegmentedSubtitles": "Subtitrare segmentată HLS", @@ -1332,7 +1319,7 @@ "OptionEnableExternalContentInSuggestions": "Activați conținut extern în sugestii", "OptionEmbedSubtitles": "Inclus în container", "OptionDownloadLogoImage": "Siglă", - "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații din Jellyfin. Activați această opțiune pentru a descărca în prealabil toate imaginile, pe măsură ce fișierele media sunt importate. Acest lucru poate provoca scanări ale bibliotecii semnificativ mai lungi.", + "OptionDownloadImagesInAdvanceHelp": "În mod implicit, majoritatea imaginilor sunt descărcate numai la cererea unei aplicații Jellyfin. Activați această opțiune pentru a descărca în avans toate imaginile, pe măsură ce fișiere media noi sunt importate. Acest lucru poate duce la mărirea semnificativă a timpilor de scanare a bibliotecii.", "OptionDownloadImagesInAdvance": "Descărcați imaginile în avans", "OptionDownloadDiscImage": "Disc", "OptionDisplayFolderViewHelp": "Afișați dosarele alături de celelalte biblioteci media. Acest lucru poate fi util dacă doriți să aveți o vizualizare direct în dosar.", @@ -1405,15 +1392,12 @@ "TabResponses": "Răspunsuri", "TabPlugins": "Pluginuri", "TabPlaylists": "Liste redare", - "TabPlayback": "Redare", "TabParentalControl": "Control Parental", "TabNfoSettings": "Setări NFO", "TabNetworking": "Rețele", - "TabMetadata": "Metadată", "TabLogs": "Jurnal", "TabLiveTV": "TV în Direct", "TabInfo": "Info", - "TabDisplay": "Afișare", "TabDirectPlay": "Redare directă", "TabDevices": "Dispozitive", "TabDashboard": "Tablou de bord", @@ -1457,7 +1441,7 @@ "LastSeen": "Văzut ultima dată {0}", "PersonRole": "ca {0}", "ListPaging": "{0}-{1} din {2}", - "WriteAccessRequired": "Jellyfin Server necesită acces de scriere la acest folder. Vă rugăm să vă asigurați accesul la scriere și încercați din nou.", + "WriteAccessRequired": "Jellyfin necesită acces de scriere la acest folder. Vă rugăm să vă asigurați accesul la scriere și încercați din nou.", "PathNotFound": "Calea nu a fost găsită. Vă rugăm să vă asigurați de validitatea căii și încercați din nou.", "YadifBob": "Gigi Bob", "Yadif": "YADIF", @@ -1467,7 +1451,6 @@ "LabelLibraryPageSizeHelp": "Setează cantitatea de elemente de afișat pe o pagină a bibliotecii. Setați la 0 pentru a dezactiva paginarea.", "LabelLibraryPageSize": "Mărimea paginii Bibliotecă:", "ButtonTogglePlaylist": "Listă de redare", - "ButtonToggleContextMenu": "Mai mult", "Filter": "Filtru", "New": "Nou", "HeaderFavoritePlaylists": "Listă Favorită", @@ -1476,7 +1459,7 @@ "LabelRequireHttps": "Trebuie HTTPS", "LabelStable": "Stabilă", "LabelChromecastVersion": "Versiunea de Chromecast", - "LabelEnableHttpsHelp": "Activează serverul să asculte pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", + "LabelEnableHttpsHelp": "Ascultă pe portul HTTPS configurat. Un certificat valid trebuie de asemenea configurat pentru ca să funcţioneze.", "LabelEnableHttps": "Activați HTTPS", "HeaderServerAddressSettings": "Setările adresei serverului", "HeaderRemoteAccessSettings": "Setări pentru aces remote", @@ -1539,5 +1522,13 @@ "LabelRepositoryNameHelp": "Un nume personalizat pentru a distinge acest repertoriu de altele adăugate la serverul dvs.", "ClearQueue": "Golește lista de redare", "StopPlayback": "Oprește redarea", - "ViewAlbumArtist": "Vezi artistul albumului" + "ViewAlbumArtist": "Vezi artistul albumului", + "NextTrack": "Sari la următorul", + "LabelUnstable": "Instabil", + "Preview": "Previzualizare", + "SubtitleVerticalPositionHelp": "Numărul de linie unde apare textul. Numerele pozitive indică de sus în jos. Numerele negative indică de jos în sus.", + "LabelSubtitleVerticalPosition": "Poziție verticală:", + "PreviousTrack": "Sari anterior", + "MessageGetInstalledPluginsError": "A apărut o eroare la obținerea listei de plugin-uri instalate în prezent.", + "MessagePluginInstallError": "A apărut o eroare la instalarea pluginului." } diff --git a/src/strings/ru.json b/src/strings/ru.json index 50ab735286..a35a15642d 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -53,7 +53,6 @@ "Browse": "Навигация", "MessageBrowsePluginCatalog": "Просмотрите каталог плагинов, чтобы ознакомиться с имеющимися плагинами.", "BurnSubtitlesHelp": "Определяется, должен ли сервер внедрять субтитры при перекодировании. Избежание этого значительно улучшит производительность. Выберите «Авто» для записи основанных на графике форматов (VOBSUB, PGS, SUB, IDX и др.) и некоторых субтитров ASS или SSA.", - "ButtonAdd": "Добавить", "ButtonAddMediaLibrary": "Добавить медиатеку", "ButtonAddScheduledTaskTrigger": "Добавить триггер", "ButtonAddServer": "Добавить сервер", @@ -79,7 +78,6 @@ "ButtonFullscreen": "Полный экран", "ButtonGotIt": "Понятно", "ButtonGuide": "Телегид", - "ButtonHelp": "Справка", "ButtonHome": "Главное", "ButtonInfo": "Инфо", "ButtonLibraryAccess": "Доступ к медиатеке", @@ -101,15 +99,12 @@ "ButtonRefreshGuideData": "Обновить данные телегида", "ButtonRemove": "Изъять", "ButtonRename": "Переименовать", - "ButtonRepeat": "Повторить", "ButtonResetEasyPassword": "Сбросить простой PIN-код", "ButtonResetPassword": "Сбросить пароль", "ButtonRestart": "Перезапустить", "ButtonResume": "Возобновить", "ButtonRevoke": "Отозвать", - "ButtonSave": "Сохранить", "ButtonScanAllLibraries": "Сканировать все медиатеки", - "ButtonSearch": "Поиск", "ButtonSelectDirectory": "Выбрать каталог", "ButtonSelectServer": "Выбрать сервер", "ButtonSelectView": "Выбрать представление", @@ -124,7 +119,6 @@ "ButtonStop": "Остановить", "ButtonSubmit": "Подтвердить", "ButtonSubtitles": "Субтитры", - "ButtonToggleContextMenu": "Ещё", "ButtonTogglePlaylist": "Плей-лист", "ButtonTrailer": "Трейлер", "ButtonUninstall": "Удалить", @@ -328,7 +322,6 @@ "HeaderDevices": "Устройства", "HeaderDirectPlayProfile": "Профиль прямого воспроизведения", "HeaderDirectPlayProfileHelp": "Добавьте профили прямого воспроизведения, чтобы указать, какие форматы могут обрабатываться устройством изначально.", - "HeaderDisplay": "Отображение", "HeaderDownloadSync": "Загрузка и синхро", "HeaderEasyPinCode": "Простой PIN-код", "HeaderEditImages": "Править изображения", @@ -338,10 +331,8 @@ "HeaderError": "Ошибка", "HeaderExternalIds": "Внешние идентификаторы:", "HeaderFeatureAccess": "Доступ к компонентам", - "HeaderFeatures": "Материалы", "HeaderFetchImages": "Отборка изображений:", "HeaderFetcherSettings": "Параметры отборщика", - "HeaderFilters": "Фильтры", "HeaderForKids": "Детям", "HeaderForgotPassword": "Забыли пароль", "HeaderFrequentlyPlayed": "Воспроизведённые часто", @@ -454,7 +445,6 @@ "HeaderSubtitleProfiles": "Профили субтитров", "HeaderSubtitleProfilesHelp": "В профилях субтитров описываются форматы субтитров поддерживаемых устройством.", "HeaderSystemDlnaProfiles": "Системные профили", - "HeaderTags": "Теги", "HeaderTaskTriggers": "Триггеры задачи", "HeaderThisUserIsCurrentlyDisabled": "В настоящее время этот пользователь заблокирован", "HeaderTracks": "Дор-ки", @@ -510,7 +500,6 @@ "LabelAlbumArtMaxWidthHelp": "Максимальное разрешение альбомных обложек представляемых с помощью upnp:albumArtURI.", "LabelAlbumArtPN": "PN альбомной обложки:", "LabelAlbumArtists": "Исполнители альбома:", - "LabelAll": "Все", "LabelAllowHWTranscoding": "Разрешить аппаратную перекодировку", "LabelAllowedRemoteAddresses": "Фильтр внешних IP-адресов:", "LabelAllowedRemoteAddressesMode": "Режим фильтра внешних IP-адресов:", @@ -1231,7 +1220,6 @@ "TabAdvanced": "Расширенное", "TabAlbumArtists": "Исполнители альбома", "TabAlbums": "Альбомы", - "TabArtists": "Исполнители", "TabCatalog": "Каталог", "TabChannels": "Каналы", "TabCodecs": "Кодеки", @@ -1240,7 +1228,6 @@ "TabDashboard": "Панель", "TabDevices": "Устройства", "TabDirectPlay": "Прямое воспроизведение", - "TabDisplay": "Отображение", "TabEpisodes": "Эпизоды", "TabFavorites": "Избранное", "TabGenres": "Жанры", @@ -1249,7 +1236,6 @@ "TabLatest": "Новейшее", "TabLiveTV": "Эфир", "TabLogs": "Журналы", - "TabMetadata": "Метаданные", "TabMovies": "Фильмы", "TabMusic": "Музыка", "TabMusicVideos": "Муз. видео", @@ -1260,7 +1246,6 @@ "TabOther": "Другое", "TabParentalControl": "Управление содержанием", "TabPassword": "Пароль", - "TabPlayback": "Воспроизведение", "TabPlaylists": "Плей-листы", "TabPlugins": "Плагины", "TabProfile": "Профиль", @@ -1275,9 +1260,7 @@ "TabShows": "Сериалы", "TabSongs": "Треки", "TabStreaming": "Трансляция", - "TabSuggestions": "Рекомендации", "TabTrailers": "Трейлеры", - "TabTranscoding": "Перекодировка", "TabUpcoming": "Ожидаемое", "TabUsers": "Пользователи", "Tags": "Теги", diff --git a/src/strings/sk.json b/src/strings/sk.json index 44c5751175..fe95801700 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -26,7 +26,6 @@ "BirthPlaceValue": "Miesto narodenia: {0}", "BookLibraryHelp": "Audioknihy a učebnice sú podporované. Prečítajte si {0} pravidlá pre názvy kníh v Jellyfine {1}.", "Books": "Knihy", - "ButtonAdd": "Pridať", "ButtonAddMediaLibrary": "Pridať knižnicu médií", "ButtonAddScheduledTaskTrigger": "Pridať spúšťač", "ButtonAddServer": "Pridať server", @@ -50,7 +49,6 @@ "ButtonForgotPassword": "Zabudnuté heslo", "ButtonFullscreen": "Celá obrazovka", "ButtonGotIt": "Rozumiem", - "ButtonHelp": "Pomoc", "ButtonHome": "Domov", "ButtonManualLogin": "Manuálne prihlásenie", "ButtonMore": "Viac", @@ -68,14 +66,11 @@ "ButtonRefreshGuideData": "Obnoviť údaje sprievodcu", "ButtonRemove": "Odstrániť", "ButtonRename": "Premenovať", - "ButtonRepeat": "Opakovať", "ButtonResetEasyPassword": "Obnoviť jednoduchý PIN kód", "ButtonResetPassword": "Obnoviť heslo", "ButtonRestart": "Reštartovať", "ButtonResume": "Pokračovať", - "ButtonSave": "Uložiť", "ButtonScanAllLibraries": "Prehľadať všetky knižnice", - "ButtonSearch": "Hľadať", "ButtonSelectDirectory": "Vybrať priečinok", "ButtonSelectServer": "Vybrať server", "ButtonSend": "Odoslať", @@ -204,7 +199,6 @@ "HeaderEpisodes": "Epizódy", "HeaderError": "Chyba", "HeaderFetchImages": "Načítať obrázky:", - "HeaderFilters": "Filtre", "HeaderForKids": "Pre deti", "HeaderForgotPassword": "Zabudnuté heslo", "HeaderFrequentlyPlayed": "Často hrané", @@ -300,7 +294,7 @@ "Horizontal": "Horizontálne", "Identify": "Identifikovať", "Images": "Obrázky", - "ImportMissingEpisodesHelp": "Ak je možnosť povolená, informácie o chýbajúcich epizódach budú importované do Vašej Jellyfin databázy a budú zobrazené v sériách a seriáloch. Toto môže spôsobiť podstatne dlhšie skenovania knižníc.", + "ImportMissingEpisodesHelp": "Informácie o chýbajúcich epizódach budú importované do Vašej databázy a budú zobrazené v sériách a seriáloch. Toto môže spôsobiť podstatne dlhšie skenovania knižníc.", "InstallingPackage": "Inštalujem {0} (verzia{1})", "ItemCount": "{0} položiek", "Items": "Položky", @@ -308,13 +302,12 @@ "Label3DFormat": "3D formát:", "LabelAccessDay": "Deň v týždni:", "LabelAirTime": "Čas vysielania:", - "LabelAll": "Všetky", "LabelAllowHWTranscoding": "Povoliť hardvérové transkódovanie", "LabelAllowedRemoteAddresses": "Filter vzdialených IP adries:", "LabelAppName": "Názov aplikácie", "LabelAppNameExample": "Príklad: Sickbeard, Sonarr", "LabelArtists": "Umelci:", - "LabelArtistsHelp": "Oddeľte pomocou ;", + "LabelArtistsHelp": "Viacej umelcov oddeľte pomocou bodkočiarky.", "LabelAudioLanguagePreference": "Uprednostňovaný jazyk zvuku:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Automaticky obnoviť metadáta z internetu:", "LabelBirthDate": "Dátum narodenia:", @@ -367,9 +360,9 @@ "LabelFont": "Písmo:", "LabelForgotPasswordUsernameHelp": "Zadajte svoje používateľské meno, ak si ho pamätáte.", "LabelFormat": "Formát:", - "LabelServerNameHelp": "Tento názov bude použitý na identifikáciu servera. Ak ostane prázdny, bude použitý názov počítača.", + "LabelServerNameHelp": "Tento názov bude použitý na identifikáciu servera. Ak ostane prázdny, bude použitý názov hostiteľa serveru.", "LabelGroupMoviesIntoCollections": "Zoskupiť filmy do kolekcií", - "LabelGroupMoviesIntoCollectionsHelp": "Pri zobrazení zoznamu filmov budú filmy patriace do kolekcie zobrazené ako jedna zoskupená položka.", + "LabelGroupMoviesIntoCollectionsHelp": "Pri zobrazení zoznamu filmov budú filmy v kolekcií zobrazené ako jedna položka.", "LabelHardwareAccelerationType": "Hardvérová akcelerácia:", "LabelHardwareAccelerationTypeHelp": "Hardvérová akcelerácia vyžaduje dodatočnú konfiguráciu.", "LabelHomeScreenSectionValue": "Sekcia domácej obrazovky {0}:", @@ -401,7 +394,7 @@ "LabelMetadata": "Metadáta:", "LabelMetadataDownloadLanguage": "Preferovaný jazyk:", "LabelMetadataPath": "Umiestnenie metadát:", - "LabelMetadataSaversHelp": "Vyberte formát súboru, do ktorého chcete ukladať vaše metadáta.", + "LabelMetadataSaversHelp": "Vyberte formát súboru, ktorý chcete použiť pre ukladanie metadát.", "LabelMinResumeDurationHelp": "Najkratšia dĺžka videa v sekundách, ktorá uloží rozpozeranú polohu a dovolí sa k nej vrátiť.", "LabelMinResumePercentageHelp": "Tituly budú považované za neprehrané ak budú zastavené pred týmto časom.", "LabelModelDescription": "Popis modelu", @@ -409,7 +402,7 @@ "LabelModelNumber": "Číslo modelu", "LabelModelUrl": "Model URL", "LabelMovieCategories": "Kategórie filmov:", - "LabelMovieRecordingPath": "Umiestnenie filmových nahrávok (voliteľné):", + "LabelMovieRecordingPath": "Umiestnenie pre nahrávanie filmov:", "LabelName": "Meno:", "LabelNewName": "Nové meno:", "LabelNewPassword": "Nové heslo:", @@ -418,7 +411,7 @@ "LabelNext": "Ďalej", "LabelNotificationEnabled": "Povoliť toto hlásenie", "LabelNumber": "Číslo:", - "LabelOptionalNetworkPath": "(Voliteľné) Zdieľaný sieťový priečinok:", + "LabelOptionalNetworkPath": "Zdieľaný sieťový priečinok:", "LabelOriginalAspectRatio": "Pôvodný pomer strán:", "LabelOriginalTitle": "Pôvodný názov:", "LabelOverview": "Prehľad:", @@ -445,14 +438,14 @@ "LabelRecordingPath": "Predvolené umiestnenie nahrávok:", "LabelRecordingPathHelp": "Uveďte predvolené umiestnenie pre ukladanie nahrávok. Ak je ponechané prázdne, použije sa priečinok s programovými dátami servera.", "LabelReleaseDate": "Dátum vydania:", - "LabelRuntimeMinutes": "Dĺžka (minúty):", + "LabelRuntimeMinutes": "Dĺžka:", "LabelSaveLocalMetadata": "Uložiť obaly a metadáta do priečinka s médiami", "LabelScreensaver": "Šetrič obrazokvy:", "LabelSeasonNumber": "Číslo série:", "LabelSelectUsers": "Zvoľte užívateľov:", "LabelSelectVersionToInstall": "Vyberte verziu, ktorú chcete nainštalovať:", "LabelSerialNumber": "Sériové číslo", - "LabelSeriesRecordingPath": "Umiestnenie seriálových nahrávok (voliteľné):", + "LabelSeriesRecordingPath": "Umiestnenie pre nahrávanie seriálov:", "LabelServerHostHelp": "192.168.1.100:8096 alebo https://mojserver.sk", "LabelSkipBackLength": "Dĺžka skoku dozadu:", "LabelSkipForwardLength": "Dĺžka skoku dopredu:", @@ -609,7 +602,7 @@ "OptionDatePlayed": "Dátum prehrania", "OptionDescending": "Zostupne", "OptionDisableUser": "Zakázať tohto používateľa", - "OptionDisableUserHelp": "Ak možnosť nie je povolená, server nepovolí žiadne pripojenia od tohto používateľa. Aktívne pripojenia budú ihneď ukončené.", + "OptionDisableUserHelp": "Server nepovolí žiadne pripojenia od tohto používateľa. Aktívne pripojenia budú ihneď ukončené.", "OptionDislikes": "Nepáči sa", "OptionDownloadArtImage": "Obal", "OptionDownloadBackImage": "Späť", @@ -761,7 +754,6 @@ "TabAccess": "Prístup", "TabAdvanced": "Pokročilé", "TabAlbums": "Albumy", - "TabArtists": "Umelci", "TabCatalog": "Katalóg", "TabChannels": "Kanály", "TabCodecs": "Kodeky", @@ -775,7 +767,6 @@ "TabGuide": "Sprievodca", "TabLatest": "Najnovšie", "TabLiveTV": "Živá TV", - "TabMetadata": "Metadáta", "TabMovies": "Filmy", "TabMusic": "Hudba", "TabMusicVideos": "Hudobné videá", @@ -797,9 +788,7 @@ "TabSettings": "Nastavenia", "TabShows": "Seriály", "TabSongs": "Skladby", - "TabSuggestions": "Návrhy", "TabTrailers": "Trailery", - "TabTranscoding": "Transkódovanie", "TabUpcoming": "Nadchádzajúce", "TabUsers": "Užívatelia", "TellUsAboutYourself": "Povedzte nám niečo o sebe", @@ -916,7 +905,7 @@ "ConfirmDeleteItems": "Zmazaním týchto položiek odstránite súbory zo súborového systému aj z knižnice médií. Ste si istý/á, že chcete pokračovať?", "Continuing": "Pokračujúci", "Default": "Predvolené", - "DirectStreamHelp2": "Priame streamovanie súboru používa veľmi málo procesorového výkonu bez straty kvality videa.", + "DirectStreamHelp2": "Priame streamovanie vyžaduje veľmi málo výkonu takmer bez straty kvality videa.", "DirectStreaming": "Priame streamovanie", "DisplayMissingEpisodesWithinSeasonsHelp": "Toto musí byť povolené pre TV knižnice v nastavení servera.", "DisplayModeHelp": "Vyberte štýl layoutu, ktorý chcete pre rozhranie.", @@ -931,7 +920,6 @@ "HeaderDownloadSync": "Sťahovanie a synchronizácia", "HeaderExternalIds": "Externé ID:", "HeaderFeatureAccess": "Prístup k funkciám", - "HeaderFeatures": "Funkcie", "HeaderHome": "Domov", "HeaderLoginFailure": "Prihlásenie zlyhalo", "HeaderMediaFolders": "Priečinky médií", @@ -941,7 +929,6 @@ "HeaderSpecialEpisodeInfo": "Informácie o špeciálnej epizóde", "HeaderSpecialFeatures": "Bonusové materiály", "HeaderSubtitleDownloads": "Sťahovanie titulkov", - "HeaderTags": "Tagy", "HeaderVideoType": "Typ videa", "HeaderVideoTypes": "Typy videí", "LabelAirsBeforeSeason": "Vysielané pred sériou:", @@ -985,7 +972,6 @@ "SortName": "Zoradiť podľa názvu", "TabDirectPlay": "Priame prehrávanie", "TabLogs": "Záznamy", - "TabPlayback": "Prehrávanie", "TabPlaylists": "Playlisty", "TabServer": "Server", "TabStreaming": "Streamovanie", @@ -998,7 +984,7 @@ "Absolute": "Absolútne", "LabelDidlMode": "DIDL režim:", "LabelDateTimeLocale": "Lokálne nastavenia dátumu:", - "LabelBlastMessageInterval": "Doba zobrazenie správy (sekundy)", + "LabelBlastMessageInterval": "Doba zobrazenia správy", "LabelAlbumArtMaxWidth": "Maximálna šírka obrázku albumu:", "LabelAlbumArtMaxHeight": "Maximálna výška obrázku albumu:", "LabelAirDays": "Vysielané:", @@ -1067,7 +1053,7 @@ "LabelAllowedRemoteAddressesMode": "Režim filtrácie vzdialenej IP adresy:", "LabelAlbumArtists": "Album umelca:", "InstantMix": "Okamžitý mix", - "ImportFavoriteChannelsHelp": "Pokiaľ je možnosť povolená, tak len kanály označené ako obľúbené budú importované na zariadenie tuneru.", + "ImportFavoriteChannelsHelp": "Len kanály označené ako obľúbené budú importované na zariadenie tuneru.", "HttpsRequiresCert": "Pre povolenie zabezpečeného pripojenia budete musieť dodať dôveryhodný SSL certifikát, ako napríklad Let's Encrypt. Prosím, buď dodajte certifikát alebo zakážte zabezpečené pripojenie.", "HeaderXmlDocumentAttributes": "Atribúty XML dokumentu", "HeaderXmlDocumentAttribute": "Atribúty XML dokumentu", @@ -1076,7 +1062,7 @@ "HeaderTranscodingProfileHelp": "Pridať transkódovacie profily pre určenie, ktoré formáty by mali byť použité, keď je transkódovanie vyžadované.", "HeaderSubtitleProfilesHelp": "Profily titulkov popisujú formáty titulkov, ktoré dané zariadenie podporuje.", "HeaderSeriesStatus": "Stav seriálu", - "HeaderSelectTranscodingPathHelp": "Prechádzať alebo zadať cestu, kde by ste chceli uložiť dočasné transkódované súbory. Priečinok musí mať oprávnenie na zapisovanie.", + "HeaderSelectTranscodingPathHelp": "Prechádzať alebo zadať cestu pre súbory transkódovania. Priečinok musí mať oprávnenie na zapisovanie.", "HeaderSelectTranscodingPath": "Vyberte cestu pre dočasné transkódované súbory", "HeaderSelectServerCachePathHelp": "Prechádzať alebo zadať cestu, kde by ste chceli uložiť cache súbory. Priečinok musí mať oprávnenie na zapisovanie.", "HeaderSelectServerCachePath": "Vyberte cestu pre Server Cache", @@ -1086,13 +1072,13 @@ "HeaderResponseProfile": "Profil odozvy", "HeaderRemoveMediaLocation": "Odobrať cestu medií", "HeaderRecordingPostProcessing": "Spracovanie nahratých nahrávok", - "HeaderProfileServerSettingsHelp": "Tieto hodnoty určujú, ako sa bude Jellyfin Server prezentovať v zariadeniach.", + "HeaderProfileServerSettingsHelp": "Tieto hodnoty určujú, ako sa bude server prezentovať klientom.", "HeaderPluginInstallation": "Inštalácia zásuvných modulov", "HeaderPlayback": "Prehrávanie medií", "HeaderPlayOn": "Prehrať na", "HeaderOnNow": "Práve teraz", "HeaderLiveTvTunerSetup": "Nastavenie TV tuneru pre živé vysielanie", - "HeaderKodiMetadataHelp": "Pokiaľ chcete povoliť alebo zakázať NFO metadáta, upravte knižnicu v nastavení Jellyfin knižníc v sekcii ukladania metadát.", + "HeaderKodiMetadataHelp": "Pokiaľ chcete povoliť alebo zakázať NFO metadáta, upravte knižnicu v sekcii ukladania metadát.", "HeaderKeepSeries": "Zachovať seriál", "HeaderKeepRecording": "Zachovať nahrávanie", "HeaderImageOptions": "Možnosti obrázkov", @@ -1102,7 +1088,6 @@ "HeaderFavoritePeople": "Obľúbené osoby", "HeaderFavoriteBooks": "Obľúbené knihy", "HeaderEnabledFieldsHelp": "Zrušte zaškrtnutie, aby ste zabránili zmenám dát.", - "HeaderDisplay": "Zobrazenie", "HeaderDirectPlayProfile": "Profil Priameho prehrávania", "HeaderDeveloperInfo": "Informácie pre vývojára", "HeaderDeleteTaskTrigger": "Vymazať spúšťač úlohy", @@ -1118,7 +1103,7 @@ "HeaderBlockItemsWithNoRating": "Blokované položky so žiadnymi alebo nerozpoznanými informáciami o hodnotení:", "HeaderAppearsOn": "Objaví sa", "HeaderApp": "Appka", - "HeaderApiKeysHelp": "Externé aplikácie musia mať vlastný API kľúč, aby mohli komunikovať s Jellyfin Serverom. Kľúče sú vydávané pomocou prihlásenia sa cez Jellyfin účet alebo manuálnym priradením kľúča aplikácií.", + "HeaderApiKeysHelp": "Externé aplikácie musia mať vlastný API kľúč, aby mohli komunikovať so serverom. Kľúče sú vydávané pomocou prihlásenia cez bežný účet alebo manuálnym priradením kľúča aplikácií.", "HeaderAdditionalParts": "Dodatočné časti", "HardwareAccelerationWarning": "Povolenie hardvérovej akcelerácie môže spôsobiť nestabilitu v niektorých podmienkach. Uistite sa, že váš operačný systém a grafické ovládače sú plne aktualizované. Pokiaľ máte po zapnutí problémy s prehrávaním videa, budete musieť zmeniť nastavenie späť na Žiadne.", "EncoderPresetHelp": "Vyberte hodnotu faster pre zlepšenie výkonu alebo hodnotu slower pre zlepšenie kvality.", @@ -1142,7 +1127,7 @@ "EnableExternalVideoPlayersHelp": "Ponuka externého prehrávača sa zobrazí pri spustení prehrávania videa.", "EnableBackdropsHelp": "Zobraziť pozadia na pozadí pre niektoré stránky pri prechádzaní knižnice.", "DisplayInOtherHomeScreenSections": "Zobrazenie v sekciách domovskej obrazovky, ako sú najnovšie médiá a pokračovať v pozeraní", - "DirectStreamHelp1": "Médium je kompatibilné zo zariadením nezávisle na rozlíšení alebo type média (H.264, AC3, atď.), je však v nekompatibilnom kontajneri (mkv, avi, wmv, atď.). Video bude za behu prebalené do kompatibilného kontajnera ešte pred streamovaním do zariadenia.", + "DirectStreamHelp1": "Médium je kompatibilné zo zariadením nezávisle na rozlíšení alebo type média (H.264, AC3, atď.), je však v nekompatibilnom kontajneri (mkv, avi, wmv, atď.). Video bude za behu prebalené do kompatibilného kontajnera ešte pred odoslaním do zariadenia.", "Depressed": "Stlačený", "DefaultSubtitlesHelp": "Titulky sú načítané v závislosti od predvolených a vynútených nastavení v zabudovaných metadátach. Jazykové predvoľby sú zobrané do úvahy až vtedy, keď je k dispozícií viacero možností.", "DefaultMetadataLangaugeDescription": "Toto sú vaše predvolené hodnoty ktoré môžu byť prispôsobené na základe jednotlivých knižníc.", @@ -1184,7 +1169,6 @@ "Thumb": "Náhľad", "TheseSettingsAffectSubtitlesOnThisDevice": "Toto nastavenie ovplyvní titulky na tomto zariadení", "TabNetworking": "Sieť", - "TabDisplay": "Zobrazenie", "TabAlbumArtists": "Umelec albumu", "SystemDlnaProfilesHelp": "Systémové profily sú len na čítanie. Zmeny systémových profilov budú uložené do nového vlastné profilu.", "SubtitleOffset": "Odchýlka titulkov", @@ -1199,7 +1183,7 @@ "SeriesCancelled": "Seriál zrušený.", "SelectAdminUsername": "Prosím, vyberte si používateľské meno pre účet administrátora.", "RefreshQueued": "Obnovenie zaradené do fronty.", - "RefreshDialogHelp": "Metadáta sa obnovujú na základe nastavení a internetových služieb, ktoré sú povolené v dashboarde Jellyfin Serveru.", + "RefreshDialogHelp": "Metadáta sa obnovujú na základe nastavení a internetových služieb, ktoré sú povolené v dashboarde.", "MessageChangeRecordingPath": "Zmenou priečinku pre nahrávanie sa existujúce nahrávky automaticky nepresunú zo starej lokácie na na novú. Budete ich musieť presunúť ručne, pokiaľ budete chcieť.", "RecordSeries": "Nahrať sériu", "Raised": "Vystupujúce", @@ -1221,18 +1205,18 @@ "OptionRandom": "Náhodne", "OptionProfileVideoAudio": "Video Zvuk", "OptionPosterCard": "Plagátová karta", - "OptionPlainVideoItemsHelp": "Pokiaľ je povolené, všetky videá sú reprezentované v DIDL ako \"object.item.videoItem\" namiesto viac špecifického typu, ako napríklad \"object.item.videoItem.movie\".", - "OptionPlainStorageFoldersHelp": "Pokiaľ je povolené, všetky priečinky sú reprezentované v DIDL ako \"object.container.storageFolder\" namiesto viac špecifického typu, ako napríklad \"object.container.person.musicArtist\".", + "OptionPlainVideoItemsHelp": "Všetky videá sú reprezentované v DIDL ako \"object.item.videoItem\" namiesto viac špecifického typu, ako napríklad \"object.item.videoItem.movie\".", + "OptionPlainStorageFoldersHelp": "Všetky priečinky sú reprezentované v DIDL ako \"object.container.storageFolder\" namiesto viac špecifického typu, ako napríklad \"object.container.person.musicArtist\".", "OptionPlainStorageFolders": "Zobraziť všetky priečinky ako jednoduché priečinky pre ukladanie", "OptionOnInterval": "V intervale", "OptionLoginAttemptsBeforeLockoutHelp": "Hodnota 0 znamená zdedenie východzej hodnoty troch pokusov pre bežného používateľa a päť pre administrátora. Nastavením na -1 sa táto funkcia zakáže.", "OptionLoginAttemptsBeforeLockout": "Určuje, koľko chybných prihlásení môže byť urobených pred uzamknutím.", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Pokiaľ je povolené, budú tieto požiadavky aj naďalej plnené, avšak hlavičky bajtových rozsahov budú ignorované.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Tieto požiadavky budú aj naďalej plnené, avšak hlavičky bajtových rozsahov budú ignorované.", "OptionIgnoreTranscodeByteRangeRequests": "Ignorovať požiadavky na transkódovanie bajtového rozsahu", "OptionHlsSegmentedSubtitles": "HLS segmentované titulky", "OptionExternallyDownloaded": "Externé sťahovanie", "OptionEnableExternalContentInSuggestionsHelp": "Povoliť zahrnutie internetových trailerov a živých TV programov do navrhovaného obsahu.", - "OptionDownloadImagesInAdvanceHelp": "Vo východzom stave sa väčšina obrázkov sťahuje až po vyžiadaní Jellyfin aplikáciou. Povolením tejto možnosti sa budú všetky obrázky sťahovať popredu, keď sa budú importovať nové médiá. Toto môže spôsobiť výrazne dlhšie skenovanie knižnice.", + "OptionDownloadImagesInAdvanceHelp": "Vo východzom stave sa väčšina obrázkov sťahuje až po vyžiadaní klientom. Povolením tejto možnosti sa budú všetky obrázky sťahovať popredu, keď sa budú importovať nové médiá. Toto môže spôsobiť výrazne dlhšie skenovanie knižnice.", "OptionDownloadBoxImage": "Krabica", "OptionDownloadBannerImage": "Banner", "OptionDisplayFolderViewHelp": "Zobraziť priečinky popri ostatných médiách v knižnici. Toto môže byť užitočné, pokiaľ chcete vidieť jednoduché zobrazenie priečinku.", @@ -1240,10 +1224,10 @@ "OptionBlockTvShows": "Seriál", "OptionBlockLiveTvChannels": "Živé TV kanály", "OptionBanner": "Banner", - "OptionAutomaticallyGroupSeriesHelp": "Pokiaľ je povolené, tak sa série, ktoré sú rozhádzané skrz rôzne priečinky, budú automaticky v tejto knižnici zlučovať do jedného seriálu.", + "OptionAutomaticallyGroupSeriesHelp": "Seriály uložené vo viacerých priečinkoch v tejto knižnici, budú automaticky zlúčené do jedného seriálu.", "OptionAllowVideoPlaybackRemuxing": "Povoliť prehrávanie videa, ktoré vyžaduje konverziu bez opätovného enkódovania", "OptionAllowSyncTranscoding": "Povoliť sťahovanie a synchronizáciu medií, ktoré vyžadujú transkódovanie", - "OptionAllowMediaPlaybackTranscodingHelp": "Obmedzenie prístupu ku transkódovaniu môže spôsobiť zlyhania prehrávania v Jellyfin aplikáciách kvôli nepodporovaným formátom medií.", + "OptionAllowMediaPlaybackTranscodingHelp": "Obmedzenie prístupu ku transkódovaniu môže spôsobiť zlyhania prehrávania v klientoch kvôli nepodporovaným formátom medií.", "MessageNoTrailersFound": "Nainštalujte Trailer kanál pre rozšírenie vášho filmového zážitku pridaním knižnice trailerov z internetu.", "LanNetworksHelp": "Zoznam IP adries alebo IP/netmask záznamov pre všetky siete oddelené čiarkami ktoré budú považované za lokálnu sieť pri vynucovaní obmedzenia šírky pásma. Pokiaľ je toto nastavené, všetky ostatné IP adresy budú považované za vonkajšiu sieť a budú podliehať obmedzeniam šírky pásma vonkajšej siete. Pokiaľ pole ostane prázdne, podsieť serveru bude považovaná za lokálnu sieť.", "LabelUserAgent": "User agent:", @@ -1258,7 +1242,7 @@ "MusicLibraryHelp": "Pozrite si {0}príručku pomenovania hudby{1}.", "MusicAlbum": "Hudobný album", "MoreMediaInfo": "Informácie o médiu", - "MetadataSettingChangeHelp": "Zmena nastavení metadát ovplyvní nový obsah, ktorý bude pridávaný v budúcnosti. Pre obnovenie existujúceho obsahu, otvorte obrazovku s detailom a kliknite na tlačítko obnoviť alebo vykonajte hromadnú obnovu cez metadata manažér.", + "MetadataSettingChangeHelp": "Zmena nastavení metadát ovplyvní nový obsah pridávaný v budúcnosti. Pre obnovenie existujúceho obsahu, otvorte obrazovku s detailom a kliknite na tlačítko obnoviť alebo vykonajte hromadnú obnovu cez metadata manažér.", "MessageUnsetContentHelp": "Obsah bude zobrazený ako jednoduché priečinky. Pre lepšie výsledky použite manažér metadát na nastavenie typu obsahu podpriečinkov.", "MessageUnableToConnectToServer": "Nie sme schopný sa aktuálne pripojiť k vybranému serveru. Prosím, uistite sa že je spustený a skúste to znovu.", "MessageReenableUser": "Pozrite nižšie pre znovu-povolenie", @@ -1291,7 +1275,7 @@ "MediaInfoBitrate": "Dátový tok", "MediaInfoAnamorphic": "Anamorfné", "MapChannels": "Nájdi kanály", - "LabelffmpegPathHelp": "Cesta k aplikačnému súboru ffmpeg alebo k priečinku obsahujúcemu ffmpeg.", + "LabelffmpegPathHelp": "Cesta k súboru aplikácie ffmpeg alebo k priečinku obsahujúcemu ffmpeg.", "LabelXDlnaDocHelp": "Určuje obsah prvku X_DLNADOC v namespace urn:schemas-dlna-org:device-1-0.", "LabelXDlnaDoc": "X-DLNA dokumentácia:", "LabelXDlnaCapHelp": "Určuje obsah prvku X_DLNACAP v namespace urn:schemas-dlna-org:device-1-0.", @@ -1382,7 +1366,7 @@ "LabelLogs": "Logy:", "LabelLoginDisclaimer": "Vyrozumenie prihlásenia:", "LabelLockItemToPreventChanges": "Uzamknúť túto položku pre zabránenie zmien v budúcnosti", - "LabelLocalHttpServerPortNumberHelp": "Číslo TCP portu, na ktoré by sa mal naviazať Jellyfin HTTP server.", + "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP serveru HTTP.", "LabelKodiMetadataUserHelp": "Uložiť dáta o pozeraní do NFO súboru pre využitie ostatnými aplikáciami.", "LabelKodiMetadataUser": "Ukladá dáta používateľa o pozeraní do NFO súboru pre:", "LabelKodiMetadataEnablePathSubstitutionHelp": "Povoľuje nahradenie ciest k obrázkom pomocou nastavenej cesty serveru pre nahradené cesty.", @@ -1395,14 +1379,14 @@ "LabelIdentificationFieldHelp": "Podreťazec citlivý na veľkosť písmen alebo na regulárne výrazy.", "LabelIconMaxWidthHelp": "Maximálne rozlíšenie ikon pomocou prostredníctvom upnp:icon.", "LabelIconMaxHeightHelp": "Maximálne rozlíšenie ikon pomocou prostredníctvom upnp:icon.", - "LabelHttpsPortHelp": "Číslo TCP portu, na ktoré by sa mal naviazať Jellyfin HTTPS server.", + "LabelHttpsPortHelp": "Číslo portu TCP serveru HTTPS.", "LabelHomeNetworkQuality": "Kvalita na domácej sieti:", "LabelEncoderPreset": "Prednastavené H264 enkódovanie:", "LabelH264Crf": "H264 enkódovanie CRF:", "LabelFriendlyName": "Priateľský názov:", "LabelFolder": "Priečinok:", "LabelExtractChaptersDuringLibraryScanHelp": "Generovať obrázky kapitol počas toho, ako sú videá importované v prvotnom prehľadávaní knižnice. Inak sa budú extrahovať počas naplánovanej úlohy generovania obrázkov kapitol, čo dovoľuje rýchlejšie dokončenie bežného prehľadávania knižnice.", - "LabelBaseUrlHelp": "Pridá vlastný reťazec na URL adresu serveru, napr: http://priklad.sk/<vlastnyretazec>", + "LabelBaseUrlHelp": "Pridá vlastný reťazec na URL adresu serveru, napr: http://priklad.sk/<vlastny-retazec>", "LabelBaseUrl": "Východzia URL:", "LabelEveryXMinutes": "Každý:", "LabelEnableSingleImageInDidlLimitHelp": "Niektoré zariadenia nebudú zobrazovať správne pokiaľ je viacero obrázkov uložených v Didl.", @@ -1410,11 +1394,11 @@ "LabelEnableDlnaDebugLoggingHelp": "Vytvára veľké súbory s logami a mal by sa použiť len v prípade potreby odstraňovania problémov.", "LabelEnableDlnaDebugLogging": "Povoliť loggovanie DLNA debugu", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje dobu trvania v sekundách medzi SSDP vyhľadávaniami vykonanými Jellyfinom.", - "LabelEnableDlnaClientDiscoveryInterval": "Interval pre objavenie klienta (sekundy)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval pre objavenie klienta", "LabelEnableAutomaticPortMapHelp": "Automatické namapovanie vejerného portu na lokálny port serveru cez UPnP. Toto nemusí fungovať so všetkými modelmi routerov alebo sieťových konfigurácií. Zmeny sa vykonajú až po reštarte servera.", "LabelEmbedAlbumArtDidlHelp": "Niektoré zariadenia preferujú túto metódu pre získavanie obrázku albumu. Ostatným môže zlyhať prehrávanie pokiaľ je táto možnosť povolená.", "LabelBlastMessageIntervalHelp": "Určuje dobu v sekundách medzi vysielaniami správ o serveri.", - "LabelBindToLocalNetworkAddressHelp": "Voliteľné. Prepísať lokálnu IP adresu viazanú na http server. Pokiaľ zostane prázdna, server sa naviaže na všetky dostupné adresy. Pri zmene tejto hodnoty sa vyžaduje reštart Jellyfin Servera.", + "LabelBindToLocalNetworkAddressHelp": "Prepísať lokálnu IP adresu http serveru. Pokiaľ zostane prázdna, server sa naviaže na všetky dostupné adresy. Pri zmene tejto hodnoty sa vyžaduje reštart Jellyfin Servera.", "LabelAlbumArtPN": "Obrázok albumu PN:", "LabelAlbumArtMaxWidthHelp": "Maximálne rozlíšenie obrázku albumu prostredníctvom upnp:albumArtURI.", "LabelAlbumArtMaxHeightHelp": "Maximálne rozlíšenie obrázku albumu prostredníctvom upnp:albumArtURI.", @@ -1470,14 +1454,13 @@ "New": "Nové", "HeaderFavoritePlaylists": "Obľúbené playlisty", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Viac", "ApiKeysCaption": "Zoznam v súčasnosti povolených API kľúčov", "LabelStable": "Stabilná", "LabelChromecastVersion": "Chromecast verzia", "TabDVR": "DVR", "LabelRequireHttpsHelp": "Pokiaľ je zaškrtnutý, server bude automaticky presmerovávať všetky HTTP požiadavky cez HTTPS. Toto nastavenie nemá žiadny efekt, pokiaľ server nepočúva na HTTPS.", "LabelRequireHttps": "Vyžadovať HTTPS", - "LabelEnableHttpsHelp": "Umožní serveru počúvať na nastavenom HTTPS porte. K správnemu fungovaniu je nutné nakonfigurovať aj platný certifikát.", + "LabelEnableHttpsHelp": "Počúvanie na nastavenom HTTPS porte. K správnemu fungovaniu je nutné nakonfigurovať aj platný certifikát.", "LabelEnableHttps": "Povoliť HTTPS", "HeaderServerAddressSettings": "Nastavenie adresy servera", "HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu", @@ -1539,5 +1522,13 @@ "Writers": "Scenáristi", "ClearQueue": "Vymazať frontu", "StopPlayback": "Zastaviť prehrávanie", - "ViewAlbumArtist": "Zobraziť interpreta albumu" + "ViewAlbumArtist": "Zobraziť interpreta albumu", + "Preview": "Náhľad", + "SubtitleVerticalPositionHelp": "Číslo riadku, na ktorom sa zobrazí text. Kladné čísla znamenajú smer zhora dole. Záporné čísla zdola hore.", + "LabelSubtitleVerticalPosition": "Vertikálne umiestnenie:", + "PreviousTrack": "Predchádzajúca", + "MessageGetInstalledPluginsError": "Pri načítaní zoznamu nainštalovaných zásuvných modulov došlo k chybe.", + "MessagePluginInstallError": "Pri inštalácií zásuvného modulu došlo k chybe.", + "NextTrack": "Ďalšia", + "LabelUnstable": "Nestabilný" } diff --git a/src/strings/sl-si.json b/src/strings/sl-si.json index 423ee7797c..6524841c3e 100644 --- a/src/strings/sl-si.json +++ b/src/strings/sl-si.json @@ -43,7 +43,6 @@ "Settings": "Nastavitve", "TabAccess": "Dostop", "TabAlbumArtists": "Izvajalci Albumov", - "TabArtists": "Izvajalci", "TabCatalog": "Katalog", "TabGenres": "Zvrsti", "TabLatest": "Zadnje", @@ -54,7 +53,6 @@ "TabProfile": "Profil", "TabProfiles": "Profili", "TabShows": "Oddaje", - "TabSuggestions": "Predlogi", "TabUpcoming": "Prihajajoče", "TellUsAboutYourself": "Povej nam nekaj o sebi", "ThisWizardWillGuideYou": "Čarovnik vas bo vodil skozi postopek namestitve. Za začetek, izberite jezik.", @@ -121,7 +119,6 @@ "Browse": "Brskaj", "MessageBrowsePluginCatalog": "Poiščite razpoložljive dodatke v našem katalogu.", "BurnSubtitlesHelp": "Določi ali naj strežnik vžge podnapise pri prekodiranju videa. Izogibanje temu lahko občutno izboljša delovanje strežnika. Izberite Samodejno za vžig slikovnih formatov podnapisov (VOBSUB, PGS, SUB, IDX, ...) in nekaterih ASS oziroma SSA podnapisov.", - "ButtonAdd": "Dodaj", "Photos": "Fotografije", "Playlists": "Seznami predvajanja", "Songs": "Pesmi", @@ -152,7 +149,6 @@ "ButtonFullscreen": "Polni zaslon", "ButtonGotIt": "Razumem", "ButtonGuide": "Vodič", - "ButtonHelp": "Pomoč", "ButtonHome": "Domov", "ButtonInfo": "Info", "ButtonLibraryAccess": "Dostop do knjižnic", @@ -173,14 +169,11 @@ "ButtonRefreshGuideData": "Osveži TV vodič", "ButtonRemove": "Odstrani", "ButtonRename": "Preimenuj", - "ButtonRepeat": "Ponovi", "ButtonResetEasyPassword": "Ponastavi preprosto PIN kodo", "ButtonRestart": "Ponovno zaženi", "ButtonResume": "Nadaljuj", "ButtonRevoke": "Razveljavi", - "ButtonSave": "Shrani", "ButtonScanAllLibraries": "Preišči vse knjižnice", - "ButtonSearch": "Išči", "ButtonSelectDirectory": "Izberi mapo", "ButtonSelectServer": "Izberi strežnik", "ButtonSelectView": "Izberi pogled", @@ -353,7 +346,6 @@ "HeaderGenres": "Žanri", "HeaderForgotPassword": "Pozabljeno geslo", "HeaderForKids": "Za otroke", - "HeaderFilters": "Filtri", "HeaderFetcherSettings": "Nastavitve pridobivanja", "HeaderFetchImages": "Pridobi slike:", "HeaderFavoriteVideos": "Priljubljeni videi", @@ -366,7 +358,6 @@ "HeaderEnabledFields": "Omogočena polja", "HeaderEditImages": "Uredi slike", "HeaderDownloadSync": "Prenos in sinhronizacija", - "HeaderDisplay": "Prikaz", "HeaderDirectPlayProfileHelp": "Dodaj profil za neposredno predvajanje in določi katere formate naprava podpira.", "HeaderDirectPlayProfile": "Profil za neposredno predvajanje", "HeaderDevices": "Naprave", @@ -516,7 +507,6 @@ "HeaderTracks": "Skladbe", "HeaderThisUserIsCurrentlyDisabled": "Ta uporabnik je trenutno onemogočen", "HeaderTaskTriggers": "Sprožilci opravil", - "HeaderTags": "Oznake", "HeaderSystemDlnaProfiles": "Sistemski profili", "HeaderSubtitleProfilesHelp": "Profili podnapisov določajo formate podnapisov, ki je naprava podpira.", "HeaderSubtitleProfiles": "Profili podnapisov", @@ -538,7 +528,6 @@ "HeaderSendMessage": "Pošlji sporočilo", "HeaderSelectTranscodingPath": "Izberi pot začasnih datotek prekodiranja", "HeaderRevisionHistory": "Pregled zgodovine", - "HeaderFeatures": "Funkcije", "HeaderFeatureAccess": "Dostop funkcij", "HeaderDeviceAccess": "Dostop naprav", "HeaderContainerProfile": "Profil kontejnerja", @@ -575,7 +564,6 @@ "LabelAirsAfterSeason": "Predvajanje po sezoni:", "LabelAirsBeforeSeason": "Predvajanje pred sezono:", "LabelAlbumArtists": "Izvajalci albuma:", - "LabelAll": "Vse", "LabelCustomRating": "Prilagojena ocena:", "LabelBirthDate": "Datum rojstva:", "LabelCache": "Predpomnilnik:", @@ -749,7 +737,6 @@ "MessagePluginInstalled": "Dodatek je bil uspešno nameščen. Za uveljavitev sprememb je potreben ponovni zagon Jellyfin strežnika.", "MessageNoMovieSuggestionsAvailable": "Trenutno ni na voljo nobenih predlogov za filme. Začnite gledati in ocenjevati vaše filme, ter se nato vrnite sem in si oglejte predloge.", "LabelSelectFolderGroups": "Samodejno združi vsebine iz spodnjih map v poglede kot so Filmi, Glasba in TV:", - "TabPlayback": "Predvajanje", "TitlePlayback": "Predvajanje", "MessagePasswordResetForUsers": "Gesla naslednjih uporabnikov so bila ponastavljena. Zdaj se lahko prijavijo s PIN kodami, ki so bile uporabljene za ponastavitev.", "OptionHideUserFromLoginHelp": "Koristno za zasebne ali skrite skrbniške račune. Uporabnik se bo moral prijaviti ročno z vpisom svojega uporabniškega imena in gesla.", @@ -1171,7 +1158,6 @@ "TabTrailers": "Napovedniki", "ClientSettings": "Nastavitve odjemalca", "ButtonTogglePlaylist": "Seznam predvajanja", - "ButtonToggleContextMenu": "Več", "Artist": "Izvajalec", "AlbumArtist": "Izvajalec albuma", "Album": "Album", @@ -1344,12 +1330,10 @@ "LabelWeb": "Splet:", "LabelLineup": "Postava:", "BoxSet": "Komplet", - "TabMetadata": "Metapodatki", "TabInfo": "Informacije", "TabGuide": "Vodič", "TabFavorites": "Priljubljeni", "TabEpisodes": "Epizode", - "TabDisplay": "Prikaz", "TabDirectPlay": "Neposredno predvajanje", "TabDevices": "Naprave", "TabCodecs": "Kodeki", @@ -1358,7 +1342,6 @@ "OptionProtocolHls": "HTTP pretakanje v živo", "OptionProfileVideoAudio": "Video zvok", "ThemeSongs": "Glavne pesmi", - "TabTranscoding": "Prekodiranje", "TabStreaming": "Pretakanje", "TabSongs": "Skladbe", "TabSettings": "Nastavitve", @@ -1429,5 +1412,23 @@ "OptionEnableM2tsModeHelp": "Omogoči m2ts način pri kodiranju v mpegts.", "OptionEnableM2tsMode": "Omogoči M2ts način", "OptionDisplayFolderViewHelp": "Prikaže mape poleg ostalih knjižnic predstavnosti. Uporabno za preprost ogled map.", - "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti" + "OptionDisplayFolderView": "Prikaži pogled mape za prikaz navadnih map predstavnosti", + "Yesterday": "Včeraj", + "Yes": "Da", + "RecommendationStarring": "Nastopa {0}", + "Recordings": "Posnetki", + "RemoveFromCollection": "Odstrani iz zbirke", + "ResumeAt": "Nadaljuj od {0}", + "SaveSubtitlesIntoMediaFolders": "Shrani podnapise v mape predstavnosti", + "ScanForNewAndUpdatedFiles": "Poišči nove in spremenjene datoteke", + "Screenshot": "Posnetek zaslona", + "Screenshots": "Posnetki zaslona", + "Search": "Iskanje", + "ShowAdvancedSettings": "Prikaži napredne nastavitve", + "New": "Novo", + "SubtitleOffset": "Zamik podnapisev", + "Subtitles": "Podnapisi", + "Sunday": "Nedelja", + "TabAdvanced": "Napredno", + "TabAlbums": "Albumi" } diff --git a/src/strings/sr.json b/src/strings/sr.json index 4682f060f1..810fa44141 100644 --- a/src/strings/sr.json +++ b/src/strings/sr.json @@ -43,7 +43,6 @@ "ButtonLibraryAccess": "Приступ библиотеци", "ButtonInfo": "Информације", "ButtonHome": "Почетна страна", - "ButtonHelp": "Помоћ", "ButtonGuide": "Водич", "ButtonGotIt": "У реду", "ButtonFullscreen": "Пун екран", @@ -67,7 +66,6 @@ "ButtonAddScheduledTaskTrigger": "Додај прекидач", "ButtonAddMediaLibrary": "Додај каталог медија", "ButtonAddImage": "Додај слику", - "ButtonAdd": "Додај", "MessageBrowsePluginCatalog": "Претражуј наш каталог доступних додатака", "Browse": "Изабери", "BoxRear": "Омот (позади)", @@ -144,15 +142,12 @@ "ButtonSelectView": "Изаберите приказ", "ButtonSelectServer": "Иѕаберите сервер", "ButtonSelectDirectory": "Изаберите Директоријум", - "ButtonSearch": "Тражи", "ButtonScanAllLibraries": "Скенирај све библиотеке", - "ButtonSave": "Сачувај", "ButtonRevoke": "Опозови", "ButtonResume": "Настави", "ButtonRestart": "Покрени поново", "ButtonResetPassword": "Ресетуј шифру", "ButtonResetEasyPassword": "Ресетујте једноставан ПИН код", - "ButtonRepeat": "Пусти поново", "ButtonRename": "Преименуј", "ButtonRemove": "Уклони", "ButtonRefreshGuideData": "Освежи податке водича", diff --git a/src/strings/sv.json b/src/strings/sv.json index ef4e849e40..23c68d9e84 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -45,7 +45,6 @@ "Browse": "Bläddra", "MessageBrowsePluginCatalog": "Besök katalogen för att se tillgängliga tillägg.", "BurnSubtitlesHelp": "Avgör ifall servern ska \"bränna in\" undertexterna under transkodning. Att undvika detta förbättrar prestandan avsevärt. Välj \"Automatisk\" för att bränna bild-baserade format (ex. VOBSUB, PGS, SUB/IDX, ...) och vissa ASS/SSA-undertexter.", - "ButtonAdd": "Lägg till", "ButtonAddMediaLibrary": "Lägg till mediabibliotek", "ButtonAddScheduledTaskTrigger": "Lägg till utlösare", "ButtonAddServer": "Lägg till server", @@ -70,7 +69,6 @@ "ButtonForgotPassword": "Glömt Lösenord", "ButtonFullscreen": "Fullskärm", "ButtonGotIt": "Ok", - "ButtonHelp": "Hjälp", "ButtonHome": "Hem", "ButtonLibraryAccess": "Biblioteksåtkomst", "ButtonManualLogin": "Manuell inloggning", @@ -91,15 +89,12 @@ "ButtonRefreshGuideData": "Uppdatera programguiden", "ButtonRemove": "Ta bort", "ButtonRename": "Ändra namn", - "ButtonRepeat": "Upprepa", "ButtonResetEasyPassword": "Återställ enkel pin-kod", "ButtonResetPassword": "Återställ lösenord", "ButtonRestart": "Starta om", "ButtonResume": "Återuppta", "ButtonRevoke": "Återkalla", - "ButtonSave": "Spara", "ButtonScanAllLibraries": "Scanna alla bibliotek", - "ButtonSearch": "Sök", "ButtonSelectDirectory": "Välj mapp", "ButtonSelectServer": "Välj server", "ButtonSelectView": "Välj vy", @@ -294,7 +289,6 @@ "HeaderDevices": "Enheter", "HeaderDirectPlayProfile": "Profil för direktuppspelning", "HeaderDirectPlayProfileHelp": "Ange direktuppspelningsprofiler för att indikera vilka format enheten kan spela upp utan omkodning.", - "HeaderDisplay": "Visning", "HeaderDownloadSync": "Ladda ner & Synka", "HeaderEasyPinCode": "Lätt pinkod", "HeaderEditImages": "Redigera bilder", @@ -304,9 +298,7 @@ "HeaderError": "Fel", "HeaderExternalIds": "Externa ID:", "HeaderFeatureAccess": "Tillgång till funktioner", - "HeaderFeatures": "Extramaterial", "HeaderFetchImages": "Hämta bilder:", - "HeaderFilters": "Filter", "HeaderForKids": "För barn", "HeaderForgotPassword": "Glömt Lösenord", "HeaderFrequentlyPlayed": "Ofta spelade", @@ -413,7 +405,6 @@ "HeaderSubtitleProfiles": "Undertextprofiler", "HeaderSubtitleProfilesHelp": "Undertextprofiler beskriver de undertextformat som stöds av enheten.", "HeaderSystemDlnaProfiles": "Systemprofiler", - "HeaderTags": "Etiketter", "HeaderTaskTriggers": "Aktivitetsutlösare", "HeaderThisUserIsCurrentlyDisabled": "Den här användaren är inaktiverad", "HeaderTracks": "Spår", @@ -465,7 +456,6 @@ "LabelAlbumArtMaxWidthHelp": "Högsta upplösning hos omslagsbilder presenterade via upnp:albumArtURI.", "LabelAlbumArtPN": "PN för omslagsbilder:", "LabelAlbumArtists": "Albumartist:", - "LabelAll": "Alla", "LabelAllowHWTranscoding": "Tillåt hårdvaruomkodning", "LabelAppName": "Appens namn", "LabelAppNameExample": "Exempel: Sickbeard, Sonarr", @@ -1139,7 +1129,6 @@ "TabAdvanced": "Avancerat", "TabAlbumArtists": "Albumartister", "TabAlbums": "Album", - "TabArtists": "Artister", "TabCatalog": "Katalog", "TabChannels": "Kanaler", "TabCodecs": "Kodningsformat", @@ -1148,7 +1137,6 @@ "TabDashboard": "Kontrollpanel", "TabDevices": "Enheter", "TabDirectPlay": "Direktuppspelning", - "TabDisplay": "Visning", "TabEpisodes": "Avsnitt", "TabFavorites": "Favoriter", "TabGenres": "Genrer", @@ -1166,7 +1154,6 @@ "TabOther": "Övrigt", "TabParentalControl": "Föräldralås", "TabPassword": "Lösenord", - "TabPlayback": "Uppspelning", "TabPlaylists": "Spellistor", "TabPlugins": "Tillägg", "TabProfile": "Profil", @@ -1180,8 +1167,6 @@ "TabShows": "Serier", "TabSongs": "Låtar", "TabStreaming": "Strömning", - "TabSuggestions": "Förslag", - "TabTranscoding": "Omkodning", "TabUpcoming": "Kommande", "TabUsers": "Användare", "Tags": "Etiketter", @@ -1345,7 +1330,6 @@ "TabTrailers": "Trailers", "TabServer": "Server", "TabNetworking": "Nätverk", - "TabMetadata": "Metadata", "TabInfo": "Info", "TabAccess": "Tillgång", "TV": "TV", @@ -1456,7 +1440,6 @@ "BoxSet": "Samlingsbox", "Artist": "Artist", "ButtonTogglePlaylist": "Spellista", - "ButtonToggleContextMenu": "Mer", "AlbumArtist": "Albumartist", "LabelLibraryPageSize": "Bibliotekets sidstorlek:", "LabelDeinterlaceMethod": "Deinterlacing-metod:", diff --git a/src/strings/tr.json b/src/strings/tr.json index 8936fa6463..80098c3222 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -6,7 +6,6 @@ "AllowRemoteAccessHelp": "Eğer işaretlenmemişse, bütün uzak bağlantılar bloke edilicek.", "AttributeNew": "Yeni", "MessageBrowsePluginCatalog": "Mevcut eklentileri görebilmek için eklenti katologuna göz atın.", - "ButtonAdd": "Ekle", "ButtonAddUser": "Kullanıcı Ekle", "ButtonArrowRight": "Sağ", "ButtonBack": "Geri", @@ -15,7 +14,6 @@ "ButtonDeleteImage": "Resmi Sil", "ButtonEdit": "Düzenle", "ButtonFilter": "Filtrele", - "ButtonHelp": "Yardım", "ButtonHome": "Anasayfa", "ButtonInfo": "Bilgi", "ButtonManualLogin": "Manuel Giriş", @@ -28,8 +26,6 @@ "ButtonRemove": "Sil", "ButtonResetPassword": "Şifre Sıfırla", "ButtonRestart": "Tekrar Başlat", - "ButtonSave": "Kayıt", - "ButtonSearch": "Arama", "ButtonSelectDirectory": "Dosyayı Seç", "ButtonSend": "Gönder", "ButtonSettings": "Ayarlar", @@ -64,7 +60,6 @@ "HeaderCustomDlnaProfiles": "Özel Profiller", "HeaderDeviceAccess": "Cihaz Erişimi", "HeaderEasyPinCode": "Kolay Pin Kodu", - "HeaderFilters": "Filtreler", "HeaderFrequentlyPlayed": "Sıkça Oynatılan", "HeaderImageSettings": "Resim Ayarları", "HeaderLatestEpisodes": "En yeni bölümler", @@ -207,7 +202,6 @@ "TabAdvanced": "Gelişmiş", "TabAlbumArtists": "Albüm Sanatçıları", "TabAlbums": "Albümler", - "TabArtists": "Sanatçılar", "TabCatalog": "Katalog", "TabChannels": "Kanallar", "TabCodecs": "Codecler", @@ -232,9 +226,7 @@ "TabSettings": "Ayarlar", "TabShows": "Diziler", "TabSongs": "Şarkılar", - "TabSuggestions": "Önerilenler", "TabTrailers": "Fragmanlar", - "TabTranscoding": "Kodlayıcı", "TabUpcoming": "Gelecek", "TellUsAboutYourself": "Kendinizden bahsedin", "ThisWizardWillGuideYou": "Bu sihirbaz kurulum işlemi boyunca size yardımcı olacaktır. Başlamak için, tercih ettiğiniz dili seçiniz.", @@ -291,7 +283,6 @@ "ButtonProfile": "Profil", "ButtonRefresh": "Yenile", "ButtonRename": "Yeniden Adlandır", - "ButtonRepeat": "Tekrar", "ButtonResume": "Devam Et", "ButtonRevoke": "Geri al", "ChannelNumber": "Kanal numarası", @@ -437,7 +428,6 @@ "HeaderForKids": "Çocuklar için", "HeaderFetcherSettings": "Alıcı Ayarları", "HeaderFetchImages": "Görüntüleri Getir:", - "HeaderFeatures": "Özellikleri", "HeaderFeatureAccess": "Özellik Erişimi", "HeaderFavoriteVideos": "Favori Videolar", "HeaderFavoriteMovies": "Favori Filmler", @@ -449,7 +439,6 @@ "HeaderEnabledFields": "Etkin Alanlar", "HeaderEditImages": "Görüntüleri Düzenle", "HeaderDownloadSync": "İndir ve Eşitle", - "HeaderDisplay": "Görüntüle", "HeaderDirectPlayProfileHelp": "Aygıtın yerel olarak hangi biçimlerde kullanılabileceğini göstermek için doğrudan oynatma profilleri ekleyin.", "HeaderDirectPlayProfile": "Doğrudan Oyun Profili", "HeaderDevices": "Cihazlar", @@ -641,7 +630,6 @@ "LabelAudio": "Ses", "LabelAppName": "Uygulama adı", "LabelAllowHWTranscoding": "Donanım kod dönüştürmesine izin ver", - "LabelAll": "Tümü", "LabelAlbumArtMaxWidth": "Albüm resmi maks. genişlik:", "LabelAlbumArtMaxHeight": "Albüm resmi maks. yükseklik:", "LabelAlbum": "Albüm:", @@ -668,7 +656,6 @@ "HeaderTypeText": "Metin Gir", "HeaderTunerDevices": "Alıcı Cihazları", "HeaderThisUserIsCurrentlyDisabled": "Bu kullanıcı şu anda pasif", - "HeaderTags": "Etiketler", "HeaderSubtitleProfiles": "Altyazı Profilleri", "HeaderSubtitleProfile": "Altyazı Profili", "HeaderSubtitleDownloads": "Altyazı İndirmeleri", diff --git a/src/strings/uk.json b/src/strings/uk.json index 95948074ea..a1100e1e69 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1,14 +1,13 @@ { - "BirthDateValue": "Народився: {0}", + "BirthDateValue": "Дата народження: {0}", "BirthPlaceValue": "Місце народження: {0}", "ButtonAddUser": "Додати користувача", "ButtonCancel": "Скасувати", "ButtonFilter": "Фільтр", - "ButtonNew": "Новий", + "ButtonNew": "Нове", "ButtonRename": "Перейменувати", "ButtonResetPassword": "Скинути пароль", - "ButtonSave": "Зберігти", - "ButtonSignOut": "Sign out", + "ButtonSignOut": "Вийти", "DeathDateValue": "Помер: {0}", "Dislike": "Не подобається", "Favorite": "Улюблене", @@ -66,7 +65,6 @@ "TabCollections": "Колекції", "TabEpisodes": "Епізоди", "TabGenres": "Жанри", - "TabMetadata": "Метадані", "TabMovies": "Фільми", "TabNetworks": "Мережі", "TabNotifications": "Повідомлення", @@ -91,29 +89,29 @@ "ValueSeriesCount": "{0} серій", "ValueSongCount": "{0} пісень", "AddToPlaylist": "Додати до списку відтворення", - "AccessRestrictedTryAgainLater": "На цей момент доступ заборонений. Повторіть спробу пізніше.", - "Actor": "Виконавець", + "AccessRestrictedTryAgainLater": "На даний момент доступ обмежений. Будь ласка, спробуйте пізніше.", + "Actor": "Актор", "AllLanguages": "Усі мови", - "AllLibraries": "Усі бібліотеки", + "AllLibraries": "Усі медіатеки", "AddToCollection": "Додати до колекції", "AddToPlayQueue": "Додати до черги відтворення", "All": "Всі", "AllChannels": "Всі канали", "AllEpisodes": "Всі епізоди", - "AllowRemoteAccess": "Дозволити віддалене підключення до цього сервера Jellyfin.", + "AllowRemoteAccess": "Дозволити віддалене підключення до цього сервера.", "AlwaysPlaySubtitles": "Завжди вмикати субтитри", "AnyLanguage": "Будь-яка мова", - "Anytime": "Завжди", + "Anytime": "У будь-який час", "Add": "Додати", - "AddedOnValue": "Додано", + "AddedOnValue": "Додано {0}", "Albums": "Альбоми", "Absolute": "Абсолютний", "HeaderFavoriteEpisodes": "Улюблені серії", "Movies": "Фільми", "Collections": "Колекції", - "Folders": "Директорії", + "Folders": "Каталоги", "HeaderNextUp": "Наступний", - "HeaderAlbumArtists": "Виконавці альбомів", + "HeaderAlbumArtists": "Виконавці альбому", "HeaderFavoriteSongs": "Улюблені пісні", "Favorites": "Улюблені", "HeaderFavoriteAlbums": "Улюблені альбоми", @@ -125,15 +123,15 @@ "HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteShows": "Улюблені шоу", "HeaderContinueWatching": "Продовжити перегляд", - "AllowedRemoteAddressesHelp": "Список з комами, в якості розділювачів, визначає IP-адреси та IP/мережеві маски для мереж, яким дозволено підключатись віддалено. Якщо залишити строку пустою, то усі віддалені підключення будуть дозволені.", + "AllowedRemoteAddressesHelp": "Список розділених комами IP-адрес або IP/мережевих масок, яким буде дозволено віддалено підключатися. Якщо залишити порожнім, усі віддалені адреси будуть дозволені.", "AllowRemoteAccessHelp": "Якщо не відмічено прапорцем, усі віддалені підключення будуть заблоковані.", "AllowFfmpegThrottling": "Примусово обмежити перекодування", - "AllowMediaConversionHelp": "Надайте або забороніть доступ для можливості перетворення медіа.", + "AllowMediaConversionHelp": "Надати або заборонити доступ до функції конвертації медіа.", "AllowMediaConversion": "Дозволити перетворення медіа", "Alerts": "Термінові сповіщення", "AlbumArtist": "Виконавець альбому", "Album": "Альбом", - "AdditionalNotificationServices": "Пошук у каталозі плагінів для встановлення додаткових сервісів сповіщень.", + "AdditionalNotificationServices": "Перегляньте каталог плагінів, щоб встановити додаткові служби сповіщення.", "ShowYear": "Відобразити рік", "ShowTitle": "Відобразити назву", "Raised": "Піднятий", @@ -141,20 +139,133 @@ "DropShadow": "Тінь", "Blacklist": "Чорний список", "BirthLocation": "Місце народження", - "Banner": "Обкладинка", + "Banner": "Банер", "Auto": "Автоматично", - "AuthProviderHelp": "Оберіть сервіс аутентифікації, який буде використаний з поточним паролем користувача.", + "AuthProviderHelp": "Оберіть сервіс аутентифікації, який буде використаний для автентифікації пароля даного користувача.", "Audio": "Аудіо", "AttributeNew": "Новий", - "AspectRatio": "Відношення сторін", - "AskAdminToCreateLibrary": "Попросіть адміністратора створити бібліотеку.", + "AspectRatio": "Співвідношення сторін", + "AskAdminToCreateLibrary": "Попросіть адміністратора створити медіатеку.", "Ascending": "У порядку зростання", - "AsManyAsPossible": "Настільки багато наскільки можливо", + "AsManyAsPossible": "Якнайбільше", "Artist": "Виконавець", - "Art": "Мистецтво", + "Art": "Обкладинка", "AllowOnTheFlySubtitleExtractionHelp": "Вбудовані субтитри можуть бути експортовані з відео і надіслані, по черзі, клієнтам у вигляді тексту. Це допоможе уникнути перекодування відео. На деяких системах, перекодування може зайняти тривалий час і зупинити відтворення відео, для того щоб забезпечити експортування. Вимкнення цієї функції дозволить вбудованим субтитрам бути інтегрованим у відео, під час перекодування, якщо вбудовані субтитри не підтримуються на стороні клієнта.", "AllowOnTheFlySubtitleExtraction": "Дозволити експортування субтитрів «на льоту»", - "AllowHWTranscodingHelp": "Дозволити клієнту перекодування на «на льоту». Це дозволить відмикати перекодування, якщо вона вимагається сервером.", + "AllowHWTranscodingHelp": "Тюнеру дозволяється перекодувати потоки на льоту. Це може допомогти зменшити перекодування, необхідне серверу.", "AllComplexFormats": "Усі складні формати (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", - "ButtonSyncPlay": "SyncPlay" + "ButtonSyncPlay": "SyncPlay", + "ValueSpecialEpisodeName": "Спецепізод - {0}", + "Sync": "Синхронізація", + "Songs": "Пісні", + "Shows": "Шоу", + "Playlists": "Плейлисти", + "Photos": "Фотографії", + "Aired": "Ефірний", + "AirDate": "Дата ефіру", + "CustomDlnaProfilesHelp": "Створіть спеціальний профіль, для нового пристрою або змініть системний профіль.", + "CriticRating": "Рейтинг критиків", + "CopyStreamURLSuccess": "URL-адреса успішно скопійована.", + "CopyStreamURL": "Скопіювати URL-адресу потоку", + "Continuing": "Продовження", + "ContinueWatching": "Продовжити перегляд", + "Connect": "Підключитись", + "ConfirmEndPlayerSession": "Ви хочете вимкнути Jellyfin на {0}?", + "ConfirmDeletion": "Підтвердити видалення", + "ConfirmDeleteItems": "Видалення цих елементів видалить їх як з файлової системи, так і з медіатеки. Ви впевнені, що хочете продовжити?", + "ConfirmDeleteItem": "Видалення цього елемента видалить його як з файлової системи, так і з медіатеки. Ви впевнені, що хочете продовжити?", + "ConfirmDeleteImage": "Видалити зображення?", + "ConfigureDateAdded": "Налаштуйте, як визначається дата додавання на інформаційній панелі в налаштуваннях медіатеки", + "Composer": "Композитор", + "CommunityRating": "Рейтинг спільноти", + "ColorTransfer": "Передача кольору", + "ColorSpace": "Кольоровий простір", + "ColorPrimaries": "Основні кольори", + "ClientSettings": "Налаштування клієнта", + "CinemaModeConfigurationHelp": "Режим кінотеатру забезпечує враження від глядацького залу прямо у вашій вітальні з можливістю відтворювати трейлери та власні відео перед фільмом.", + "ChannelNumber": "Номер каналу", + "ChannelNameOnly": "Тільки канал {0}", + "ChannelAccessHelp": "Виберіть канали, якими поділитись з даним користувачем. Адміністратори зможуть редагувати всі канали за допомогою менеджера метаданих.", + "ChangingMetadataImageSettingsNewContent": "Зміни в налаштуваннях завантаження метаданих або зображень стосуються лише нового вмісту, доданого до вашої бібліотеки. Щоб застосувати зміни до існуючих творів, потрібно оновити їх метадані вручну.", + "Categories": "Категорії", + "CancelSeries": "Скасувати серіал", + "CancelRecording": "Скасувати запис", + "ButtonWebsite": "Веб-сайт", + "ButtonUp": "Вгору", + "ButtonUninstall": "Видалити", + "ButtonTrailer": "Трейлер", + "ButtonTogglePlaylist": "Плейлист", + "ButtonSubtitles": "Субтитри", + "ButtonSubmit": "Підтвердити", + "ButtonSplit": "Розділити", + "ButtonStop": "Зупинити", + "ButtonStart": "Почати", + "ButtonSort": "Сортувати", + "ButtonSignIn": "Вхід", + "ButtonShutdown": "Завершити роботу", + "ButtonShuffle": "Перемішати", + "ButtonSettings": "Налаштування", + "ButtonSend": "Надіслати", + "ButtonSelectView": "Вибрати вигляд", + "ButtonSelectServer": "Вибрати сервер", + "ButtonSelectDirectory": "Вибрати каталог", + "ButtonScanAllLibraries": "Сканувати всі медіатеки", + "ButtonRevoke": "Відмінити", + "ButtonResume": "Відновити", + "ButtonRestart": "Перезапустити", + "ButtonResetEasyPassword": "Скинути простий пін-код", + "ButtonRemove": "Видалити", + "ButtonRefreshGuideData": "Оновити дані телегіда", + "ButtonRefresh": "Оновити", + "ButtonQuickStartGuide": "Посібник по швидкому запуску", + "ButtonProfile": "Профіль", + "ButtonPreviousTrack": "Попередня доріжка", + "ButtonPlay": "Відтворити", + "ButtonPause": "Пауза", + "ButtonParentalControl": "Батьківський контроль", + "ButtonOpen": "Відкрити", + "ButtonOk": "Ок", + "ButtonOff": "Вимкнути", + "ButtonNextTrack": "Наступна доріжка", + "ButtonNetwork": "Мережа", + "ButtonMore": "Більше", + "ButtonManualLogin": "Ввести ім’я користувача вручну", + "ButtonLibraryAccess": "Доступ до медіатеки", + "ButtonInfo": "Інформація", + "ButtonHome": "Головна", + "ButtonGuide": "Телегід", + "ButtonGotIt": "Зрозуміло", + "ButtonFullscreen": "Повноекранний режим", + "ButtonForgotPassword": "Забув(ла) пароль", + "ButtonEditOtherUserPreferences": "Редагувати профіль, зображення та особисті налаштування даного користувача.", + "ButtonEditImages": "Редагувати зображення", + "ButtonEdit": "Редагувати", + "ButtonDownload": "Завантажити", + "ButtonDown": "Вниз", + "ButtonDeleteImage": "Видалити зображення", + "ButtonDelete": "Видалити", + "ButtonConnect": "Підключитись", + "ButtonChangeServer": "Змінити сервер", + "ButtonBack": "Назад", + "ButtonAudioTracks": "Аудіозаписи", + "ButtonArrowUp": "Вниз", + "ButtonArrowRight": "Вправо", + "ButtonArrowLeft": "Вліво", + "ButtonArrowDown": "Вниз", + "ButtonAddServer": "Додати сервер", + "ButtonAddScheduledTaskTrigger": "Додати тригер", + "ButtonAddMediaLibrary": "Додати медіатеку", + "ButtonAddImage": "Додати зображення", + "BurnSubtitlesHelp": "Визначає, чи повинен сервер додавати субтитри під час перекодування відео. Уникнення цього значно покращить продуктивність. Виберіть Автоматично для записування форматів на основі зображень (VOBSUB, PGS, SUB, IDX, ...) та певні субтитри ASS або SSA.", + "MessageBrowsePluginCatalog": "Перегляньте каталог плагінів, щоб ознайомитися з доступними плагінами.", + "Browse": "Огляд", + "BoxRear": "Коробка (задня частина)", + "BoxSet": "Колекція", + "Box": "Коробка", + "BookLibraryHelp": "Підтримуються аудіо та текстові книги. Перегляньте {0} посібник з іменування книг {1}.", + "Backdrops": "Фони", + "Backdrop": "Фон", + "AroundTime": "Приблизно", + "AlwaysPlaySubtitlesHelp": "Субтитри, що відповідають мовним параметрам, завантажуватимуться незалежно від мови звуку.", + "AllowFfmpegThrottlingHelp": "Коли перекодування або перепакування досить далеко випереджає поточну позицію відтворення, процес призупиняється, щоб зекономити ресурси. Це найкорисніше при перегляді, коли рідко міняється позиція відтворення. Вимкніть це, якщо виникнуть проблеми з відтворенням." } diff --git a/src/strings/vi.json b/src/strings/vi.json index b4d79f267b..431644be16 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -2,15 +2,12 @@ "Add": "Thêm", "All": "Tất cả", "MessageBrowsePluginCatalog": "Duyệt qua các danh mục plugin của chúng tôi để xem các plugin có sẵn.", - "ButtonAdd": "Thêm", "ButtonAddUser": "Thêm người dùng", "ButtonCancel": "Thoát", "ButtonDeleteImage": "Xóa hình ảnh", "ButtonNew": "Mới", "ButtonRemove": "Gỡ bỏ", "ButtonResetPassword": "Reset mật khẩu", - "ButtonSave": "Lưu", - "ButtonSearch": "Tìm kiếm", "ButtonSelectDirectory": "Lựa chọn trực tiếp", "ButtonSignOut": "Sign out", "ButtonSort": "Phân loại", @@ -93,7 +90,6 @@ "Sunday": "Chủ Nhật", "TabAlbumArtists": "Các Album nghệ sỹ", "TabAlbums": "Các Album", - "TabArtists": "Các nghệ sỹ", "TabCatalog": "Danh mục", "TabEpisodes": "Các tập phim", "TabGenres": "Các thể loại", @@ -107,7 +103,6 @@ "TabProfiles": "Hồ sơ", "TabServer": "Máy chủ", "TabSongs": "Các ca khúc", - "TabTranscoding": "Mã hóa", "TabUpcoming": "Sắp diễn ra", "TellUsAboutYourself": "Nói cho chúng tôi biết đôi điều về Bạn", "ThisWizardWillGuideYou": "Thủ thuật này sẽ hướng dẫn quá trình cài đặt cho bạn. Để bắt đầu, vui lòng lựa chọn ngôn ngữ bạn ưa thích.", @@ -120,7 +115,6 @@ "ButtonResume": "Tiếp tục", "ButtonRestart": "Khởi động lại", "ButtonResetEasyPassword": "Đặt lại mã pin nhanh", - "ButtonRepeat": "Lặp lại", "ButtonRename": "Đổi tên", "ButtonRefreshGuideData": "Làm mới dữ liệu hướng dẫn", "ButtonRefresh": "Làm mới", @@ -139,7 +133,6 @@ "ButtonLibraryAccess": "Truy cập thư viện", "ButtonInfo": "Thông tin", "ButtonHome": "Trang chủ", - "ButtonHelp": "Giúp đỡ", "ButtonGuide": "Hướng dẫn", "ButtonGotIt": "Hiểu rồi", "ButtonFullscreen": "Toàn màn hình", @@ -263,7 +256,6 @@ "ChangingMetadataImageSettingsNewContent": "Thay đổi về thiết lập của việc tải thông tin hoặc hình ảnh sẽ chỉ có tác dụng với những nội dung mới được thêm vào thư viện. Để những thiết lập mới có tác dụng với nội dung đã có sẵn, bạn sẽ phải cập nhật lại thông tin của chúng.", "CancelSeries": "Ngưng series", "ButtonTogglePlaylist": "Danh sách phát", - "ButtonToggleContextMenu": "Thêm", "BoxSet": "Tuyển tập", "Box": "Hộp", "Banner": "Ảnh bìa", @@ -487,10 +479,8 @@ "HeaderGenres": "Thể Loại", "HeaderForgotPassword": "Quên Mật Khẩu", "HeaderForKids": "Dành Cho Trẻ Em", - "HeaderFilters": "Bộ Lọc", "HeaderFetcherSettings": "Cài Đặt Chương Trình Tải Xuống", "HeaderFetchImages": "Tải Hình Ảnh:", - "HeaderFeatures": "Nổi Bật", "HeaderFavoritePeople": "Nhân Vật Yêu Thích", "HeaderFavoriteAlbums": "Album Yêu Thích", "HeaderFavoriteBooks": "Sách Yêu Thích", @@ -508,7 +498,6 @@ "HeaderEditImages": "Chỉnh Sửa Hình Ảnh", "HeaderEasyPinCode": "Mã PIN Đơn Giản", "HeaderDownloadSync": "Tải Xuống Và Đồng Bộ", - "HeaderDisplay": "Hiển Thị", "HeaderDirectPlayProfileHelp": "Thêm thiết lập phát trực tiếp để chỉ ra những định dạng mà thiết bị có thể phát trực tiếp mà không cần chuyển mã.", "HeaderDirectPlayProfile": "Thiết Lập Phát Trực Tiếp", "HeaderDevices": "Thiết Bị", @@ -557,7 +546,6 @@ "HeaderTracks": "Bài Hát", "HeaderThisUserIsCurrentlyDisabled": "Người dùng này hiện tại đang bị khoá", "HeaderTaskTriggers": "Kích Hoạt Tác Vụ", - "HeaderTags": "Nhãn", "HeaderSubtitleProfilesHelp": "Hồ sơ phụ đề chỉ ra những định dạng phụ đề được hỗ trợ bởi thiết bị phát.", "HeaderSubtitleProfiles": "Hồ Sơ Phụ Đề", "HeaderSubtitleProfile": "Hồ Sơ Phụ Đề", @@ -609,7 +597,6 @@ "LabelAllowedRemoteAddressesMode": "Chế độ bộ lọc địa chỉ IP từ xa:", "LabelAllowedRemoteAddresses": "Bộ lọc địa chỉ IP từ xa:", "LabelAllowHWTranscoding": "Cho phép chuyển mã bằng phần cứng", - "LabelAll": "Tất Cả", "LabelAlbumArtists": "Nghệ sĩ album:", "LabelAlbumArtPN": "Bìa album PN:", "LabelAlbumArtMaxWidthHelp": "Độ phân giải cao nhất của bìa album thông qua upnp:albumArtURI.", diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 553445404a..e8e43426f0 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -47,7 +47,6 @@ "Browse": "浏览", "MessageBrowsePluginCatalog": "浏览我们的插件目录来查看现有插件。", "BurnSubtitlesHelp": "服务器在转换视频时是否应压制字幕。避免压制字幕会提高服务器性能。选择“自动”以压制基于图像的字幕格式(如 VOBSUB, PGS, SUB, IDX 等)和一些复杂的 ASS/SSA 字幕。", - "ButtonAdd": "添加", "ButtonAddMediaLibrary": "添加媒体库", "ButtonAddScheduledTaskTrigger": "添加触发器", "ButtonAddServer": "添加服务器", @@ -73,7 +72,6 @@ "ButtonFullscreen": "全屏", "ButtonGotIt": "知道了", "ButtonGuide": "指南", - "ButtonHelp": "帮助", "ButtonHome": "首页", "ButtonInfo": "详情", "ButtonLibraryAccess": "媒体库访问", @@ -95,15 +93,12 @@ "ButtonRefreshGuideData": "刷新指南数据", "ButtonRemove": "移除", "ButtonRename": "重命名", - "ButtonRepeat": "重播", "ButtonResetEasyPassword": "复位简易PIN码", "ButtonResetPassword": "重置密码", "ButtonRestart": "重启", "ButtonResume": "恢复播放", "ButtonRevoke": "撤销", - "ButtonSave": "保存", "ButtonScanAllLibraries": "扫描所有媒体库", - "ButtonSearch": "搜索", "ButtonSelectDirectory": "选择目录", "ButtonSelectServer": "选择服务器", "ButtonSelectView": "选择视图", @@ -159,7 +154,7 @@ "DetectingDevices": "正在侦测设备", "DeviceAccessHelp": "这仅适用于可以唯一标识的设备,而不会阻止浏览器访问。限制用户设备访问会阻止使用未在此被批准的新增设备。", "DirectPlaying": "直接播放", - "DirectStreamHelp2": "直接串流只占用占用很少的CPU并且视频的品质不会有任何损失。", + "DirectStreamHelp2": "直接串流只占用占用很少的CPU并且视频的品质只会有极小程度的损失。", "DirectStreaming": "直接串流", "Director": "导演", "Disabled": "已禁用", @@ -261,7 +256,7 @@ "HeaderAllowMediaDeletionFrom": "允许从中删除媒体", "HeaderApiKey": "API 密钥", "HeaderApiKeys": "API 密钥", - "HeaderApiKeysHelp": "外部应用程序需要 API 密钥才能与 Jellyfin Server 进行通信。使用 Jellyfin 账户进行登录时密钥将会自动生成,您也可以手动为某个应用程序分配一个密钥。", + "HeaderApiKeysHelp": "外部应用程序需要 API 密钥才能与服务器进行通信。密钥会在使用普通账户登录时自动生成,或是手动为应用分配。", "HeaderAudioBooks": "有声读物", "HeaderAudioSettings": "声音设置", "HeaderBlockItemsWithNoRating": "通过没有评级和设置不允许的评级锁定内容:", @@ -298,7 +293,6 @@ "HeaderDevices": "设备", "HeaderDirectPlayProfile": "直接播放配置", "HeaderDirectPlayProfileHelp": "添加直接播放配置文件标明哪些媒体格式设备可以自己处理。", - "HeaderDisplay": "显示", "HeaderDownloadSync": "下载与同步", "HeaderEasyPinCode": "简单 PIN 码", "HeaderEditImages": "修改图片", @@ -308,10 +302,8 @@ "HeaderError": "错误", "HeaderExternalIds": "外部 ID:", "HeaderFeatureAccess": "可使用的功能", - "HeaderFeatures": "功能", "HeaderFetchImages": "获取图像:", "HeaderFetcherSettings": "读取器设置", - "HeaderFilters": "筛选", "HeaderForKids": "给儿童", "HeaderForgotPassword": "忘记密码", "HeaderFrequentlyPlayed": "多次播放", @@ -327,7 +319,7 @@ "HeaderInstall": "安装", "HeaderInstantMix": "速成合辑", "HeaderItems": "项目", - "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据, 请在 Jellyfin 库安装程序中编辑库, 然后找到“元数据储户”部分。", + "HeaderKodiMetadataHelp": "要启用或禁用 NFO 元数据, 请编辑库, 然后找到“元数据储户”部分。", "HeaderLatestEpisodes": "最新剧集", "HeaderLatestMedia": "最新媒体", "HeaderLatestMovies": "最新电影", @@ -372,7 +364,7 @@ "HeaderPreferredMetadataLanguage": "首选元数据语言", "HeaderProfile": "配置", "HeaderProfileInformation": "配置信息", - "HeaderProfileServerSettingsHelp": "这些参数将控制 Jellyfin 媒体服务器如何呈现给设备。", + "HeaderProfileServerSettingsHelp": "这些参数将控制服务器如何将自己呈现给客户端。", "HeaderRecentlyPlayed": "最近播放", "HeaderRecordingOptions": "录制选项", "HeaderRecordingPostProcessing": "记录后处理", @@ -396,7 +388,7 @@ "HeaderSelectServerCachePath": "选择服务器缓存路径", "HeaderSelectServerCachePathHelp": "浏览或输入一个路径用于服务器缓存文件,此文件夹必须可写。", "HeaderSelectTranscodingPath": "选择临时解码路径", - "HeaderSelectTranscodingPathHelp": "浏览或输入一个路径用于临时转码,此文件夹必须可写。", + "HeaderSelectTranscodingPathHelp": "浏览或输入一个路径用于转码文件,此文件夹必须可写。", "HeaderSendMessage": "发送消息", "HeaderSeries": "电视剧", "HeaderSeriesOptions": "系列选项", @@ -416,7 +408,6 @@ "HeaderSubtitleProfiles": "字幕配置", "HeaderSubtitleProfilesHelp": "字幕配置文件描述设备所支持的字幕格式。", "HeaderSystemDlnaProfiles": "系统配置", - "HeaderTags": "标签", "HeaderTaskTriggers": "任务触发条件", "HeaderThisUserIsCurrentlyDisabled": "此用户当前已禁用", "HeaderTracks": "音轨", @@ -445,8 +436,8 @@ "HttpsRequiresCert": "要启用安全连接, 您需要提供一个受信任的 SSL 证书, 例如 Let's Encrypt 。请提供证书或禁用安全连接。", "Identify": "识别", "Images": "图片", - "ImportFavoriteChannelsHelp": "如果启用,只有在协调器设备中被标记为我的最爱的频道才会被导入。", - "ImportMissingEpisodesHelp": "如果启用,会将缺少的剧集信息导入到你的 Jellyfin 数据库并分季分剧显示。可能会大大延长媒体库扫描时间。", + "ImportFavoriteChannelsHelp": "只有在协调器设备中被标记为我的最爱的频道才会被导入。", + "ImportMissingEpisodesHelp": "缺少的剧集信息将被导入到你的数据库并分季分剧显示。可能会大大延长媒体库扫描时间。", "InstallingPackage": "正在安装 {0}(版本 {1})", "InstantMix": "即时混音", "ItemCount": "{0} 项", @@ -469,21 +460,20 @@ "LabelAlbumArtMaxWidthHelp": "通过UPnP显示的专辑封面超链接的最大分辨率。", "LabelAlbumArtPN": "专辑封面PN:", "LabelAlbumArtists": "专辑作家:", - "LabelAll": "所有", "LabelAllowHWTranscoding": "允许硬件转码", "LabelAllowedRemoteAddresses": "远程IP地址过滤器:", "LabelAllowedRemoteAddressesMode": "远程IP地址过滤器模式:", "LabelAppName": "APP名称", "LabelAppNameExample": "例如:Sickbeard, Sonarr", "LabelArtists": "艺术家:", - "LabelArtistsHelp": "独立多功能 ;", + "LabelArtistsHelp": "将多个艺术家用分号分隔", "LabelAudioLanguagePreference": "首选音频语言:", "LabelAutomaticallyRefreshInternetMetadataEvery": "自动从互联网获取元数据并刷新:", "LabelBindToLocalNetworkAddress": "监听的本地网络地址:", - "LabelBindToLocalNetworkAddressHelp": "(可选的)覆盖 HTTP 服务器绑定的本地 IP 地址。如果留空,服务器将会监听所有可用的地址。改变这个值需要重启 Jellyfin 服务器。", + "LabelBindToLocalNetworkAddressHelp": "覆盖 HTTP 服务器绑定的本地 IP 地址。如果留空,服务器将会监听所有可用的地址。改变这个值需要重启 Jellyfin 服务器。", "LabelBirthDate": "出生日期:", "LabelBirthYear": "出生年份:", - "LabelBlastMessageInterval": "活动信号的时间间隔(秒)", + "LabelBlastMessageInterval": "活动信号的时间间隔", "LabelBlastMessageIntervalHelp": "确定爆炸活动消息之间的持续时间(以秒为单位)。", "LabelBlockContentWithTags": "通过标签锁定内容:", "LabelBurnSubtitles": "烧录字幕:", @@ -541,7 +531,7 @@ "LabelEnableAutomaticPortMapHelp": "通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", "LabelEnableBlastAliveMessages": "爆发活动信号", "LabelEnableBlastAliveMessagesHelp": "如果该服务器不能被网络中的其他UPnP设备检测到,请启用此选项。", - "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔(秒)", + "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔", "LabelEnableDlnaClientDiscoveryIntervalHelp": "确定由 Jellyfin 执行的 SSDP 搜索之间的持续时间 (以秒为单位)。", "LabelEnableDlnaDebugLogging": "启用 DLNA 调试日志", "LabelEnableDlnaDebugLoggingHelp": "创建一个很大的日志文件,仅应在排除故障时使用。", @@ -567,9 +557,9 @@ "LabelForgotPasswordUsernameHelp": "输入你的用户名,如果你还记得。", "LabelFormat": "格式:", "LabelFriendlyName": "好记的名称:", - "LabelServerNameHelp": "此名称将用做服务器名,如果留空,将使用计算机名。", + "LabelServerNameHelp": "此名称将用做服务器名,默认使用服务器的主机名。", "LabelGroupMoviesIntoCollections": "批量添加电影到收藏", - "LabelGroupMoviesIntoCollectionsHelp": "显示电影列表时,属于一个收藏的电影将显示为一个分组。", + "LabelGroupMoviesIntoCollectionsHelp": "显示电影列表时,同一收藏的电影将显示为一个分组。", "LabelH264Crf": "H264 CRF 编码质量等级:", "LabelEncoderPreset": "H264 和 H265 编码预设:", "LabelHardwareAccelerationType": "硬件加速:", @@ -577,7 +567,7 @@ "LabelHomeNetworkQuality": "家庭网络质量:", "LabelHomeScreenSectionValue": "主屏幕模块{0}:", "LabelHttpsPort": "本地 HTTPS 端口号:", - "LabelHttpsPortHelp": "Jellyfin HTTPS 服务器监听端口。", + "LabelHttpsPortHelp": "HTTPS 服务器监听的 TCP 端口号。", "LabelIconMaxHeight": "图标最大高度:", "LabelIconMaxHeightHelp": "通过UPnP显示的图标最大分辨率。", "LabelIconMaxWidth": "图标最大宽度:", @@ -604,7 +594,7 @@ "LabelLanguage": "语言:", "LabelLineup": "排队:", "LabelLocalHttpServerPortNumber": "本地 HTTP 端口号:", - "LabelLocalHttpServerPortNumberHelp": "Jellyfin HTTP 服务器监听的 TCP 端口。", + "LabelLocalHttpServerPortNumberHelp": "HTTP 服务器监听的 TCP 端口号。", "LabelLockItemToPreventChanges": "锁定此项目防止改动", "LabelLoginDisclaimer": "登录声明:", "LabelLoginDisclaimerHelp": "将在登录页面底部显示的信息。", @@ -646,9 +636,9 @@ "LabelMovieCategories": "电影分类:", "LabelMoviePrefix": "电影前缀:", "LabelMoviePrefixHelp": "如果将前缀应用于影片标题, 请在此处输入它, 以便服务器可以正确处理它。", - "LabelMovieRecordingPath": "电影录制路径 (可选的):", + "LabelMovieRecordingPath": "电影录制路径:", "LabelMusicStreamingTranscodingBitrate": "音乐转码的比特率:", - "LabelMusicStreamingTranscodingBitrateHelp": "请指定一个音乐媒体串流时的最大比特率。", + "LabelMusicStreamingTranscodingBitrateHelp": "请指定音乐媒体串流时的最大比特率。", "LabelName": "名字:", "LabelNewName": "新名字:", "LabelNewPassword": "新密码:", @@ -659,7 +649,7 @@ "LabelNumber": "编号:", "LabelNumberOfGuideDays": "下载几天的节目指南:", "LabelNumberOfGuideDaysHelp": "下载更多天的节目指南可以帮你进一步查看节目列表并做出提前安排,但下载过程也将耗时更久。它将基于频道数量自动选择。", - "LabelOptionalNetworkPath": "(可选的)共享的网络文件夹:", + "LabelOptionalNetworkPath": "共享的网络文件夹:", "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件,例如 {0} 或者 {1}。", "LabelOriginalAspectRatio": "原始长宽比:", "LabelOriginalTitle": "原标题:", @@ -704,7 +694,7 @@ "LabelReleaseDate": "发行日期:", "LabelRemoteClientBitrateLimit": "互联网流媒体传输比特率限制(Mbps):", "LabelRemoteClientBitrateLimitHelp": "所有网络设备都有一个可选的每流比特率限制。这对于防止设备请求比 internet 连接所能处理的更高的比特率非常有用。这可能会导致服务器上的 CPU 负载增加, 以便将视频转码到较低的比特率。", - "LabelRuntimeMinutes": "播放时长(分钟):", + "LabelRuntimeMinutes": "播放时长:", "LabelSaveLocalMetadata": "将媒体图像保存到媒体所在文件夹", "LabelSaveLocalMetadataHelp": "直接将媒体图像保存到媒体所在文件夹以方便编辑。", "LabelScheduledTaskLastRan": "最后运行 {0}, 花费时间 {1}.", @@ -714,7 +704,7 @@ "LabelSelectVersionToInstall": "选择安装版本:", "LabelSendNotificationToUsers": "发送通知至:", "LabelSerialNumber": "序列号", - "LabelSeriesRecordingPath": "电视剧录制路径 (可选的):", + "LabelSeriesRecordingPath": "电视剧录制路径:", "LabelServerHost": "主机:", "LabelServerHostHelp": "192.168.1.100:8096 或 https://myserver.com", "LabelSimultaneousConnectionLimit": "并发流限制:", @@ -786,7 +776,7 @@ "LabelYoureDone": "完成!", "LabelZipCode": "邮编:", "LabelffmpegPath": "FFmpeg 路径:", - "LabelffmpegPathHelp": "FFmpeg 应用程序的文件,或者包含了 FFmpeg 的文件夹的路径。", + "LabelffmpegPathHelp": "FFmpeg 应用文件或包含 FFmpeg 的文件夹的路径。", "LanNetworksHelp": "在强制带宽限制时,认作本地网络上的 IP 地址或 IP/网络掩码条目的逗号分隔列表。如果设置此项,所有其它 IP 地址将被视为在外部网络上,并且将受到外部带宽限制。如果保留为空,则只将服务器的子网视为本地网络。", "Large": "大", "LatestFromLibrary": "最新的{0}", @@ -918,7 +908,7 @@ "OptionAllowLinkSharingHelp": "只有网页包含的媒体信息会被共享。媒体文件不会被公开共享。共享是有时间限制的并且会在 {0} 天后到期。", "OptionAllowManageLiveTv": "允许电视直播录制管理", "OptionAllowMediaPlayback": "允许播放媒体", - "OptionAllowMediaPlaybackTranscodingHelp": "由于不支持的媒体格式, 限制对代码转换的访问可能会导致 Jellyfin 应用程序中的播放失败。", + "OptionAllowMediaPlaybackTranscodingHelp": "限制对转码的访问可能会由于不支持的媒体格式导致客户端中播放失败。", "OptionAllowRemoteControlOthers": "允许其他用户全程控制", "OptionAllowRemoteSharedDevices": "允许远程控制共享的设备", "OptionAllowRemoteSharedDevicesHelp": "DLNA 设备在用户对他们进行控制前都被视为是共享的。", @@ -931,7 +921,7 @@ "OptionAuto": "自动", "OptionAutomatic": "自动", "OptionAutomaticallyGroupSeries": "自动合并分布在不同文件夹的电视剧", - "OptionAutomaticallyGroupSeriesHelp": "如果启用,分布在这个媒体库的多个文件夹中的同一部电视剧将会自动整合成一部电视剧。", + "OptionAutomaticallyGroupSeriesHelp": "在这个媒体库的多个文件夹中的同一部电视剧将会自动整合成一部电视剧。", "OptionBlockBooks": "书籍", "OptionBlockChannelContent": "互联网频道内容", "OptionBlockLiveTvChannels": "电视直播频道", @@ -952,7 +942,7 @@ "OptionDatePlayed": "播放日期", "OptionDescending": "降序", "OptionDisableUser": "禁用此用户", - "OptionDisableUserHelp": "如果禁用该用户,服务器将不允许该用户连接。现有的连接将被终止。", + "OptionDisableUserHelp": "服务器将不允许来自该用户的任何连接。现有的连接将立即被终止。", "OptionDislikes": "不喜欢", "OptionDisplayFolderView": "显示一个“文件夹”类别用于按文件夹分类浏览你的媒体文件夹", "OptionDisplayFolderViewHelp": "在你的媒体库列表中显示文件夹。如果你有按文件夹分类进行浏览的需求,这会非常有用。", @@ -962,7 +952,7 @@ "OptionDownloadBoxImage": "包装", "OptionDownloadDiscImage": "光盘", "OptionDownloadImagesInAdvance": "提前下载图片", - "OptionDownloadImagesInAdvanceHelp": "默认下,大部分图片只有在 Jellyfin 应用程序请求时下载。开启此选项将随着媒体导入时下载所有图片。这可能需要更久媒体库扫描时间。", + "OptionDownloadImagesInAdvanceHelp": "默认大多数图片只在客户端请求时下载。开启此选项将在新媒体导入时预先下载所有图片。这可能大大延长媒体库扫描时间。", "OptionDownloadMenuImage": "菜单", "OptionDownloadPrimaryImage": "封面图", "OptionDownloadThumbImage": "缩略图", @@ -994,7 +984,7 @@ "OptionHlsSegmentedSubtitles": "HLS分段字幕", "OptionHomeVideos": "照片", "OptionIgnoreTranscodeByteRangeRequests": "忽略转码字节范围请求", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "如果启用,这些请求会被兑现,但会忽略的字节范围标头。", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "这些请求会被兑现,但会忽略的字节范围标头。", "OptionImdbRating": "IMDb 评分", "OptionIsHD": "HD高清", "OptionIsSD": "SD标清", @@ -1009,9 +999,9 @@ "OptionOnInterval": "在一个期间", "OptionParentalRating": "家长分级", "OptionPlainStorageFolders": "显示所有文件夹作为一般存储文件夹", - "OptionPlainStorageFoldersHelp": "如果启用,所有文件夹在DIDL中显示为“ object.container.storageFolder ”,而不是一个更具体的类型,如“ object.container.person.musicArtist ” 。", + "OptionPlainStorageFoldersHelp": "所有文件夹在DIDL中显示为 \"object.container.storageFolder\" ,而不是一个更具体的类型,如 \"object.container.person.musicArtist\" 。", "OptionPlainVideoItems": "显示所有视频为一般视频项目", - "OptionPlainVideoItemsHelp": "如果启用,所有视频在DIDL中显示为“object.item.videoItem”,而不是一个更具体的类型,如“object.item.videoItem.movie ” 。", + "OptionPlainVideoItemsHelp": "所有视频在DIDL中显示为 \"object.item.videoItem\" ,而不是一个更具体的类型,如 \"object.item.videoItem.movie\" 。", "OptionPlayCount": "播放次数", "OptionPlayed": "已播放", "OptionPremiereDate": "首映日期", @@ -1167,7 +1157,6 @@ "TabAdvanced": "高级", "TabAlbumArtists": "专辑艺术家", "TabAlbums": "专辑", - "TabArtists": "艺术家", "TabCatalog": "目录", "TabChannels": "频道", "TabCodecs": "编解码器", @@ -1176,7 +1165,6 @@ "TabDashboard": "控制台", "TabDevices": "设备", "TabDirectPlay": "直接播放", - "TabDisplay": "显示", "TabEpisodes": "剧集", "TabFavorites": "我的最爱", "TabGenres": "风格", @@ -1185,7 +1173,6 @@ "TabLatest": "最新", "TabLiveTV": "电视直播", "TabLogs": "日志", - "TabMetadata": "元数据", "TabMovies": "电影", "TabMusic": "音乐", "TabMusicVideos": "音乐视频", @@ -1196,7 +1183,6 @@ "TabOther": "其他", "TabParentalControl": "家长控制", "TabPassword": "密码", - "TabPlayback": "播放", "TabPlaylists": "播放列表", "TabPlugins": "插件", "TabProfile": "个人配置", @@ -1211,9 +1197,7 @@ "TabShows": "节目", "TabSongs": "歌曲", "TabStreaming": "流媒体传输", - "TabSuggestions": "建议", "TabTrailers": "预告片", - "TabTranscoding": "转码", "TabUpcoming": "即将发布", "TabUsers": "用户", "Tags": "标签", @@ -1316,7 +1300,7 @@ "ErrorDeletingItem": "从 Jellyfin Server 删除项目时出错。请确认 Jellyfin Server 是否拥有对媒体目录的写权限,然后重试。", "GroupBySeries": "按系列分组", "HeaderApp": "应用程序", - "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但容器格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", + "DirectStreamHelp1": "该媒体文件的分辨率和编码(H.264、AC3 等)与您的设备兼容,但文件格式(.mkv、.avi、.wmv 等)不受支持。因此,视频在串流至您的设备之前将会被即时封装为另一种格式。", "HeaderAppearsOn": "同时出现于", "HeaderCancelSeries": "取消系列", "HeaderFavoriteEpisodes": "最爱的剧集", @@ -1361,14 +1345,14 @@ "OptionDownloadLogoImage": "标志", "OptionLoginAttemptsBeforeLockout": "确定在锁定之前可以进行多少次不正确的登录尝试。", "OptionLoginAttemptsBeforeLockoutHelp": "如果值为0,则表示将允许普通用户尝试三次、管理员尝试五次的默认值。将此设置为-1将禁用此功能。", - "PasswordResetProviderHelp": "选择一个密码重置提供者用于密码重置", + "PasswordResetProviderHelp": "选择一个密码重置提供者用于此用户申请重置密码", "PlaceFavoriteChannelsAtBeginning": "将最喜爱的频道置顶", "PlayNext": "播放下一个", "PlayNextEpisodeAutomatically": "自动播放下一集", "Premieres": "首映", "Raised": "提高", "Recordings": "录音", - "RefreshDialogHelp": "元数据根据设置和Jellyfin服务器中启用的网络服务进行刷新。", + "RefreshDialogHelp": "元数据根据设置和仪表盘中启用的网络服务进行刷新。", "RepeatEpisodes": "重复剧集", "Schedule": "日程", "Screenshot": "屏幕截图", @@ -1421,7 +1405,7 @@ "ButtonAddImage": "添加图片", "LabelPlayer": "播放器:", "LabelBaseUrl": "基础 URL:", - "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>。", + "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>", "MusicLibraryHelp": "重播 {0}音乐命名指南{1}。", "HeaderFavoritePeople": "最喜欢的人物", "OptionRandom": "随机", @@ -1470,7 +1454,6 @@ "New": "新的", "HeaderFavoritePlaylists": "收藏的播放列表", "ButtonTogglePlaylist": "播放列表", - "ButtonToggleContextMenu": "更多", "HeaderServerAddressSettings": "服务器地址设置", "HeaderRemoteAccessSettings": "远程访问设置", "HeaderHttpsSettings": "HTTPS 设置", @@ -1480,7 +1463,7 @@ "LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。", "LabelRequireHttps": "强制 HTTPS", "LabelStable": "稳定版", - "LabelEnableHttpsHelp": "开启服务器对所配置 HTTPS 端口的监听。必须配置有效的证书才会生效。", + "LabelEnableHttpsHelp": "监听已配置的 HTTPS 端口。必须配置有效的证书才会生效。", "LabelEnableHttps": "启用 HTTPS", "LabelChromecastVersion": "Chromecast版本", "HeaderDVR": "DVR", @@ -1539,5 +1522,13 @@ "ClearQueue": "清空队列", "StopPlayback": "停止播放", "Writers": "作者", - "ViewAlbumArtist": "查看专辑艺术家" + "ViewAlbumArtist": "查看专辑艺术家", + "Preview": "预览", + "SubtitleVerticalPositionHelp": "文字出现的行号。正数表示由上到下,负数表示由下到上。", + "LabelSubtitleVerticalPosition": "垂直位置:", + "PreviousTrack": "上一曲", + "MessageGetInstalledPluginsError": "获取已安装插件列表时出现错误。", + "MessagePluginInstallError": "安装插件时出现错误。", + "NextTrack": "下一曲", + "LabelUnstable": "不稳定" } diff --git a/src/strings/zh-hk.json b/src/strings/zh-hk.json index 830ab8b400..b8f48d474c 100644 --- a/src/strings/zh-hk.json +++ b/src/strings/zh-hk.json @@ -1,6 +1,5 @@ { "Add": "添加", - "ButtonAdd": "增加", "ButtonAddScheduledTaskTrigger": "新增觸發點", "ButtonAddUser": "添加用戶", "ButtonCancel": "取消", @@ -8,7 +7,6 @@ "ButtonDeleteImage": "刪除圖像", "ButtonEdit": "編輯", "ButtonFilter": "過濾", - "ButtonHelp": "幫助", "ButtonManualLogin": "手動登入", "ButtonNew": "新增", "ButtonOk": "確定", @@ -20,7 +18,6 @@ "ButtonRename": "重新命名", "ButtonResetPassword": "重設密碼", "ButtonRestart": "重新啟動", - "ButtonSave": "儲存", "ButtonSelectDirectory": "選擇目錄", "ButtonSignIn": "登入", "ButtonSignOut": "登出", @@ -57,7 +54,6 @@ "HeaderEasyPinCode": "簡易 Pin 碼", "HeaderFeatureAccess": "可以使用的功能", "HeaderFetchImages": "獲取圖像:", - "HeaderFilters": "篩選條件", "HeaderFrequentlyPlayed": "經常播放", "HeaderImageSettings": "圖像設置", "HeaderLatestEpisodes": "最新劇集", @@ -281,7 +277,6 @@ "TabAdvanced": "進階", "TabAlbumArtists": "唱片歌手", "TabAlbums": "專輯", - "TabArtists": "藝人", "TabCatalog": "目錄", "TabChannels": "頻道", "TabCollections": "藏品", @@ -293,7 +288,6 @@ "TabGuide": "指南", "TabInfo": "資訊", "TabLatest": "最新", - "TabMetadata": "媒體資料屬性", "TabMovies": "電影", "TabMusic": "音樂", "TabMusicVideos": "MV", @@ -310,9 +304,7 @@ "TabSettings": "設定", "TabShows": "節目", "TabSongs": "歌曲", - "TabSuggestions": "建議", "TabTrailers": "預告", - "TabTranscoding": "轉碼中", "TabUpcoming": "即將發佈", "TabUsers": "用戶", "TellUsAboutYourself": "介紹一下自己", @@ -375,7 +367,7 @@ "AlwaysPlaySubtitlesHelp": "無論語言是哪種音頻,都將加載與語言首選項匹配的字幕。", "AllowedRemoteAddressesHelp": "IP地址或IP /網絡掩碼條目的逗號分隔列表,用於允許遠程連接的網絡。 如果保留為空白,將允許所有遠程地址。", "AllowRemoteAccessHelp": "如果未選中,則將阻止所有遠程連接。", - "AllowRemoteAccess": "允許與此Jellyfin服務器的遠程連接。", + "AllowRemoteAccess": "允許與此Jellyfin伺服器的遠端連接。", "AllowFfmpegThrottlingHelp": "當轉碼或無損複製進度遠超於當前播放位置,暫停進程可減少資源消耗。 在不經常觀看的情況下,此功能最為有用。 如果遇到播放問題,請關閉此功能。", "AllowOnTheFlySubtitleExtractionHelp": "可以從視頻中提取嵌入式字幕,然後以純文本格式將其交付給客戶端,以幫助防止視頻轉碼。 在某些系統上,這可能需要很長時間,並且會導致提取過程中視頻播放停止。 如果客戶端設備本身不支持嵌入的字幕,則可以禁用此選項以通過視頻轉碼刻錄字幕。", "AllowOnTheFlySubtitleExtraction": "允許即時提取字幕", @@ -402,7 +394,7 @@ "Disabled": "已停用", "Directors": "導演", "Director": "導演", - "DirectStreaming": "直接直播", + "DirectStreaming": "直接串流", "DirectPlaying": "直接播放", "DetectingDevices": "偵測裝置中", "Desktop": "桌面", @@ -414,14 +406,14 @@ "DeleteImage": "刪除圖片", "ErrorDefault": "處理此請求時發生錯誤,請稍後再嘗試。", "Default": "預設", - "DateAdded": "日期已新增", - "CopyStreamURLSuccess": "成功複製URL。", - "CopyStreamURL": "複製直播URL", + "DateAdded": "新增日期", + "CopyStreamURLSuccess": "成功複製網址。", + "CopyStreamURL": "複製串流網址", "ContinueWatching": "繼續觀看", "Connect": "連接", - "ConfirmEndPlayerSession": "你要關閉 {0} 的Jellyfin嗎?", + "ConfirmEndPlayerSession": "是否關閉 {0} 的Jellyfin?", "ConfirmDeleteImage": "刪除圖片?", - "CommunityRating": "社群分數", + "CommunityRating": "社群評分", "ClientSettings": "客戶端設定", "ChannelNumber": "頻道號碼", "ChannelNameOnly": "只有頻道 {0}", @@ -432,7 +424,6 @@ "ButtonUninstall": "解除安裝", "ButtonTrailer": "預告", "ButtonTogglePlaylist": "播放清單", - "ButtonToggleContextMenu": "更多", "ButtonSplit": "分開", "ButtonStop": "停止", "ButtonStart": "開始", @@ -440,12 +431,10 @@ "ButtonSettings": "設定", "ButtonSend": "傳送", "ButtonSelectServer": "選擇伺服器", - "ButtonSearch": "搜尋", "ButtonScanAllLibraries": "掃瞄所有媒體櫃", "ButtonRevoke": "撤銷", "ButtonResume": "恢復", "ButtonResetEasyPassword": "重設PIN碼", - "ButtonRepeat": "重複", "ButtonProfile": "檔案", "ButtonPause": "暫停", "ButtonParentalControl": "家長控制", @@ -495,5 +484,21 @@ "Box": "盒裝", "Composer": "作曲家", "ButtonPreviousTrack": "上一曲", - "ButtonNextTrack": "下一曲" + "ButtonNextTrack": "下一曲", + "ColorTransfer": "色彩移動", + "ColorSpace": "色彩空間", + "ColorPrimaries": "主色調", + "CinemaModeConfigurationHelp": "影院模式可在播放主影片前加入預告片及自定引言片段,帶來戲院式體驗。", + "ChangingMetadataImageSettingsNewContent": "任何資料變更只適用於新加入到媒體庫的內容。如要更改舊有媒體的內容,請手動刷新資料。", + "ButtonUp": "上", + "ButtonShuffle": "隨機播放", + "ButtonSelectView": "選擇介面", + "ButtonOff": "關閉", + "ButtonLibraryAccess": "媒體庫存取", + "BookLibraryHelp": "支援文字及有聲書本。請查閱{0} 書本命名教學 {1}。", + "DatePlayed": "播放日期", + "CriticRating": "評論家評分", + "ConfirmDeletion": "確定刪除", + "ConfirmDeleteItems": "刪除這些物品將會從檔案系統及媒體庫中刪除。請問是否繼續?", + "ConfirmDeleteItem": "刪除此物品將會從檔案系統及媒體庫中刪除。請問是否繼續?" } diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index 086d163240..c937cdb161 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -3,8 +3,7 @@ "All": "全部", "AllowRemoteAccessHelp": "如果未勾選,所有連線都將被阻擋。", "Browse": "瀏覽", - "MessageBrowsePluginCatalog": "瀏覽我們的模組目錄來查看可用的模組。", - "ButtonAdd": "新增", + "MessageBrowsePluginCatalog": "瀏覽我們的附加元件目錄來查看可用的附加元件。", "ButtonAddServer": "新增伺服器", "ButtonAddUser": "新增使用者", "ButtonCancel": "取消", @@ -25,8 +24,6 @@ "ButtonRefreshGuideData": "更新電視節目表", "ButtonRemove": "清除", "ButtonResetPassword": "重設密碼", - "ButtonSave": "保存", - "ButtonSearch": "搜尋", "ButtonSelectDirectory": "選擇目錄", "ButtonSelectServer": "選擇伺服器", "ButtonSignIn": "登入", @@ -95,7 +92,7 @@ "HeaderUsers": "使用者", "Help": "說明", "ItemCount": "{0}個項目", - "LabelAudioLanguagePreference": "音頻語言偏好選項:", + "LabelAudioLanguagePreference": "音訊語言偏好選項:", "LabelCachePath": "快取路徑:", "LabelCollection": "收藏櫃:", "LabelContentType": "內容類型:", @@ -103,16 +100,16 @@ "LabelCurrentPassword": "當前的密碼:", "LabelDay": "日:", "LabelDisplayMissingEpisodesWithinSeasons": "顯示節目季度內缺少的單元", - "LabelEnableDlnaClientDiscoveryInterval": "尋找客戶端時間間隔(秒)", + "LabelEnableDlnaClientDiscoveryInterval": "尋找用戶端時間間隔", "LabelEnableDlnaDebugLogging": "記錄 DLNA 除錯資料到日誌", "LabelEnableDlnaDebugLoggingHelp": "將會建立非常大的日誌檔案,建議在進行故障排除時啟用。", "LabelEnableDlnaPlayTo": "播放到 DLNA 設備", - "LabelEnableRealtimeMonitor": "啟用實時監控", - "LabelEnableRealtimeMonitorHelp": "支持的檔案系統上的更改,將會立即處理。", + "LabelEnableRealtimeMonitor": "啟用即時監控", + "LabelEnableRealtimeMonitorHelp": "檔案的更改將在支援的檔案系統上立即處理。", "LabelEvent": "事件:", "LabelEveryXMinutes": "每:", "LabelFinish": "完成", - "LabelServerNameHelp": "名稱將用於辨識服務器,預設是伺服器的電腦名稱。", + "LabelServerNameHelp": "名稱將作為伺服器名稱,預設是伺服器的主機名稱。", "LabelLanguage": "語言:", "LabelMaxBackdropsPerItem": "每個項目背景的最大數目:", "LabelMaxParentalRating": "最大允許的家長評級:", @@ -134,8 +131,8 @@ "LabelPlaylist": "播放清單:", "LabelPrevious": "上一個", "LabelRefreshMode": "更新模式:", - "LabelSaveLocalMetadata": "將媒體圖像及資料檔存到媒體所在的資料夾", - "LabelSaveLocalMetadataHelp": "直接儲存圖片及資料到媒體所在的資料夾能使編輯更容易。", + "LabelSaveLocalMetadata": "將媒體圖像及中繼資料存到媒體所在的資料夾", + "LabelSaveLocalMetadataHelp": "直接儲存圖片及中繼資料到媒體所在的資料夾能使編輯更容易。", "LabelTime": "時間:", "LabelTriggerType": "觸發類型:", "LabelUser": "使用者:", @@ -148,14 +145,14 @@ "MessageItemsAdded": "已新增項目。", "MessageNoMovieSuggestionsAvailable": "目前並沒有推薦的電影,開始觀看並對您的電影評分後,我們就會為您推薦您可能會喜歡的內容。", "MessageNothingHere": "這裡沒有什麼。", - "MessagePasswordResetForUsers": "下列使用者的密碼已被重新設置。該使用者現在可以使用在密碼重置時所使用之 PIN 代碼進行登入。", - "MessagePleaseEnsureInternetMetadata": "請確保已啟用從互聯網下載媒體資料。", + "MessagePasswordResetForUsers": "下列使用者的密碼已被重新設置。該使用者現在可以使用在密碼重設時所使用之 PIN 代碼進行登入。", + "MessagePleaseEnsureInternetMetadata": "請確保已啟用從網際網路下載媒體資料。", "Monday": "星期一", "MoreUsersCanBeAddedLater": "也可於控制台內新增使用者。", "MySubtitles": "我的字幕", "NewCollection": "新合集", "NewCollectionHelp": "收藏櫃讓您能夠建立個人化的影音及其他媒體的分類。", - "NewCollectionNameExample": "例子:星球大戰合集", + "NewCollectionNameExample": "例子:星際大戰合集", "MessageNoNextUpItems": "沒有找到。開始看你的節目!", "NoSubtitleSearchResultsFound": "無結果。", "OptionAlbum": "專輯", @@ -176,7 +173,7 @@ "OptionDatePlayed": "播放日期", "OptionDescending": "降序", "OptionDisableUser": "停用該使用者", - "OptionDisableUserHelp": "被停用的使用者將被伺服器封鎖,即便正處於連線狀態也將被中斷。", + "OptionDisableUserHelp": "被停用的使用者將被伺服器封鎖,現有的連線也將中斷。", "OptionDislikes": "不喜歡", "OptionDownloadArtImage": "圖像", "OptionDownloadBackImage": "媒體包裝背面", @@ -185,7 +182,7 @@ "OptionDownloadLogoImage": "標誌", "OptionDownloadMenuImage": "菜單", "OptionDownloadPrimaryImage": "主要圖", - "OptionDownloadThumbImage": "縮略圖", + "OptionDownloadThumbImage": "縮圖", "OptionDvd": "DVD", "OptionEnded": "完結", "OptionFavorite": "我的最愛", @@ -195,7 +192,7 @@ "OptionHasThemeVideo": "主題影片", "OptionHideUser": "在登入頁面隱藏此使用者", "OptionImdbRating": "IMDB評分", - "OptionIsHD": "高清", + "OptionIsHD": "高畫質", "OptionIsSD": "標清", "OptionLikes": "喜歡", "OptionMissingEpisode": "缺少了的單元", @@ -234,17 +231,17 @@ "RecordingCancelled": "已取消排程錄製。", "RecordingScheduled": "已排程錄製。", "Refresh": "重新整理", - "RefreshDialogHelp": "詳細資料的更新方式會依據 Jellyfin 的設定及已經啟用的網路服務來進行。", + "RefreshDialogHelp": "中繼資料的更新方式會依據伺服器設定及已經啟用的網路服務來進行。", "ReplaceAllMetadata": "取代所有中繼資料", "ReplaceExistingImages": "取代現有圖片", "Saturday": "星期六", "Save": "保存", "Search": "搜尋", - "SearchForCollectionInternetMetadata": "在互聯網上搜索媒體圖像和資料", - "SearchForMissingMetadata": "搜尋遺失的詳細資料", + "SearchForCollectionInternetMetadata": "在網路上搜尋媒體圖像和資料", + "SearchForMissingMetadata": "搜尋遺失的中繼資料", "SearchForSubtitles": "搜尋字幕", "SeriesRecordingScheduled": "已排程錄製整個系列。", - "ServerUpdateNeeded": "此Jellyfin伺服器需要更新,請至{0}取得最新版本", + "ServerUpdateNeeded": "伺服器需要更新,請至 {0} 取得最新版本", "SettingsSaved": "設置已保存。", "Share": "分享", "Subtitles": "字幕", @@ -252,7 +249,6 @@ "TabAdvanced": "進階", "TabAlbumArtists": "專輯歌手", "TabAlbums": "專輯", - "TabArtists": "歌手", "TabCatalog": "目錄", "TabChannels": "頻道", "TabEpisodes": "單元", @@ -261,11 +257,10 @@ "TabInfo": "資訊", "TabLatest": "最新", "TabLiveTV": "電視", - "TabMetadata": "媒體資料", "TabMovies": "電影", "TabMusic": "音樂", - "TabMyPlugins": "我的插件", - "TabNetworks": "網絡", + "TabMyPlugins": "我的附加元件", + "TabNetworks": "網路", "TabPassword": "密碼", "TabProfile": "設定", "TabProfiles": "設定", @@ -275,9 +270,7 @@ "TabSettings": "設定", "TabShows": "節目", "TabSongs": "歌曲", - "TabSuggestions": "建議", "TabTrailers": "預告", - "TabTranscoding": "轉碼中", "TabUpcoming": "接下來", "TellUsAboutYourself": "介紹一下自己", "ThisWizardWillGuideYou": "此精靈將帶你完成安裝過程,開始之前,請選擇您慣用的語言。", @@ -285,14 +278,14 @@ "TrackCount": "{0} 個曲目", "Tuesday": "星期二", "UninstallPluginConfirmation": "你確定要解除安裝 {0}?", - "HeaderUninstallPlugin": "解除安裝插件", + "HeaderUninstallPlugin": "解除安裝附加元件", "UserProfilesIntro": "Jellyfin 可單獨對使用者進行設定,所有使用者擁有自己的顯示設置,播放狀態和家長控制。", "Wednesday": "星期三", "WelcomeToProject": "歡迎使用 Jellyfin!", "WizardCompleted": "這就是我們所需的全部資訊,Jellyfin 現在正在收集你的媒體櫃的資料,在這段時間內,不妨參考我們推出的應用程式。按一下完成進入控制台。", "Actor": "演員", "AddToPlayQueue": "加入播放清單", - "AddToPlaylist": "加入播放列表", + "AddToPlaylist": "加入播放清單", "Absolute": "絕對", "AccessRestrictedTryAgainLater": "目前存取受限,請稍後再試。", "AddedOnValue": "已加入 {0}", @@ -305,7 +298,7 @@ "AllLibraries": "所有媒體", "AllowMediaConversion": "允許媒體轉檔", "AllowMediaConversionHelp": "授予或拒絕存取媒體轉檔功能。", - "AllowRemoteAccess": "允許遠端存取該 Jellyfin 伺服器。", + "AllowRemoteAccess": "允許遠端存取伺服器。", "AlwaysPlaySubtitles": "總是顯示字幕", "AlwaysPlaySubtitlesHelp": "將會載入符合語音設定的字幕,無論語音是哪一個語言。", "AnyLanguage": "任何語言", @@ -338,21 +331,20 @@ "AirDate": "播出日期", "Aired": "已播於", "AllEpisodes": "所有集數", - "AllowHWTranscodingHelp": "若啟用,將會允許調諧器同步轉檔,這會減少 Jellyfin 伺服器轉檔必要。", - "AllowOnTheFlySubtitleExtraction": "允許同步字幕截取", - "AllowOnTheFlySubtitleExtractionHelp": "可以從影片中提取內建字幕並以純文字的形式給 Jellyfin 應用程式以避免影片轉檔。某些系統中提取的過程可能會花費較長時間並導致影片播放出現停滯。若停用這個選項,當內建字幕不被播放端設備支援時,字幕將透過轉檔嵌入影片中。", - "AllowedRemoteAddressesHelp": "可以從非本地連線的IP位址,用冒號分隔,留白則允許所有IP。", + "AllowHWTranscodingHelp": "若啟用,將會允許調諧器同步轉檔,能夠減少伺服器轉檔需求。", + "AllowOnTheFlySubtitleExtraction": "允許即時擷取字幕", + "AllowOnTheFlySubtitleExtractionHelp": "從影片中提取內建字幕並以純文字的形式顯示以避免影片轉檔。某些系統中提取的過程可能花費較長時間並導致影片播放出現停滯。若停用這個選項,當內建字幕不被播放端設備支援時,字幕將透過轉檔嵌入影片中。", + "AllowedRemoteAddressesHelp": "可以從非本地連線的 IP 位址或 IP/子網域遮罩清單,用冒號分隔。 留白則允許所有IP。", "BookLibraryHelp": "支援有聲書和電子書。請瀏覽 {0}書籍命名指南{1}。", "Box": "盒子", "BoxRear": "盒子(背面)", - "BurnSubtitlesHelp": "根據字幕格式決定伺服器在影片轉檔時是否燒錄字幕。避免燒錄字幕時消耗過多伺服器資源。選擇「自動」以燒錄基於圖片的字幕格式(如 VOBSUB、PGS 或 SUB/IDX 等)和一些複雜的 ASS/SSA 字幕。", + "BurnSubtitlesHelp": "根據字幕格式決定伺服器在影片轉檔時是否燒錄字幕。避免燒錄字幕時消耗過多伺服器資源。選擇「自動」以燒錄圖片格式的字幕(如 VOBSUB、PGS 或 SUB/IDX 等)與複雜的 ASS/SSA 字幕。", "ButtonArrowDown": "下", "ButtonConnect": "連結", "ButtonDown": "下", "ButtonDownload": "下載", "ButtonEditOtherUserPreferences": "編輯使用者個人檔案、大頭貼和個人設定。", "ButtonFullscreen": "全螢幕", - "ButtonHelp": "幫助", "ButtonInfo": "詳細資料", "ButtonLibraryAccess": "媒體庫存取", "ButtonManualLogin": "手動登入", @@ -366,7 +358,6 @@ "ButtonPreviousTrack": "上一首", "ButtonProfile": "個人首頁", "ButtonRename": "重新命名", - "ButtonRepeat": "重覆播放", "ButtonResetEasyPassword": "重設簡單 PIN 碼", "ButtonRestart": "重新啟動", "ButtonResume": "繼續播放", @@ -388,7 +379,7 @@ "CancelRecording": "取消錄影", "CancelSeries": "取消系列", "Categories": "類別", - "ChangingMetadataImageSettingsNewContent": "更改影片中繼資料或媒體圖像的下載設定僅對以後添加至媒體庫中的新内容生效,若需更改已存在的資料,請手動更新中繼資料。", + "ChangingMetadataImageSettingsNewContent": "更改影片中繼資料或媒體圖像的下載設定僅對以後添加至媒體庫中的新內容生效,若需更改已存在的資料,請手動更新中繼資料。", "ChannelAccessHelp": "選擇與此使用者共享之頻道。管理員將能夠使用中繼資料管理器編輯所有資料夾。", "ChannelNameOnly": "只在頻道 {0}", "ChannelNumber": "頻道號碼", @@ -424,7 +415,7 @@ "DeviceAccessHelp": "只適用於用唯一辨識方法的裝置,並不會阻止瀏覽器存取。已過濾的使用者裝置會被拒絕存取,直到他們被批准。", "DirectPlaying": "直接播放", "DirectStreamHelp1": "媒體在畫質和媒體類型(H.264、AC3 等)方面與裝置相容。但是在不相容的檔案格式(.mkv、.avi、.wmv 等)中,在影片傳輸到裝置之前,將會重新轉檔。", - "DirectStreamHelp2": "直接串流檔案會占用非常少的處理效能並且影片的品質不會有任何損失。", + "DirectStreamHelp2": "直接串流檔案會占用較少的 CPU 且影片品質不會有任何損失。", "DirectStreaming": "直接串流", "Director": "導演", "Directors": "導演", @@ -441,7 +432,7 @@ "Down": "下", "DownloadsValue": "{0} 個下載", "DrmChannelsNotImported": "受 DMR 保護的頻道將不會被導入。", - "DropShadow": "背景投影", + "DropShadow": "陰影", "EasyPasswordHelp": "你的簡易 PIN 碼將會用於在支援的 Jellyfin 應用上進行離線存取,同時也可被用於區域網路的登入。", "EditMetadata": "編輯中繼資料", "EditSubtitles": "編輯字幕", @@ -454,7 +445,7 @@ "EnableExternalVideoPlayersHelp": "當你開始播放影片時,將會顯示外部播放器選單。", "EnableHardwareEncoding": "啟用硬體編碼", "EnableNextVideoInfoOverlay": "在播放時顯示下一個影片資訊", - "EnableNextVideoInfoOverlayHelp": "在影片結束前,顯示當前播放列表中下一個影片的資訊。", + "EnableNextVideoInfoOverlayHelp": "在影片結束前,顯示當前播放清單中下一個影片的資訊。", "EnablePhotos": "顯示圖片", "EnablePhotosHelp": "圖片將被偵測到並和其他媒體檔案一起顯示。", "EnableStreamLooping": "自動循環播放直播", @@ -464,21 +455,21 @@ "EnableThemeVideos": "啟用主題影片", "EnableThemeVideosHelp": "瀏覽媒體庫時主題影片將作為背景影片播放。", "Episodes": "劇集", - "ErrorAddingListingsToSchedulesDirect": "在將電視節目時間表新增到您的 Schedules Direct 帳號時出現錯誤。每個 Schedules Direct 帳號只允許有限的時間表。您在繼續前可能需要登入 Schedules Direct 網站並刪除帳號中的其它列表。", - "ErrorAddingMediaPathToVirtualFolder": "新增媒體路徑時發生錯誤,請確認路徑是否有效,且你的 Jellyfin 伺服器有對該位置的存取權。", - "ErrorAddingTunerDevice": "新增調諧器設備時發生錯誤,請確認它是否可被存取後再試一次。", - "ErrorAddingXmlTvFile": "存取 XMLTV 檔案時發生錯誤,請確認該檔案是否存在後再試一次。", - "ErrorDeletingItem": "從 Jellyfin 伺服器刪除項目時發生錯誤,請確認伺服器對該磁碟有寫入權限並再試一次。", + "ErrorAddingListingsToSchedulesDirect": "在將電視節目時間表新增到您的 Schedules Direct 帳號時出現錯誤。每個 Schedules Direct 帳號只允許有限的時間表。您在繼續前可能需要登入 Schedules Direct 網站並刪除帳號中的其它清單。", + "ErrorAddingMediaPathToVirtualFolder": "新增媒體路徑時發生錯誤,請確認路徑是否有效,且 Jellyfin 具該位置的存取權。", + "ErrorAddingTunerDevice": "新增調諧器設備時發生錯誤,請確認它是否可被存取後重試。", + "ErrorAddingXmlTvFile": "存取 XMLTV 檔案時發生錯誤,請確認該檔案是否存在後重試。", + "ErrorDeletingItem": "從伺服器刪除項目時發生錯誤,請確認伺服器對該位址有寫入權限並重試。", "ErrorGettingTvLineups": "下載電視節目表時發生錯誤,請確認你的資訊是否正確並重試。", "ErrorStartHourGreaterThanEnd": "結束時間必須在開始時間後。", - "ErrorPleaseSelectLineup": "請選擇節目表,然後再試一次。如果沒有可用的節目表,請檢查您的使用者名稱、密碼和郵遞區號是否正確。", + "ErrorPleaseSelectLineup": "請選擇節目表,然後重試。若無可用的節目表,請檢查您的使用者名稱、密碼和郵遞區號是否正確。", "ErrorSavingTvProvider": "儲存電視供應商時發生錯誤,請確認是否可存取並重試。", "EveryNDays": "每 {0} 天", "ExitFullscreen": "結束全螢幕", "ExtraLarge": "特大", "ExtractChapterImagesHelp": "擷取章節圖片將允許 Jellyfin 顯示圖片形式的章節選單,過程可能會非常緩慢、佔用大量 CPU 資源,並且可能需要幾 GB 的硬碟空間。\n擷取會在影片被偵測到時啟動,同時也可作為一個夜間計劃任務運行,這個任務可以在「計劃任務」選項中進行設定,不建議在尖峰時刻使用時間進行這個任務。", "Extras": "額外", - "FFmpegSavePathNotFound": "我們無法通過你輸入的路徑找到 FFmpeg。 FFprobe 同樣也是必要且應該被放在同一個資料夾中。他們通常會被打包在一起以供下載。請檢查這個路徑然後再試一次。", + "FFmpegSavePathNotFound": "我們無法通過你輸入的路徑找到 FFmpeg。 FFprobe 同樣也是必要且應該被放在同一個資料夾中。他們通常會被打包在一起以供下載。請檢查這個路徑後重試。", "FastForward": "快轉", "Favorites": "我的最愛", "Features": "功能", @@ -496,7 +487,7 @@ "GroupVersions": "按版本分組", "GuestStar": "客串", "Guide": "指南", - "GuideProviderSelectListings": "選擇列表", + "GuideProviderSelectListings": "選擇清單", "H264CrfHelp": "CRF 是 x264 編碼器的預設畫質設置。此方法允許編碼器自動分配位元速率來試著達到一定輸出品質。讓每個畫格得到它需要的位元數來保持所需的品質等級。CRF 會得到最佳的位元速率分配結果。", "EncoderPresetHelp": "速度越慢則會得到更好的壓縮編碼效率。", "HDPrograms": "HD 節目", @@ -507,7 +498,7 @@ "HeaderActivity": "活動", "HeaderAddScheduledTaskTrigger": "新增觸發", "HeaderAddToCollection": "加到收藏", - "HeaderAddToPlaylist": "加到播放列表", + "HeaderAddToPlaylist": "加到播放清單", "HeaderAddUpdateImage": "新增/更新圖片", "HeaderAlbumArtists": "專輯演出者", "HeaderAlbums": "專輯", @@ -515,7 +506,7 @@ "HeaderAllowMediaDeletionFrom": "允許從中刪除媒體", "HeaderApiKey": "API 金鑰", "HeaderApiKeys": "API 金鑰", - "HeaderApiKeysHelp": "外部應用程式需要有一個 API 金鑰以用於和 Jellyfin 伺服器溝通。金鑰會在 Jellyfin 使用者登入時自動發行,或者你可以手動為應用程式產生一個金鑰。", + "HeaderApiKeysHelp": "外部應用程式需要有一個 API 金鑰以用於和伺服器溝通。金鑰會在使用者登入時自動發行,也可以手動產生一個金鑰。", "HeaderApp": "應用程式", "HeaderAppearsOn": "同時出現於", "HeaderAudioBooks": "有聲書", @@ -529,15 +520,15 @@ "HeaderChannelAccess": "節目存取", "HeaderChapterImages": "章節圖片", "HeaderCodecProfile": "編碼設定檔", - "HeaderCodecProfileHelp": "編碼器的設定檔標明了設備播放特定編碼時的限制;如果在限制之內則媒體將被轉碼,否則編碼器將被設定為直接播放。", + "HeaderCodecProfileHelp": "編碼器的設定檔標明了設備播放特定編碼時的限制;如果在限制之內則媒體將被轉檔,否則編碼器將被設定為直接播放。", "HeaderConfigureRemoteAccess": "設定遠端控制", - "HeaderConfirmPluginInstallation": "確認插件安裝", + "HeaderConfirmPluginInstallation": "確認附加元件安裝", "HeaderConfirmProfileDeletion": "確認刪除個人資料", - "HeaderConfirmRevokeApiKey": "重置 API 金鑰", + "HeaderConfirmRevokeApiKey": "重設 API 金鑰", "HeaderConnectToServer": "連結至伺服器", "HeaderConnectionFailure": "連結失敗", "HeaderContainerProfile": "影片載體設定", - "HeaderContainerProfileHelp": "影片容器的設定檔標明了設備播放特定媒體格式時的限制。如果在限制之內則媒體將被轉碼,否則媒體格式將被設定為直接播放。", + "HeaderContainerProfileHelp": "影片容器的設定檔標明了設備播放特定媒體格式時的限制。如果在限制之內則媒體將被轉檔,否則媒體格式將被設定為直接播放。", "HeaderContinueListening": "繼續聆聽", "HeaderContinueWatching": "繼續觀賞", "HeaderDateIssued": "發布日期", @@ -552,7 +543,6 @@ "HeaderDevices": "裝置", "HeaderDirectPlayProfile": "直接播放設定檔", "HeaderDirectPlayProfileHelp": "新增直接播放設定檔,標明哪些媒體格式設備可以自己處理。", - "HeaderDisplay": "顯示", "HeaderDownloadSync": "下載與同步", "HeaderEditImages": "編輯圖片", "HeaderEnabledFields": "已啟用的欄位", @@ -564,13 +554,11 @@ "HeaderFavoriteArtists": "最愛演出者", "HeaderFavoriteEpisodes": "最愛影集", "HeaderFavoriteMovies": "最愛電影", - "HeaderFavoritePlaylists": "最愛播放列表", + "HeaderFavoritePlaylists": "最愛播放清單", "HeaderFavoriteShows": "最愛節目", "HeaderFavoriteSongs": "最愛歌曲", "HeaderFavoriteVideos": "最愛的影片", - "HeaderFeatures": "功能", "HeaderFetcherSettings": "擷取器設置", - "HeaderFilters": "篩選條件", "HeaderForKids": "給兒童", "HeaderGenres": "類型", "HeaderHttpHeaders": "HTTP 標頭", @@ -583,7 +571,7 @@ "HeaderItems": "項目", "HeaderKeepRecording": "繼續錄製", "HeaderKeepSeries": "保存系列", - "HeaderKodiMetadataHelp": "要啟用或停用 NFO 中繼資料,請在 Jellyfin「建立媒體庫」頁面中編輯該媒體庫「中繼資料儲存」部分。", + "HeaderKodiMetadataHelp": "要啟用或停用 NFO 中繼資料,請在設定裡「建立媒體庫」頁面中編輯「中繼資料儲存」部分。", "HeaderLatestMedia": "最新媒體", "HeaderLatestMusic": "最新音樂", "HeaderLibraries": "媒體庫", @@ -617,7 +605,7 @@ "HeaderPlayOn": "播放在", "HeaderPlayback": "媒體播放", "HeaderPlaybackError": "播放錯誤", - "HeaderPluginInstallation": "插件安裝", + "HeaderPluginInstallation": "附加元件安裝", "HeaderRecordingOptions": "錄影選項", "HeaderRecordingPostProcessing": "錄影後製", "HeaderRemoteControl": "遙控", @@ -629,8 +617,8 @@ "HeaderSeasons": "季數", "HeaderSecondsValue": "{0} 秒", "HeaderSelectPath": "選擇位址", - "HeaderSelectTranscodingPath": "選擇轉碼暫放位址", - "HeaderSelectTranscodingPathHelp": "瀏覽或輸入轉碼用來存暫時資料的位址。資料夾需可讀取。", + "HeaderSelectTranscodingPath": "選擇轉檔暫放位址", + "HeaderSelectTranscodingPathHelp": "瀏覽或輸入轉檔用來存暫時資料的位址。資料夾需具寫入權限。", "HeaderSendMessage": "傳送訊息", "HeaderSeriesOptions": "系列選項", "HeaderSeriesStatus": "系列狀態", @@ -645,7 +633,6 @@ "HeaderStopRecording": "停止錄影", "HeaderSubtitleAppearance": "字幕外觀", "HeaderSubtitleDownloads": "字幕下載", - "HeaderTags": "標籤", "HeaderThisUserIsCurrentlyDisabled": "這個使用者目前停用", "HeaderTracks": "軌", "HeaderTunerDevices": "調諧器裝置", @@ -659,19 +646,19 @@ "AuthProviderHelp": "選擇用於驗證使用者密碼的身份驗證提供者。", "HeaderParentalRatings": "家長評級", "HeaderProfile": "設定檔", - "HeaderProfileInformation": "設定檔信息", - "HeaderProfileServerSettingsHelp": "這些數值將控制 Jellyfin 伺服器如何呈現給設備。", + "HeaderProfileInformation": "設定檔訊息", + "HeaderProfileServerSettingsHelp": "這些數值將控制伺服器如何呈現給設備。", "HeaderResponseProfile": "回覆設定檔", "HeaderResponseProfileHelp": "當播放某些類型的媒體時,回覆設定檔提供一種方法來發送自定訊息到設備。", "HeaderSchedule": "日程表", "HeaderSelectCertificatePath": "選擇證書路徑", - "HeaderSelectMetadataPath": "選擇數據路徑", + "HeaderSelectMetadataPath": "選擇中繼資料路徑", "HeaderSubtitleProfile": "字幕設定檔", "HeaderSubtitleProfiles": "字幕設定檔", "HeaderSubtitleProfilesHelp": "字幕設定檔描述設備所支援的字幕格式。", "HeaderTaskTriggers": "任務觸發", - "HeaderTranscodingProfile": "轉碼設定", - "HeaderTranscodingProfileHelp": "新增轉碼設定檔標明哪些媒體格式需要轉碼處理。", + "HeaderTranscodingProfile": "轉檔設定", + "HeaderTranscodingProfileHelp": "新增轉檔設定檔標明哪些媒體格式需要轉檔處理。", "HeaderTuners": "調諧器", "HeaderTypeImageFetchers": "{0} 圖片獲取程序", "HeaderTypeText": "輸入文字", @@ -694,15 +681,15 @@ "HttpsRequiresCert": "要啟用安全連線,您需要提供受信任的SSL證書,如 Let's Encrypt。 請提供證書,或停用安全連線。", "Identify": "識別", "Images": "圖片", - "ImportFavoriteChannelsHelp": "若啟用,只有在調諧器設備中被標記為我的最愛的頻道才會被導入。", - "ImportMissingEpisodesHelp": "若啟用,有關缺失劇集的數據導入您的 Jellyfin 媒體庫,並在季節和系列中顯示。 這可能會導致媒體庫掃描延長。", - "InstallingPackage": "正在安装 {0}(版本 {1})", + "ImportFavoriteChannelsHelp": "若啟用,僅於調諧器設備中被標記為我的最愛的頻道才會被導入。", + "ImportMissingEpisodesHelp": "缺失劇集的資料將導入您的媒體庫,並在季度與系列中顯示。 可能導致媒體庫掃描延長。", + "InstallingPackage": "正在安裝 {0}(版本 {1})", "InstantMix": "即時混音", "Items": "項目", "Kids": "兒童", "Label3DFormat": "3D 格式:", "LabelAbortedByServerShutdown": "(因為伺服器關閉被中止)", - "LabelAccessDay": "一周中的何時:", + "LabelAccessDay": "一週中的何時:", "LabelAccessEnd": "結束時間:", "LabelAccessStart": "開始時間:", "LabelAirDays": "播出日期:", @@ -713,27 +700,26 @@ "LabelAlbum": "專輯:", "LabelAlbumArtHelp": "PN 在 upnp:albumArtURI 裡的 dlna:profileID 屬性用於專輯封面。某些設備不管圖像的尺寸大小,都會要求特定的值。", "LabelAlbumArtMaxHeight": "專輯封面最大高度:", - "LabelAlbumArtMaxHeightHelp": "通過 upnp:albumArtURI 顯示的專輯封面超鏈接的最大分辨率。", + "LabelAlbumArtMaxHeightHelp": "通過 upnp:albumArtURI 顯示的專輯封面超連結的最大解析度。", "LabelAlbumArtMaxWidth": "專輯封面最大寬度:", - "LabelAlbumArtMaxWidthHelp": "通過 upnp:albumArtURI 顯示的專輯封面超鏈接的最大解析度。", + "LabelAlbumArtMaxWidthHelp": "通過 upnp:albumArtURI 顯示的專輯封面超連結的最大解析度。", "LabelAlbumArtPN": "專輯封面 PN :", "LabelAlbumArtists": "專輯作家:", - "LabelAll": "所有", - "LabelAllowHWTranscoding": "允許硬體轉碼", - "LabelAllowedRemoteAddresses": "遠端 IP 過濾:", - "LabelAllowedRemoteAddressesMode": "遠端 IP 過濾模式:", + "LabelAllowHWTranscoding": "允許硬體轉檔", + "LabelAllowedRemoteAddresses": "遠端 IP 位址過濾:", + "LabelAllowedRemoteAddressesMode": "遠端 IP 位址過濾模式:", "LabelAppName": "APP 名稱", "LabelAppNameExample": "例如:可愛蹦蹦主機、小安的 Jellyfin", "LabelArtists": "藝人:", - "LabelArtistsHelp": "分開多重使用 ;", - "LabelAudio": "音頻", + "LabelArtistsHelp": "將多位演出者以「;」分隔。", + "LabelAudio": "音訊", "LabelAuthProvider": "認證提供者:", "LabelAutomaticallyRefreshInternetMetadataEvery": "從網路自動抓取中繼資料:", - "LabelBindToLocalNetworkAddress": "綁定本地網絡地址:", - "LabelBindToLocalNetworkAddressHelp": "(選用)覆蓋 HTTP 伺服器綁定的本地 IP 地址。如果留空,伺服器將會監聽所有可用的地址。改變此欄位需重啟 Jellyfin 伺服器。", + "LabelBindToLocalNetworkAddress": "綁定本地網路地址:", + "LabelBindToLocalNetworkAddressHelp": "(選用)覆蓋 HTTP 伺服器綁定的本地 IP 位址。如果留空,伺服器將會監聽所有可用的地址。改變此欄位需重啟 Jellyfin 伺服器。", "LabelBirthDate": "出生日期:", "LabelBirthYear": "出生年:", - "LabelBlastMessageInterval": "活動信號的時間間隔(秒)", + "LabelBlastMessageInterval": "活動信號的時間間隔", "LabelBlastMessageIntervalHelp": "確定伺服器活動消息之間的持續時間(秒)。", "LabelBlockContentWithTags": "通過標籤鎖定內容:", "LabelBurnSubtitles": "燒錄字幕:", @@ -748,7 +734,7 @@ "LabelCustomCertificatePath": "自訂 SSL 證書路徑:", "LabelCustomCertificatePathHelp": "提供包含證書和金鑰的 PKCS #12 文件的路徑以在自訂域名上啟用 TLS。", "LabelCustomCss": "自訂 CSS:", - "LabelCustomCssHelp": "於 Web 介面套用您的自訂樣式。", + "LabelCustomCssHelp": "於網頁介面套用您的自訂樣式。", "LabelCustomDeviceDisplayName": "顯示名稱:", "Depressed": "凹陷", "HeaderHome": "首頁", @@ -757,7 +743,7 @@ "LabelCustomDeviceDisplayNameHelp": "指定自訂的顯示名稱,或者留空以使用設備自己報告的名稱。", "LabelCustomRating": "自訂分級:", "LabelDateAdded": "新增日期:", - "LabelDateAddedBehavior": "新内容加入的日期應使用:", + "LabelDateAddedBehavior": "新內容加入的日期應使用:", "LabelDateTimeLocale": "設定時區:", "LabelDeathDate": "死亡時間:", "LabelDefaultScreen": "預設分頁:", @@ -769,7 +755,7 @@ "LabelDisplayLanguageHelp": "翻譯 Jellyfin 是個進行中的專案。", "LabelDisplayMode": "顯示模式:", "LabelDisplayName": "顯示名稱:", - "MessageNoPluginsInstalled": "您尚未安裝任何模組。", + "MessageNoPluginsInstalled": "您尚未安裝任何附加元件。", "Mobile": "手機", "Option3D": "3D", "OptionEveryday": "每天", @@ -777,17 +763,17 @@ "LabelAudioBitDepth": "音訊位元深度:", "LabelBaseUrl": "根路徑:", "LabelIconMaxHeight": "圖示最高高度:", - "LabelHttpsPortHelp": "Jellyfin 的 HTTPS 伺服器應綁定的 TCP 端口。", - "LabelIconMaxHeightHelp": "通過 upnp:icon 的圖標最大解析度。", + "LabelHttpsPortHelp": "HTTPS 伺服器的 TCP 埠。", + "LabelIconMaxHeightHelp": "通過 upnp:icon 的圖示最大解析度。", "CopyStreamURL": "複製串流連結", "MediaInfoDefault": "預設", "MediaInfoStreamTypeAudio": "音訊", - "LabelDateAddedBehaviorHelp": "如果原本就有中繼資料,將會優先使用其數據。", + "LabelDateAddedBehaviorHelp": "若原本就有中繼資料,將會優先使用。", "LabelScreensaver": "螢幕保護程式:", "LabelSeasonNumber": "季:", "LabelDropImageHere": "拖移圖片到這裡,或是點擊來選取。", "LabelImageType": "圖片格式:", - "LabelIdentificationFieldHelp": "不區分大小寫的子字符串或正則表達式。", + "LabelIdentificationFieldHelp": "不區分大小寫的子字串或正則表達式。", "Large": "大", "LabelTranscodingAudioCodec": "音訊編碼:", "MessageSettingsSaved": "設定已儲存.", @@ -837,9 +823,9 @@ "Shuffle": "隨機播放", "Smart": "智慧", "HeaderFavoriteBooks": "最愛的書籍", - "LabelAudioBitrate": "音訊比特率:", + "LabelAudioBitrate": "音訊位元率:", "LabelAudioCodec": "音訊編碼:", - "LabelBitrate": "比特率:", + "LabelBitrate": "位元率:", "LabelAudioChannels": "音訊聲道:", "LabelAudioSampleRate": "音訊取樣率:", "LabelFont": "字體:", @@ -847,12 +833,12 @@ "LabelDisplayOrder": "顯示順序:", "LabelEnableBlastAliveMessages": "活動訊息", "LabelEnableDlnaServer": "啟用 DLNA 伺服器", - "LabelEnableDlnaServerHelp": "允許網絡上的 UPnP 設備瀏覽和播放內容。", + "LabelEnableDlnaServerHelp": "允許網路上的 UPnP 設備瀏覽和播放內容。", "LabelEnableHardwareDecodingFor": "為以下啟用硬體解碼:", "LabelEpisodeNumber": "集:", "LabelBaseUrlHelp": "您可以在此處自訂伺服器 URL 路徑的子目錄,如:http://example.com/<baseurl>", "LabelExtractChaptersDuringLibraryScan": "於媒體庫掃描時擷取章節圖片", - "LabelHttpsPort": "本地 HTTPS 端口:", + "LabelHttpsPort": "本地 HTTPS 埠:", "LabelFailed": "失敗", "LabelSubtitles": "字幕", "LabelSupportedMediaTypes": "支援的媒體類型:", @@ -878,7 +864,7 @@ "ManageLibrary": "管理媒體庫", "MarkPlayed": "標記為已播放", "MarkUnplayed": "標記為未播放", - "MediaInfoBitrate": "比特率", + "MediaInfoBitrate": "位元率", "MediaInfoChannels": "聲道", "MediaInfoCodec": "編碼", "MediaInfoContainer": "影片容器", @@ -892,7 +878,7 @@ "MessageConfirmDeleteTunerDevice": "您確定要刪除這部裝置嗎?", "MessageConfirmRecordingCancellation": "取消錄製?", "MessageImageFileTypeAllowed": "僅支援 JPEG 和 PNG。", - "MessageInvalidUser": "錯誤的使用者名稱或密碼,請再試一次。", + "MessageInvalidUser": "錯誤的使用者名稱或密碼,請重試。", "MessageItemSaved": "項目已儲存。", "MessagePleaseWait": "請稍候。", "MinutesBefore": "分前", @@ -926,7 +912,7 @@ "RecommendationBecauseYouLike": "因為您喜歡 {0}", "SearchResults": "搜尋結果", "TabPlaylists": "播放清單", - "TabPlugins": "模組", + "TabPlugins": "附加元件", "Transcoding": "轉檔", "ValueTimeLimitMultiHour": "時間限制:{0} 小時", "ValueVideoCodec": "影片編碼:{0}", @@ -936,10 +922,10 @@ "LabelServerName": "伺服器名稱:", "LabelTag": "標記:", "LabelTranscodingTempPathHelp": "指定轉檔後的儲存路徑,留空將使用預設值。", - "LabelffmpegPathHelp": "路徑指向 FFmpeg,或是包含其的資料夾。", + "LabelffmpegPathHelp": "FFmpeg 的路徑,或是包含其的資料夾。", "ManageRecording": "管理錄影", "MessageAlreadyInstalled": "已安裝此版本。", - "MessageConfirmRestart": "您確定要重新啟動嗎?", + "MessageConfirmRestart": "您確定要重新啟動 Jellyfin 嗎?", "Metadata": "中繼資料", "OptionAllUsers": "所有使用者", "OptionHomeVideos": "圖片", @@ -960,7 +946,7 @@ "LabelDownloadLanguages": "下載語言:", "LabelDynamicExternalId": "{0} Id:", "LabelEasyPinCode": "簡易代碼:", - "LabelEnableAutomaticPortMap": "啟用自動端口映射", + "LabelEnableAutomaticPortMap": "啟用自動埠映射", "LabelEnableSingleImageInDidlLimit": "限制單個嵌入式圖片", "LabelEndDate": "結束日期:", "LabelLockItemToPreventChanges": "鎖定此項目來避免被更改", @@ -973,7 +959,7 @@ "LabelSelectVersionToInstall": "選擇要安裝的版本:", "LabelSendNotificationToUsers": "傳送通知給:", "LabelSortBy": "排序按照:", - "LabelVideoBitrate": "影片比特率:", + "LabelVideoBitrate": "影片位元率:", "MediaInfoSize": "大小", "MediaInfoTimestamp": "時間戳", "MediaInfoStreamTypeData": "檔案", @@ -982,7 +968,7 @@ "MediaInfoStreamTypeVideo": "影片", "Menu": "選單", "MetadataManager": "中繼資料管理器", - "MessageNoPluginConfiguration": "這個模組沒有設定選項可供更改。", + "MessageNoPluginConfiguration": "這個附加元件沒有選項可供更改。", "NoSubtitlesHelp": "字幕不會自動讀取,但可於播放時手動選取。", "Normal": "正常", "OptionAllowContentDownloading": "允許下載及同步媒體", @@ -1001,18 +987,18 @@ "LabelGroupMoviesIntoCollections": "將電影分組", "LabelKodiMetadataDateFormat": "釋出日期格式:", "LabelIconMaxWidth": "Icon 最寬寬度:", - "LabelGroupMoviesIntoCollectionsHelp": "顯示電影列表時,屬於相同集合的電影將作為分組項目顯示。", + "LabelGroupMoviesIntoCollectionsHelp": "顯示電影清單時,屬於相同集合的電影將作為分組項目顯示。", "LabelEncoderPreset": "H264 解碼品質:", "LabelHardwareAccelerationType": "硬體加速:", - "LabelIconMaxWidthHelp": "通過 upnp:icon 的圖標最大解析度。", + "LabelIconMaxWidthHelp": "通過 upnp:icon 的圖示最大解析度。", "LabelImportOnlyFavoriteChannels": "僅限收藏的頻道", "LabelInNetworkSignInWithEasyPassword": "啟用以簡易密碼進行區域網路登入", "LabelH264Crf": "H264 編碼 CRF:", "LabelMaxStreamingBitrate": "最大串流畫質:", - "LabelMaxStreamingBitrateHelp": "指定最大串流比特率。", + "LabelMaxStreamingBitrateHelp": "指定最大串流位元率。", "LabelMessageText": "訊息文字:", "LabelMessageTitle": "訊息標題:", - "LabelMetadataDownloadLanguage": "首選下載語言:", + "LabelMetadataDownloadLanguage": "偏好下載語言:", "LabelMetadata": "中繼資料:", "LabelMethod": "方法:", "LabelNewName": "新名稱:", @@ -1029,7 +1015,6 @@ "TabCodecs": "編碼", "TabContainers": "影片容器", "TabDashboard": "控制台", - "TabDisplay": "顯示", "TabFavorites": "最愛", "TabLogs": "日誌", "TabNotifications": "通知", @@ -1078,7 +1063,7 @@ "OptionEnableM2tsMode": "啟用 M2ts 模式", "LabelKeepUpTo": "保持:", "LabelKidsCategories": "兒童分類:", - "LabelKodiMetadataEnablePathSubstitution": "啟用路徑替換", + "LabelKodiMetadataEnablePathSubstitution": "啟用路徑取代", "LabelKodiMetadataEnableExtraThumbs": "複製 extrafanart 到 extrathumbs 欄位", "LabelMovieCategories": "電影分類:", "LabelMoviePrefix": "電影前綴:", @@ -1088,24 +1073,23 @@ "LabelTranscodingContainer": "影片容器:", "MovieLibraryHelp": "查看 {0}Jellyfin 電影命名指南{1}。", "None": "無", - "OptionAllowMediaPlaybackTranscodingHelp": "由於不支持的媒體格式,限制轉檔可能會導致 Jellyfin 應用程式播放失敗。", + "OptionAllowMediaPlaybackTranscodingHelp": "限制轉檔可能會導致播放不支援的格式時失敗。", "MediaInfoLevel": "等級", "MessageNoTrailersFound": "安裝 Trailer channel 來新增網路上預告片,以增進你的電影體驗。", "OptionHasSpecialFeatures": "特色", "RecommendationStarring": "主演 {0}", "Rewind": "倒帶", "SubtitleOffset": "字幕偏移", - "TabPlayback": "播放", "Unrated": "尚未評等", "Up": "上", "ValueOneSeries": "1 劇集", "Writer": "編劇", "XmlTvMovieCategoriesHelp": "有這些類別的節目會被當作電影,以「|」來分隔多個項目。", "ValueSeriesCount": "{0} 劇集", - "LabelHardwareAccelerationTypeHelp": "硬件加速需要額外的配置。", + "LabelHardwareAccelerationTypeHelp": "硬體加速需要額外的配置。", "LabelHomeNetworkQuality": "區域網路畫質:", - "LabelHomeScreenSectionValue": "主畫面模塊 {0}:", - "LabelMetadataDownloadersHelp": "啟用媒體屬性下載器的優先次序,愈下次序只會用來填補缺少的信息。", + "LabelHomeScreenSectionValue": "主畫面模組 {0}:", + "LabelMetadataDownloadersHelp": "啟用中繼資料下載器的優先次序,愈下次序只會用來填補缺少的訊息。", "LabelMetadataReaders": "中繼資料閱讀器:", "LabelMetadataSaversHelp": "選取儲存中繼資料的檔案格式。", "LabelModelNumber": "型號", @@ -1115,7 +1099,7 @@ "OptionDownloadBannerImage": "橫幅", "OptionEnableAccessToAllChannels": "允許存取所有頻道", "OptionEnableAccessToAllLibraries": "允許存取所有媒體庫", - "OptionEnableForAllTuners": "开启所有调谐器", + "OptionEnableForAllTuners": "開啟所有調諧器", "OptionExtractChapterImage": "開啟章節圖片擷取", "OptionEnableM2tsModeHelp": "當編碼為 MPEGTS 時啟用 M2TS 模式。", "OptionEquals": "等於", @@ -1130,10 +1114,10 @@ "SeriesDisplayOrderHelp": "按播出日期、DVD 順序或編號對劇集進行排序。", "SeriesSettings": "系列設定", "SeriesYearToPresent": "{0} - 現在", - "ServerNameIsRestarting": "Jellyfin Server - {0} 重啟中。", - "ServerNameIsShuttingDown": "Jellyfin 伺服器 - {0} 正在關閉。", + "ServerNameIsRestarting": "伺服器將於 {0} 後重新啟動。", + "ServerNameIsShuttingDown": "伺服器將於 {0} 後關閉。", "SimultaneousConnectionLimitHelp": "允許的同時串流的最大數量。輸入 0 表示無限制。", - "SkipEpisodesAlreadyInMyLibrary": "不錄製我的媒體庫裡已存在的劇集", + "SkipEpisodesAlreadyInMyLibrary": "不錄製已存在於媒體庫中的劇集", "SmallCaps": "小型大寫字母", "SortChannelsBy": "頻道排序方式:", "SortName": "排序名稱", @@ -1141,27 +1125,27 @@ "StopRecording": "停止錄影", "LabelDefaultUserHelp": "確定哪些使用者媒體庫將顯示在連接裝置上。這可以為每個裝置提供不同的使用者設定檔。", "LabelEnableBlastAliveMessagesHelp": "若此伺服器無法被其他 UPnP 裝置偵測到,請啟用此選項。", - "LabelEnableDlnaClientDiscoveryIntervalHelp": "由 Jellyfin 決定進行 SSDP 搜尋之間的持續時間(以秒為單位)。", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "決定進行 SSDP 搜尋之間的持續時間(以秒為單位)。", "LabelEnableDlnaPlayToHelp": "偵測您網路裡的設備並遠端控制它們。", "LabelExtractChaptersDuringLibraryScanHelp": "當媒體庫匯入影片並掃描時,將擷取章節圖片。\n否則,章節圖片將在之後的計畫任務中擷取,而媒體庫會更快完成掃描。", "LabelMoviePrefixHelp": "若前綴套用到電影標題,請在此處輸入它來方便伺服器能夠正確處理。", - "LabelMovieRecordingPath": "電影錄製路徑(選用):", + "LabelMovieRecordingPath": "電影錄製路徑:", "LabelNotificationEnabled": "啟用這個通知", "LabelProfileContainersHelp": "以逗號分隔,留空則適用於所有影片容器。", "LabelSelectFolderGroupsHelp": "未選中的資料夾將在其自己的檢視中顯示。", "LabelSerialNumber": "序號", - "LabelSeriesRecordingPath": "電視劇錄影路徑(選用):", + "LabelSeriesRecordingPath": "影集錄影路徑:", "LabelServerHost": "主機:", "LabelSimultaneousConnectionLimit": "同時串流限制:", "LabelSize": "大小:", "LabelSkipBackLength": "跳過長度:", - "LabelSkipIfGraphicalSubsPresentHelp": "保留文字版本的字幕會更有效率傳遞,減低影片轉碼的機會。", + "LabelSkipIfGraphicalSubsPresentHelp": "保留文字版本的字幕會更有效率傳遞,減低影片轉檔的機會。", "LabelStopWhenPossible": "當可能時自動停止:", "LabelStopping": "停止", "LabelTagline": "個性宣言:", "LabelSubtitleDownloaders": "字幕下載器:", - "LabelSubtitleFormatHelp": "例如:SRT", - "LabelSubtitlePlaybackMode": "字幕模式:", + "LabelSubtitleFormatHelp": "如:SRT", + "LabelSubtitlePlaybackMode": "字幕載入:", "LabelTranscodingThreadCount": "轉檔執行緒數:", "LabelTunerIpAddress": "調諧器 IP 位址:", "LabelTunerType": "調諧器類型:", @@ -1193,22 +1177,22 @@ "MessageConfirmDeleteGuideProvider": "您確定要刪除此指南提供者嗎?", "MessageConfirmProfileDeletion": "您確定要刪除這個設定檔嗎?", "MessageConfirmRemoveMediaLocation": "您確定要移除這個位置嗎?", - "MessageConfirmRevokeApiKey": "您確定要撤銷這個 API 金鑰嗎?這個應用程式與 Jellyfin 伺服器的連接將立即中斷。", + "MessageConfirmRevokeApiKey": "您確定要撤銷這個 API 金鑰嗎?這個應用程式與伺服器的連接將立即中斷。", "MessageCreateAccountAt": "在 {0} 建立使用者", "MessageDeleteTaskTrigger": "您確定要刪除這個任務觸發器嗎?", "MessageDirectoryPickerBSDInstruction": "對於 BSD 系統,您需要設定包含您 FreeNAS Jail 虛擬機的儲存以便 Jellyfin 存取。", "MessageDirectoryPickerLinuxInstruction": "使用 Linux on Arch Linux、CentOS、Debian、Fedora、openSUSE 或 Ubuntu 作業系統,您必須授權使用者至少讀取權限來存取您的儲存路徑。", "MessageEnablingOptionLongerScans": "啟用這個選項可能會延長媒體庫的掃描時間。", "MessageFileReadError": "讀取檔案時發生錯誤。", - "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再開始密碼重置流程。", + "MessageForgotPasswordInNetworkRequired": "請檢查您的區域網路後再開始密碼重設流程。", "MessageForgotPasswordFileCreated": "已在伺服器上建立了以下檔案,並包含有關後續步驟說明:", - "MessageNoAvailablePlugins": "沒有可用的模組。", + "MessageNoAvailablePlugins": "沒有可用的附加元件。", "MessageNoServersAvailable": "無法自動偵測伺服器。", "MessageLeaveEmptyToInherit": "保留為空以繼承父項或全域預設值的設定。", "MessageNoCollectionsAvailable": "分組能夠讓您享受個性化的影片、劇集和專輯。點擊+按鈕開始建立分組。", "MessagePlayAccessRestricted": "此內容的播放受到限制,聯繫您的伺服器管理員以獲取更多訊息。", - "MessagePluginConfigurationRequiresLocalAccess": "請直接登入你的本地伺服器以設定這個模組。", - "MessagePluginInstallDisclaimer": "安裝 Jellyfin 社區成員建立的模組來獲取額外的功能可以最佳化您的 Jellyfin。但他們可能會對你的 Jellyfin 伺服器造成的影響,如更長的媒體庫掃描時間、額外的背景資料處理和降低系統穩定性等。", + "MessagePluginConfigurationRequiresLocalAccess": "請直接登入你的本地伺服器以設定這個附加元件。", + "MessagePluginInstallDisclaimer": "安裝 Jellyfin 社區成員建立的附加元件來讓您的 Jellyfin 獲取額外的功能最佳化您的使用體驗。但可能會對你的 Jellyfin 伺服器效能造成影響,如更長的媒體庫掃描時間、額外的背景資料處理與系統穩定性降低等。", "MessageReenableUser": "請參閱以下以重新啟用", "MessageUnableToConnectToServer": "無法連上所選的伺服器,請確保該伺服器正在運作中。", "MessageYouHaveVersionInstalled": "你目前安裝了 {0} 版本。", @@ -1217,7 +1201,7 @@ "NoNewDevicesFound": "找不到裝置,要添加新調諧器,請關閉此對話框並手動輸入裝置訊息。", "OnlyForcedSubtitles": "僅顯示強制字幕", "OnlyImageFormats": "圖片格式(VOBSUB、PGS、SUB)", - "OptionAllowLinkSharingHelp": "只有網頁包含的媒體訊息會被共享,媒體檔案本身不會被公開共享,共享的內容會在 {0} 天後到期。", + "OptionAllowLinkSharingHelp": "只有網頁包含的媒體訊息會被共享,媒體檔案本身不會被公開共享,共享的內容會在 {0} 天后到期。", "OptionAllowRemoteSharedDevices": "允許遠端控制共享裝置", "OptionAllowSyncTranscoding": "允許需要轉檔的媒體下載和同步", "OptionAllowVideoPlaybackRemuxing": "允許播放需轉換但無需重新編碼的影片", @@ -1225,13 +1209,13 @@ "MetadataSettingChangeHelp": "更改中繼資料設定將影響新增的新內容。要重新整理現有內容,請打開詳細訊息視窗並點選「重新整理」按鈕,或使用中繼資料管理器執行批次重新整理。", "MusicLibraryHelp": "查看{0}音樂命名指南{1}。", "OptionAutomaticallyGroupSeries": "自動合併分布在不同資料夾的電視劇", - "OptionAutomaticallyGroupSeriesHelp": "分布在這個媒體庫的多個文件夾中的同一部電視劇將會自動整合成一部電視劇。", + "OptionAutomaticallyGroupSeriesHelp": "媒體庫中同一部電視劇的多個資料夾將會自動整合一起。", "OptionDateAddedImportTime": "使用加入媒體庫時的掃描日期", "OptionDisplayFolderView": "顯示「資料夾」類別來瀏覽你的媒體資料夾", "OptionEmbedSubtitles": "在影片容器中嵌入", "OptionDownloadImagesInAdvance": "提前下載圖片", "OptionEnableAccessFromAllDevices": "允許所有裝置存取", - "OptionDownloadImagesInAdvanceHelp": "預設情況下,大多數圖片僅在Jellyfin應用程式請求時下載。啟用此選項可於匯入新媒體後提前下載所有圖片,可能會延長的媒體庫掃描時間。", + "OptionDownloadImagesInAdvanceHelp": "預設情況下,大多數圖片僅在應用程式請求時下載。啟用此選項可於匯入新媒體時提前下載所有圖片,可能會延長的媒體庫掃描時間。", "OptionEnableExternalContentInSuggestionsHelp": "允許將網際網路預告片和直播電視節目包含在建議的內容中。", "OptionHideUserFromLoginHelp": "對私人或隱藏的管理員帳戶很有用,但需手動輸入使用者名稱和密碼登入。", "OptionPlainStorageFolders": "顯示所有資料夾作為一般存儲資料夾", @@ -1239,16 +1223,16 @@ "OptionPlainVideoItemsHelp": "所有影片在 DIDL 中顯示為「object.item.videoItem」,而不是一個更具體的類型,如「object.item.videoItem.movie」。", "OptionProtocolHls": "HTTP 直播串流", "OptionReportByteRangeSeekingWhenTranscoding": "轉檔時,回報伺服器支持的位元組查詢", - "OptionSaveMetadataAsHidden": "儲存媒體資料和圖片為隱藏文件", + "OptionSaveMetadataAsHidden": "儲存中繼資料和圖片為隱藏文件", "OptionSubstring": "子串", "OptionWeekdays": "工作日", "Overview": "概述", "PackageInstallCancelled": "{0} (版本 {1})安裝被取消。", "PlayAllFromHere": "從這裡開始全部播放", "PleaseAddAtLeastOneFolder": "請點擊新增按鈕,新增至少一個資料夾到這個媒體庫。", - "PleaseConfirmPluginInstallation": "點擊「OK」以確認您已經閱讀了上述內容並希望繼續安裝模組。", + "PleaseConfirmPluginInstallation": "點擊「OK」以確認您已經閱讀了上述內容並希望繼續安裝附加元件。", "PleaseEnterNameOrId": "請輸入一個名稱或一個外部 ID。", - "PleaseRestartServerName": "請重啟 Jellyfin 伺服器 - {0}。", + "PleaseRestartServerName": "請重啟 Jellyfin 於 {0}。", "PleaseSelectTwoItems": "請至少選擇 2 個項目。", "PreferEmbeddedTitlesOverFileNames": "優先使用內建的標題而不是檔案名稱", "PreferEmbeddedTitlesOverFileNamesHelp": "這將在沒有網路上的中繼資料或本地中繼資料可用時顯示預設標題。", @@ -1261,33 +1245,33 @@ "Programs": "節目", "Quality": "品質", "PackageInstallFailed": "{0} (版本 {1}) 安裝失敗。", - "Raised": "提高", + "Raised": "浮凹", "Rate": "評等", "Recordings": "錄影", - "ServerRestartNeededAfterPluginInstall": "安裝模組後,Jellyfin 伺服器需要重啟以使模組生效。", + "ServerRestartNeededAfterPluginInstall": "安裝附加元件後,Jellyfin 伺服器需要重啟使其生效。", "ShowIndicatorsFor": "顯示指標:", "Sort": "排序", "Studios": "工作室", "TheseSettingsAffectSubtitlesOnThisDevice": "這些設定僅影響該裝置的字幕顯示", "TitleHardwareAcceleration": "硬體加速", "TitleHostingSettings": "主機設定", - "Uniform": "輪廓", + "Uniform": "邊框", "Unmute": "取消靜音", "Unplayed": "尚未播放", "TvLibraryHelp": "查看 {0} 電視命名指南 {1} 。", "LabelMonitorUsers": "監控活動:", "LabelPleaseRestart": "改動將在手動重啟用戶端後生效。", "LabelProfileCodecsHelp": "以逗號分隔。留空則適用於所有編解碼器。", - "OptionPlainStorageFoldersHelp": "如果啟用,所有文件夾在DIDL中顯示為「object.container.storageFolder 」,而不是一個更具體的類型,如「object.container.person.musicArtist」。", + "OptionPlainStorageFoldersHelp": "所有資料夾在 DIDL 中顯示為「object.container.storageFolder 」,而不是一個更具體的類型,如「object.container.person.musicArtist」。", "LabelInNetworkSignInWithEasyPasswordHelp": "你可以在你的家庭網路中使用你的簡易 PIN 碼登錄 Jellyfin 應用程式,僅在你使用外部網路時才需要輸入密碼,如果 PIN 碼留空,那麼在你的區域網路中便不需輸入密碼。", "LabelReleaseDate": "釋出日期:", "LabelRemoteClientBitrateLimit": "網際網路串流傳輸位元率限制(Mbps):", - "LanNetworksHelp": "在強制頻寬限制時,認作本地網路上的 IP 地址或 IP/子網域遮罩項目的逗號分隔列表。若設置此項,所有其它 IP 地址將被視作在外部網路上,并且將受到外部頻寬限制。如果保留爲空,則只將服務器的子網域遮罩作本地網路。", + "LanNetworksHelp": "在強制頻寬限制時,認作本地網路上的 IP 位址或 IP/子網域遮罩項目的逗號分隔清單。若設置此項,所有其它 IP 位址將被視作在外部網路上,並且將受到外部頻寬限制。如果保留為空,則只將伺服器的子網域遮罩作本地網路。", "OptionIgnoreTranscodeByteRangeRequests": "忽略轉檔位元組範圍請求", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "如果啟用,這些請求會被兌現,但會忽略的位元組範圍標頭。", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "這些請求會被接受,但會忽略的位元組範圍標頭。", "OptionLoginAttemptsBeforeLockoutHelp": "若值為 0,則表示將允許普通使用者嘗試三次、管理員嘗試五次的預設值,設定為 -1 來停用此功能。", "OptionRequirePerfectSubtitleMatchHelp": "僅下載經過測試並確認跟此影片檔案完美匹配的字幕。取消勾選這個項目可以增加找到並下載字幕的可能性,但可能會下載時間軸、翻譯不正確的字幕。", - "MessagePluginInstalled": "這個模組安裝成功,但 Jellyfin 伺服器需要重啟以使模組生效。", + "MessagePluginInstalled": "附加元件安裝成功,但需要重新啟動 Jellyfin 伺服器以使附加元件生效。", "MessageChangeRecordingPath": "更改錄製資料夾不會將現有錄製從舊位置遷移到新的,您需要手動移動它們。", "LabelEmbedAlbumArtDidl": "於 Didl 中嵌入專輯封面", "LabelEnableAutomaticPortMapHelp": "透過 UPnP 自動將路由器上的公共埠轉發到伺服器上的本地埠。這可能不適用於某些路由器型號或網路設定。在伺服器重新啟動後才會進行更改。", @@ -1296,9 +1280,9 @@ "LabelEnableSingleImageInDidlLimitHelp": "若在 Didl 中嵌入多個圖片,某些裝置可能無法正常顯示。", "SortByValue": "排序方式:{0}", "LabelLineup": "排隊:", - "LabelLocalHttpServerPortNumber": "本地 HTTP 端口:", - "LabelLocalHttpServerPortNumberHelp": "Jellyfin HTTP 伺服器監聽的 TCP 端口。", - "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "這些設定也會被套用在任何透過此裝置發起的 Chromecast 播放。", + "LabelLocalHttpServerPortNumber": "本地 HTTP 埠:", + "LabelLocalHttpServerPortNumberHelp": "HTTP 伺服器的 TCP 埠。", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "此設定也會影響透過此裝置投放的 Chromecast。", "LabelLoginDisclaimer": "登入字句:", "LabelLogs": "日誌:", "SubtitleDownloadersHelp": "按優先順序啟用並排列您的首選字幕下載程式。", @@ -1306,18 +1290,18 @@ "SystemDlnaProfilesHelp": "系統設定檔案是唯讀的,更改系統設定檔案將被儲存到自訂的新設定檔案。", "LabelNumber": "編號:", "LabelNumberOfGuideDays": "下載電視指南日數:", - "OnlyForcedSubtitlesHelp": "僅被標記為「強制」的字幕會被載入。", + "OnlyForcedSubtitlesHelp": "僅標記為「強制」的字幕會被載入。", "PackageInstallCompleted": "{0} (版本 {1}) 安裝完成。", "OptionDisplayFolderViewHelp": "在其他媒體庫旁邊顯示資料夾,對想要一個普通的資料夾檢視很有用。", "LabelReasonForTranscoding": "轉檔原因:", "LabelRecord": "錄影:", "LabelRecordingPath": "預設錄影路徑:", "LabelRecordingPathHelp": "指定用於存儲轉檔的位置,留空將使用伺服器的程式根目錄。", - "LabelRemoteClientBitrateLimitHelp": "所有網路裝置都有可選的流位元率限制,這對於防止設備請求比網路連接所能處理的更高的位元率非常有用。這可能會導致伺服器上的 CPU 負載增加,以便將影片轉檔到較低的位元率。", + "LabelRemoteClientBitrateLimitHelp": "所有網路裝置都能夠調整流位元率限制,這對於防止設備請求比網路連接所能處理的更高的位元率非常有用。這可能會導致伺服器上的 CPU 負載增加,以便將影片轉檔到較低的位元率。", "SmartSubtitlesHelp": "當音訊為外語時,將載入與語言偏好匹配的字幕。", "SubtitleAppearanceSettingsDisclaimer": "這些設定將不會套用在圖形字幕(如 PGS、DVD 等),或者一些有著自己的內建樣式的字幕(ASS/SSA)。", "UserAgentHelp": "提供自訂的使用者代理 HTTP 標頭。", - "LabelRuntimeMinutes": "播放時長(分鐘):", + "LabelRuntimeMinutes": "播放時間:", "LabelScheduledTaskLastRan": "最後執行 {0},花費時間 {1}。", "LabelSkipForwardLength": "快轉長度:", "LabelSkipIfAudioTrackPresent": "如果預設音軌的語言和下載語言一樣則跳過", @@ -1334,7 +1318,7 @@ "LabelVaapiDevice": "VA API 裝置:", "DashboardArchitecture": "架構:{0}", "MediaInfoSampleRate": "採樣率", - "MessageContactAdminToResetPassword": "請聯繫您的管理員來重置密碼。", + "MessageContactAdminToResetPassword": "請聯絡您的管理員來重設密碼。", "MessageUnsetContentHelp": "內容將顯示為純資料夾,建議使用中繼資料管理器設置子資料夾的內容類型。", "OptionAllowAudioPlaybackTranscoding": "允許播放需要轉檔的音訊", "OptionCustomUsers": "自訂", @@ -1343,18 +1327,18 @@ "XmlTvNewsCategoriesHelp": "有這些類別的節目會被當作新聞節目,以「|」來分隔多個項目。", "LabelKodiMetadataEnableExtraThumbsHelp": "為了相容 Kodi 主題,下載的圖片將被同時儲存在 extrafanart 和 extrathumbs 資料夾中。", "LabelInternetQuality": "網路畫質:", - "LabelKodiMetadataEnablePathSubstitutionHelp": "允許將圖片的路徑以伺服器路徑來替換。", - "LabelKodiMetadataSaveImagePathsHelp": "如果您的圖片檔案名稱不符合 Kodi 規範,建議啟用。", - "LabelKodiMetadataUser": "儲存這些使用者的觀看資料到 NFO 檔案裏:", + "LabelKodiMetadataEnablePathSubstitutionHelp": "允許將圖片的路徑以伺服器路徑取代。", + "LabelKodiMetadataSaveImagePathsHelp": "若如果您的圖片檔案名稱不符合 Kodi 規範,建議啟用。", + "LabelKodiMetadataUser": "儲存這些使用者的觀看資料到 NFO 檔案中:", "LabelKodiMetadataUserHelp": "儲存觀看資料到 NFO 檔案中以便其他應用程式使用。", - "LabelMetadataReadersHelp": "優先排序您的首選資料屬性來源,首個找到的文件將被讀取。", + "LabelMetadataReadersHelp": "優先排序您的中繼資料屬性來源,首個找到的文件將被讀取。", "LabelMetadataSavers": "中繼資料儲存方式:", "LabelModelDescription": "型號描述", "LabelModelName": "型號名稱", "LabelModelUrl": "型號網址", - "LabelMusicStreamingTranscodingBitrate": "音樂轉檔比特率:", - "LabelMusicStreamingTranscodingBitrateHelp": "指定音樂串流時的最大比特率。", - "LabelOptionalNetworkPathHelp": "如果這個資料夾在網路上分享,提供網路分享路徑可以供其他 Jellyfin 應用程式直接存取媒體檔案,例如 {0} 或者 {1}。", + "LabelMusicStreamingTranscodingBitrate": "音樂轉檔位元率:", + "LabelMusicStreamingTranscodingBitrateHelp": "指定音樂串流時的最大位元率。", + "LabelOptionalNetworkPathHelp": "如果這個資料夾在網路上分享,提供網路分享路徑可以供其他應用程式直接存取媒體檔案,例如 {0} 或者 {1}。", "LabelOriginalAspectRatio": "原始長寬比:", "LabelOverview": "內容概述:", "LabelParentalRating": "家長分級:", @@ -1377,8 +1361,8 @@ "LabelPreferredSubtitleLanguage": "字幕語言偏好:", "LabelProtocol": "協議:", "LabelProtocolInfo": "協議資訊:", - "LabelPublicHttpPort": "公開 HTTP 端口:", - "LabelPublicHttpsPort": "公開 HTTPS 端口:", + "LabelPublicHttpPort": "公開 HTTP 埠:", + "LabelPublicHttpsPort": "公開 HTTPS 埠:", "LabelProtocolInfoHelp": "當響應來自裝置的 GetProtocolInfo(獲取協議訊息)請求時,該值將被使用。", "LabelPublicHttpPortHelp": "公開連接埠應映射到本地 HTTP 連接埠。", "LabelPublicHttpsPortHelp": "公開連接埠應映射到本地 HTTPS 連接埠。", @@ -1401,49 +1385,49 @@ "ViewPlaybackInfo": "查看播放訊息", "XmlTvSportsCategoriesHelp": "有這些類別的節目會被當作體育節目,以「|」來分隔多個項目。", "XmlTvPathHelp": "XML 電視檔案的路徑,Jellyfin 將讀取該檔案並定期檢查其更新,您負責建立和更新檔案。", - "MessageInvalidForgotPasswordPin": "簡易代碼錯誤或已過期,請再試一次。", + "MessageInvalidForgotPasswordPin": "簡易代碼錯誤或已過期,請重試。", "OptionAllowVideoPlaybackTranscoding": "允許播放需要轉檔的影片", "Small": "小", "Smaller": "更小", "XmlTvKidsCategoriesHelp": "有這些類別的節目會被當作兒童節目,以「|」來分隔多個項目。", "TabResponses": "響應", "LabelDisplaySpecialsWithinSeasons": "顯示劇集季度中的特集", - "LabelNumberOfGuideDaysHelp": "下載更多電視指南資料會提供更好時間表查看能力,但將需要更長的下載時間。自動基於頻道數目來選擇。", - "LabelOptionalNetworkPath": "(選用)分享的網路資料夾:", + "LabelNumberOfGuideDaysHelp": "下載多日的節目指南可以幫你進一步查看節目列表並做出提前安排,但下載過程也將耗時更久。它將基於頻道數量自動選擇。", + "LabelOptionalNetworkPath": "分享的網路資料夾:", "OptionResElement": "res 元素", "PinCodeResetComplete": "PIN 碼已被重設。", "PinCodeResetConfirmation": "你確定要重設 PIN 碼?", - "PasswordResetProviderHelp": "選擇密碼重設提供者以便使用者重設密碼", + "PasswordResetProviderHelp": "選擇重設密碼提供者以便使用者重設密碼。", "PlaceFavoriteChannelsAtBeginning": "將喜愛的頻道置頂", "PlaybackData": "恢復播放資料", "OptionRandom": "隨機", "HeaderFavoritePeople": "最愛人物", - "XmlDocumentAttributeListHelp": "這些屬性會在每一個XML回應的根元素上應用。", + "XmlDocumentAttributeListHelp": "這些屬性會在每一個 XML 回應的根元素上套用。", "SkipEpisodesAlreadyInMyLibraryHelp": "劇集將使用季和劇集編號進行比較。", - "SelectAdminUsername": "請為管理員賬戶選擇一個用戶名。", - "OptionSaveMetadataAsHiddenHelp": "更改此項將應用於以後保存的元數據。現有元數據文件將在下一次 Jellyfin 伺服器保存它們時被更新。", + "SelectAdminUsername": "請為管理員帳戶選擇一個使用者名稱。", + "OptionSaveMetadataAsHiddenHelp": "更改此項將套用至未來保存的中繼資料。現有中繼資料檔案將在下一次伺服器儲存它們時被更新。", "OptionAllowRemoteSharedDevicesHelp": "DLNA裝置將被視為共享中,直至有使用者控制。", - "OptionForceRemoteSourceTranscoding": "强制遠端轉碼(像電視直播一樣)", + "OptionForceRemoteSourceTranscoding": "強制遠端轉檔(像電視直播一樣)", "MessageConfirmAppExit": "您要退出嗎?", - "LabelVideoResolution": "視頻解析度:", + "LabelVideoResolution": "影片解析度:", "LabelStreamType": "串流類型:", "LabelPlayerDimensions": "播放器尺寸:", "LabelDroppedFrames": "丟棄的幀:", "LabelCorruptedFrames": "損壞的幀:", "ButtonSplit": "分割", - "AskAdminToCreateLibrary": "如要建立資料庫,請聯繫管理員。", + "AskAdminToCreateLibrary": "如要建立資料庫,請聯絡管理員。", "NoCreatedLibraries": "看來您還未創任何媒體庫。{0}立刻創一個新的嗎?{1}", - "ClientSettings": "客戶端設定", - "AllowFfmpegThrottlingHelp": "當轉檔或重組進度大量超前目前播放進度時,將暫停轉檔節省消耗的資源。在不常跳播的時候最有效。如果遇到播放問題,請關閉此功能。", + "ClientSettings": "用戶端設定", + "AllowFfmpegThrottlingHelp": "當轉檔或重組進度遠超於目前播放進度時,將暫停轉檔節省消耗的資源。在不常跳播的時候最有效。如果遇到播放問題,請關閉此功能。", "AllowFfmpegThrottling": "限制轉檔", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "這將會使用內建劇集資料。", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "這將會使用內建中繼資料。", "PlaybackErrorNoCompatibleStream": "用戶端與該媒體不相容,伺服器也未傳送相容的媒體格式。", "PreferEmbeddedEpisodeInfosOverFileNames": "優先使用內建劇集資訊而不是檔案名稱", "Artist": "演出者", "AlbumArtist": "專輯歌手", "Album": "專輯", "YadifBob": "YADIF Bob", - "WriteAccessRequired": "Jellyfin 伺服器需要此資料夾的寫入權限,請確認是否擁有寫入權限並重試。", + "WriteAccessRequired": "伺服器需要此資料夾的寫入權限,請確認是否擁有寫入權限並重試。", "PathNotFound": "無法找到此路徑,請確認路徑可用並重試。", "Track": "音軌", "Yadif": "YADIF", @@ -1464,14 +1448,13 @@ "LabelLibraryPageSize": "媒體庫分頁大小:", "LabelDeinterlaceMethod": "反交錯方法:", "Episode": "劇集", - "DeinterlaceMethodHelp": "選擇對隔行掃描內容進行轉碼時所用的反交錯方法。", + "DeinterlaceMethodHelp": "選擇對隔行掃描內容進行轉檔時所用的反交錯方法。", "BoxSet": "套裝", "UnsupportedPlayback": "Jellyfin 無法解密受 DRM 保護的內容,但仍然會嘗試播放所有內容。某些檔案由於被加密或包含如互動標題等不受支援的內容,在播放時可能會沒有畫面。", "Filter": "篩選器", "New": "新增", - "ApiKeysCaption": "目前已啟用的API金鑰列表", + "ApiKeysCaption": "目前已啟用的 API 金鑰清單", "ButtonTogglePlaylist": "播放清單", - "ButtonToggleContextMenu": "更多", "ButtonSyncPlay": "SyncPlay", "LabelRequireHttpsHelp": "開啟後伺服器將自動將所有 HTTP 請求導向 HTTPS。如果伺服器沒有啟用 HTTPS 則不生效。", "EnableFasterAnimationsHelp": "使用更快的動畫與過渡效果", @@ -1479,7 +1462,7 @@ "LabelRequireHttps": "強制 HTTPS", "LabelStable": "穩定版", "LabelChromecastVersion": "Chromecast 版本", - "LabelEnableHttpsHelp": "讓伺服器監聽指定的 HTTPS 端口。須設定有效的證書以便使其生效。", + "LabelEnableHttpsHelp": "監聽指定的 HTTPS 埠。須設定有效的證書使其生效。", "LabelEnableHttps": "啟用HTTPS", "HeaderServerAddressSettings": "伺服器位置設定", "HeaderRemoteAccessSettings": "遠端存取設定", @@ -1501,7 +1484,7 @@ "ShowMore": "顯示更多", "SyncPlayAccessHelp": "選取該使用者對同步播放的存取權。此功能能讓你與其他裝置同步播放進度。", "EnableBlurHash": "啟用模糊的占位圖片", - "EnableBlurHashHelp": "尚未讀取完畢的圖片會先顯示模糊的版本", + "EnableBlurHashHelp": "尚未讀取完畢的圖片會先顯示模糊的版本。", "ClearQueue": "清空佇列", "StopPlayback": "停止播放", "ButtonPlayer": "播放器", @@ -1510,7 +1493,7 @@ "ViewAlbumArtist": "檢視專輯演出者", "TabDVR": "DVR", "TabRepositories": "儲存庫", - "MessageSyncPlayErrorAccessingGroups": "存取群組列表時發生錯誤。", + "MessageSyncPlayErrorAccessingGroups": "存取群組清單時發生錯誤。", "MessageSyncPlayLibraryAccessDenied": "存取受限。", "MessageSyncPlayJoinGroupDenied": "需要同步播放之權限。", "MessageSyncPlayCreateGroupDenied": "需要建立群組之權限。", @@ -1539,5 +1522,13 @@ "MillisecondsUnit": "毫秒", "HeaderSyncPlayEnabled": "已啟用同步播放", "HeaderSyncPlaySelectGroup": "加入群組", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "SubtitleVerticalPositionHelp": "文字出現的行號。正數表示由上到下,負數表示由下到上。", + "MessagePluginInstallError": "安裝附加元件時發生了錯誤。", + "MessageGetInstalledPluginsError": "取得已安裝的附加元件清單時發生了錯誤。", + "Preview": "預覽", + "LabelSubtitleVerticalPosition": "垂直位置:", + "PreviousTrack": "上一首", + "NextTrack": "下一首", + "LabelUnstable": "不穩定" } diff --git a/yarn.lock b/yarn.lock index ad55e16176..269b3b93cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,19 +40,19 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/eslint-parser@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.11.0.tgz#b123924edd44508782c030066c926f1b807151cd" - integrity sha512-dJDM2Pc01D9TwKL3Mmz2xgVF9X953RBHq9H4gywbN1q8MrfvXmNHfsCt06vvByBVQqm+9WxMs+doEH/R09TwWQ== +"@babel/eslint-parser@^7.11.3": + version "7.11.3" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.11.3.tgz#ceb94cb6e2457c4a4d2d87db29925e6b48d20786" + integrity sha512-OdCt/CVXdR/eTNTYDEobf4e55m/AAc04ki+/Oe2/GE8ivh2FxX4yDab48lA6t7ysP4M7luap6Fxx3hUVNTwzFQ== dependencies: eslint-scope "5.1.0" eslint-visitor-keys "^1.3.0" semver "^6.3.0" -"@babel/eslint-plugin@^7.11.0": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.11.0.tgz#55d5b6bd29859cabce152f16d01b3a8150d5b295" - integrity sha512-+gfPM0/T6d25jKBgmxWp38W0jqRs16Vt7DPBxGOcnN/7nS2A/6QoaXOYEaccvWS5a9UpWlMIAylivp6UtH8/sQ== +"@babel/eslint-plugin@^7.11.3": + version "7.11.3" + resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.11.3.tgz#66b531f90592f8f0621d072b59ea2c37c91e8d0d" + integrity sha512-gmi3lgaWlYpNb+h7qPfv5GVz2ZVwzCDyV+kAGj+3il+Mv5uan5Yccvdw7m14UAAY2tdTbB0VgRF6ZLjUbrUm0g== dependencies: eslint-rule-composer "^0.3.0" @@ -5465,10 +5465,10 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hls.js@^0.14.7: - version "0.14.7" - resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.7.tgz#47fbd2662b13121ab17c07aea06b1c07828240cf" - integrity sha512-9JY0D9nwMrfQPRWc8/kEJTKK0TYfDTzIs6Xq+gdCvasRxdvQKQ2T76rdueTkS0AsFV6sQlJN0wxbnI44aRvvUA== +hls.js@^0.14.8: + version "0.14.8" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.8.tgz#c2c6ca7005524c81eece316c2a4a199258bd0590" + integrity sha512-4fh8k/sl1SmYXsT4Om8AY5fKa5tUUtAxup2sffrSMh5MNk4Kt4FOZxbjqTGL5VwkroY1oJ9twSciNQNFbPA/WQ== dependencies: eventemitter3 "^4.0.3" url-toolkit "^2.1.6" @@ -11994,10 +11994,10 @@ webworkify@^1.5.0: resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" integrity sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g== -whatwg-fetch@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" - integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== +whatwg-fetch@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz#e11de14f4878f773fbebcde8871b2c0699af8b30" + integrity sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ== which-module@^1.0.0: version "1.0.0"