diff --git a/package.json b/package.json index a759484ecf..e580255bd5 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "webpack": "^4.41.5", "webpack-cli": "^3.3.10", "webpack-concat-plugin": "^3.0.0", - "webpack-dev-server": "^3.10.3", + "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2", "webpack-stream": "^5.2.1" }, @@ -57,7 +57,7 @@ "alameda": "^1.4.0", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "core-js": "^3.6.5", - "date-fns": "^2.12.0", + "date-fns": "^2.13.0", "document-register-element": "^1.14.3", "fast-text-encoding": "^1.0.1", "flv.js": "^1.5.0", @@ -67,16 +67,16 @@ "intersection-observer": "^0.10.0", "jellyfin-apiclient": "^1.1.1", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", - "jquery": "^3.5.0", + "jquery": "^3.5.1", "jstree": "^3.3.7", - "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova", + "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", "material-design-icons-iconfont": "^5.0.1", "native-promise-only": "^0.8.0-a", "page": "^1.11.6", "query-string": "^6.11.1", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", - "shaka-player": "^2.5.10", + "shaka-player": "^2.5.11", "sortablejs": "^1.10.2", "swiper": "^5.3.7", "webcomponents.js": "^0.7.24", diff --git a/postcss.config.js b/postcss.config.js index 0e19ca6e10..bd1651fa19 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -5,8 +5,10 @@ const cssnano = require('cssnano'); const config = () => ({ plugins: [ + // Explicitly specify browserslist to override ones from node_modules + // For example, Swiper has it in its package.json postcssPresetEnv({browsers: packageConfig.browserslist}), - autoprefixer(), + autoprefixer({overrideBrowserslist: packageConfig.browserslist}), cssnano() ] }); diff --git a/src/addplugin.html b/src/addplugin.html index 30e42a2c5c..81c671d5bb 100644 --- a/src/addplugin.html +++ b/src/addplugin.html @@ -5,7 +5,7 @@

- ${Help} + ${Help}

diff --git a/src/addserver.html b/src/addserver.html index 763f56851b..02850fffb8 100644 --- a/src/addserver.html +++ b/src/addserver.html @@ -3,7 +3,7 @@

${HeaderConnectToServer}

- +
${LabelServerHostHelp}

diff --git a/src/apikeys.html b/src/apikeys.html index 7ca490724b..3cb7cd6de1 100644 --- a/src/apikeys.html +++ b/src/apikeys.html @@ -4,18 +4,19 @@

${HeaderApiKeys}

${HeaderApiKeysHelp}


+ - - - - + + + + diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 4ef017f7b7..963630d851 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -6,6 +6,17 @@ html { overflow-y: scroll; } +.clipForScreenReader { + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; +} + .material-icons { /* Fix font ligatures on older WebOS versions */ -webkit-font-feature-settings: "liga"; diff --git a/src/bundle.js b/src/bundle.js index a04be15eb9..d7ba6c6a51 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -158,7 +158,7 @@ _define('headroom', function () { var apiclient = require('jellyfin-apiclient'); _define('apiclient', function () { - return apiclient.apiclient; + return apiclient.ApiClient; }); _define('events', function () { diff --git a/src/components/accessschedule/accessschedule.template.html b/src/components/accessschedule/accessschedule.template.html index 906d20869c..493150ae5e 100644 --- a/src/components/accessschedule/accessschedule.template.html +++ b/src/components/accessschedule/accessschedule.template.html @@ -1,6 +1,6 @@
-

${HeaderAccessSchedule} diff --git a/src/components/displaysettings/displaysettings.template.html b/src/components/displaysettings/displaysettings.template.html index 62cb493e82..b8ab1a9ba5 100644 --- a/src/components/displaysettings/displaysettings.template.html +++ b/src/components/displaysettings/displaysettings.template.html @@ -56,7 +56,7 @@

diff --git a/src/components/guide/guide-settings.template.html b/src/components/guide/guide-settings.template.html index 6888ca80de..edb2ffa8d3 100644 --- a/src/components/guide/guide-settings.template.html +++ b/src/components/guide/guide-settings.template.html @@ -1,5 +1,7 @@
- +

${Settings}

diff --git a/src/components/guide/tvguide.template.html b/src/components/guide/tvguide.template.html index bd4c9cc3cc..367aac6c17 100644 --- a/src/components/guide/tvguide.template.html +++ b/src/components/guide/tvguide.template.html @@ -9,8 +9,8 @@
-
@@ -29,10 +29,10 @@
- -
diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index fb84bc19f0..3f17eeb336 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -194,7 +194,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve } }; events.map(function (name) { - element.addEventListener(name, onMediaChange); + return element.addEventListener(name, onMediaChange); }); } } diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index e5c11c9879..a1e51b44f1 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -106,10 +106,16 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa }); } + function hidePrePlaybackPage() { + let animatedPage = document.querySelector('.page:not(.hide)'); + animatedPage.classList.add('hide'); + } + function zoomIn(elem) { return new Promise(function (resolve, reject) { var duration = 240; elem.style.animation = 'htmlvideoplayer-zoomin ' + duration + 'ms ease-in normal'; + hidePrePlaybackPage(); dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { once: true }); @@ -290,7 +296,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return createMediaElement(options).then(function (elem) { - return updateVideoUrl(options, options.mediaSource).then(function () { + return updateVideoUrl(options).then(function () { return setCurrentSrc(elem, options); }); }); @@ -602,7 +608,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa // if .ass currently rendering if (currentSubtitlesOctopus) { updateCurrentTrackOffset(offsetValue); - currentSubtitlesOctopus.timeOffset = offsetValue; + currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; } else { var trackElement = getTextTrack(); // if .vtt currently rendering @@ -836,7 +842,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function onNavigatedToOsd() { var dlg = videoDialog; if (dlg) { - dlg.classList.remove('videoPlayerContainer-withBackdrop'); dlg.classList.remove('videoPlayerContainer-onTop'); onStartedAndNavigatedToOsd(); @@ -860,7 +865,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa loading.hide(); htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks, function () { - if (currentSubtitlesOctopus) currentSubtitlesOctopus.resize(); + if (currentSubtitlesOctopus) { + currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + currentTrackOffset; + currentSubtitlesOctopus.resize(); + currentSubtitlesOctopus.resetRenderAheadCache(false); + } }); if (self._currentPlayOptions.fullscreen) { @@ -869,7 +878,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } else { appRouter.setTransparency('backdrop'); - videoDialog.classList.remove('videoPlayerContainer-withBackdrop'); videoDialog.classList.remove('videoPlayerContainer-onTop'); onStartedAndNavigatedToOsd(); @@ -1066,6 +1074,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa onError: function() { htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror'); }, + timeOffset: (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, // new octopus options; override all, even defaults renderMode: 'blend', @@ -1285,12 +1294,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa function createMediaElement(options) { - if (browser.tv || browser.iOS || browser.mobile) { - // too slow - // also on iOS, the backdrop image doesn't look right - // on android mobile, it works, but can be slow to have the video surface fully cover the backdrop - options.backdropUrl = null; - } return new Promise(function (resolve, reject) { var dlg = document.querySelector('.videoPlayerContainer'); @@ -1305,11 +1308,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa dlg.classList.add('videoPlayerContainer'); - if (options.backdropUrl) { - dlg.classList.add('videoPlayerContainer-withBackdrop'); - dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"; - } - if (options.fullscreen) { dlg.classList.add('videoPlayerContainer-onTop'); } @@ -1343,6 +1341,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa videoElement.addEventListener('play', onPlay); videoElement.addEventListener('click', onClick); videoElement.addEventListener('dblclick', onDblClick); + if (options.backdropUrl) { + videoElement.poster = options.backdropUrl; + } document.body.insertBefore(dlg, document.body.firstChild); videoDialog = dlg; @@ -1362,15 +1363,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa resolve(videoElement); }); } else { + hidePrePlaybackPage(); resolve(videoElement); } }); } else { - if (options.backdropUrl) { - dlg.classList.add('videoPlayerContainer-withBackdrop'); - dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"; - } - resolve(dlg.querySelector('video')); } }); diff --git a/src/components/htmlvideoplayer/style.css b/src/components/htmlvideoplayer/style.css index 5ecf4af66a..b83a7816f5 100644 --- a/src/components/htmlvideoplayer/style.css +++ b/src/components/htmlvideoplayer/style.css @@ -6,20 +6,9 @@ right: 0; display: flex; align-items: center; -} - -.videoPlayerContainer:not(.videoPlayerContainer-withBackdrop) { background: #000 !important; } -.videoPlayerContainer-withBackdrop { - background-repeat: no-repeat; - background-position: center center; - background-size: cover; - background-attachment: fixed; - background-color: #000; -} - .videoPlayerContainer-onTop { z-index: 1000; } diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 6fc93b3bd9..fec4656406 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -109,8 +109,8 @@ define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], funct html += '
'; html += '

' + globalize.translate('LabelTypeMetadataDownloaders', globalize.translate(availableTypeOptions.Type)) + '

'; html += '
'; - for (var i = 0; i < plugins.length; i++) { - var plugin = plugins[i]; + + plugins.forEach((plugin, index) => { html += '
'; var isChecked = libraryOptionsForType.MetadataFetchers ? -1 !== libraryOptionsForType.MetadataFetchers.indexOf(plugin.Name) : plugin.DefaultEnabled; var checkedHtml = isChecked ? ' checked="checked"' : ''; @@ -120,8 +120,9 @@ define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], funct html += plugin.Name; html += ''; html += '
'; - i > 0 ? html += '' : plugins.length > 1 && (html += ''), html += '
'; - } + index > 0 ? html += '' : plugins.length > 1 && (html += ''), html += '
'; + }); + html += '
'; html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; html += ''; @@ -292,11 +293,15 @@ define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], funct function showImageOptionsForType(type) { require(['imageoptionseditor'], function(ImageOptionsEditor) { var typeOptions = getTypeOptions(currentLibraryOptions, type); - typeOptions || (typeOptions = { - Type: type - }, currentLibraryOptions.TypeOptions.push(typeOptions)); + if (!typeOptions) { + typeOptions = { + Type: type + }; + currentLibraryOptions.TypeOptions.push(typeOptions); + } var availableOptions = getTypeOptions(currentAvailableOptions || {}, type); - (new ImageOptionsEditor).show(type, typeOptions, availableOptions); + var imageOptionsEditor = new ImageOptionsEditor(); + imageOptionsEditor.show(type, typeOptions, availableOptions); }); } @@ -315,10 +320,16 @@ define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], funct var list = dom.parentWithClass(li, 'paperList'); if (btnSortable.classList.contains('btnSortableMoveDown')) { var next = li.nextSibling; - next && (li.parentNode.removeChild(li), next.parentNode.insertBefore(li, next.nextSibling)); + if (next) { + li.parentNode.removeChild(li); + next.parentNode.insertBefore(li, next.nextSibling); + } } else { var prev = li.previousSibling; - prev && (li.parentNode.removeChild(li), prev.parentNode.insertBefore(li, prev)); + if (prev) { + li.parentNode.removeChild(li); + prev.parentNode.insertBefore(li, prev); + } } Array.prototype.forEach.call(list.querySelectorAll('.sortableOption'), adjustSortableListElement); } diff --git a/src/components/metadataeditor/metadataeditor.js b/src/components/metadataeditor/metadataeditor.js index 7f1e50b5ff..e1c1c8001f 100644 --- a/src/components/metadataeditor/metadataeditor.js +++ b/src/components/metadataeditor/metadataeditor.js @@ -791,11 +791,7 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi return a.Name; }).join(';'); - if (item.Type === 'Series') { - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - } else { - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - } + context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { return a.Name; diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index ee85f9acb1..dea419c8cc 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -309,13 +309,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (codecProfile.Type === 'Audio') { (codecProfile.Conditions || []).map(function (condition) { if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { - maxAudioBitDepth = condition.Value; - } - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { - maxAudioSampleRate = condition.Value; - } - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { - maxAudioBitrate = condition.Value; + return maxAudioBitDepth = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') { + return maxAudioSampleRate = condition.Value; + } else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') { + return maxAudioBitrate = condition.Value; } }); } diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index 8ce1fd5803..e9bcc0bfca 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -232,11 +232,7 @@ define(['dialogHelper', 'require', 'layoutManager', 'globalize', 'userSettings', html += ''; } html += '

' + provider + '

'; - if (layoutManager.tv) { - html += '
'; - } else { - html += '
'; - } + html += '
'; lastProvider = provider; } diff --git a/src/components/subtitleeditor/subtitleeditor.template.html b/src/components/subtitleeditor/subtitleeditor.template.html index 01066ecc66..7471972f59 100644 --- a/src/components/subtitleeditor/subtitleeditor.template.html +++ b/src/components/subtitleeditor/subtitleeditor.template.html @@ -2,8 +2,7 @@

${Subtitles}

- ${Help} - + ${Help}
diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index 4927029189..fb986ec348 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -32,8 +32,12 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', this.textContent = offset + 's'; }; - subtitleSyncTextField.addEventListener('keypress', function(event) { + subtitleSyncTextField.addEventListener('click', function () { + // 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); @@ -55,7 +59,7 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', this.hasFocus = false; event.preventDefault(); } else { - // keep focus to prevent fade with bottom layout + // keep focus to prevent fade with osd this.hasFocus = true; if (event.key.match(/[+-\d.s]/) === null) { event.preventDefault(); @@ -63,6 +67,13 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', } }); + 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; @@ -140,12 +151,10 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', /* eslint-disable no-fallthrough */ switch (action) { case undefined: - // if showing subtitle sync is enabled - if (playbackManager.isShowingSubtitleOffsetEnabled(player) && - // if there is an external subtitle stream enabled - playbackManager.canHandleOffsetOnCurrentSubtitle(player)) { - // if no subtitle offset is defined - if (!playbackManager.getPlayerSubtitleOffset(player)) { + // if showing subtitle sync is enabled and if there is an external subtitle stream enabled + if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) { + // if no subtitle offset is defined or element has focus (offset being defined) + if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) { // set default offset to '0' = 50% subtitleSyncSlider.value = '50'; subtitleSyncTextField.textContent = '0s'; diff --git a/src/components/tvproviders/schedulesdirect.template.html b/src/components/tvproviders/schedulesdirect.template.html index 3cfba06fe1..abe19b50f5 100644 --- a/src/components/tvproviders/schedulesdirect.template.html +++ b/src/components/tvproviders/schedulesdirect.template.html @@ -1,7 +1,7 @@

Schedules Direct

- ${Help} + ${Help}

diff --git a/src/components/tvproviders/xmltv.template.html b/src/components/tvproviders/xmltv.template.html index e8dc434165..72c29904b3 100644 --- a/src/components/tvproviders/xmltv.template.html +++ b/src/components/tvproviders/xmltv.template.html @@ -1,7 +1,7 @@

Xml TV

- ${Help} + ${Help}
diff --git a/src/components/upnextdialog/upnextdialog.css b/src/components/upnextdialog/upnextdialog.css index 15f91b29d9..05e3b10f57 100644 --- a/src/components/upnextdialog/upnextdialog.css +++ b/src/components/upnextdialog/upnextdialog.css @@ -63,8 +63,8 @@ height: auto; width: 100%; box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); - border: 0; user-drag: none; + border: 0; user-select: none; -moz-user-select: none; -webkit-user-drag: none; diff --git a/src/config.example.json b/src/config.template.json similarity index 100% rename from src/config.example.json rename to src/config.template.json diff --git a/src/controllers/list.js b/src/controllers/list.js index cef6ceda6c..edd4469007 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1016,7 +1016,6 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' if ('Programs' === params.type) { filters.push('Genres'); } else { - params.type; filters.push('IsUnplayed'); filters.push('IsPlayed'); diff --git a/src/controllers/livetvtuner.js b/src/controllers/livetvtuner.js index e8a03e7f2b..d7a4d92db2 100644 --- a/src/controllers/livetvtuner.js +++ b/src/controllers/livetvtuner.js @@ -82,7 +82,6 @@ define(['globalize', 'loading', 'libraryMenu', 'dom', 'emby-input', 'emby-button info.Id = id; } - info.Id; ApiClient.ajax({ type: 'POST', url: ApiClient.getUrl('LiveTv/TunerHosts'), diff --git a/src/controllers/usernew.js b/src/controllers/usernew.js index e59143a9d8..5646b239d0 100644 --- a/src/controllers/usernew.js +++ b/src/controllers/usernew.js @@ -8,12 +8,12 @@ define(['jQuery', 'loading', 'globalize', 'fnchecked', 'emby-checkbox'], functio for (var i = 0; i < mediaFolders.length; i++) { var folder = mediaFolders[i]; - html += ''; + html += ''; } html += '
'; $('.folderAccess', page).html(html).trigger('create'); - $('#chkEnableAllFolders', page).checked(true).trigger('change'); + $('#chkEnableAllFolders', page).checked(false).trigger('change'); } function loadChannels(page, channels) { @@ -23,7 +23,7 @@ define(['jQuery', 'loading', 'globalize', 'fnchecked', 'emby-checkbox'], functio for (var i = 0; i < channels.length; i++) { var folder = channels[i]; - html += ''; + html += ''; } html += '
'; @@ -35,7 +35,7 @@ define(['jQuery', 'loading', 'globalize', 'fnchecked', 'emby-checkbox'], functio $('.channelAccessContainer', page).hide(); } - $('#chkEnableAllChannels', page).checked(true).trigger('change'); + $('#chkEnableAllChannels', page).checked(false).trigger('change'); } function loadUser(page) { diff --git a/src/dashboard.html b/src/dashboard.html index 8be9f54eba..9063f55965 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -112,7 +112,7 @@
diff --git a/src/dashboardgeneral.html b/src/dashboardgeneral.html index 59c8e623d7..03bfa9af8d 100644 --- a/src/dashboardgeneral.html +++ b/src/dashboardgeneral.html @@ -5,7 +5,7 @@

${TabSettings}

- ${Help} + ${Help}
@@ -19,7 +19,7 @@
${LabelPreferredDisplayLanguageHelp}
- ${LabelReadHowYouCanContribute} + ${LabelReadHowYouCanContribute}
diff --git a/src/device.html b/src/device.html index 11fc9671b1..093b311208 100644 --- a/src/device.html +++ b/src/device.html @@ -5,7 +5,7 @@
diff --git a/src/devices.html b/src/devices.html index 4e6552f05e..55f51d7e23 100644 --- a/src/devices.html +++ b/src/devices.html @@ -4,7 +4,7 @@

${TabDevices}

- ${Help} + ${Help}
diff --git a/src/dlnaprofile.html b/src/dlnaprofile.html index 45182fa59e..dbe9131d70 100644 --- a/src/dlnaprofile.html +++ b/src/dlnaprofile.html @@ -5,7 +5,7 @@

${HeaderProfileInformation}

- ${Help} + ${Help}
diff --git a/src/dlnasettings.html b/src/dlnasettings.html index 8068acf5ab..703dd66a9f 100644 --- a/src/dlnasettings.html +++ b/src/dlnasettings.html @@ -8,7 +8,7 @@

${TabSettings}

- ${Help} + ${Help}
diff --git a/src/elements/emby-slider/emby-slider.css b/src/elements/emby-slider/emby-slider.css index f59c2a3cd0..7661895f15 100644 --- a/src/elements/emby-slider/emby-slider.css +++ b/src/elements/emby-slider/emby-slider.css @@ -87,10 +87,6 @@ transform: scale(1.3); } -.slider-no-webkit-thumb::-webkit-slider-thumb { - opacity: 0 !important; -} - .mdl-slider::-moz-range-thumb { -moz-appearance: none; width: 1.08em; diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 03d64719e2..e37455dfe1 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -148,9 +148,6 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli this.classList.add('mdl-slider'); this.classList.add('mdl-js-slider'); - if (browser.noFlex) { - this.classList.add('slider-no-webkit-thumb'); - } if (browser.edge || browser.msie) { this.classList.add('slider-browser-edge'); } diff --git a/src/encodingsettings.html b/src/encodingsettings.html index 27cfe404c1..8971957b71 100644 --- a/src/encodingsettings.html +++ b/src/encodingsettings.html @@ -5,7 +5,7 @@

${TabTranscoding}

- ${Help} + ${Help}
@@ -21,7 +21,7 @@
diff --git a/src/libraries/apiclient/apiclientcore.js b/src/libraries/apiclient/apiclientcore.js deleted file mode 100644 index 2046cae69b..0000000000 --- a/src/libraries/apiclient/apiclientcore.js +++ /dev/null @@ -1,1630 +0,0 @@ -define(["events", "appStorage"], function(events, appStorage) { - "use strict"; - - /** Report rate limits in ms for different events */ - const reportRateLimits = { - "timeupdate": 10000, - "volumechange": 3000 - }; - - function redetectBitrate(instance) { - stopBitrateDetection(instance), instance.accessToken() && !1 !== instance.enableAutomaticBitrateDetection && setTimeout(redetectBitrateInternal.bind(instance), 6e3) - } - - function redetectBitrateInternal() { - this.accessToken() && this.detectBitrate() - } - - function stopBitrateDetection(instance) { - instance.detectTimeout && clearTimeout(instance.detectTimeout) - } - - function replaceAll(originalString, strReplace, strWith) { - var reg = new RegExp(strReplace, "ig"); - return originalString.replace(reg, strWith) - } - - function onFetchFail(instance, url, response) { - events.trigger(instance, "requestfail", [{ - url: url, - status: response.status, - errorCode: response.headers ? response.headers.get("X-Application-Error-Code") : null - }]) - } - - function paramsToString(params) { - var values = []; - for (var key in params) { - var value = params[key]; - null !== value && void 0 !== value && "" !== value && values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)) - } - return values.join("&") - } - - function fetchWithTimeout(url, options, timeoutMs) { - return new Promise(function(resolve, reject) { - var timeout = setTimeout(reject, timeoutMs); - options = options || {}, options.credentials = "same-origin", fetch(url, options).then(function(response) { - clearTimeout(timeout), resolve(response) - }, function(error) { - clearTimeout(timeout), reject(error) - }) - }) - } - - function getFetchPromise(request) { - var headers = request.headers || {}; - "json" === request.dataType && (headers.accept = "application/json"); - var fetchRequest = { - headers: headers, - method: request.type, - credentials: "same-origin" - }, - contentType = request.contentType; - return request.data && ("string" == typeof request.data ? fetchRequest.body = request.data : (fetchRequest.body = paramsToString(request.data), contentType = contentType || "application/x-www-form-urlencoded; charset=UTF-8")), contentType && (headers["Content-Type"] = contentType), request.timeout ? fetchWithTimeout(request.url, fetchRequest, request.timeout) : fetch(request.url, fetchRequest) - } - - function ApiClient(serverAddress, appName, appVersion, deviceName, deviceId) { - if (!serverAddress) { - throw new Error("Must supply a serverAddress"); - } - - console.debug("ApiClient serverAddress: " + serverAddress); - console.debug("ApiClient appName: " + appName); - console.debug("ApiClient appVersion: " + appVersion); - console.debug("ApiClient deviceName: " + deviceName); - console.debug("ApiClient deviceId: " + deviceId); - - this._serverInfo = {}; - this._serverAddress = serverAddress; - this._deviceId = deviceId; - this._deviceName = deviceName; - this._appName = appName; - this._appVersion = appVersion; - } - - function setSavedEndpointInfo(instance, info) { - instance._endPointInfo = info - } - - function getTryConnectPromise(instance, url, state, resolve, reject) { - console.debug("getTryConnectPromise " + url); - fetchWithTimeout(instance.getUrl("system/info/public", null, url), { - method: "GET", - accept: "application/json" - }, 15e3).then(function() { - state.resolved || (state.resolved = !0, console.debug("Reconnect succeeded to " + url), instance.serverAddress(url), resolve()) - }, function() { - state.resolved || (console.error("Reconnect failed to " + url), ++state.rejects >= state.numAddresses && reject()) - }) - } - - function tryReconnectInternal(instance) { - var addresses = [], - addressesStrings = [], - serverInfo = instance.serverInfo(); - return serverInfo.LocalAddress && -1 === addressesStrings.indexOf(serverInfo.LocalAddress) && (addresses.push({ - url: serverInfo.LocalAddress, - timeout: 0 - }), addressesStrings.push(addresses[addresses.length - 1].url)), serverInfo.ManualAddress && -1 === addressesStrings.indexOf(serverInfo.ManualAddress) && (addresses.push({ - url: serverInfo.ManualAddress, - timeout: 100 - }), addressesStrings.push(addresses[addresses.length - 1].url)), serverInfo.RemoteAddress && -1 === addressesStrings.indexOf(serverInfo.RemoteAddress) && (addresses.push({ - url: serverInfo.RemoteAddress, - timeout: 200 - }), addressesStrings.push(addresses[addresses.length - 1].url)), console.debug("tryReconnect: " + addressesStrings.join("|")), new Promise(function(resolve, reject) { - var state = {}; - state.numAddresses = addresses.length, state.rejects = 0, addresses.map(function(url) { - setTimeout(function() { - state.resolved || getTryConnectPromise(instance, url.url, state, resolve, reject) - }, url.timeout) - }) - }) - } - - function tryReconnect(instance, retryCount) { - return retryCount = retryCount || 0, retryCount >= 20 ? Promise.reject() : tryReconnectInternal(instance).catch(function(err) { - return console.error("error in tryReconnectInternal: " + (err || "")), new Promise(function(resolve, reject) { - setTimeout(function() { - tryReconnect(instance, retryCount + 1).then(resolve, reject) - }, 500) - }) - }) - } - - function getCachedUser(instance, userId) { - var serverId = instance.serverId(); - if (!serverId) return null; - var json = appStorage.getItem("user-" + userId + "-" + serverId); - return json ? JSON.parse(json) : null - } - - function onWebSocketMessage(msg) { - var instance = this; - msg = JSON.parse(msg.data), onMessageReceivedInternal(instance, msg) - } - - function onMessageReceivedInternal(instance, msg) { - var messageId = msg.MessageId; - if (messageId) { - if (messageIdsReceived[messageId]) return; - messageIdsReceived[messageId] = !0 - } - if ("UserDeleted" === msg.MessageType) instance._currentUser = null; - else if ("UserUpdated" === msg.MessageType || "UserConfigurationUpdated" === msg.MessageType) { - var user = msg.Data; - user.Id === instance.getCurrentUserId() && (instance._currentUser = null) - } - events.trigger(instance, "message", [msg]) - } - - function onWebSocketOpen() { - var instance = this; - console.debug("web socket connection opened"), events.trigger(instance, "websocketopen") - } - - function onWebSocketError() { - var instance = this; - events.trigger(instance, "websocketerror") - } - - function setSocketOnClose(apiClient, socket) { - socket.onclose = function() { - console.debug("web socket closed"); - if (apiClient._webSocket === socket) { - console.debug("nulling out web socket"); - apiClient._webSocket = null; - } - setTimeout(function() { - events.trigger(apiClient, "websocketclose") - }, 0) - } - } - - function normalizeReturnBitrate(instance, bitrate) { - if (!bitrate) return instance.lastDetectedBitrate ? instance.lastDetectedBitrate : Promise.reject(); - var result = Math.round(.7 * bitrate); - if (instance.getMaxBandwidth) { - var maxRate = instance.getMaxBandwidth(); - maxRate && (result = Math.min(result, maxRate)) - } - return instance.lastDetectedBitrate = result, instance.lastDetectedBitrateTime = (new Date).getTime(), result - } - - function detectBitrateInternal(instance, tests, index, currentBitrate) { - if (index >= tests.length) return normalizeReturnBitrate(instance, currentBitrate); - var test = tests[index]; - return instance.getDownloadSpeed(test.bytes).then(function(bitrate) { - return bitrate < test.threshold ? normalizeReturnBitrate(instance, bitrate) : detectBitrateInternal(instance, tests, index + 1, bitrate) - }, function() { - return normalizeReturnBitrate(instance, currentBitrate) - }) - } - - function detectBitrateWithEndpointInfo(instance, endpointInfo) { - if (endpointInfo.IsInNetwork) { - return instance.lastDetectedBitrate = 14e7, instance.lastDetectedBitrateTime = (new Date).getTime(), 14e7 - } - return detectBitrateInternal(instance, [{ - bytes: 5e5, - threshold: 5e5 - }, { - bytes: 1e6, - threshold: 2e7 - }, { - bytes: 3e6, - threshold: 5e7 - }], 0) - } - - function getRemoteImagePrefix(instance, options) { - var urlPrefix; - return options.artist ? (urlPrefix = "Artists/" + instance.encodeName(options.artist), delete options.artist) : options.person ? (urlPrefix = "Persons/" + instance.encodeName(options.person), delete options.person) : options.genre ? (urlPrefix = "Genres/" + instance.encodeName(options.genre), delete options.genre) : options.musicGenre ? (urlPrefix = "MusicGenres/" + instance.encodeName(options.musicGenre), delete options.musicGenre) : options.studio ? (urlPrefix = "Studios/" + instance.encodeName(options.studio), delete options.studio) : (urlPrefix = "Items/" + options.itemId, delete options.itemId), urlPrefix - } - - function normalizeImageOptions(instance, options) { - var ratio = window.devicePixelRatio; - ratio && (options.minScale && (ratio = Math.max(options.minScale, ratio)), options.width && (options.width = Math.round(options.width * ratio)), options.height && (options.height = Math.round(options.height * ratio)), options.maxWidth && (options.maxWidth = Math.round(options.maxWidth * ratio)), options.maxHeight && (options.maxHeight = Math.round(options.maxHeight * ratio))), options.quality = options.quality || instance.getDefaultImageQuality(options.type), instance.normalizeImageOptions && instance.normalizeImageOptions(options) - } - - function compareVersions(a, b) { - a = a.split("."), b = b.split("."); - for (var i = 0, length = Math.max(a.length, b.length); i < length; i++) { - var aVal = parseInt(a[i] || "0"), - bVal = parseInt(b[i] || "0"); - if (aVal < bVal) return -1; - if (aVal > bVal) return 1 - } - return 0 - } - - function cancelReportPlaybackProgressPromise(instance) { - if (typeof instance.reportPlaybackProgressCancel === "function") instance.reportPlaybackProgressCancel(); - } - - ApiClient.prototype.appName = function() { - return this._appName - }, ApiClient.prototype.setRequestHeaders = function(headers) { - var currentServerInfo = this.serverInfo(), - appName = this._appName, - accessToken = currentServerInfo.AccessToken, - values = []; - if (appName && values.push('Client="' + appName + '"'), this._deviceName && values.push('Device="' + this._deviceName + '"'), this._deviceId && values.push('DeviceId="' + this._deviceId + '"'), this._appVersion && values.push('Version="' + this._appVersion + '"'), accessToken && values.push('Token="' + accessToken + '"'), values.length) { - var auth = "MediaBrowser " + values.join(", "); - headers["X-Emby-Authorization"] = auth - } - }, ApiClient.prototype.appVersion = function() { - return this._appVersion - }, ApiClient.prototype.deviceName = function() { - return this._deviceName - }, ApiClient.prototype.deviceId = function() { - return this._deviceId - }, ApiClient.prototype.serverAddress = function(val) { - if (null != val) { - if (0 !== val.toLowerCase().indexOf("http")) throw new Error("Invalid url: " + val); - var changed = val !== this._serverAddress; - this._serverAddress = val, this.onNetworkChange(), changed && events.trigger(this, "serveraddresschanged") - } - return this._serverAddress - }, ApiClient.prototype.onNetworkChange = function() { - this.lastDetectedBitrate = 0, this.lastDetectedBitrateTime = 0, setSavedEndpointInfo(this, null), redetectBitrate(this) - }, ApiClient.prototype.getUrl = function(name, params, serverAddress) { - if (!name) throw new Error("Url name cannot be empty"); - var url = serverAddress || this._serverAddress; - if (!url) throw new Error("serverAddress is yet not set"); - var lowered = url.toLowerCase(); - return "/" !== name.charAt(0) && (url += "/"), url += name, params && (params = paramsToString(params)) && (url += "?" + params), url - }, ApiClient.prototype.fetchWithFailover = function(request, enableReconnection) { - console.debug("Requesting " + request.url), request.timeout = 3e4; - var instance = this; - return getFetchPromise(request).then(function(response) { - return instance.lastFetch = (new Date).getTime(), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : "text" === request.dataType || 0 === (response.headers.get("Content-Type") || "").toLowerCase().indexOf("text/") ? response.text() : response : (onFetchFail(instance, request.url, response), Promise.reject(response)) - }, function(error) { - if (error ? console.error("Request failed to " + request.url + " " + (error.status || "") + " " + error.toString()) : console.error("Request timed out to " + request.url), error && error.status || !enableReconnection) throw console.error("Reporting request failure"), onFetchFail(instance, request.url, {}), error; - console.debug("Attempting reconnection"); - var previousServerAddress = instance.serverAddress(); - return tryReconnect(instance).then(function() { - return console.debug("Reconnect succeesed"), request.url = request.url.replace(previousServerAddress, instance.serverAddress()), instance.fetchWithFailover(request, !1) - }, function(innerError) { - throw console.error("Reconnect failed"), onFetchFail(instance, request.url, {}), innerError - }) - }) - }, ApiClient.prototype.fetch = function(request, includeAuthorization) { - if (!request) throw new Error("Request cannot be null"); - if (request.headers = request.headers || {}, !1 !== includeAuthorization && this.setRequestHeaders(request.headers), !1 === this.enableAutomaticNetworking || "GET" !== request.type) { - console.debug("Requesting url without automatic networking: " + request.url); - var instance = this; - return getFetchPromise(request).then(function(response) { - return instance.lastFetch = (new Date).getTime(), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : "text" === request.dataType || 0 === (response.headers.get("Content-Type") || "").toLowerCase().indexOf("text/") ? response.text() : response : (onFetchFail(instance, request.url, response), Promise.reject(response)) - }, function(error) { - throw onFetchFail(instance, request.url, {}), error - }) - } - return this.fetchWithFailover(request, !0) - }, ApiClient.prototype.setAuthenticationInfo = function(accessKey, userId) { - this._currentUser = null, this._serverInfo.AccessToken = accessKey, this._serverInfo.UserId = userId, redetectBitrate(this) - }, ApiClient.prototype.serverInfo = function(info) { - return info && (this._serverInfo = info), this._serverInfo - }, ApiClient.prototype.getCurrentUserId = function() { - return this._serverInfo.UserId - }, ApiClient.prototype.accessToken = function() { - return this._serverInfo.AccessToken - }, ApiClient.prototype.serverId = function() { - return this.serverInfo().Id - }, ApiClient.prototype.serverName = function() { - return this.serverInfo().Name - }, ApiClient.prototype.ajax = function(request, includeAuthorization) { - if (!request) throw new Error("Request cannot be null"); - return this.fetch(request, includeAuthorization) - }, ApiClient.prototype.getCurrentUser = function(enableCache) { - if (this._currentUser) return Promise.resolve(this._currentUser); - var userId = this.getCurrentUserId(); - if (!userId) return Promise.reject(); - var user, instance = this, - serverPromise = this.getUser(userId).then(function(user) { - return appStorage.setItem("user-" + user.Id + "-" + user.ServerId, JSON.stringify(user)), instance._currentUser = user, user - }, function(response) { - if (!response.status && userId && instance.accessToken() && (user = getCachedUser(instance, userId))) return Promise.resolve(user); - throw response - }); - return !this.lastFetch && !1 !== enableCache && (user = getCachedUser(instance, userId)) ? Promise.resolve(user) : serverPromise - }, ApiClient.prototype.isLoggedIn = function() { - var info = this.serverInfo(); - return !!(info && info.UserId && info.AccessToken) - }, ApiClient.prototype.logout = function() { - stopBitrateDetection(this), this.closeWebSocket(); - var done = function() { - appStorage.removeItem("user-" + this._currentUser.Id + "-" + this._currentUser.ServerId) - this.setAuthenticationInfo(null, null) - }.bind(this); - if (this.accessToken()) { - var url = this.getUrl("Sessions/Logout"); - return this.ajax({ - type: "POST", - url: url - }).then(done, done) - } - return done(), Promise.resolve() - }, ApiClient.prototype.authenticateUserByName = function(name, password) { - if (!name) return Promise.reject(); - var url = this.getUrl("Users/authenticatebyname"), - instance = this; - return new Promise(function(resolve, reject) { - var postData = { - Username: name, - Pw: password || "" - }; - instance.ajax({ - type: "POST", - url: url, - data: JSON.stringify(postData), - dataType: "json", - contentType: "application/json" - }).then(function(result) { - var afterOnAuthenticated = function() { - redetectBitrate(instance), resolve(result) - }; - instance.onAuthenticated ? instance.onAuthenticated(instance, result).then(afterOnAuthenticated) : afterOnAuthenticated() - }, reject) - }) - }, ApiClient.prototype.ensureWebSocket = function() { - if (!this.isWebSocketOpenOrConnecting() && this.isWebSocketSupported()) try { - this.openWebSocket() - } catch (err) { - console.error("error opening web socket: " + err) - } - }; - var messageIdsReceived = {}; - return ApiClient.prototype.openWebSocket = function() { - var accessToken = this.accessToken(); - if (!accessToken) throw new Error("Cannot open web socket without access token."); - var url = this.getUrl("socket"); - url = replaceAll(url, "emby/socket", "embywebsocket"), url = replaceAll(url, "https:", "wss:"), url = replaceAll(url, "http:", "ws:"), url += "?api_key=" + accessToken, url += "&deviceId=" + this.deviceId(), console.debug("opening web socket with url: " + url); - var webSocket = new WebSocket(url); - webSocket.onmessage = onWebSocketMessage.bind(this), webSocket.onopen = onWebSocketOpen.bind(this), webSocket.onerror = onWebSocketError.bind(this), setSocketOnClose(this, webSocket), this._webSocket = webSocket - }, ApiClient.prototype.closeWebSocket = function() { - var socket = this._webSocket; - socket && socket.readyState === WebSocket.OPEN && socket.close() - }, ApiClient.prototype.sendWebSocketMessage = function(name, data) { - console.debug("Sending web socket message: " + name); - var msg = { - MessageType: name - }; - data && (msg.Data = data), msg = JSON.stringify(msg), this._webSocket.send(msg) - }, ApiClient.prototype.sendMessage = function(name, data) { - this.isWebSocketOpen() && this.sendWebSocketMessage(name, data) - }, ApiClient.prototype.isMessageChannelOpen = function() { - return this.isWebSocketOpen() - }, ApiClient.prototype.isWebSocketOpen = function() { - var socket = this._webSocket; - return !!socket && socket.readyState === WebSocket.OPEN - }, ApiClient.prototype.isWebSocketOpenOrConnecting = function() { - var socket = this._webSocket; - return !!socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) - }, ApiClient.prototype.get = function(url) { - return this.ajax({ - type: "GET", - url: url - }) - }, ApiClient.prototype.getJSON = function(url, includeAuthorization) { - return this.fetch({ - url: url, - type: "GET", - dataType: "json", - headers: { - accept: "application/json" - } - }, includeAuthorization) - }, ApiClient.prototype.updateServerInfo = function(server, serverUrl) { - if (null == server) throw new Error("server cannot be null"); - if (this.serverInfo(server), !serverUrl) throw new Error("serverUrl cannot be null. serverInfo: " + JSON.stringify(server)); - console.debug("Setting server address to " + serverUrl), this.serverAddress(serverUrl) - }, ApiClient.prototype.isWebSocketSupported = function() { - try { - return null != WebSocket - } catch (err) { - return !1 - } - }, ApiClient.prototype.clearAuthenticationInfo = function() { - this.setAuthenticationInfo(null, null) - }, ApiClient.prototype.encodeName = function(name) { - name = name.split("/").join("-"), name = name.split("&").join("-"), name = name.split("?").join("-"); - var val = paramsToString({ - name: name - }); - return val.substring(val.indexOf("=") + 1).replace("'", "%27") - }, ApiClient.prototype.getDownloadSpeed = function(byteSize) { - var url = this.getUrl("Playback/BitrateTest", { - Size: byteSize - }), - now = (new Date).getTime(); - return this.ajax({ - type: "GET", - url: url, - timeout: 5e3 - }).then(function() { - var responseTimeSeconds = ((new Date).getTime() - now) / 1e3, - bytesPerSecond = byteSize / responseTimeSeconds; - return Math.round(8 * bytesPerSecond) - }) - }, ApiClient.prototype.detectBitrate = function(force) { - if (!force && this.lastDetectedBitrate && (new Date).getTime() - (this.lastDetectedBitrateTime || 0) <= 36e5) return Promise.resolve(this.lastDetectedBitrate); - var instance = this; - return this.getEndpointInfo().then(function(info) { - return detectBitrateWithEndpointInfo(instance, info) - }, function(info) { - return detectBitrateWithEndpointInfo(instance, {}) - }) - }, ApiClient.prototype.getItem = function(userId, itemId) { - if (!itemId) throw new Error("null itemId"); - var url = userId ? this.getUrl("Users/" + userId + "/Items/" + itemId) : this.getUrl("Items/" + itemId); - return this.getJSON(url) - }, ApiClient.prototype.getRootFolder = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/Items/Root"); - return this.getJSON(url) - }, ApiClient.prototype.getNotificationSummary = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Notifications/" + userId + "/Summary"); - return this.getJSON(url) - }, ApiClient.prototype.getNotifications = function(userId, options) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Notifications/" + userId, options || {}); - return this.getJSON(url) - }, ApiClient.prototype.markNotificationsRead = function(userId, idList, isRead) { - if (!userId) throw new Error("null userId"); - if (!idList) throw new Error("null idList"); - var suffix = isRead ? "Read" : "Unread", - params = { - UserId: userId, - Ids: idList.join(",") - }, - url = this.getUrl("Notifications/" + userId + "/" + suffix, params); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getRemoteImageProviders = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages/Providers", options); - return this.getJSON(url) - }, ApiClient.prototype.getAvailableRemoteImages = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages", options); - return this.getJSON(url) - }, ApiClient.prototype.downloadRemoteImage = function(options) { - if (!options) throw new Error("null options"); - var urlPrefix = getRemoteImagePrefix(this, options), - url = this.getUrl(urlPrefix + "/RemoteImages/Download", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getRecordingFolders = function(userId) { - var url = this.getUrl("LiveTv/Recordings/Folders", { - userId: userId - }); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvInfo = function(options) { - var url = this.getUrl("LiveTv/Info", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvGuideInfo = function(options) { - var url = this.getUrl("LiveTv/GuideInfo", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvChannel = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Channels/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvChannels = function(options) { - var url = this.getUrl("LiveTv/Channels", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvPrograms = function(options) { - return options = options || {}, options.channelIds && options.channelIds.length > 1800 ? this.ajax({ - type: "POST", - url: this.getUrl("LiveTv/Programs"), - data: JSON.stringify(options), - contentType: "application/json", - dataType: "json" - }) : this.ajax({ - type: "GET", - url: this.getUrl("LiveTv/Programs", options), - dataType: "json" - }) - }, ApiClient.prototype.getLiveTvRecommendedPrograms = function(options) { - return options = options || {}, this.ajax({ - type: "GET", - url: this.getUrl("LiveTv/Programs/Recommended", options), - dataType: "json" - }) - }, ApiClient.prototype.getLiveTvRecordings = function(options) { - var url = this.getUrl("LiveTv/Recordings", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingSeries = function(options) { - var url = this.getUrl("LiveTv/Recordings/Series", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingGroups = function(options) { - var url = this.getUrl("LiveTv/Recordings/Groups", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecordingGroup = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Recordings/Groups/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvRecording = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Recordings/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvProgram = function(id, userId) { - if (!id) throw new Error("null id"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("LiveTv/Programs/" + id, options); - return this.getJSON(url) - }, ApiClient.prototype.deleteLiveTvRecording = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Recordings/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.cancelLiveTvTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Timers/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.getLiveTvTimers = function(options) { - var url = this.getUrl("LiveTv/Timers", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Timers/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getNewLiveTvTimerDefaults = function(options) { - options = options || {}; - var url = this.getUrl("LiveTv/Timers/Defaults", options); - return this.getJSON(url) - }, ApiClient.prototype.createLiveTvTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/Timers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updateLiveTvTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/Timers/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.resetLiveTvTuner = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/Tuners/" + id + "/Reset"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getLiveTvSeriesTimers = function(options) { - var url = this.getUrl("LiveTv/SeriesTimers", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getLiveTvSeriesTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/SeriesTimers/" + id); - return this.getJSON(url) - }, ApiClient.prototype.cancelLiveTvSeriesTimer = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("LiveTv/SeriesTimers/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.createLiveTvSeriesTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/SeriesTimers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updateLiveTvSeriesTimer = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("LiveTv/SeriesTimers/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.getRegistrationInfo = function(feature) { - var url = this.getUrl("Registrations/" + feature); - return this.getJSON(url) - }, ApiClient.prototype.getSystemInfo = function() { - var url = this.getUrl("System/Info"), - instance = this; - return this.getJSON(url).then(function(info) { - return instance.setSystemInfo(info), Promise.resolve(info) - }) - }, ApiClient.prototype.getSyncStatus = function(itemId) { - var url = this.getUrl("Sync/" + itemId + "/Status"); - return this.ajax({ - url: url, - type: "POST", - dataType: "json", - contentType: "application/json", - data: JSON.stringify({ - TargetId: this.deviceId() - }) - }) - }, ApiClient.prototype.getPublicSystemInfo = function() { - var url = this.getUrl("System/Info/Public"), - instance = this; - return this.getJSON(url).then(function(info) { - return instance.setSystemInfo(info), Promise.resolve(info) - }) - }, ApiClient.prototype.getInstantMixFromItem = function(itemId, options) { - var url = this.getUrl("Items/" + itemId + "/InstantMix", options); - return this.getJSON(url) - }, ApiClient.prototype.getEpisodes = function(itemId, options) { - var url = this.getUrl("Shows/" + itemId + "/Episodes", options); - return this.getJSON(url) - }, ApiClient.prototype.getDisplayPreferences = function(id, userId, app) { - var url = this.getUrl("DisplayPreferences/" + id, { - userId: userId, - client: app - }); - return this.getJSON(url) - }, ApiClient.prototype.updateDisplayPreferences = function(id, obj, userId, app) { - var url = this.getUrl("DisplayPreferences/" + id, { - userId: userId, - client: app - }); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(obj), - contentType: "application/json" - }) - }, ApiClient.prototype.getSeasons = function(itemId, options) { - var url = this.getUrl("Shows/" + itemId + "/Seasons", options); - return this.getJSON(url) - }, ApiClient.prototype.getSimilarItems = function(itemId, options) { - var url = this.getUrl("Items/" + itemId + "/Similar", options); - return this.getJSON(url) - }, ApiClient.prototype.getCultures = function() { - var url = this.getUrl("Localization/cultures"); - return this.getJSON(url) - }, ApiClient.prototype.getCountries = function() { - var url = this.getUrl("Localization/countries"); - return this.getJSON(url) - }, ApiClient.prototype.getPlaybackInfo = function(itemId, options, deviceProfile) { - var postData = { - DeviceProfile: deviceProfile - }; - return this.ajax({ - url: this.getUrl("Items/" + itemId + "/PlaybackInfo", options), - type: "POST", - data: JSON.stringify(postData), - contentType: "application/json", - dataType: "json" - }) - }, ApiClient.prototype.getLiveStreamMediaInfo = function(liveStreamId) { - var postData = { - LiveStreamId: liveStreamId - }; - return this.ajax({ - url: this.getUrl("LiveStreams/MediaInfo"), - type: "POST", - data: JSON.stringify(postData), - contentType: "application/json", - dataType: "json" - }) - }, ApiClient.prototype.getIntros = function(itemId) { - return this.getJSON(this.getUrl("Users/" + this.getCurrentUserId() + "/Items/" + itemId + "/Intros")) - }, ApiClient.prototype.getDirectoryContents = function(path, options) { - if (!path) throw new Error("null path"); - if ("string" != typeof path) throw new Error("invalid path"); - options = options || {}, options.path = path; - var url = this.getUrl("Environment/DirectoryContents", options); - return this.getJSON(url) - }, ApiClient.prototype.getNetworkShares = function(path) { - if (!path) throw new Error("null path"); - var options = {}; - options.path = path; - var url = this.getUrl("Environment/NetworkShares", options); - return this.getJSON(url) - }, ApiClient.prototype.getParentPath = function(path) { - if (!path) throw new Error("null path"); - var options = {}; - options.path = path; - var url = this.getUrl("Environment/ParentPath", options); - return this.ajax({ - type: "GET", - url: url, - dataType: "text" - }) - }, ApiClient.prototype.getDrives = function() { - var url = this.getUrl("Environment/Drives"); - return this.getJSON(url) - }, ApiClient.prototype.getNetworkDevices = function() { - var url = this.getUrl("Environment/NetworkDevices"); - return this.getJSON(url) - }, ApiClient.prototype.cancelPackageInstallation = function(installationId) { - if (!installationId) throw new Error("null installationId"); - var url = this.getUrl("Packages/Installing/" + installationId); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.refreshItem = function(itemId, options) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId + "/Refresh", options || {}); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.installPlugin = function(name, guid, updateClass, version) { - if (!name) throw new Error("null name"); - if (!updateClass) throw new Error("null updateClass"); - var options = { - updateClass: updateClass, - AssemblyGuid: guid - }; - version && (options.version = version); - var url = this.getUrl("Packages/Installed/" + name, options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.restartServer = function() { - var url = this.getUrl("System/Restart"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.shutdownServer = function() { - var url = this.getUrl("System/Shutdown"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getPackageInfo = function(name, guid) { - if (!name) throw new Error("null name"); - var options = { - AssemblyGuid: guid - }, - url = this.getUrl("Packages/" + name, options); - return this.getJSON(url) - }, ApiClient.prototype.getVirtualFolders = function() { - var url = "Library/VirtualFolders"; - return url = this.getUrl(url), this.getJSON(url) - }, ApiClient.prototype.getPhysicalPaths = function() { - var url = this.getUrl("Library/PhysicalPaths"); - return this.getJSON(url) - }, ApiClient.prototype.getServerConfiguration = function() { - var url = this.getUrl("System/Configuration"); - return this.getJSON(url) - }, ApiClient.prototype.getDevicesOptions = function() { - var url = this.getUrl("System/Configuration/devices"); - return this.getJSON(url) - }, ApiClient.prototype.getContentUploadHistory = function() { - var url = this.getUrl("Devices/CameraUploads", { - DeviceId: this.deviceId() - }); - return this.getJSON(url) - }, ApiClient.prototype.getNamedConfiguration = function(name) { - var url = this.getUrl("System/Configuration/" + name); - return this.getJSON(url) - }, ApiClient.prototype.getScheduledTasks = function(options) { - options = options || {}; - var url = this.getUrl("ScheduledTasks", options); - return this.getJSON(url) - }, ApiClient.prototype.startScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/Running/" + id); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getNextUpEpisodes = function(options) { - var url = this.getUrl("Shows/NextUp", options); - return this.getJSON(url) - }, ApiClient.prototype.stopScheduledTask = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("ScheduledTasks/Running/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.getPluginConfiguration = function(id) { - if (!id) throw new Error("null Id"); - var url = this.getUrl("Plugins/" + id + "/Configuration"); - return this.getJSON(url) - }, ApiClient.prototype.getAvailablePlugins = function(options) { - options = options || {}, options.PackageType = "UserInstalled"; - var url = this.getUrl("Packages", options); - return this.getJSON(url) - }, ApiClient.prototype.uninstallPlugin = function(id) { - if (!id) throw new Error("null Id"); - var url = this.getUrl("Plugins/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.removeVirtualFolder = function(name, refreshLibrary) { - if (!name) throw new Error("null name"); - var url = "Library/VirtualFolders"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - name: name - }), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.addVirtualFolder = function(name, type, refreshLibrary, libraryOptions) { - if (!name) throw new Error("null name"); - var options = {}; - type && (options.collectionType = type), options.refreshLibrary = !!refreshLibrary, options.name = name; - var url = "Library/VirtualFolders"; - return url = this.getUrl(url, options), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - LibraryOptions: libraryOptions - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateVirtualFolderOptions = function(id, libraryOptions) { - if (!id) throw new Error("null name"); - var url = "Library/VirtualFolders/LibraryOptions"; - return url = this.getUrl(url), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Id: id, - LibraryOptions: libraryOptions - }), - contentType: "application/json" - }) - }, ApiClient.prototype.renameVirtualFolder = function(name, newName, refreshLibrary) { - if (!name) throw new Error("null name"); - var url = "Library/VirtualFolders/Name"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - newName: newName, - name: name - }), this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.addMediaPath = function(virtualFolderName, mediaPath, networkSharePath, refreshLibrary) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!mediaPath) throw new Error("null mediaPath"); - var url = "Library/VirtualFolders/Paths", - pathInfo = { - Path: mediaPath - }; - return networkSharePath && (pathInfo.NetworkPath = networkSharePath), url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary - }), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Name: virtualFolderName, - PathInfo: pathInfo - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateMediaPath = function(virtualFolderName, pathInfo) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!pathInfo) throw new Error("null pathInfo"); - var url = "Library/VirtualFolders/Paths/Update"; - return url = this.getUrl(url), this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - Name: virtualFolderName, - PathInfo: pathInfo - }), - contentType: "application/json" - }) - }, ApiClient.prototype.removeMediaPath = function(virtualFolderName, mediaPath, refreshLibrary) { - if (!virtualFolderName) throw new Error("null virtualFolderName"); - if (!mediaPath) throw new Error("null mediaPath"); - var url = "Library/VirtualFolders/Paths"; - return url = this.getUrl(url, { - refreshLibrary: !!refreshLibrary, - path: mediaPath, - name: virtualFolderName - }), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteUser = function(id) { - if (!id) throw new Error("null id"); - var url = this.getUrl("Users/" + id); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteUserImage = function(userId, imageType, imageIndex) { - if (!userId) throw new Error("null userId"); - if (!imageType) throw new Error("null imageType"); - var url = this.getUrl("Users/" + userId + "/Images/" + imageType); - return null != imageIndex && (url += "/" + imageIndex), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteItemImage = function(itemId, imageType, imageIndex) { - if (!imageType) throw new Error("null imageType"); - var url = this.getUrl("Items/" + itemId + "/Images"); - return url += "/" + imageType, null != imageIndex && (url += "/" + imageIndex), this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.deleteItem = function(itemId) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.stopActiveEncodings = function(playSessionId) { - var options = { - deviceId: this.deviceId() - }; - playSessionId && (options.PlaySessionId = playSessionId); - var url = this.getUrl("Videos/ActiveEncodings", options); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.reportCapabilities = function(options) { - var url = this.getUrl("Sessions/Capabilities/Full"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(options), - contentType: "application/json" - }) - }, ApiClient.prototype.updateItemImageIndex = function(itemId, imageType, imageIndex, newIndex) { - if (!imageType) throw new Error("null imageType"); - var options = { - newIndex: newIndex - }, - url = this.getUrl("Items/" + itemId + "/Images/" + imageType + "/" + imageIndex + "/Index", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getItemImageInfos = function(itemId) { - var url = this.getUrl("Items/" + itemId + "/Images"); - return this.getJSON(url) - }, ApiClient.prototype.getCriticReviews = function(itemId, options) { - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Items/" + itemId + "/CriticReviews", options); - return this.getJSON(url) - }, ApiClient.prototype.getItemDownloadUrl = function(itemId) { - if (!itemId) throw new Error("itemId cannot be empty"); - var url = "Items/" + itemId + "/Download"; - return this.getUrl(url, { - api_key: this.accessToken() - }) - }, ApiClient.prototype.getSessions = function(options) { - var url = this.getUrl("Sessions", options); - return this.getJSON(url) - }, ApiClient.prototype.uploadUserImage = function(userId, imageType, file) { - if (!userId) throw new Error("null userId"); - if (!imageType) throw new Error("null imageType"); - if (!file) throw new Error("File must be an image."); - if ("image/png" !== file.type && "image/jpeg" !== file.type && "image/jpeg" !== file.type) throw new Error("File must be an image."); - var instance = this; - return new Promise(function(resolve, reject) { - var reader = new FileReader; - reader.onerror = function() { - reject() - }, reader.onabort = function() { - reject() - }, reader.onload = function(e) { - var data = e.target.result.split(",")[1], - url = instance.getUrl("Users/" + userId + "/Images/" + imageType); - instance.ajax({ - type: "POST", - url: url, - data: data, - contentType: "image/" + file.name.substring(file.name.lastIndexOf(".") + 1) - }).then(resolve, reject) - }, reader.readAsDataURL(file) - }) - }, ApiClient.prototype.uploadItemImage = function(itemId, imageType, file) { - if (!itemId) throw new Error("null itemId"); - if (!imageType) throw new Error("null imageType"); - if (!file) throw new Error("File must be an image."); - if ("image/png" !== file.type && "image/jpeg" !== file.type && "image/jpeg" !== file.type) throw new Error("File must be an image."); - var url = this.getUrl("Items/" + itemId + "/Images"); - url += "/" + imageType; - var instance = this; - return new Promise(function(resolve, reject) { - var reader = new FileReader; - reader.onerror = function() { - reject() - }, reader.onabort = function() { - reject() - }, reader.onload = function(e) { - var data = e.target.result.split(",")[1]; - instance.ajax({ - type: "POST", - url: url, - data: data, - contentType: "image/" + file.name.substring(file.name.lastIndexOf(".") + 1) - }).then(resolve, reject) - }, reader.readAsDataURL(file) - }) - }, ApiClient.prototype.getInstalledPlugins = function() { - var options = {}, - url = this.getUrl("Plugins", options); - return this.getJSON(url) - }, ApiClient.prototype.getUser = function(id) { - if (!id) throw new Error("Must supply a userId"); - var url = this.getUrl("Users/" + id); - return this.getJSON(url) - }, ApiClient.prototype.getStudio = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Studios/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getGenre = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Genres/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getMusicGenre = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("MusicGenres/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getArtist = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Artists/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getPerson = function(name, userId) { - if (!name) throw new Error("null name"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Persons/" + this.encodeName(name), options); - return this.getJSON(url) - }, ApiClient.prototype.getPublicUsers = function() { - var url = this.getUrl("users/public"); - return this.ajax({ - type: "GET", - url: url, - dataType: "json" - }, !1) - }, ApiClient.prototype.getUsers = function(options) { - var url = this.getUrl("users", options || {}); - return this.getJSON(url) - }, ApiClient.prototype.getParentalRatings = function() { - var url = this.getUrl("Localization/ParentalRatings"); - return this.getJSON(url) - }, ApiClient.prototype.getDefaultImageQuality = function(imageType) { - return "backdrop" === imageType.toLowerCase() ? 80 : 90 - }, ApiClient.prototype.getUserImageUrl = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}; - var url = "Users/" + userId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), normalizeImageOptions(this, options), delete options.type, delete options.index, this.getUrl(url, options) - }, ApiClient.prototype.getImageUrl = function(itemId, options) { - if (!itemId) throw new Error("itemId cannot be empty"); - options = options || {}; - var url = "Items/" + itemId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), options.quality = options.quality || this.getDefaultImageQuality(options.type), this.normalizeImageOptions && this.normalizeImageOptions(options), delete options.type, delete options.index, this.getUrl(url, options) - }, ApiClient.prototype.getScaledImageUrl = function(itemId, options) { - if (!itemId) throw new Error("itemId cannot be empty"); - options = options || {}; - var url = "Items/" + itemId + "/Images/" + options.type; - return null != options.index && (url += "/" + options.index), normalizeImageOptions(this, options), delete options.type, delete options.index, delete options.minScale, this.getUrl(url, options) - }, ApiClient.prototype.getThumbImageUrl = function(item, options) { - if (!item) throw new Error("null item"); - return options = options || {}, options.imageType = "thumb", item.ImageTags && item.ImageTags.Thumb ? (options.tag = item.ImageTags.Thumb, this.getImageUrl(item.Id, options)) : item.ParentThumbItemId ? (options.tag = item.ImageTags.ParentThumbImageTag, this.getImageUrl(item.ParentThumbItemId, options)) : null - }, ApiClient.prototype.updateUserPassword = function(userId, currentPassword, newPassword) { - if (!userId) return Promise.reject(); - var url = this.getUrl("Users/" + userId + "/Password"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify({ - CurrentPw: currentPassword || "", - NewPw: newPassword - }), - contentType: "application/json" - }) - }, ApiClient.prototype.updateEasyPassword = function(userId, newPassword) { - if (!userId) return void Promise.reject(); - var url = this.getUrl("Users/" + userId + "/EasyPassword"); - return this.ajax({ - type: "POST", - url: url, - data: { - NewPw: newPassword - } - }) - }, ApiClient.prototype.resetUserPassword = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/Password"), - postData = {}; - return postData.resetPassword = !0, this.ajax({ - type: "POST", - url: url, - data: postData - }) - }, ApiClient.prototype.resetEasyPassword = function(userId) { - if (!userId) throw new Error("null userId"); - var url = this.getUrl("Users/" + userId + "/EasyPassword"), - postData = {}; - return postData.resetPassword = !0, this.ajax({ - type: "POST", - url: url, - data: postData - }) - }, ApiClient.prototype.updateServerConfiguration = function(configuration) { - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("System/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateNamedConfiguration = function(name, configuration) { - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("System/Configuration/" + name); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateItem = function(item) { - if (!item) throw new Error("null item"); - var url = this.getUrl("Items/" + item.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(item), - contentType: "application/json" - }) - }, ApiClient.prototype.updatePluginSecurityInfo = function(info) { - var url = this.getUrl("Plugins/SecurityInfo"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(info), - contentType: "application/json" - }) - }, ApiClient.prototype.createUser = function(user) { - var url = this.getUrl("Users/New"); - return this.ajax({ - type: "POST", - url: url, - data: user, - dataType: "json" - }) - }, ApiClient.prototype.updateUser = function(user) { - if (!user) throw new Error("null user"); - var url = this.getUrl("Users/" + user.Id); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(user), - contentType: "application/json" - }) - }, ApiClient.prototype.updateUserPolicy = function(userId, policy) { - if (!userId) throw new Error("null userId"); - if (!policy) throw new Error("null policy"); - var url = this.getUrl("Users/" + userId + "/Policy"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(policy), - contentType: "application/json" - }) - }, ApiClient.prototype.updateUserConfiguration = function(userId, configuration) { - if (!userId) throw new Error("null userId"); - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("Users/" + userId + "/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.updateScheduledTaskTriggers = function(id, triggers) { - if (!id) throw new Error("null id"); - if (!triggers) throw new Error("null triggers"); - var url = this.getUrl("ScheduledTasks/" + id + "/Triggers"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(triggers), - contentType: "application/json" - }) - }, ApiClient.prototype.updatePluginConfiguration = function(id, configuration) { - if (!id) throw new Error("null Id"); - if (!configuration) throw new Error("null configuration"); - var url = this.getUrl("Plugins/" + id + "/Configuration"); - return this.ajax({ - type: "POST", - url: url, - data: JSON.stringify(configuration), - contentType: "application/json" - }) - }, ApiClient.prototype.getAncestorItems = function(itemId, userId) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Items/" + itemId + "/Ancestors", options); - return this.getJSON(url) - }, ApiClient.prototype.getItems = function(userId, options) { - var url; - return url = "string" === (typeof userId).toString().toLowerCase() ? this.getUrl("Users/" + userId + "/Items", options) : this.getUrl("Items", options), this.getJSON(url) - }, ApiClient.prototype.getResumableItems = function(userId, options) { - return this.isMinServerVersion("3.2.33") ? this.getJSON(this.getUrl("Users/" + userId + "/Items/Resume", options)) : this.getItems(userId, Object.assign({ - SortBy: "DatePlayed", - SortOrder: "Descending", - Filters: "IsResumable", - Recursive: !0, - CollapseBoxSetItems: !1, - ExcludeLocationTypes: "Virtual" - }, options)) - }, ApiClient.prototype.getMovieRecommendations = function(options) { - return this.getJSON(this.getUrl("Movies/Recommendations", options)) - }, ApiClient.prototype.getUpcomingEpisodes = function(options) { - return this.getJSON(this.getUrl("Shows/Upcoming", options)) - }, ApiClient.prototype.getUserViews = function(options, userId) { - options = options || {}; - var url = this.getUrl("Users/" + (userId || this.getCurrentUserId()) + "/Views", options); - return this.getJSON(url) - }, ApiClient.prototype.getArtists = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Artists", options); - return this.getJSON(url) - }, ApiClient.prototype.getAlbumArtists = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Artists/AlbumArtists", options); - return this.getJSON(url) - }, ApiClient.prototype.getGenres = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Genres", options); - return this.getJSON(url) - }, ApiClient.prototype.getMusicGenres = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("MusicGenres", options); - return this.getJSON(url) - }, ApiClient.prototype.getPeople = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Persons", options); - return this.getJSON(url) - }, ApiClient.prototype.getStudios = function(userId, options) { - if (!userId) throw new Error("null userId"); - options = options || {}, options.userId = userId; - var url = this.getUrl("Studios", options); - return this.getJSON(url) - }, ApiClient.prototype.getLocalTrailers = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/LocalTrailers"); - return this.getJSON(url) - }, ApiClient.prototype.getAdditionalVideoParts = function(userId, itemId) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Videos/" + itemId + "/AdditionalParts", options); - return this.getJSON(url) - }, ApiClient.prototype.getThemeMedia = function(userId, itemId, inherit) { - if (!itemId) throw new Error("null itemId"); - var options = {}; - userId && (options.userId = userId), options.InheritFromParent = inherit || !1; - var url = this.getUrl("Items/" + itemId + "/ThemeMedia", options); - return this.getJSON(url) - }, ApiClient.prototype.getSearchHints = function(options) { - var url = this.getUrl("Search/Hints", options), - serverId = this.serverId(); - return this.getJSON(url).then(function(result) { - return result.SearchHints.forEach(function(i) { - i.ServerId = serverId - }), result - }) - }, ApiClient.prototype.getSpecialFeatures = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/SpecialFeatures"); - return this.getJSON(url) - }, ApiClient.prototype.getDateParamValue = function(date) { - function formatDigit(i) { - return i < 10 ? "0" + i : i - } - var d = date; - return "" + d.getFullYear() + formatDigit(d.getMonth() + 1) + formatDigit(d.getDate()) + formatDigit(d.getHours()) + formatDigit(d.getMinutes()) + formatDigit(d.getSeconds()) - }, ApiClient.prototype.markPlayed = function(userId, itemId, date) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var options = {}; - date && (options.DatePlayed = this.getDateParamValue(date)); - var url = this.getUrl("Users/" + userId + "/PlayedItems/" + itemId, options); - return this.ajax({ - type: "POST", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.markUnplayed = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/PlayedItems/" + itemId); - return this.ajax({ - type: "DELETE", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.updateFavoriteStatus = function(userId, itemId, isFavorite) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/FavoriteItems/" + itemId), - method = isFavorite ? "POST" : "DELETE"; - return this.ajax({ - type: method, - url: url, - dataType: "json" - }) - }, ApiClient.prototype.updateUserItemRating = function(userId, itemId, likes) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/Rating", { - likes: likes - }); - return this.ajax({ - type: "POST", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.getItemCounts = function(userId) { - var options = {}; - userId && (options.userId = userId); - var url = this.getUrl("Items/Counts", options); - return this.getJSON(url) - }, ApiClient.prototype.clearUserItemRating = function(userId, itemId) { - if (!userId) throw new Error("null userId"); - if (!itemId) throw new Error("null itemId"); - var url = this.getUrl("Users/" + userId + "/Items/" + itemId + "/Rating"); - return this.ajax({ - type: "DELETE", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.reportPlaybackStart = function(options) { - if (!options) throw new Error("null options"); - this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, stopBitrateDetection(this); - cancelReportPlaybackProgressPromise(this); - var url = this.getUrl("Sessions/Playing"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.reportPlaybackProgress = function(options) { - if (!options) throw new Error("null options"); - - const eventName = options.EventName || "timeupdate"; - let reportRateLimitTime = reportRateLimits[eventName] || 0; - - const now = (new Date).getTime(); - const msSinceLastReport = now - (this.lastPlaybackProgressReport || 0); - const newPositionTicks = options.PositionTicks; - - if (msSinceLastReport < reportRateLimitTime && eventName === "timeupdate" && newPositionTicks) { - const expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0); - if (Math.abs(newPositionTicks - expectedReportTicks) >= 5e7) reportRateLimitTime = 0; - } - - if (reportRateLimitTime < (this.reportPlaybackProgressTimeout !== undefined ? this.reportPlaybackProgressTimeout : 1e6)) { - cancelReportPlaybackProgressPromise(this); - } - - this.lastPlaybackProgressOptions = options; - - if (this.reportPlaybackProgressPromise) return Promise.resolve(); - - let instance = this; - let promise; - let cancelled = false; - - let resetPromise = function () { - if (instance.reportPlaybackProgressPromise !== promise) return; - - delete instance.lastPlaybackProgressOptions; - delete instance.reportPlaybackProgressTimeout; - delete instance.reportPlaybackProgressPromise; - delete instance.reportPlaybackProgressCancel; - }; - - let sendReport = function (lastOptions) { - resetPromise(); - - if (!lastOptions) throw new Error("null options"); - - instance.lastPlaybackProgressReport = (new Date).getTime(); - instance.lastPlaybackProgressReportTicks = lastOptions.PositionTicks; - - const url = instance.getUrl("Sessions/Playing/Progress"); - return instance.ajax({ - type: "POST", - data: JSON.stringify(lastOptions), - contentType: "application/json", - url: url - }); - }; - - let delay = Math.max(0, reportRateLimitTime - msSinceLastReport); - - promise = new Promise((resolve, reject) => setTimeout(resolve, delay)).then(() => { - if (cancelled) return Promise.resolve(); - return sendReport(instance.lastPlaybackProgressOptions); - }).finally(() => { - resetPromise(); - }); - - this.reportPlaybackProgressTimeout = reportRateLimitTime; - this.reportPlaybackProgressPromise = promise; - this.reportPlaybackProgressCancel = function () { - cancelled = true; - resetPromise(); - }; - - return promise; - }, ApiClient.prototype.reportOfflineActions = function(actions) { - if (!actions) throw new Error("null actions"); - var url = this.getUrl("Sync/OfflineActions"); - return this.ajax({ - type: "POST", - data: JSON.stringify(actions), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.syncData = function(data) { - if (!data) throw new Error("null data"); - var url = this.getUrl("Sync/Data"); - return this.ajax({ - type: "POST", - data: JSON.stringify(data), - contentType: "application/json", - url: url, - dataType: "json" - }) - }, ApiClient.prototype.getReadySyncItems = function(deviceId) { - if (!deviceId) throw new Error("null deviceId"); - var url = this.getUrl("Sync/Items/Ready", { - TargetId: deviceId - }); - return this.getJSON(url) - }, ApiClient.prototype.reportSyncJobItemTransferred = function(syncJobItemId) { - if (!syncJobItemId) throw new Error("null syncJobItemId"); - var url = this.getUrl("Sync/JobItems/" + syncJobItemId + "/Transferred"); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.cancelSyncItems = function(itemIds, targetId) { - if (!itemIds) throw new Error("null itemIds"); - var url = this.getUrl("Sync/" + (targetId || this.deviceId()) + "/Items", { - ItemIds: itemIds.join(",") - }); - return this.ajax({ - type: "DELETE", - url: url - }) - }, ApiClient.prototype.reportPlaybackStopped = function(options) { - if (!options) throw new Error("null options"); - this.lastPlaybackProgressReport = 0, this.lastPlaybackProgressReportTicks = null, redetectBitrate(this); - cancelReportPlaybackProgressPromise(this); - var url = this.getUrl("Sessions/Playing/Stopped"); - return this.ajax({ - type: "POST", - data: JSON.stringify(options), - contentType: "application/json", - url: url - }) - }, ApiClient.prototype.sendPlayCommand = function(sessionId, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!options) throw new Error("null options"); - var url = this.getUrl("Sessions/" + sessionId + "/Playing", options); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.sendCommand = function(sessionId, command) { - if (!sessionId) throw new Error("null sessionId"); - if (!command) throw new Error("null command"); - var url = this.getUrl("Sessions/" + sessionId + "/Command"), - ajaxOptions = { - type: "POST", - url: url - }; - return ajaxOptions.data = JSON.stringify(command), ajaxOptions.contentType = "application/json", this.ajax(ajaxOptions) - }, ApiClient.prototype.sendMessageCommand = function(sessionId, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!options) throw new Error("null options"); - var url = this.getUrl("Sessions/" + sessionId + "/Message"), - ajaxOptions = { - type: "POST", - url: url - }; - return ajaxOptions.data = JSON.stringify(options), ajaxOptions.contentType = "application/json", this.ajax(ajaxOptions) - }, ApiClient.prototype.sendPlayStateCommand = function(sessionId, command, options) { - if (!sessionId) throw new Error("null sessionId"); - if (!command) throw new Error("null command"); - var url = this.getUrl("Sessions/" + sessionId + "/Playing/" + command, options || {}); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.createPackageReview = function(review) { - var url = this.getUrl("Packages/Reviews/" + review.id, review); - return this.ajax({ - type: "POST", - url: url - }) - }, ApiClient.prototype.getPackageReviews = function(packageId, minRating, maxRating, limit) { - if (!packageId) throw new Error("null packageId"); - var options = {}; - minRating && (options.MinRating = minRating), maxRating && (options.MaxRating = maxRating), limit && (options.Limit = limit); - var url = this.getUrl("Packages/" + packageId + "/Reviews", options); - return this.getJSON(url) - }, ApiClient.prototype.getSavedEndpointInfo = function() { - return this._endPointInfo - }, ApiClient.prototype.getEndpointInfo = function() { - var savedValue = this._endPointInfo; - if (savedValue) return Promise.resolve(savedValue); - var instance = this; - return this.getJSON(this.getUrl("System/Endpoint")).then(function(endPointInfo) { - return setSavedEndpointInfo(instance, endPointInfo), endPointInfo - }) - }, ApiClient.prototype.getLatestItems = function(options) { - return options = options || {}, this.getJSON(this.getUrl("Users/" + this.getCurrentUserId() + "/Items/Latest", options)) - }, ApiClient.prototype.getFilters = function(options) { - return this.getJSON(this.getUrl("Items/Filters2", options)) - }, ApiClient.prototype.setSystemInfo = function(info) { - this._serverVersion = info.Version - }, ApiClient.prototype.serverVersion = function() { - return this._serverVersion - }, ApiClient.prototype.isMinServerVersion = function(version) { - var serverVersion = this.serverVersion(); - return !!serverVersion && compareVersions(serverVersion, version) >= 0 - }, ApiClient.prototype.handleMessageReceived = function(msg) { - onMessageReceivedInternal(this, msg) - }, ApiClient -}); diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index 9c15fbc184..d9c246b406 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -260,7 +260,6 @@ define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, TouchMenuLA.prototype.showMask = function () { mask.classList.remove("hide"); - mask.offsetWidth; mask.classList.add("backdrop"); }; diff --git a/src/library.html b/src/library.html index 398613f9e6..1bca607249 100644 --- a/src/library.html +++ b/src/library.html @@ -6,7 +6,7 @@ ${ButtonScanAllLibraries} - ${Help} + ${Help}
diff --git a/src/livetvsettings.html b/src/livetvsettings.html index bffd6ae4b6..a3a43c792f 100644 --- a/src/livetvsettings.html +++ b/src/livetvsettings.html @@ -4,7 +4,7 @@

DVR

- ${Help} + ${Help}
diff --git a/src/livetvstatus.html b/src/livetvstatus.html index cc5f8ae0ba..3aa27637d0 100644 --- a/src/livetvstatus.html +++ b/src/livetvstatus.html @@ -10,7 +10,7 @@ - ${Help} + ${Help}
diff --git a/src/livetvtuner.html b/src/livetvtuner.html index 816d30f340..f45a3a1d04 100644 --- a/src/livetvtuner.html +++ b/src/livetvtuner.html @@ -5,7 +5,7 @@

${HeaderLiveTvTunerSetup}

- ${Help} + ${Help}
diff --git a/src/networking.html b/src/networking.html index 2cb354e7df..cc824eaabb 100644 --- a/src/networking.html +++ b/src/networking.html @@ -5,7 +5,7 @@

${TabNetworking}

- ${Help} + ${Help}
diff --git a/src/notificationsetting.html b/src/notificationsetting.html index f982aa29ce..67b35981f1 100644 --- a/src/notificationsetting.html +++ b/src/notificationsetting.html @@ -8,7 +8,7 @@ diff --git a/src/scheduledtask.html b/src/scheduledtask.html index 6cfc8ace4b..f88f6a04c6 100644 --- a/src/scheduledtask.html +++ b/src/scheduledtask.html @@ -4,7 +4,7 @@ diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 276f580dfa..b1912862b3 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -53,45 +53,6 @@ define([], function () { return false; } - function isStyleSupported(prop, value) { - - if (typeof window === 'undefined') { - return false; - } - - // If no value is supplied, use "inherit" - value = arguments.length === 2 ? value : 'inherit'; - // Try the native standard method first - if ('CSS' in window && 'supports' in window.CSS) { - return window.CSS.supports(prop, value); - } - // Check Opera's native method - if ('supportsCSS' in window) { - return window.supportsCSS(prop, value); - } - - // need try/catch because it's failing on tizen - - try { - // Convert to camel-case for DOM interactions - var camel = prop.replace(/-([a-z]|[0-9])/ig, function (all, letter) { - return (letter + '').toUpperCase(); - }); - // Create test element - var el = document.createElement('div'); - // Check if the property is supported - var support = (camel in el.style); - // Assign the property and value to invoke - // the CSS interpreter - el.style.cssText = prop + ':' + value; - // Ensure both the property and value are - // supported and return - return support && (el.style[camel] !== ''); - } catch (err) { - return false; - } - } - function hasKeyboard(browser) { if (browser.touch) { @@ -283,10 +244,6 @@ define([], function () { browser.tv = isTv(); browser.operaTv = browser.tv && userAgent.toLowerCase().indexOf('opr/') !== -1; - if (!isStyleSupported('display', 'flex')) { - browser.noFlex = true; - } - if (browser.mobile || browser.tv) { browser.slow = true; } diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 31530705cb..a5ed0a00af 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -38,6 +38,14 @@ define([ controller: 'auth/selectserver', type: 'selectserver' }); + defineRoute({ + path: '/login.html', + autoFocus: false, + anonymous: true, + startup: true, + controller: 'auth/login', + type: 'login' + }); defineRoute({ path: '/forgotpassword.html', anonymous: true, @@ -52,12 +60,6 @@ define([ controller: 'auth/forgotpasswordpin' }); - defineRoute({ - path: '/addplugin.html', - autoFocus: false, - roles: 'admin', - controller: 'dashboard/plugins/add' - }); defineRoute({ path: '/mypreferencesmenu.html', autoFocus: false, @@ -137,6 +139,24 @@ define([ roles: 'admin', controller: 'dashboard/dlna/dlnaprofiles' }); + defineRoute({ + path: '/addplugin.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/plugins/add' + }); + defineRoute({ + path: '/library.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/medialibrarypage' + }); + defineRoute({ + path: '/librarydisplay.html', + autoFocus: false, + roles: 'admin', + controller: 'dashboard/librarydisplay' + }); defineRoute({ path: '/dlnasettings.html', autoFocus: false, @@ -154,6 +174,7 @@ define([ roles: 'admin', controller: 'dashboard/encodingsettings' }); + defineRoute({ path: '/home.html', autoFocus: false, @@ -161,6 +182,10 @@ define([ transition: 'fade', type: 'home' }); + defineRoute({ + path: '/search.html', + controller: 'searchpage' + }); defineRoute({ path: '/list.html', autoFocus: false, @@ -173,18 +198,6 @@ define([ autoFocus: false, transition: 'fade' }); - defineRoute({ - path: '/library.html', - autoFocus: false, - roles: 'admin', - controller: 'dashboard/medialibrarypage' - }); - defineRoute({ - path: '/librarydisplay.html', - autoFocus: false, - roles: 'admin', - controller: 'dashboard/librarydisplay' - }); defineRoute({ path: '/livetv.html', controller: 'livetv/livetvsuggested', @@ -219,14 +232,6 @@ define([ roles: 'admin', controller: 'dashboard/logs' }); - defineRoute({ - path: '/login.html', - autoFocus: false, - anonymous: true, - startup: true, - controller: 'auth/login', - type: 'login' - }); defineRoute({ path: '/metadataimages.html', autoFocus: false, @@ -293,10 +298,6 @@ define([ roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtasks' }); - defineRoute({ - path: '/search.html', - controller: 'searchpage' - }); defineRoute({ path: '/serveractivity.html', autoFocus: false, @@ -321,6 +322,7 @@ define([ controller: 'shows/tvrecommended', transition: 'fade' }); + defineRoute({ path: '/useredit.html', autoFocus: false, @@ -373,7 +375,7 @@ define([ path: '/wizardlibrary.html', autoFocus: false, anonymous: true, - controller: 'medialibrarypage' + controller: 'dashboard/medialibrarypage' }); defineRoute({ path: '/wizardsettings.html', diff --git a/src/scripts/settings/webSettings.js b/src/scripts/settings/webSettings.js index 92093dfbe5..d999724aff 100644 --- a/src/scripts/settings/webSettings.js +++ b/src/scripts/settings/webSettings.js @@ -2,7 +2,17 @@ let data; function getConfig() { if (data) return Promise.resolve(data); - return fetch('/config.json?nocache=' + new Date().getUTCMilliseconds()).then(function (response) { + return fetch('/config.json?nocache=' + new Date().getUTCMilliseconds()).then(response => { + data = response.json(); + return data; + }).catch(error => { + console.warn('web config file is missing so the template will be used'); + return getDefaultConfig(); + }); +} + +function getDefaultConfig() { + return fetch('/config.template.json').then(function (response) { data = response.json(); return data; }); diff --git a/src/strings/de.json b/src/strings/de.json index 07cca966f2..0c333860a3 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -17,7 +17,7 @@ "Albums": "Alben", "All": "Alle", "AllChannels": "Alle Kanäle", - "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX)", + "AllComplexFormats": "Alle komplexen Formate (ASS, SSA, VOBSUB, PGS, SUB/IDX, ...)", "AllEpisodes": "Alle Folgen", "AllLanguages": "Alle Sprachen", "AllLibraries": "Alle Bibliotheken", @@ -60,7 +60,7 @@ "BoxRear": "Box (Rückseite)", "Browse": "Blättern", "BrowsePluginCatalogMessage": "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.", + "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", @@ -1000,7 +1000,7 @@ "OptionLikes": "Mag ich", "OptionMissingEpisode": "Fehlende Episoden", "OptionMonday": "Montag", - "OptionNew": "Neu...", + "OptionNew": "Neu…", "OptionNone": "Keines", "OptionOnAppStartup": "Bei Anwendungsstart", "OptionOnInterval": "Nach einem Intervall", @@ -1528,5 +1528,6 @@ "MessageUnauthorizedUser": "Sie sind im Moment nicht berechtigt, auf den Server zuzugreifen. Bitte kontaktieren Sie Ihren Server-Administrator für weitere Informationen.", "HeaderFavoritePlaylists": "Lieblings-Wiedergabeliste", "ButtonTogglePlaylist": "Wiedergabeliste", - "ButtonToggleContextMenu": "Mehr" + "ButtonToggleContextMenu": "Mehr", + "ApiKeysCaption": "Liste der aktuell aktivierten API-Schlüssel" } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 9895bbc05c..2f75f2d1c7 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -17,7 +17,7 @@ "Alerts": "Alerts", "All": "All", "AllChannels": "All channels", - "AllComplexFormats": "All Complex Formats (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AllComplexFormats": "All Complex Formats (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllEpisodes": "All episodes", "AllLanguages": "All languages", "AllLibraries": "All libraries", @@ -25,7 +25,7 @@ "AllowMediaConversion": "Allow media conversion", "AllowMediaConversionHelp": "Grant or deny access to the convert media feature.", "AllowOnTheFlySubtitleExtraction": "Allow subtitle extraction on the fly", - "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.", + "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.", @@ -62,7 +62,7 @@ "BoxRear": "Box (rear)", "Browse": "Browse", "BrowsePluginCatalogMessage": "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.", + "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", @@ -311,6 +311,7 @@ "HeaderApiKey": "API Key", "HeaderApiKeys": "API Keys", "HeaderApiKeysHelp": "External applications are required to have an API key in order to communicate with Jellyfin Server. Keys are issued by logging in with a Jellyfin account, or by manually granting the application a key.", + "ApiKeysCaption": "List of the currently enabled API keys", "HeaderApp": "App", "HeaderAppearsOn": "Appears On", "HeaderAudioBooks": "Audio Books", @@ -1166,7 +1167,7 @@ "OptionMissingEpisode": "Missing Episodes", "OptionMonday": "Monday", "OptionNameSort": "Name", - "OptionNew": "New...", + "OptionNew": "New…", "OptionNone": "None", "OptionOnAppStartup": "On application startup", "OptionOnInterval": "On an interval", diff --git a/src/strings/eo.json b/src/strings/eo.json new file mode 100644 index 0000000000..3151c9e434 --- /dev/null +++ b/src/strings/eo.json @@ -0,0 +1,5 @@ +{ + "AddToCollection": "Aldoni al kolekto", + "Actor": "Aktoro", + "Absolute": "Absoluto" +} diff --git a/src/strings/es.json b/src/strings/es.json index 9a0997628d..1ce4425797 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -957,7 +957,7 @@ "OptionMissingEpisode": "Episodios que faltan", "OptionMonday": "Lunes", "OptionNameSort": "Nombre", - "OptionNew": "Nuevo...", + "OptionNew": "Nuevo…", "OptionNone": "Nada", "OptionOnAppStartup": "Al iniciar la aplicación", "OptionOnInterval": "En un intervalo", @@ -1513,5 +1513,6 @@ "ButtonToggleContextMenu": "Más", "Filter": "Filtro", "New": "Nuevo", - "HeaderFavoritePlaylists": "Lista reproducción favorita" + "HeaderFavoritePlaylists": "Lista reproducción favorita", + "ApiKeysCaption": "Lista de las claves API actuales" } diff --git a/src/strings/fa.json b/src/strings/fa.json index ba462509eb..76f8ec4e2b 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -690,5 +690,17 @@ "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "این تنظیمات همچنین در مورد هر پخش Chromecast که توسط این دستگاه شروع شده است اعمال می شود.", "SmartSubtitlesHelp": "زیرنویس‌های متناسب با توجه به اولویت زبان بدون در نظر گرفتن زبان صوتی ویدیو پخش می شوند.", "SkipEpisodesAlreadyInMyLibrary": "قسمت‌هایی که هم اکنون در کتابخانه من موجود است را ضبط نکن", - "SimultaneousConnectionLimitHelp": "حداکثر تعداد پخش‌های مجاز همزمان. ۰ را برای بدون محدودیت وارد کنید." + "SimultaneousConnectionLimitHelp": "حداکثر تعداد پخش‌های مجاز همزمان. ۰ را برای بدون محدودیت وارد کنید.", + "MessagePluginConfigurationRequiresLocalAccess": "برای پیکربندی این افزونه، لطفاً مستقیماً به سرور محلی خود وارد شوید.", + "MessagePleaseWait": "لطفا صبر کنید. این ممکن است چند دقیقه طول بکشد.", + "MessageNoServersAvailable": "هیچ سروری با استفاده از کشف خودکار سرور یافت نشد.", + "MessageNoPluginsInstalled": "هیچ افزونه‌ی نصب نشده است.", + "MessageNoMovieSuggestionsAvailable": "هیچ پیشنهادی برای فیلم در دسترس نیست. شروع به تماشای و رتبه بندی فیلم‌های خود بکنید ، و سپس دوباره به دیدن پیشنهاد‌های خود بیایید.", + "MessageNoAvailablePlugins": "افزونه‌ای موجود نیست.", + "MessageItemsAdded": "آیتم‌ها اضافه شدند.", + "MessageItemSaved": "آیتم ذخیره شد.", + "MessageUnauthorizedUser": "در حال حاضر مجاز به دسترسی به سرور نیستید. لطفا برای اطلاعات بیشتر با مدیر سرور خود تماس بگیرید.", + "MessageInvalidUser": "نام کاربری یا گذرواژه نامعتبر است. لطفا دوباره تلاش کنید.", + "MessageInvalidForgotPasswordPin": "کد پین نامعتبر یا منقضی شده وارد شد. لطفا دوباره تلاش کنید.", + "MessageInstallPluginFromApp": "این افزونه باید از داخل برنامه‌ای که قصد استفاده از آن را دارید نصب شود." } diff --git a/src/strings/gsw.json b/src/strings/gsw.json index a154e4fc8e..2c5210969a 100644 --- a/src/strings/gsw.json +++ b/src/strings/gsw.json @@ -7,7 +7,7 @@ "ButtonQuickStartGuide": "Schnellstart Instruktione", "ButtonResetPassword": "Passwort zrug setze", "ButtonSave": "Speichere", - "ButtonSignOut": "Sign out", + "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.", "Continuing": "Fortlaufend", @@ -29,7 +29,7 @@ "HeaderLatestSongs": "Letschti Songs", "HeaderLatestTrailers": "Letschti Trailer", "HeaderManagement": "Verwaltig", - "HeaderNextUp": "Als nächts", + "HeaderNextUp": "Als Nächstes", "HeaderNowPlaying": "Jetz am spelle", "HeaderParentalRating": "Parental Rating", "HeaderPaths": "Pfad", @@ -131,22 +131,22 @@ "Wednesday": "Mittwoch", "WelcomeToProject": "Willkomme bi Jellyfin!", "WizardCompleted": "Das esch alles wo mer momentan müend wüsse. Jellyfin het i de zwüscheziit agfange informatione über diini medie-bibliothek z'sammle. Lueg der es paar vo eusne Apps a und denn klick uf Beende um zum Server Dashboard z'cho.", - "Albums": "Albom", - "Artists": "Könstler", - "Books": "Büecher", - "Channels": "Kanäu", - "Collections": "Sammlige", - "Favorites": "Favorite", + "Albums": "Alben", + "Artists": "Künstler", + "Books": "Bücher", + "Channels": "Kanäle", + "Collections": "Sammlungen", + "Favorites": "Favoriten", "Folders": "Ordner", "Genres": "Genres", - "HeaderAlbumArtists": "Albom-Könstler", - "HeaderContinueWatching": "Wiiterluege", - "HeaderFavoriteAlbums": "Lieblingsalbe", - "HeaderFavoriteArtists": "Lieblings-Interprete", - "HeaderFavoriteEpisodes": "Lieblingsepisode", - "HeaderFavoriteShows": "Lieblingsserie", + "HeaderAlbumArtists": "Album-Künstler", + "HeaderContinueWatching": "weiter schauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblings-Künstler", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-Färnseh", + "HeaderLiveTV": "Live-Fernseh", "HeaderRecordingGroups": "Ufnahmegruppe", "LabelIpAddressValue": "IP-Adrässe: {0}", "LabelRunningTimeValue": "Loufziit: {0}", diff --git a/src/strings/he.json b/src/strings/he.json index 8d507070a8..a3e4bc9326 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -770,5 +770,7 @@ "Raised": "מורם", "LabelSpecialSeasonsDisplayName": "שם תצוגת \"עונה מיוחדת\":", "LabelSource": "מקור:", - "LabelSoundEffects": "אפקטי סאונד:" + "LabelSoundEffects": "אפקטי סאונד:", + "ButtonTogglePlaylist": "רשימת ניגון", + "ButtonToggleContextMenu": "עוד" } diff --git a/src/strings/hu.json b/src/strings/hu.json index 065460a644..61d4dfbf08 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -424,7 +424,7 @@ "OptionMissingEpisode": "Hiányzó Epizódok", "OptionMonday": "Hétfő", "OptionNameSort": "Név", - "OptionNew": "Új...", + "OptionNew": "Új…", "OptionParentalRating": "Korhatár besorolás", "OptionPlayCount": "Lejátszások száma", "OptionPlayed": "Megnézett", @@ -584,7 +584,7 @@ "Aired": "Adásban", "Albums": "Albumok", "AllChannels": "Minden csatorna", - "AllComplexFormats": "Minden összetett formátum (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "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.", @@ -1511,5 +1511,6 @@ "ButtonToggleContextMenu": "Továbbiak", "Filter": "Szűrés", "New": "Új", - "HeaderFavoritePlaylists": "Kedvenc lejátszási listák" + "HeaderFavoritePlaylists": "Kedvenc lejátszási listák", + "ApiKeysCaption": "A jelenleg engedélyezett API kulcsok listája" } diff --git a/src/strings/it.json b/src/strings/it.json index 3adbeb2031..f24282786e 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -14,13 +14,13 @@ "Albums": "Album", "All": "Tutto", "AllChannels": "Tutti i canali", - "AllComplexFormats": "Tutti i formati complessi (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AllComplexFormats": "Tutti i formati complessi (ASS, SSA, VOBSUB, PGS, SUB, IDX, ...)", "AllEpisodes": "Tutti gli episodi", "AllLanguages": "Tutte le lingue", "AllLibraries": "Tutte le librerie", "AllowHWTranscodingHelp": "Abilita il sintonizzatore per codificare i flussi al volo. Ciò potrebbe contribuire a ridurre la transcodifica richiesta dal server.", "AllowOnTheFlySubtitleExtraction": "Consenti l'estrazione sottotitoli al volo", - "AllowOnTheFlySubtitleExtractionHelp": "I sottotitoli incorporati possono essere estratti dai video e consegnati ad applicazioni in testo semplice per evitare la transcodifica dei video. In alcuni sistemi questo può richiedere molto tempo e causare un rallentamento della riproduzione video durante il processo di estrazione. Disattivare questa opzione per avere i sottotitoli incorporati con la transcodifica video quando non sono supportati nativamente dal dispositivo client.", + "AllowOnTheFlySubtitleExtractionHelp": "I sottotitoli incorporati possono essere estratti dai video e consegnati ai client in testo semplice per evitare la transcodifica dei video. In alcuni sistemi questo può richiedere molto tempo e causare un rallentamento della riproduzione video durante il processo di estrazione. Disattivare questa opzione per avere i sottotitoli incorporati con la transcodifica video quando non sono supportati nativamente dal dispositivo client.", "AllowRemoteAccess": "Abilita connessioni remote a questo Server Jellyfin.", "AllowRemoteAccessHelp": "Se deselezionato, tutte le connessioni remote saranno bloccate.", "AllowedRemoteAddressesHelp": "Elenco separato da virgola di indirizzi IP o voci IP / maschera di rete per reti che potranno connettersi da remoto. Se lasciato vuoto, saranno consentiti tutti gli indirizzi remoti.", @@ -45,7 +45,7 @@ "BoxRear": "Box (retro)", "Browse": "Esplora", "BrowsePluginCatalogMessage": "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.", + "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", @@ -995,7 +995,7 @@ "OptionMissingEpisode": "Episodi mancanti", "OptionMonday": "Lunedì", "OptionNameSort": "Nome", - "OptionNew": "Nuovo...", + "OptionNew": "Nuovo…", "OptionNone": "Nessuno", "OptionOnAppStartup": "All'avvio", "OptionOnInterval": "Su intervallo", @@ -1504,5 +1504,8 @@ "MessageUnauthorizedUser": "Non sei autorizzato ad accedere in questo momento al server. Contatta l'amministratore per ulteriori dettagli.", "ButtonTogglePlaylist": "Playlist", "ButtonToggleContextMenu": "Altro", - "HeaderFavoritePlaylists": "Playlist Favorite" + "HeaderFavoritePlaylists": "Playlist Favorite", + "Filter": "Filtro", + "New": "Nuovo", + "ApiKeysCaption": "Elenco chiavi API abilitate" } diff --git a/src/strings/ko.json b/src/strings/ko.json index 9e0bd42604..33c44334ae 100644 --- a/src/strings/ko.json +++ b/src/strings/ko.json @@ -1325,7 +1325,7 @@ "MediaInfoPixelFormat": "픽셀 형식", "MapChannels": "채널 매핑", "LaunchWebAppOnStartupHelp": "서버가 처음 시작되면 웹 브라우저에서 웹 클라이언트를 실행하십시오. 서버 재시작의 경우에는 적용되지 않습니다.", - "Large": "큰", + "Large": "크게", "LanNetworksHelp": "대역폭을 강제로 제한할 때 로컬 네트워크로 간주되는 쉼표로 구분된 IP 주소 및 IP/서브넷 마스크 목록입니다. 지정될 경우 모든 다른 IP 주소는 외부 네트워크로 간주되며 외부 대역폭 제한이 적용됩니다. 공백일 경우 서버의 서브넷만이 로컬 네트워크로 간주됩니다.", "LabelffmpegPathHelp": "ffmpeg 실행 파일 혹은 ffmpeg를 포함하는 폴더 경로입니다.", "LabelXDlnaDocHelp": "urn:schemas-dlna-org:device-1-0 네임스페이스에 포함된 X_DLNADOC 요소의 내용을 결정합니다.", @@ -1427,5 +1427,11 @@ "NoSubtitlesHelp": "자막을 자동으로 불러오지 않습니다. 재생 중에 수동으로 켤 수 있습니다.", "MusicLibraryHelp": "{0}음악 이름 지정 규칙{1}을 확인하십시오.", "MovieLibraryHelp": "{0}영화 이름 지정 규칙{1}을 확인하십시오.", - "MessageUnauthorizedUser": "현재 서버에 접속할 권한이 없습니다. 자세한 정보는 서버 관리자에게 문의하십시오." + "MessageUnauthorizedUser": "현재 서버에 접속할 권한이 없습니다. 자세한 정보는 서버 관리자에게 문의하십시오.", + "HeaderFavoritePlaylists": "즐겨찾는 플레이리스트", + "ButtonTogglePlaylist": "플레이리스트", + "ButtonToggleContextMenu": "더보기", + "Rate": "평", + "PerfectMatch": "정확히 일치", + "OtherArtist": "다른 아티스트" } diff --git a/src/strings/nl.json b/src/strings/nl.json index 06bd4ca22c..a3c3a84f13 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -1513,5 +1513,8 @@ "YadifBob": "YADIF Bob", "Yadif": "YADIF", "Track": "Nummer", - "SelectAdminUsername": "Selecteer een gebruikersnaam voor het beheerder account." + "SelectAdminUsername": "Selecteer een gebruikersnaam voor het beheerder account.", + "HeaderFavoritePlaylists": "Favoriete afspeellijsten", + "ButtonTogglePlaylist": "Afspeellijst", + "ButtonToggleContextMenu": "Meer" } diff --git a/src/strings/pl.json b/src/strings/pl.json index 431ea3feb9..1cea825bf8 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -54,7 +54,7 @@ "BoxRear": "Pudełko (tył)", "Browse": "Przeglądaj", "BrowsePluginCatalogMessage": "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 poprawia wydajność serwera. Wybierz Automatycznie, w celu wypalania zarówno napisów w formatach graficznych (np. VOBSUB, PGS, SUB/IDX, itp.), jak i pewnych napisów ASS/SSA.", + "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", @@ -190,7 +190,7 @@ "DisplayInOtherHomeScreenSections": "Wyświetlaj na ekranie startowym sekcje Ostatnio dodane i Kontynuuj odtwarzanie", "DisplayMissingEpisodesWithinSeasons": "Wyświetlaj w sezonach brakujące odcinki", "DisplayMissingEpisodesWithinSeasonsHelp": "Ta opcja musi zostać dodatkowo aktywowana w bibliotece seriali, w konfiguracji serwera.", - "DisplayModeHelp": "Określa typ urządzenia, na którym uruchomiono Jellyfin.", + "DisplayModeHelp": "Wybierz styl układu interfejsu.", "DoNotRecord": "Nie nagrywaj", "Down": "W dół", "Download": "Pobierz", @@ -490,7 +490,7 @@ "Images": "Obrazy", "ImportFavoriteChannelsHelp": "Jeśli aktywne, tylko kanały oznaczone jako ulubione na tunerze, będą importowane.", "ImportMissingEpisodesHelp": "W przypadku aktywacji tej opcji, informacje o brakujących odcinkach zostaną zaimportowane do bazy Jellyfin i będą wyświetlane na listach sezonów i seriali. Może to jednak znacznie wydłużyć czas skanowania biblioteki.", - "InstallingPackage": "Instalowanie {0}", + "InstallingPackage": "Instalowanie {0} (wersja {1})", "InstantMix": "Szybki remiks", "ItemCount": "{0} pozycje", "Items": "Pozycje", @@ -953,16 +953,16 @@ "NoNextUpItemsMessage": "Nie znaleziono niczego. Zacznij oglądać swoje seriale!", "NoPluginConfigurationMessage": "Ta wtyczka nie ma żadnych ustawień.", "NoSubtitleSearchResultsFound": "Brak wyników wyszukiwania.", - "NoSubtitles": "Brak napisów", + "NoSubtitles": "Brak", "NoSubtitlesHelp": "Domyślnie napisy nie będą wczytywane. Można je ciągle włączyć ręcznie podczas odtwarzania.", "None": "Brak", "Normal": "Normalny", "NumLocationsValue": "{0} foldery", "Off": "Wyłączone", "OneChannel": "Jeden kanał", - "OnlyForcedSubtitles": "Tylko wymuszone napisy", + "OnlyForcedSubtitles": "Tylko wymuszone", "OnlyForcedSubtitlesHelp": "Tylko napisy oznaczone jako wymuszone będą wczytywane.", - "OnlyImageFormats": "Tylko formaty graficzne (VOBSUB, PGS, SUB, itp.)", + "OnlyImageFormats": "Tylko Formaty Graficzne (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administratorzy", "OptionAlbumArtist": "Wykonawca albumu", "OptionAllUsers": "Wszyscy użytkownicy", @@ -1100,7 +1100,7 @@ "OptionWeekly": "Cotygodniowo", "OriginalAirDateValue": "Data pierwszej emisji: {0}", "Overview": "Opis", - "PackageInstallCancelled": "Instalacja {0} anulowana.", + "PackageInstallCancelled": "Instalacja {0} (wersja {1}) anulowana.", "PackageInstallCompleted": "Instalacja {0} zakończona.", "PackageInstallFailed": "Instalacja {0} nieudana.", "ParentalRating": "Kategoria wiekowa", @@ -1470,5 +1470,18 @@ "EnableFastImageFadeIn": "Szybkie pojawianie się obrazów", "Artist": "Artysta", "AlbumArtist": "Album artysty", - "Album": "Album" + "Album": "Album", + "Person": "Osoba", + "OtherArtist": "Inny artysta", + "Movie": "Film", + "MessageUnauthorizedUser": "Nie masz dostępu do zasobów serwera. Skontaktuj się z administratorem sieci, aby uzyskać więcej informacji.", + "LabelLibraryPageSizeHelp": "Ustaw liczbę pozycji pokazywanych na stronie biblioteki. Ustaw 0, aby wyłączyć podział na strony.", + "LabelLibraryPageSize": "Rozmiar strony biblioteki:", + "LabelDeinterlaceMethod": "Metoda usuwania przeplotu:", + "HeaderFavoritePlaylists": "Ulubione Playlisty", + "Episode": "Odcinek", + "DeinterlaceMethodHelp": "Wybierz metodę usuwania przeplotu używaną podczas transkodowania.", + "ClientSettings": "Ustawienia klienta", + "ButtonTogglePlaylist": "Playlista", + "ButtonToggleContextMenu": "Więcej" } diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index 16b6e0313c..37772d654b 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -182,7 +182,7 @@ "DisplayInOtherHomeScreenSections": "Exibir nas seções da tela inicial como mídia recente e continuar assistindo", "DisplayMissingEpisodesWithinSeasons": "Exibir episódios em falta nas temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Isto também deve ser ativado para as bibliotecas de TV na configuração do servidor.", - "DisplayModeHelp": "Seleciona o tipo de tela para executar o Jellyfin.", + "DisplayModeHelp": "Selecione o estilo de layout que deseje para a interface.", "DoNotRecord": "Não gravar", "Down": "Baixo", "DrmChannelsNotImported": "Canais com DRM não serão importados.", @@ -626,7 +626,7 @@ "LabelKodiMetadataEnablePathSubstitution": "Ativar substituição de local", "LabelKodiMetadataEnablePathSubstitutionHelp": "Ativa a substituição do local das imagens usando as configurações de substituição de local do servidor.", "LabelKodiMetadataSaveImagePaths": "Salvar o local das imagens dentro dos arquivos nfo", - "LabelKodiMetadataSaveImagePathsHelp": "Isto é recomendado se os nomes dos arquivos de imagem não estão de acordo com as recomendações do Kodi.", + "LabelKodiMetadataSaveImagePathsHelp": "Isto é recomendado se os nomes dos arquivos de imagem não estão de acordo com as exigências do Kodi.", "LabelKodiMetadataUser": "Salvar informações do que o usuário assiste aos NFO's para:", "LabelKodiMetadataUserHelp": "Salva os dados para arquivos NFO para que outras aplicações possam usar.", "LabelLanNetworks": "Redes LAN:", @@ -1503,5 +1503,8 @@ "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.", "MessageUnauthorizedUser": "Você não está autorizado a acessar o servidor neste momento. Por favor, contate o administrador do servidor para mais informações.", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Mais" + "ButtonToggleContextMenu": "Mais", + "Filter": "Filtro", + "New": "Novo", + "HeaderFavoritePlaylists": "Playlists Favoritas" } diff --git a/src/strings/sk.json b/src/strings/sk.json index d56b9fc59a..12da34c1bd 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -5,7 +5,7 @@ "Albums": "Albumy", "All": "Všetko", "AllChannels": "Všetky kanály", - "AllComplexFormats": "Všetky komplexné formáty (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AllComplexFormats": "Všetky komplexné formáty (ASS, SSA, VOBSUB, PGS, SUB, IDX, …)", "AllEpisodes": "Všetky epizódy", "AllLanguages": "Všetky jazyky", "AllLibraries": "Všetky knižnice", @@ -648,7 +648,7 @@ "OptionMissingEpisode": "Chýbajúce epizódy", "OptionMonday": "Pondelok", "OptionNameSort": "Názov", - "OptionNew": "Nové...", + "OptionNew": "Nové…", "OptionNone": "Žiadne", "OptionOnAppStartup": "Pri spustení aplikácie", "OptionParentalRating": "Rodičovské hodnotenie", @@ -1180,7 +1180,7 @@ "CancelSeries": "Ukončiť seriál", "ButtonSplit": "Rozdeliť", "ButtonAddImage": "Pridať obrázok", - "BurnSubtitlesHelp": "Určuje, či má server vpáliť titulky počas transkódovania videa. Vynechanie tejto možnosti výrazne zvýši výkon. Vyberte možnosť Auto, pokiaľ chcete vpáliť do obrazu titulky v grafickom formáte (VOBSUB, PGS, SUB, IDX) a niektoré ASS alebo SSA titulky.", + "BurnSubtitlesHelp": "Určuje, či má server vpáliť titulky počas transkódovania videa. Vynechanie tejto možnosti výrazne zvýši výkon. Vyberte možnosť Auto, pokiaľ chcete vpáliť do obrazu titulky v grafickom formáte (VOBSUB, PGS, SUB, IDX, …) a niektoré ASS alebo SSA titulky.", "BrowsePluginCatalogMessage": "Prehliadnite si náš katalóg dostupných zásuvných modulov.", "Browse": "Prechádzať", "Blacklist": "Blacklist", @@ -1189,7 +1189,7 @@ "Art": "Umenie", "AlwaysPlaySubtitlesHelp": "Titulky odpovedajúce jazykovej preferencií sa načítajú bez ohľadu na jazyk zvuku.", "AllowedRemoteAddressesHelp": "Zoznam IP adries alebo IP/netmask záznamov pre siete oddelené čiarkami z ktorých sa dá vzdialene pripojiť. Pokiaľ zoznam bude prázdny, všetky adresy budú povolené.", - "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky môžu byť extrahované z videa a prenesené na klienta vo forme jednoduchého textu aby sa zabránilo transkódovaniu videa. Na niektorých systémoch táto operácia môže trvať dlhší čas a a spôsobiť sekanie videa počas počas extrahovania. Vypnutie tejto funkcie bude mať za následok, že titulky budú počas transkódovania vypálené do samotného videa pokiaľ klientské zariadenie natívne nepodporuje ich formát.", + "AllowOnTheFlySubtitleExtractionHelp": "Vložené titulky môžu byť extrahované z videa a prenesené na klienta vo forme jednoduchého textu, aby sa zabránilo transkódovaniu videa. Na niektorých systémoch táto operácia môže trvať dlhší čas a a spôsobiť sekanie videa počas počas extrahovania. Vypnutie tejto funkcie bude mať za následok, že titulky budú počas transkódovania vypálené do samotného videa pokiaľ klientské zariadenie natívne nepodporuje ich formát.", "Watched": "Pozreté", "TvLibraryHelp": "Pozrite sa na {0}sprievodcu pomenovania TV programov{1}.", "LabelLineup": "Lineup:", @@ -1507,5 +1507,6 @@ "New": "Nové", "HeaderFavoritePlaylists": "Obľúbené playlisty", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "Viac" + "ButtonToggleContextMenu": "Viac", + "ApiKeysCaption": "Zoznam v súčasnosti povolených API kľúčov" } diff --git a/src/strings/sv.json b/src/strings/sv.json index d932221993..443549e797 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -2,7 +2,7 @@ "AccessRestrictedTryAgainLater": "För närvarande är åtkomsten begränsad. Försök igen senare.", "Actor": "Skådespelare", "Add": "Lägg till", - "AddItemToCollectionHelp": "Lägg till objekt i samlingar genom att söka efter dem och använda deras högerklick- eller knackmeny", + "AddItemToCollectionHelp": "Lägg till objekt i samlingar genom att söka efter dem och använda deras högerklick- eller pekmeny för att lägga till dem i en samling.", "AddToCollection": "Lägg till i samling", "AddToPlayQueue": "Lägg till i spelkö", "AddToPlaylist": "Lägg till i spellista", @@ -898,7 +898,7 @@ "NumLocationsValue": "{0} mappar", "Off": "Av", "OneChannel": "En kanal", - "OnlyForcedSubtitles": "Endast tvingande undertexter", + "OnlyForcedSubtitles": "Endast påtvingad", "OnlyForcedSubtitlesHelp": "Endast undertexter markerade som tvingande kommer att laddas.", "OnlyImageFormats": "Endast bildbaserade format (VOBSUB, PGS, SUB, etc)", "OptionAdminUsers": "Administratörer", @@ -1492,5 +1492,20 @@ "Artist": "Artist", "ButtonTogglePlaylist": "Spellista", "ButtonToggleContextMenu": "Mer", - "AlbumArtist": "Albumartist" + "AlbumArtist": "Albumartist", + "LabelLibraryPageSize": "Bibliotekets sidstorlek:", + "LabelDeinterlaceMethod": "Deinterlacing-metod:", + "WeeklyAt": "{0}s vid {1}", + "LastSeen": "Senast sedd {0}", + "YadifBob": "YADIF Bob", + "Yadif": "YADIF", + "Filter": "Filter", + "New": "Ny", + "MessageUnauthorizedUser": "Du har inte behörighet att komma åt servern just nu. Kontakta din serveradministratör för mer information.", + "HeaderFavoritePlaylists": "Favoritspellista", + "OnWakeFromSleep": "Vid start från vilande läge", + "UnsupportedPlayback": "Jellyfin kan inte dekryptera inehåll skyddat av DRM men allt inehåll kommer ändå försökas, även skyddade titlar. Vissa filer kan se helt svart ut på grund av kryptering eller andra funktioner som inte stöds, till exempel interaktiva titlar.", + "LabelLibraryPageSizeHelp": "Sätter en begränsad sidstorlek i bibliotek. Sätt 0 för att avaktivera begränsad sidstorlek.", + "ApiKeysCaption": "Lista av aktiva API-nycklar", + "DeinterlaceMethodHelp": "Välj metod för borttagning av inflätning vid konvertering av inflätat inehåll." } diff --git a/src/strings/vi.json b/src/strings/vi.json index 65fcd88ef9..e8fbe64eff 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -564,5 +564,113 @@ "HeaderSortOrder": "Thứ tự Sắp xếp", "HeaderSortBy": "Sắp xếp theo", "HeaderStartNow": "Bắt đầu", - "HeaderSetupLibrary": "Thiết lập thư viện nội dung của bạn" + "HeaderSetupLibrary": "Thiết lập thư viện nội dung của bạn", + "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ụ Đề", + "HeaderSubtitleDownloads": "Tải Phụ Đề", + "HeaderSubtitleAppearance": "Giao Diện Phụ Đề", + "HeaderStopRecording": "Ngừng Ghi Hình/Ghi Âm", + "HeaderSpecialFeatures": "Những Phần Đặc Biệt Nổi Bật", + "HeaderSpecialEpisodeInfo": "Thông Tin Tập Đặc Biệt", + "HeaderShutdown": "Tắt Máy Chủ", + "LabelCustomDeviceDisplayNameHelp": "Cung cấp một tên hiển thị riêng hoặc bỏ trống để sử dụng tên có sẵn của thiết bị.", + "LabelCustomDeviceDisplayName": "Tên hiển thị:", + "LabelCustomCssHelp": "Áp dụng tuỳ chỉnh riêng của bạn vào giao diện trang web.", + "LabelCustomCss": "CSS tuỳ chọn:", + "LabelCustomCertificatePathHelp": "Đường dẫn đến tập tin PKCS #12 chứa chứng chỉ (certificate) và khoá riêng (private key) để bật tính năng TLS trên một tên miền tuỳ chọn.", + "LabelCustomCertificatePath": "Đường dẫn đến chứng chỉ SSL:", + "LabelCriticRating": "Đánh giá phê bình:", + "LabelCorruptedFrames": "Những khung hình bị lỗi:", + "LabelContentType": "Loại nội dung:", + "LabelCommunityRating": "Đánh giá của cộng đồng:", + "LabelCollection": "Bộ Sưu Tập:", + "LabelChannels": "Kênh:", + "LabelCertificatePasswordHelp": "Nếu chứng chỉ của bạn cần mật khẩu, hãy nhập nó ở đây.", + "LabelCertificatePassword": "Mật khẩu chứng chỉ:", + "LabelCancelled": "Đã Huỷ", + "LabelCachePathHelp": "Chọn một đường dẫn cho những tập tin lưu tạm như là hình ảnh. Bỏ trống để sử dụng cài đặt mặc định của máy chủ.", + "LabelCachePath": "Đường dẫn cache:", + "LabelCache": "Cache:", + "LabelBurnSubtitles": "Nhúng phụ đề:", + "LabelBlockContentWithTags": "Chặn những mục có nhãn:", + "LabelBlastMessageIntervalHelp": "Xác định thời gian tồn tại giữa các tin nhắn (tính bằng giây).", + "LabelBlastMessageInterval": "Thời gian tồn tại của tin nhắn (giây)", + "LabelBitrate": "Bitrate:", + "LabelBirthYear": "Năm sinh:", + "LabelBirthDate": "Ngày sinh:", + "LabelBindToLocalNetworkAddressHelp": "Không bắt buộc. Cài đặt đè địa chỉ IP nội bộ để kết nối đến máy chủ HTTP. Nếu bỏ trống, máy chủ sẽ cài đặt vào toàn bộ những địa chỉ nội bộ có sẵn. Nếu thay đổi tuỳ chọn này, cần phải khởi động lại máy chủ Jellyfin để có tác dụng.", + "LabelBindToLocalNetworkAddress": "Cài đặt vào địa chỉ nội bộ:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Tự động cập nhật dữ liệu bổ trợ từ Internet:", + "LabelAuthProvider": "Nhà Cung Cấp Xác Thực:", + "LabelAudioSampleRate": "Sample rate âm thanh:", + "LabelAudioCodec": "Bộ giải mã âm thanh:", + "LabelAudioChannels": "Các kênh âm thanh:", + "LabelAudioBitrate": "Bitrate của âm thanh:", + "LabelAudioBitDepth": "Chiều sâu của âm thanh:", + "LabelAudio": "Âm Thanh", + "LabelArtistsHelp": "Sử dụng dấu ; để tách rời nhiều nghệ", + "LabelArtists": "Nghệ sĩ:", + "LabelAppNameExample": "Ví dụ: Sickbeard, Sonarr", + "LabelAppName": "Tên ứng dụng", + "LabelAllowedRemoteAddressesMode": "Chế độ bộ lọc địa chỉ IP từ xa:", + "LabelAllowedRemoteAddresses": "Bộ lọc địa chỉ IP từ xa:", + "LabelAllowServerAutoRestartHelp": "Máy chủ chỉ khởi động lại trong thời gian rỗi khi không có người dùng đang sử dụng.", + "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.", + "LabelAlbumArtMaxWidth": "Chiều ngang lớn nhất của bìa album:", + "LabelAlbumArtMaxHeightHelp": "Độ phân giải cao nhất của bìa album thông qua upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Chiều cao lớn nhất của bìa album:", + "LabelAlbumArtHelp": "PN được sử dụng cho bìa album, trong dlna:profileID thuộc tính upnp:albumArtURI. Một vài thiết bị phát cần một giá trị đặc biệt, không ảnh hưởng đến kích thước của hình ảnh.", + "LabelAlbum": "Album:", + "LabelAirsBeforeSeason": "Phát sóng trước mùa:", + "LabelAirsBeforeEpisode": "Phát sóng trước tập:", + "LabelAirsAfterSeason": "Phát sóng sau mùa:", + "LabelAirTime": "Thời gian phát sóng:", + "LabelAirDays": "Ngày phát sóng:", + "LabelAccessStart": "Thời gian bắt đầu:", + "LabelAccessEnd": "Thời gian kết thúc:", + "LabelAccessDay": "Ngày trong tuần:", + "LabelAbortedByServerShutdown": "(Đã huỷ bởi máy chủ ngừng hoạt động)", + "Label3DFormat": "Định dạng 3D:", + "Kids": "Trẻ Em", + "Items": "Mục", + "ItemCount": "{0} mục", + "InstantMix": "Trộn Lẫn Nhanh", + "InstallingPackage": "Đang cài đặt {0} (phiên bản {1})", + "ImportMissingEpisodesHelp": "Nếu bật tuỳ chọn này, thông tin bị thiếu trong các tập phim sẽ được nhập vào cơ sở dữ liệu của máy chủ Jellyfin và hiển thị trong các phần và series. Điều này có thể làm việc quét thư viện lâu hơn rất nhiều.", + "ImportFavoriteChannelsHelp": "Nếu bật tuỳ chọn này, chỉ những kênh yêu thích trong thiết bị bắt sóng sẽ được nhập vào.", + "Images": "Hình Ảnh", + "Identify": "Nhận Dạng", + "HttpsRequiresCert": "Để bật kết nối bảo mật, bạn cần phải cung cấp một Chứng Chỉ SSL đáng tin cậy, ví dụ như \"Let's Encrypt\". Hãy cung cấp Chứng Chỉ SSL hoặc là tắt tính năng kết nối bảo mật.", + "Horizontal": "Nằm Ngang", + "Home": "Trang Chủ", + "HideWatchedContentFromLatestMedia": "Ẩn những nội dung đã xem khỏi phần nội dung mới nhất", + "Hide": "Ẩn", + "Help": "Trợ Giúp", + "HeadersFolders": "Thư Mục", + "HeaderYears": "Năm", + "HeaderXmlSettings": "Cài Đặt XML", + "HeaderXmlDocumentAttributes": "Những Thuộc Tính Tài Liệu XML", + "HeaderXmlDocumentAttribute": "Thuộc Tính Tài Liệu XML", + "HeaderVideos": "Videos", + "HeaderVideoTypes": "Kiểu Video", + "HeaderVideoType": "Kiểu Video", + "HeaderVideoQuality": "Chất Lượng Video", + "HeaderUser": "Người Dùng", + "HeaderUploadImage": "Tải Lên Hình Ảnh", + "HeaderUpcomingOnTV": "Sắp Phát Sóng Trên TV", + "HeaderTypeText": "Nhập nội dung", + "HeaderTypeImageFetchers": "{0} Trình Tải Hình Ảnh", + "HeaderTuners": "Bộ Điều Khiển Thu Phát Sóng", + "HeaderTunerDevices": "Thiết Bị Bắt Sóng", + "HeaderTranscodingProfileHelp": "Thêm hồ sơ chuyển mã để chỉ ra những định dạng nên dùng khi cần chuyển mã.", + "HeaderTranscodingProfile": "Hồ Sơ Chuyển Mã" } diff --git a/src/useredit.html b/src/useredit.html index 0de3069dc7..253387b4ac 100644 --- a/src/useredit.html +++ b/src/useredit.html @@ -6,7 +6,7 @@ diff --git a/src/userlibraryaccess.html b/src/userlibraryaccess.html index 4653fd07eb..8a6177585a 100644 --- a/src/userlibraryaccess.html +++ b/src/userlibraryaccess.html @@ -6,7 +6,7 @@ diff --git a/src/usernew.html b/src/usernew.html index bca1e72fea..fac036aa8d 100644 --- a/src/usernew.html +++ b/src/usernew.html @@ -5,7 +5,7 @@

${HeaderAddUser}

- ${Help} + ${Help}
diff --git a/src/userparentalcontrol.html b/src/userparentalcontrol.html index f320c361f9..2c13ec8012 100644 --- a/src/userparentalcontrol.html +++ b/src/userparentalcontrol.html @@ -4,7 +4,7 @@ diff --git a/src/userpassword.html b/src/userpassword.html index f38e395999..119a0212de 100644 --- a/src/userpassword.html +++ b/src/userpassword.html @@ -4,7 +4,7 @@ diff --git a/src/userprofiles.html b/src/userprofiles.html index 1eaa1006dc..98237645dd 100644 --- a/src/userprofiles.html +++ b/src/userprofiles.html @@ -8,7 +8,7 @@ - ${Help} + ${Help}
diff --git a/src/wizardlibrary.html b/src/wizardlibrary.html index b8a4fab9d5..3a5ca5069b 100644 --- a/src/wizardlibrary.html +++ b/src/wizardlibrary.html @@ -3,7 +3,7 @@

${HeaderSetupLibrary}

- +

diff --git a/src/wizardstart.html b/src/wizardstart.html index c640b8eff8..05e282bee3 100644 --- a/src/wizardstart.html +++ b/src/wizardstart.html @@ -4,7 +4,7 @@

${WelcomeToProject}

- + ${ButtonQuickStartGuide}
diff --git a/webpack.dev.js b/webpack.dev.js index e4663e1656..716cfb2d07 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -2,7 +2,6 @@ const path = require('path'); const common = require('./webpack.common'); const merge = require('webpack-merge'); const packageConfig = require('./package.json'); -const postcssConfig = require('./postcss.config.js'); module.exports = merge(common, { mode: 'development', @@ -16,7 +15,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: { @@ -31,7 +30,11 @@ module.exports = merge(common, { 'css-loader', { loader: 'postcss-loader', - options: postcssConfig() + options: { + config: { + path: __dirname + } + } } ] }, diff --git a/webpack.prod.js b/webpack.prod.js index c213946883..eb39f82cd4 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -1,7 +1,6 @@ const common = require('./webpack.common'); const merge = require('webpack-merge'); const packageConfig = require('./package.json'); -const postcssConfig = require('./postcss.config.js'); module.exports = merge(common, { mode: 'production', @@ -9,7 +8,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: { @@ -24,7 +23,11 @@ module.exports = merge(common, { 'css-loader', { loader: 'postcss-loader', - options: postcssConfig() + options: { + config: { + path: __dirname + } + } } ] }, diff --git a/yarn.lock b/yarn.lock index 18ebcd60d2..28182ae1dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2436,15 +2436,6 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3174,10 +3165,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.12.0.tgz#01754c8a2f3368fc1119cf4625c3dad8c1845ee6" - integrity sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw== +date-fns@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.13.0.tgz#d7b8a0a2d392e8d88a8024d0a46b980bbfdbd708" + integrity sha512-xm0c61mevGF7f0XpCGtDTGpzEFC/1fpLXHbmFpxZZQJuvByIK2ozm6cSYuU+nxFYOPh2EuCfzUwlTEFwKG+h5w== dateformat@^2.0.0: version "2.2.0" @@ -5642,7 +5633,7 @@ html-comment-regex@^1.1.0, html-comment-regex@^1.1.2: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-entities@^1.2.1: +html-entities@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== @@ -6572,10 +6563,10 @@ jellyfin-apiclient@^1.1.1: version "1.0.3" resolved "https://github.com/jellyfin/jellyfin-noto#b784602db063734c721a46563ae5d6577ec2b35d" -jquery@>=1.9.1, jquery@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" - integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ== +jquery@>=1.9.1, jquery@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" + integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8, js-base64@^2.1.9: version "2.5.2" @@ -6807,9 +6798,9 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -"libass-wasm@https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-cordova": +"libass-wasm@https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv": version "4.0.0" - resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#b38056588bfaebc18a8353cb1757de0a815ac879" + resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639" liftoff@^3.1.0: version "3.1.0" @@ -7109,7 +7100,7 @@ logalot@^2.0.0, logalot@^2.1.0: figures "^1.3.5" squeak "^1.0.0" -loglevel@^1.6.6: +loglevel@^1.6.8: version "1.6.8" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== @@ -8178,7 +8169,7 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-locale@^3.0.0, os-locale@^3.1.0: +os-locale@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== @@ -8698,7 +8689,7 @@ plur@^3.0.1: dependencies: irregular-plurals "^2.0.0" -portfinder@^1.0.25: +portfinder@^1.0.26: version "1.0.26" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== @@ -10591,10 +10582,10 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shaka-player@^2.5.10: - version "2.5.10" - resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.10.tgz#6f4e72e2433002d11824a223b02edd5004e30e2b" - integrity sha512-kS9TQL6bWODo4XNnozERZWsEiWlLZ6huspPx4ZjmMjeOBL9gwqlULLfLyO+5gA3CYV/dk9LaAi1WAEaLWckGpA== +shaka-player@^2.5.11: + version "2.5.11" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.11.tgz#af550a0ee3aadf7be4e64e1a4d615c8d728e0b0f" + integrity sha512-SiZd/vCUPeKXNPnfWcBdraskdUYLtm+DITWceCZvRP4eoxxQuRI0MekVJTGqu5d7B2yW9TdQh5ojyRAjbQPFGA== dependencies: eme-encryption-scheme-polyfill "^2.0.1" @@ -10763,13 +10754,14 @@ sockjs-client@1.4.0: json3 "^3.3.2" url-parse "^1.4.3" -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== dependencies: faye-websocket "^0.10.0" - uuid "^3.0.1" + uuid "^3.4.0" + websocket-driver "0.6.5" sort-keys-length@^1.0.0: version "1.0.1" @@ -10886,7 +10878,7 @@ spdy-transport@^3.0.0: readable-stream "^3.0.6" wbuf "^1.7.3" -spdy@^4.0.1: +spdy@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== @@ -11084,7 +11076,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -12298,7 +12290,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -12535,10 +12527,10 @@ webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.10.3: - version "3.10.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" - integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== +webpack-dev-server@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -12548,31 +12540,31 @@ webpack-dev-server@^3.10.3: debug "^4.1.1" del "^4.1.1" express "^4.17.1" - html-entities "^1.2.1" + html-entities "^1.3.1" http-proxy-middleware "0.19.1" import-local "^2.0.0" internal-ip "^4.3.0" ip "^1.1.5" is-absolute-url "^3.0.3" killable "^1.0.1" - loglevel "^1.6.6" + loglevel "^1.6.8" opn "^5.5.0" p-retry "^3.0.1" - portfinder "^1.0.25" + portfinder "^1.0.26" schema-utils "^1.0.0" selfsigned "^1.10.7" semver "^6.3.0" serve-index "^1.9.1" - sockjs "0.3.19" + sockjs "0.3.20" sockjs-client "1.4.0" - spdy "^4.0.1" + spdy "^4.0.2" strip-ansi "^3.0.1" supports-color "^6.1.0" url "^0.11.0" webpack-dev-middleware "^3.7.2" webpack-log "^2.0.0" ws "^6.2.1" - yargs "12.0.5" + yargs "^13.3.2" webpack-log@^2.0.0: version "2.0.0" @@ -12641,6 +12633,13 @@ webpack@^4.26.1, webpack@^4.41.5: watchpack "^1.6.1" webpack-sources "^1.4.1" +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" + websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" @@ -12793,7 +12792,7 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== @@ -12822,14 +12821,6 @@ yargs-parser@^10.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^13.1.0, yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -12860,24 +12851,6 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@12.0.5: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - yargs@13.2.4: version "13.2.4" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
${ApiKeysCaption}
${HeaderApiKey}${HeaderApp}${HeaderDateIssued}${HeaderApiKey}${HeaderApp}${HeaderDateIssued}