diff --git a/package.json b/package.json index 8e725e1052..f794b8b6da 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "gulp-postcss": "^8.0.0", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", - "gulp-terser": "^1.2.1", + "gulp-terser": "^1.3.0", "html-webpack-plugin": "^4.3.0", "lazypipe": "^1.0.2", "node-sass": "^4.13.1", @@ -140,6 +140,7 @@ "src/components/playback/mediasession.js", "src/components/playback/nowplayinghelper.js", "src/components/playback/playbackorientation.js", + "src/components/playback/playbackmanager.js", "src/components/playback/playerSelectionMenu.js", "src/components/playback/playersettingsmenu.js", "src/components/playback/playmethodhelper.js", @@ -165,6 +166,7 @@ "src/components/syncPlay/playbackPermissionManager.js", "src/components/syncPlay/syncPlayManager.js", "src/components/syncPlay/timeSyncManager.js", + "src/components/viewContainer.js", "src/controllers/session/addServer/index.js", "src/controllers/session/forgotPassword/index.js", "src/controllers/session/redeemPassword/index.js", @@ -270,8 +272,10 @@ "src/scripts/themeManager.js", "src/scripts/keyboardNavigation.js", "src/scripts/libraryBrowser.js", + "src/scripts/mouseManager.js", "src/scripts/multiDownload.js", "src/scripts/playlists.js", + "src/scripts/routes.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", "src/scripts/settings/webSettings.js", diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 5dbcf765be..da3b08317c 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -1,6 +1,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdrop', 'browser', 'page', 'appSettings', 'apphost', 'connectionManager'], function (loading, globalize, events, viewManager, skinManager, backdrop, browser, page, appSettings, appHost, connectionManager) { 'use strict'; + browser = browser.default || browser; loading = loading.default || loading; var appRouter = { diff --git a/src/components/apphost.js b/src/components/apphost.js index 931fcae1db..3ed590b546 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -1,6 +1,8 @@ define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) { 'use strict'; + browser = browser.default || browser; + function getBaseProfileOptions(item) { var disableHlsVideoAudioCodecs = []; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index abc7d81fee..61caa9188f 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1,6 +1,8 @@ define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + browser = browser.default || browser; loading = loading.default || loading; function showViewSettings(instance) { diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index c9d3e6fdb2..0effcc7a57 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -205,7 +205,7 @@ import 'css!./style'; /* eslint-enable indent */ export default { - serLazyImage: setLazyImage, + setLazyImage: setLazyImage, fillImages: fillImages, fillImage: fillImage, lazyImage: lazyImage, diff --git a/src/components/itemsrefresher.js b/src/components/itemsrefresher.js index a210af31af..74b08db07f 100644 --- a/src/components/itemsrefresher.js +++ b/src/components/itemsrefresher.js @@ -1,6 +1,8 @@ define(['playbackManager', 'serverNotifications', 'events'], function (playbackManager, serverNotifications, events) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + function onUserDataChanged(e, apiClient, userData) { var instance = this; diff --git a/src/components/layoutManager.js b/src/components/layoutManager.js index bec7d4ae3b..85d78f8ff4 100644 --- a/src/components/layoutManager.js +++ b/src/components/layoutManager.js @@ -1,6 +1,8 @@ define(['browser', 'appSettings', 'events'], function (browser, appSettings, events) { 'use strict'; + browser = browser.default || browser; + function setLayout(instance, layout, selectedLayout) { if (layout === selectedLayout) { instance[layout] = true; diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index b4647bbf25..0bf270f2a1 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,6 +1,8 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'require'], function (serverNotifications, playbackManager, events, globalize, require) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + function onOneDocumentClick() { document.removeEventListener('click', onOneDocumentClick); document.removeEventListener('keydown', onOneDocumentClick); diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 3c6394db79..437127be18 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1,730 +1,738 @@ -define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'screenfull'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, screenfull) { - 'use strict'; - - loading = loading.default || loading; - PlayQueueManager = PlayQueueManager.default || PlayQueueManager; - - function enableLocalPlaylistManagement(player) { - if (player.getPlaylist) { - return false; - } - - if (player.isLocalPlayer) { - return true; - } +import events from 'events'; +import datetime from 'datetime'; +import appSettings from 'appSettings'; +import itemHelper from 'itemHelper'; +import pluginManager from 'pluginManager'; +import PlayQueueManager from 'playQueueManager'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import connectionManager from 'connectionManager'; +import loading from 'loading'; +import apphost from 'apphost'; +import screenfull from 'screenfull'; +function enableLocalPlaylistManagement(player) { + if (player.getPlaylist) { return false; } - function bindToFullscreenChange(player) { - if (screenfull.isEnabled) { - screenfull.on('change', function () { - events.trigger(player, 'fullscreenchange'); - }); - } else { - // iOS Safari - document.addEventListener('webkitfullscreenchange', function () { - events.trigger(player, 'fullscreenchange'); - }, false); - } - } - - function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) { - if (!newPlayer && !previousPlayer) { - return; - } - - if (newTarget && previousTargetInfo) { - if (newTarget.id === previousTargetInfo.id) { - return; - } - } - - events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); - } - - function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { - if (!serverId) { - // Not a server item - // We can expand on this later and possibly report them - events.trigger(playbackManagerInstance, 'reportplayback', [false]); - return; - } - - var info = Object.assign({}, state.PlayState); - info.ItemId = state.NowPlayingItem.Id; - - if (progressEventName) { - info.EventName = progressEventName; - } - - if (reportPlaylist) { - addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); - } - - var apiClient = connectionManager.getApiClient(serverId); - var reportPlaybackPromise = apiClient[method](info); - // Notify that report has been sent - reportPlaybackPromise.then(() => { - events.trigger(playbackManagerInstance, 'reportplayback', [true]); - }); - } - - function getPlaylistSync(playbackManagerInstance, player) { - player = player || playbackManagerInstance._currentPlayer; - if (player && !enableLocalPlaylistManagement(player)) { - return player.getPlaylistSync(); - } - - return playbackManagerInstance._playQueueManager.getPlaylist(); - } - - function addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId) { - info.NowPlayingQueue = getPlaylistSync(playbackManagerInstance, player).map(function (i) { - var itemInfo = { - Id: i.Id, - PlaylistItemId: i.PlaylistItemId - }; - - if (i.ServerId !== serverId) { - itemInfo.ServerId = i.ServerId; - } - - return itemInfo; - }); - } - - function normalizeName(t) { - return t.toLowerCase().replace(' ', ''); - } - - function getItemsForPlayback(serverId, query) { - var apiClient = connectionManager.getApiClient(serverId); - - if (query.Ids && query.Ids.split(',').length === 1) { - var itemId = query.Ids.split(','); - - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - return { - Items: [item], - TotalRecordCount: 1 - }; - }); - } else { - query.Limit = query.Limit || 300; - query.Fields = 'Chapters'; - query.ExcludeLocationTypes = 'Virtual'; - query.EnableTotalRecordCount = false; - query.CollapseBoxSetItems = false; - - return apiClient.getItems(apiClient.getCurrentUserId(), query); - } - } - - function createStreamInfoFromUrlItem(item) { - // Check item.Path for games - return { - url: item.Url || item.Path, - playMethod: 'DirectPlay', - item: item, - textTracks: [], - mediaType: item.MediaType - }; - } - - function mergePlaybackQueries(obj1, obj2) { - var query = Object.assign(obj1, obj2); - - var filters = query.Filters ? query.Filters.split(',') : []; - if (filters.indexOf('IsNotFolder') === -1) { - filters.push('IsNotFolder'); - } - query.Filters = filters.join(','); - return query; - } - - function backdropImageUrl(apiClient, item, options) { - options = options || {}; - options.type = options.type || 'Backdrop'; - - // If not resizing, get the original image - if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { - options.quality = 100; - } - - if (item.BackdropImageTags && item.BackdropImageTags.length) { - options.tag = item.BackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.Id, options); - } - - if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - options.tag = item.ParentBackdropImageTags[0]; - return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); - } - - return null; - } - - function getMimeType(type, container) { - container = (container || '').toLowerCase(); - - if (type === 'audio') { - if (container === 'opus') { - return 'audio/ogg'; - } - if (container === 'webma') { - return 'audio/webm'; - } - if (container === 'm4a') { - return 'audio/mp4'; - } - } else if (type === 'video') { - if (container === 'mkv') { - return 'video/x-matroska'; - } - if (container === 'm4v') { - return 'video/mp4'; - } - if (container === 'mov') { - return 'video/quicktime'; - } - if (container === 'mpg') { - return 'video/mpeg'; - } - if (container === 'flv') { - return 'video/x-flv'; - } - } - - return type + '/' + container; - } - - function getParam(name, url) { - name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); - var regexS = '[\\?&]' + name + '=([^&#]*)'; - var regex = new RegExp(regexS, 'i'); - - var results = regex.exec(url); - if (results == null) { - return ''; - } else { - return decodeURIComponent(results[1].replace(/\+/g, ' ')); - } - } - - function isAutomaticPlayer(player) { - if (player.isLocalPlayer) { - return true; - } - - return false; - } - - function getAutomaticPlayers(instance, forceLocalPlayer) { - if (!forceLocalPlayer) { - var player = instance._currentPlayer; - if (player && !isAutomaticPlayer(player)) { - return [player]; - } - } - - return instance.getPlayers().filter(isAutomaticPlayer); - } - - function isServerItem(item) { - if (!item.Id) { - return false; - } + if (player.isLocalPlayer) { return true; } - function enableIntros(item) { - if (item.MediaType !== 'Video') { - return false; - } - if (item.Type === 'TvChannel') { - return false; - } - // disable for in-progress recordings - if (item.Status === 'InProgress') { - return false; - } + return false; +} - return isServerItem(item); +function bindToFullscreenChange(player) { + if (screenfull.isEnabled) { + screenfull.on('change', function () { + events.trigger(player, 'fullscreenchange'); + }); + } else { + // iOS Safari + document.addEventListener('webkitfullscreenchange', function () { + events.trigger(player, 'fullscreenchange'); + }, false); + } +} + +function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) { + if (!newPlayer && !previousPlayer) { + return; } - function getIntros(firstItem, apiClient, options) { - if (options.startPositionTicks || options.startIndex || options.fullscreen === false || !enableIntros(firstItem) || !userSettings.enableCinemaMode()) { - return Promise.resolve({ - Items: [] - }); + if (newTarget && previousTargetInfo) { + if (newTarget.id === previousTargetInfo.id) { + return; } - - return apiClient.getIntros(firstItem.Id).then(function (result) { - return result; - }, function (err) { - return Promise.resolve({ - Items: [] - }); - }); } - function getAudioMaxValues(deviceProfile) { - // TODO - this could vary per codec and should be done on the server using the entire profile - var maxAudioSampleRate = null; - var maxAudioBitDepth = null; - var maxAudioBitrate = null; + events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); +} - deviceProfile.CodecProfiles.map(function (codecProfile) { - if (codecProfile.Type === 'Audio') { - (codecProfile.Conditions || []).map(function (condition) { - if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { - 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; - } - }); - } - }); +function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) { + if (!serverId) { + // Not a server item + // We can expand on this later and possibly report them + events.trigger(playbackManagerInstance, 'reportplayback', [false]); + return; + } - return { - maxAudioSampleRate: maxAudioSampleRate, - maxAudioBitDepth: maxAudioBitDepth, - maxAudioBitrate: maxAudioBitrate + const info = Object.assign({}, state.PlayState); + info.ItemId = state.NowPlayingItem.Id; + + if (progressEventName) { + info.EventName = progressEventName; + } + + if (reportPlaylist) { + addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); + } + + const apiClient = connectionManager.getApiClient(serverId); + const reportPlaybackPromise = apiClient[method](info); + // Notify that report has been sent + reportPlaybackPromise.then(() => { + events.trigger(playbackManagerInstance, 'reportplayback', [true]); + }); +} + +function getPlaylistSync(playbackManagerInstance, player) { + player = player || playbackManagerInstance._currentPlayer; + if (player && !enableLocalPlaylistManagement(player)) { + return player.getPlaylistSync(); + } + + return playbackManagerInstance._playQueueManager.getPlaylist(); +} + +function addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId) { + info.NowPlayingQueue = getPlaylistSync(playbackManagerInstance, player).map(function (i) { + const itemInfo = { + Id: i.Id, + PlaylistItemId: i.PlaylistItemId }; + + if (i.ServerId !== serverId) { + itemInfo.ServerId = i.ServerId; + } + + return itemInfo; + }); +} + +function normalizeName(t) { + return t.toLowerCase().replace(' ', ''); +} + +function getItemsForPlayback(serverId, query) { + const apiClient = connectionManager.getApiClient(serverId); + + if (query.Ids && query.Ids.split(',').length === 1) { + const itemId = query.Ids.split(','); + + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } else { + query.Limit = query.Limit || 300; + query.Fields = 'Chapters'; + query.ExcludeLocationTypes = 'Virtual'; + query.EnableTotalRecordCount = false; + query.CollapseBoxSetItems = false; + + return apiClient.getItems(apiClient.getCurrentUserId(), query); + } +} + +function createStreamInfoFromUrlItem(item) { + // Check item.Path for games + return { + url: item.Url || item.Path, + playMethod: 'DirectPlay', + item: item, + textTracks: [], + mediaType: item.MediaType + }; +} + +function mergePlaybackQueries(obj1, obj2) { + const query = Object.assign(obj1, obj2); + + const filters = query.Filters ? query.Filters.split(',') : []; + if (filters.indexOf('IsNotFolder') === -1) { + filters.push('IsNotFolder'); + } + query.Filters = filters.join(','); + return query; +} + +function backdropImageUrl(apiClient, item, options) { + options = options || {}; + options.type = options.type || 'Backdrop'; + + // If not resizing, get the original image + if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) { + options.quality = 100; } - var startingPlaySession = new Date().getTime(); - function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) { - var url = 'Audio/' + item.Id + '/universal'; + if (item.BackdropImageTags && item.BackdropImageTags.length) { + options.tag = item.BackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.Id, options); + } - startingPlaySession++; - return apiClient.getUrl(url, { - UserId: apiClient.getCurrentUserId(), - DeviceId: apiClient.deviceId(), - MaxStreamingBitrate: maxAudioBitrate || maxBitrate, - Container: directPlayContainers, - TranscodingContainer: transcodingProfile.Container || null, - TranscodingProtocol: transcodingProfile.Protocol || null, - AudioCodec: transcodingProfile.AudioCodec, - MaxAudioSampleRate: maxAudioSampleRate, - MaxAudioBitDepth: maxAudioBitDepth, - api_key: apiClient.accessToken(), - PlaySessionId: startingPlaySession, - StartTimeTicks: startPosition || 0, - EnableRedirection: true, - EnableRemoteMedia: apphost.supports('remoteaudio') + if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + options.tag = item.ParentBackdropImageTags[0]; + return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); + } + + return null; +} + +function getMimeType(type, container) { + container = (container || '').toLowerCase(); + + if (type === 'audio') { + if (container === 'opus') { + return 'audio/ogg'; + } + if (container === 'webma') { + return 'audio/webm'; + } + if (container === 'm4a') { + return 'audio/mp4'; + } + } else if (type === 'video') { + if (container === 'mkv') { + return 'video/x-matroska'; + } + if (container === 'm4v') { + return 'video/mp4'; + } + if (container === 'mov') { + return 'video/quicktime'; + } + if (container === 'mpg') { + return 'video/mpeg'; + } + if (container === 'flv') { + return 'video/x-flv'; + } + } + + return type + '/' + container; +} + +function getParam(name, url) { + name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]'); + const regexS = '[\\?&]' + name + '=([^&#]*)'; + const regex = new RegExp(regexS, 'i'); + + const results = regex.exec(url); + if (results == null) { + return ''; + } else { + return decodeURIComponent(results[1].replace(/\+/g, ' ')); + } +} + +function isAutomaticPlayer(player) { + if (player.isLocalPlayer) { + return true; + } + + return false; +} + +function getAutomaticPlayers(instance, forceLocalPlayer) { + if (!forceLocalPlayer) { + const player = instance._currentPlayer; + if (player && !isAutomaticPlayer(player)) { + return [player]; + } + } + + return instance.getPlayers().filter(isAutomaticPlayer); +} + +function isServerItem(item) { + if (!item.Id) { + return false; + } + return true; +} + +function enableIntros(item) { + if (item.MediaType !== 'Video') { + return false; + } + if (item.Type === 'TvChannel') { + return false; + } + // disable for in-progress recordings + if (item.Status === 'InProgress') { + return false; + } + + return isServerItem(item); +} + +function getIntros(firstItem, apiClient, options) { + if (options.startPositionTicks || options.startIndex || options.fullscreen === false || !enableIntros(firstItem) || !userSettings.enableCinemaMode()) { + return Promise.resolve({ + Items: [] }); } - function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition) { - var transcodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { - return p.Type === 'Audio' && p.Context === 'Streaming'; + return apiClient.getIntros(firstItem.Id).then(function (result) { + return result; + }, function (err) { + return Promise.resolve({ + Items: [] + }); + }); +} + +function getAudioMaxValues(deviceProfile) { + // TODO - this could vary per codec and should be done on the server using the entire profile + let maxAudioSampleRate = null; + let maxAudioBitDepth = null; + let maxAudioBitrate = null; + + deviceProfile.CodecProfiles.map(function (codecProfile) { + if (codecProfile.Type === 'Audio') { + (codecProfile.Conditions || []).map(function (condition) { + if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') { + 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; + } + }); + } + }); + + return { + maxAudioSampleRate: maxAudioSampleRate, + maxAudioBitDepth: maxAudioBitDepth, + maxAudioBitrate: maxAudioBitrate + }; +} + +let startingPlaySession = new Date().getTime(); +function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) { + const url = 'Audio/' + item.Id + '/universal'; + + startingPlaySession++; + return apiClient.getUrl(url, { + UserId: apiClient.getCurrentUserId(), + DeviceId: apiClient.deviceId(), + MaxStreamingBitrate: maxAudioBitrate || maxBitrate, + Container: directPlayContainers, + TranscodingContainer: transcodingProfile.Container || null, + TranscodingProtocol: transcodingProfile.Protocol || null, + AudioCodec: transcodingProfile.AudioCodec, + MaxAudioSampleRate: maxAudioSampleRate, + MaxAudioBitDepth: maxAudioBitDepth, + api_key: apiClient.accessToken(), + PlaySessionId: startingPlaySession, + StartTimeTicks: startPosition || 0, + EnableRedirection: true, + EnableRemoteMedia: apphost.supports('remoteaudio') + }); +} + +function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition) { + const transcodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { + return p.Type === 'Audio' && p.Context === 'Streaming'; + })[0]; + + let directPlayContainers = ''; + + deviceProfile.DirectPlayProfiles.map(function (p) { + if (p.Type === 'Audio') { + if (directPlayContainers) { + directPlayContainers += ',' + p.Container; + } else { + directPlayContainers = p.Container; + } + + if (p.AudioCodec) { + directPlayContainers += '|' + p.AudioCodec; + } + } + }); + + const maxValues = getAudioMaxValues(deviceProfile); + + return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); +} + +function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { + const audioTranscodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { + return p.Type === 'Audio' && p.Context === 'Streaming'; + })[0]; + + let audioDirectPlayContainers = ''; + + deviceProfile.DirectPlayProfiles.map(function (p) { + if (p.Type === 'Audio') { + if (audioDirectPlayContainers) { + audioDirectPlayContainers += ',' + p.Container; + } else { + audioDirectPlayContainers = p.Container; + } + + if (p.AudioCodec) { + audioDirectPlayContainers += '|' + p.AudioCodec; + } + } + }); + + const maxValues = getAudioMaxValues(deviceProfile); + + const streamUrls = []; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let streamUrl; + + if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) { + streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); + } + + streamUrls.push(streamUrl || ''); + + if (i === 0) { + startPosition = 0; + } + } + + return Promise.resolve(streamUrls); +} + +function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { + return getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition).then(function (streamUrls) { + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + const streamUrl = streamUrls[i]; + + if (streamUrl) { + item.PresetMediaSource = { + StreamUrl: streamUrl, + Id: item.Id, + MediaStreams: [], + RunTimeTicks: item.RunTimeTicks + }; + } + } + }); +} + +function getPlaybackInfo(player, + apiClient, + item, + deviceProfile, + maxBitrate, + startPosition, + isPlayback, + mediaSourceId, + audioStreamIndex, + subtitleStreamIndex, + liveStreamId, + enableDirectPlay, + enableDirectStream, + allowVideoStreamCopy, + allowAudioStreamCopy) { + if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') { + return Promise.resolve({ + MediaSources: [ + { + StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition), + Id: item.Id, + MediaStreams: [], + RunTimeTicks: item.RunTimeTicks + }] + }); + } + + if (item.PresetMediaSource) { + return Promise.resolve({ + MediaSources: [item.PresetMediaSource] + }); + } + + const itemId = item.Id; + + const query = { + UserId: apiClient.getCurrentUserId(), + StartTimeTicks: startPosition || 0 + }; + + if (isPlayback) { + query.IsPlayback = true; + query.AutoOpenLiveStream = true; + } else { + query.IsPlayback = false; + query.AutoOpenLiveStream = false; + } + + if (audioStreamIndex != null) { + query.AudioStreamIndex = audioStreamIndex; + } + if (subtitleStreamIndex != null) { + query.SubtitleStreamIndex = subtitleStreamIndex; + } + if (enableDirectPlay != null) { + query.EnableDirectPlay = enableDirectPlay; + } + + if (enableDirectStream != null) { + query.EnableDirectStream = enableDirectStream; + } + if (allowVideoStreamCopy != null) { + query.AllowVideoStreamCopy = allowVideoStreamCopy; + } + if (allowAudioStreamCopy != null) { + query.AllowAudioStreamCopy = allowAudioStreamCopy; + } + if (mediaSourceId) { + query.MediaSourceId = mediaSourceId; + } + if (liveStreamId) { + query.LiveStreamId = liveStreamId; + } + if (maxBitrate) { + query.MaxStreamingBitrate = maxBitrate; + } + if (player.enableMediaProbe && !player.enableMediaProbe(item)) { + query.EnableMediaProbe = false; + } + + // lastly, enforce player overrides for special situations + if (query.EnableDirectStream !== false) { + if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { + query.EnableDirectStream = false; + } + } + + if (player.getDirectPlayProtocols) { + query.DirectPlayProtocols = player.getDirectPlayProtocols(); + } + + return apiClient.getPlaybackInfo(itemId, query, deviceProfile); +} + +function getOptimalMediaSource(apiClient, item, versions) { + const promises = versions.map(function (v) { + return supportsDirectPlay(apiClient, item, v); + }); + + if (!promises.length) { + return Promise.reject(); + } + + return Promise.all(promises).then(function (results) { + for (let i = 0, length = versions.length; i < length; i++) { + versions[i].enableDirectPlay = results[i] || false; + } + let optimalVersion = versions.filter(function (v) { + return v.enableDirectPlay; })[0]; - var directPlayContainers = ''; + if (!optimalVersion) { + optimalVersion = versions.filter(function (v) { + return v.SupportsDirectStream; + })[0]; + } - deviceProfile.DirectPlayProfiles.map(function (p) { - if (p.Type === 'Audio') { - if (directPlayContainers) { - directPlayContainers += ',' + p.Container; - } else { - directPlayContainers = p.Container; - } - - if (p.AudioCodec) { - directPlayContainers += '|' + p.AudioCodec; - } - } - }); - - var maxValues = getAudioMaxValues(deviceProfile); - - return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); - } - - function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { - var audioTranscodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) { - return p.Type === 'Audio' && p.Context === 'Streaming'; + optimalVersion = optimalVersion || versions.filter(function (s) { + return s.SupportsTranscoding; })[0]; - var audioDirectPlayContainers = ''; + return optimalVersion || versions[0]; + }); +} - deviceProfile.DirectPlayProfiles.map(function (p) { - if (p.Type === 'Audio') { - if (audioDirectPlayContainers) { - audioDirectPlayContainers += ',' + p.Container; - } else { - audioDirectPlayContainers = p.Container; - } +function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) { + const postData = { + DeviceProfile: deviceProfile, + OpenToken: mediaSource.OpenToken + }; - if (p.AudioCodec) { - audioDirectPlayContainers += '|' + p.AudioCodec; + const query = { + UserId: apiClient.getCurrentUserId(), + StartTimeTicks: startPosition || 0, + ItemId: item.Id, + PlaySessionId: playSessionId + }; + + if (maxBitrate) { + query.MaxStreamingBitrate = maxBitrate; + } + if (audioStreamIndex != null) { + query.AudioStreamIndex = audioStreamIndex; + } + if (subtitleStreamIndex != null) { + query.SubtitleStreamIndex = subtitleStreamIndex; + } + + // lastly, enforce player overrides for special situations + if (query.EnableDirectStream !== false) { + if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { + query.EnableDirectStream = false; + } + } + + return apiClient.ajax({ + url: apiClient.getUrl('LiveStreams/Open', query), + type: 'POST', + data: JSON.stringify(postData), + contentType: 'application/json', + dataType: 'json' + + }); +} + +function isHostReachable(mediaSource, apiClient) { + if (mediaSource.IsRemote) { + return Promise.resolve(true); + } + + return apiClient.getEndpointInfo().then(function (endpointInfo) { + if (endpointInfo.IsInNetwork) { + if (!endpointInfo.IsLocal) { + const path = (mediaSource.Path || '').toLowerCase(); + if (path.indexOf('localhost') !== -1 || path.indexOf('127.0.0.1') !== -1) { + // This will only work if the app is on the same machine as the server + return Promise.resolve(false); } } - }); - var maxValues = getAudioMaxValues(deviceProfile); - - var streamUrls = []; - - for (var i = 0, length = items.length; i < length; i++) { - var item = items[i]; - var streamUrl; - - if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) { - streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); - } - - streamUrls.push(streamUrl || ''); - - if (i === 0) { - startPosition = 0; - } - } - - return Promise.resolve(streamUrls); - } - - function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { - return getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition).then(function (streamUrls) { - for (var i = 0, length = items.length; i < length; i++) { - var item = items[i]; - var streamUrl = streamUrls[i]; - - if (streamUrl) { - item.PresetMediaSource = { - StreamUrl: streamUrl, - Id: item.Id, - MediaStreams: [], - RunTimeTicks: item.RunTimeTicks - }; - } - } - }); - } - - function getPlaybackInfo(player, - apiClient, - item, - deviceProfile, - maxBitrate, - startPosition, - isPlayback, - mediaSourceId, - audioStreamIndex, - subtitleStreamIndex, - liveStreamId, - enableDirectPlay, - enableDirectStream, - allowVideoStreamCopy, - allowAudioStreamCopy) { - if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') { - return Promise.resolve({ - MediaSources: [ - { - StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition), - Id: item.Id, - MediaStreams: [], - RunTimeTicks: item.RunTimeTicks - }] - }); - } - - if (item.PresetMediaSource) { - return Promise.resolve({ - MediaSources: [item.PresetMediaSource] - }); - } - - var itemId = item.Id; - - var query = { - UserId: apiClient.getCurrentUserId(), - StartTimeTicks: startPosition || 0 - }; - - if (isPlayback) { - query.IsPlayback = true; - query.AutoOpenLiveStream = true; - } else { - query.IsPlayback = false; - query.AutoOpenLiveStream = false; - } - - if (audioStreamIndex != null) { - query.AudioStreamIndex = audioStreamIndex; - } - if (subtitleStreamIndex != null) { - query.SubtitleStreamIndex = subtitleStreamIndex; - } - if (enableDirectPlay != null) { - query.EnableDirectPlay = enableDirectPlay; - } - - if (enableDirectStream != null) { - query.EnableDirectStream = enableDirectStream; - } - if (allowVideoStreamCopy != null) { - query.AllowVideoStreamCopy = allowVideoStreamCopy; - } - if (allowAudioStreamCopy != null) { - query.AllowAudioStreamCopy = allowAudioStreamCopy; - } - if (mediaSourceId) { - query.MediaSourceId = mediaSourceId; - } - if (liveStreamId) { - query.LiveStreamId = liveStreamId; - } - if (maxBitrate) { - query.MaxStreamingBitrate = maxBitrate; - } - if (player.enableMediaProbe && !player.enableMediaProbe(item)) { - query.EnableMediaProbe = false; - } - - // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } - } - - if (player.getDirectPlayProtocols) { - query.DirectPlayProtocols = player.getDirectPlayProtocols(); - } - - return apiClient.getPlaybackInfo(itemId, query, deviceProfile); - } - - function getOptimalMediaSource(apiClient, item, versions) { - var promises = versions.map(function (v) { - return supportsDirectPlay(apiClient, item, v); - }); - - if (!promises.length) { - return Promise.reject(); - } - - return Promise.all(promises).then(function (results) { - for (var i = 0, length = versions.length; i < length; i++) { - versions[i].enableDirectPlay = results[i] || false; - } - var optimalVersion = versions.filter(function (v) { - return v.enableDirectPlay; - })[0]; - - if (!optimalVersion) { - optimalVersion = versions.filter(function (v) { - return v.SupportsDirectStream; - })[0]; - } - - optimalVersion = optimalVersion || versions.filter(function (s) { - return s.SupportsTranscoding; - })[0]; - - return optimalVersion || versions[0]; - }); - } - - function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) { - var postData = { - DeviceProfile: deviceProfile, - OpenToken: mediaSource.OpenToken - }; - - var query = { - UserId: apiClient.getCurrentUserId(), - StartTimeTicks: startPosition || 0, - ItemId: item.Id, - PlaySessionId: playSessionId - }; - - if (maxBitrate) { - query.MaxStreamingBitrate = maxBitrate; - } - if (audioStreamIndex != null) { - query.AudioStreamIndex = audioStreamIndex; - } - if (subtitleStreamIndex != null) { - query.SubtitleStreamIndex = subtitleStreamIndex; - } - - // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } - } - - return apiClient.ajax({ - url: apiClient.getUrl('LiveStreams/Open', query), - type: 'POST', - data: JSON.stringify(postData), - contentType: 'application/json', - dataType: 'json' - - }); - } - - function isHostReachable(mediaSource, apiClient) { - if (mediaSource.IsRemote) { return Promise.resolve(true); } - return apiClient.getEndpointInfo().then(function (endpointInfo) { - if (endpointInfo.IsInNetwork) { - if (!endpointInfo.IsLocal) { - var path = (mediaSource.Path || '').toLowerCase(); - if (path.indexOf('localhost') !== -1 || path.indexOf('127.0.0.1') !== -1) { - // This will only work if the app is on the same machine as the server - return Promise.resolve(false); - } - } + // media source is in network, but connection is out of network + return Promise.resolve(false); + }); +} - return Promise.resolve(true); - } +function supportsDirectPlay(apiClient, item, mediaSource) { + // folder rip hacks due to not yet being supported by the stream building engine + const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; - // media source is in network, but connection is out of network + if (mediaSource.SupportsDirectPlay || isFolderRip) { + if (mediaSource.IsRemote && !apphost.supports('remotevideo')) { return Promise.resolve(false); - }); - } + } - function supportsDirectPlay(apiClient, item, mediaSource) { - // folder rip hacks due to not yet being supported by the stream building engine - var isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd'; - - if (mediaSource.SupportsDirectPlay || isFolderRip) { - if (mediaSource.IsRemote && !apphost.supports('remotevideo')) { - return Promise.resolve(false); + if (mediaSource.Protocol === 'Http' && !mediaSource.RequiredHttpHeaders.length) { + // If this is the only way it can be played, then allow it + if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) { + return Promise.resolve(true); + } else { + return isHostReachable(mediaSource, apiClient); } + } else if (mediaSource.Protocol === 'File') { + return new Promise(function (resolve, reject) { + // Determine if the file can be accessed directly + import('filesystem').then((filesystem) => { + const method = isFolderRip ? + 'directoryExists' : + 'fileExists'; - if (mediaSource.Protocol === 'Http' && !mediaSource.RequiredHttpHeaders.length) { - // If this is the only way it can be played, then allow it - if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) { - return Promise.resolve(true); - } else { - return isHostReachable(mediaSource, apiClient); - } - } else if (mediaSource.Protocol === 'File') { - return new Promise(function (resolve, reject) { - // Determine if the file can be accessed directly - require(['filesystem'], function (filesystem) { - var method = isFolderRip ? - 'directoryExists' : - 'fileExists'; - - filesystem[method](mediaSource.Path).then(function () { - resolve(true); - }, function () { - resolve(false); - }); + filesystem[method](mediaSource.Path).then(function () { + resolve(true); + }, function () { + resolve(false); }); }); - } - } - - return Promise.resolve(false); - } - - function validatePlaybackInfoResult(instance, result) { - if (result.ErrorCode) { - showPlaybackInfoErrorMessage(instance, result.ErrorCode); - return false; - } - - return true; - } - - function showPlaybackInfoErrorMessage(instance, errorCode, playNextTrack) { - require(['alert'], function (alert) { - alert.default({ - text: globalize.translate('PlaybackError' + errorCode), - title: globalize.translate('HeaderPlaybackError') - }).then(function () { - if (playNextTrack) { - instance.nextTrack(); - } }); + } + } + + return Promise.resolve(false); +} + +function validatePlaybackInfoResult(instance, result) { + if (result.ErrorCode) { + showPlaybackInfoErrorMessage(instance, result.ErrorCode); + return false; + } + + return true; +} + +function showPlaybackInfoErrorMessage(instance, errorCode, playNextTrack) { + import('alert').then(({ default: alert }) => { + alert({ + text: globalize.translate('PlaybackError' + errorCode), + title: globalize.translate('HeaderPlaybackError') + }).then(function () { + if (playNextTrack) { + instance.nextTrack(); + } }); + }); +} + +function normalizePlayOptions(playOptions) { + playOptions.fullscreen = playOptions.fullscreen !== false; +} + +function truncatePlayOptions(playOptions) { + return { + fullscreen: playOptions.fullscreen, + mediaSourceId: playOptions.mediaSourceId, + audioStreamIndex: playOptions.audioStreamIndex, + subtitleStreamIndex: playOptions.subtitleStreamIndex, + startPositionTicks: playOptions.startPositionTicks + }; +} + +function getNowPlayingItemForReporting(player, item, mediaSource) { + const nowPlayingItem = Object.assign({}, item); + + if (mediaSource) { + nowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks; + nowPlayingItem.MediaStreams = mediaSource.MediaStreams; + + // not needed + nowPlayingItem.MediaSources = null; } - function normalizePlayOptions(playOptions) { - playOptions.fullscreen = playOptions.fullscreen !== false; + nowPlayingItem.RunTimeTicks = nowPlayingItem.RunTimeTicks || player.duration() * 10000; + + return nowPlayingItem; +} + +function displayPlayerIndividually(player) { + return !player.isLocalPlayer; +} + +function createTarget(instance, player) { + return { + name: player.name, + id: player.id, + playerName: player.name, + playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'].map(player.canPlayMediaType), + isLocalPlayer: player.isLocalPlayer, + supportedCommands: instance.getSupportedCommands(player) + }; +} + +function getPlayerTargets(player) { + if (player.getTargets) { + return player.getTargets(); } - function truncatePlayOptions(playOptions) { - return { - fullscreen: playOptions.fullscreen, - mediaSourceId: playOptions.mediaSourceId, - audioStreamIndex: playOptions.audioStreamIndex, - subtitleStreamIndex: playOptions.subtitleStreamIndex, - startPositionTicks: playOptions.startPositionTicks - }; - } + return Promise.resolve([createTarget(player)]); +} - function getNowPlayingItemForReporting(player, item, mediaSource) { - var nowPlayingItem = Object.assign({}, item); +function sortPlayerTargets(a, b) { + let aVal = a.isLocalPlayer ? 0 : 1; + let bVal = b.isLocalPlayer ? 0 : 1; - if (mediaSource) { - nowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks; - nowPlayingItem.MediaStreams = mediaSource.MediaStreams; + aVal = aVal.toString() + a.name; + bVal = bVal.toString() + b.name; - // not needed - nowPlayingItem.MediaSources = null; - } + return aVal.localeCompare(bVal); +} - nowPlayingItem.RunTimeTicks = nowPlayingItem.RunTimeTicks || player.duration() * 10000; +class PlaybackManager { + constructor() { + const self = this; - return nowPlayingItem; - } - - function displayPlayerIndividually(player) { - return !player.isLocalPlayer; - } - - function createTarget(instance, player) { - return { - name: player.name, - id: player.id, - playerName: player.name, - playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'].map(player.canPlayMediaType), - isLocalPlayer: player.isLocalPlayer, - supportedCommands: instance.getSupportedCommands(player) - }; - } - - function getPlayerTargets(player) { - if (player.getTargets) { - return player.getTargets(); - } - - return Promise.resolve([createTarget(player)]); - } - - function sortPlayerTargets(a, b) { - var aVal = a.isLocalPlayer ? 0 : 1; - var bVal = b.isLocalPlayer ? 0 : 1; - - aVal = aVal.toString() + a.name; - bVal = bVal.toString() + b.name; - - return aVal.localeCompare(bVal); - } - - function PlaybackManager() { - var self = this; - - var players = []; - var currentTargetInfo; - var currentPairingId = null; + const players = []; + let currentTargetInfo; + let currentPairingId = null; this._playNextAfterEnded = true; - var playerStates = {}; + const playerStates = {}; this._playQueueManager = new PlayQueueManager(); @@ -737,7 +745,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.currentItem(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.item : null; }; @@ -750,7 +758,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.currentMediaSource(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.mediaSource : null; }; @@ -763,7 +771,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.playMethod(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.playMethod : null; }; @@ -776,21 +784,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.playSessionId(); } - var data = getPlayerData(player); + const data = getPlayerData(player); return data.streamInfo ? data.streamInfo.playSessionId : null; }; self.getPlayerInfo = function () { - var player = self._currentPlayer; + const player = self._currentPlayer; if (!player) { return null; } - var target = currentTargetInfo || {}; + const target = currentTargetInfo || {}; return { - name: player.name, isLocalPlayer: player.isLocalPlayer, id: target.id, @@ -846,7 +853,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla currentPairingId = targetInfo.id; - var promise = player.tryPair ? + const promise = player.tryPair ? player.tryPair(targetInfo) : Promise.resolve(); @@ -864,11 +871,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; self.getTargets = function () { - var promises = players.filter(displayPlayerIndividually).map(getPlayerTargets); + const promises = players.filter(displayPlayerIndividually).map(getPlayerTargets); return Promise.all(promises).then(function (responses) { return connectionManager.currentApiClient().getCurrentUser().then(function (user) { - var targets = []; + const targets = []; targets.push({ name: globalize.translate('HeaderMyDevice'), @@ -882,10 +889,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla user: user }); - for (var i = 0; i < responses.length; i++) { - var subTargets = responses[i]; + for (let i = 0; i < responses.length; i++) { + const subTargets = responses[i]; - for (var j = 0; j < subTargets.length; j++) { + for (let j = 0; j < subTargets.length; j++) { targets.push(subTargets[j]); } } @@ -900,7 +907,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var index = getPlayerData(player).subtitleStreamIndex; + const index = getPlayerData(player).subtitleStreamIndex; if (index == null || index === -1) { return null; @@ -929,7 +936,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function removeCurrentPlayer(player) { - var previousPlayer = self._currentPlayer; + const previousPlayer = self._currentPlayer; if (!previousPlayer || player.id === previousPlayer.id) { setCurrentPlayerInternal(null); @@ -937,8 +944,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function setCurrentPlayerInternal(player, targetInfo) { - var previousPlayer = self._currentPlayer; - var previousTargetInfo = currentTargetInfo; + const previousPlayer = self._currentPlayer; + const previousTargetInfo = currentTargetInfo; if (player && !targetInfo && player.isLocalPlayer) { targetInfo = createTarget(self, player); @@ -989,7 +996,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (self.isPlaying(player)) { - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); return playerData.streamInfo.mediaType === mediaType; } @@ -1028,7 +1035,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.canPlay = function (item) { - var itemType = item.Type; + const itemType = item.Type; if (itemType === 'PhotoAlbum' || itemType === 'MusicGenre' || itemType === 'Season' || itemType === 'Series' || itemType === 'BoxSet' || itemType === 'MusicAlbum' || itemType === 'MusicArtist' || itemType === 'Playlist') { return true; @@ -1058,12 +1065,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla player = player || self._currentPlayer; if (player) { - var current = self.getAspectRatio(player); + const current = self.getAspectRatio(player); - var supported = self.getSupportedAspectRatios(player); + const supported = self.getSupportedAspectRatios(player); - var index = -1; - for (var i = 0, length = supported.length; i < length; i++) { + let index = -1; + for (let i = 0, length = supported.length; i < length; i++) { if (supported[i].id === current) { index = i; break; @@ -1105,7 +1112,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - var brightnessOsdLoaded; + let brightnessOsdLoaded; self.setBrightness = function (val, player) { player = player || self._currentPlayer; @@ -1113,7 +1120,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (!brightnessOsdLoaded) { brightnessOsdLoaded = true; // TODO: Have this trigger an event instead to get the osd out of here - require(['brightnessOsd']); + import('brightnessOsd').then(); } player.setBrightness(val); } @@ -1169,11 +1176,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentMediaSource = self.currentMediaSource(player); - var mediaStreams = []; - var i; - var length; - for (i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { + const currentMediaSource = self.currentMediaSource(player); + const mediaStreams = []; + for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { if (currentMediaSource.MediaStreams[i].Type === 'Audio') { mediaStreams.push(currentMediaSource.MediaStreams[i]); } @@ -1184,16 +1189,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentStreamIndex = self.getAudioStreamIndex(player); - var indexInList = -1; - for (i = 0, length = mediaStreams.length; i < length; i++) { + const currentStreamIndex = self.getAudioStreamIndex(player); + let indexInList = -1; + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Index === currentStreamIndex) { indexInList = i; break; } } - var nextIndex = indexInList + 1; + let nextIndex = indexInList + 1; if (nextIndex >= mediaStreams.length) { nextIndex = 0; } @@ -1213,11 +1218,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentMediaSource = self.currentMediaSource(player); - var mediaStreams = []; - var i; - var length; - for (i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { + const currentMediaSource = self.currentMediaSource(player); + const mediaStreams = []; + for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) { if (currentMediaSource.MediaStreams[i].Type === 'Subtitle') { mediaStreams.push(currentMediaSource.MediaStreams[i]); } @@ -1228,16 +1231,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var currentStreamIndex = self.getSubtitleStreamIndex(player); - var indexInList = -1; - for (i = 0, length = mediaStreams.length; i < length; i++) { + const currentStreamIndex = self.getSubtitleStreamIndex(player); + let indexInList = -1; + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Index === currentStreamIndex) { indexInList = i; break; } } - var nextIndex = indexInList + 1; + let nextIndex = indexInList + 1; if (nextIndex >= mediaStreams.length) { nextIndex = -1; } @@ -1257,12 +1260,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function isAudioStreamSupported(mediaSource, index, deviceProfile) { - var mediaStream; - var i; - var length; - var mediaStreams = mediaSource.MediaStreams; + let mediaStream; + const mediaStreams = mediaSource.MediaStreams; - for (i = 0, length = mediaStreams.length; i < length; i++) { + for (let i = 0, length = mediaStreams.length; i < length; i++) { if (mediaStreams[i].Type === 'Audio' && mediaStreams[i].Index === index) { mediaStream = mediaStreams[i]; break; @@ -1273,13 +1274,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return false; } - var codec = (mediaStream.Codec || '').toLowerCase(); + const codec = (mediaStream.Codec || '').toLowerCase(); if (!codec) { return false; } - var profiles = deviceProfile.DirectPlayProfiles || []; + const profiles = deviceProfile.DirectPlayProfiles || []; return profiles.filter(function (p) { if (p.Type === 'Video') { @@ -1328,7 +1329,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla apiClient = connectionManager.currentApiClient(); } - var endpointInfo = apiClient.getSavedEndpointInfo() || {}; + const endpointInfo = apiClient.getSavedEndpointInfo() || {}; return appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType); } @@ -1339,16 +1340,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.getMaxStreamingBitrate(); } - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); if (playerData.maxStreamingBitrate) { return playerData.maxStreamingBitrate; } - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var currentItem = self.currentItem(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; + const currentItem = self.currentItem(player); - var apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); + const apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); return getSavedMaxStreamingBitrate(apiClient, mediaType); }; @@ -1358,12 +1359,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.enableAutomaticBitrateDetection(); } - var playerData = getPlayerData(player); - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var currentItem = self.currentItem(player); + const playerData = getPlayerData(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; + const currentItem = self.currentItem(player); - var apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); - var endpointInfo = apiClient.getSavedEndpointInfo() || {}; + const apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient(); + const endpointInfo = apiClient.getSavedEndpointInfo() || {}; return appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType); }; @@ -1374,13 +1375,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.setMaxStreamingBitrate(options); } - var apiClient = connectionManager.getApiClient(self.currentItem(player).ServerId); + const apiClient = connectionManager.getApiClient(self.currentItem(player).ServerId); apiClient.getEndpointInfo().then(function (endpointInfo) { - var playerData = getPlayerData(player); - var mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; + const playerData = getPlayerData(player); + const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null; - var promise; + let promise; if (options.enableAutomaticBitrateDetection) { appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType, true); promise = apiClient.detectBitrate(true); @@ -1473,17 +1474,17 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.setSubtitleStreamIndex(index); } - var currentStream = getCurrentSubtitleStream(player); + const currentStream = getCurrentSubtitleStream(player); - var newStream = getSubtitleStream(player, index); + const newStream = getSubtitleStream(player, index); if (!currentStream && !newStream) { return; } - var selectedTrackElementIndex = -1; + let selectedTrackElementIndex = -1; - var currentPlayMethod = self.playMethod(player); + const currentPlayMethod = self.playMethod(player); if (currentStream && !newStream) { if (getDeliveryMethod(currentStream) === 'Encode' || (getDeliveryMethod(currentStream) === 'Embed' && currentPlayMethod === 'Transcode')) { @@ -1520,30 +1521,30 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla getPlayerData(player).subtitleStreamIndex = index; }; - self.supportSubtitleOffset = function(player) { + self.supportSubtitleOffset = function (player) { player = player || self._currentPlayer; return player && 'setSubtitleOffset' in player; }; - self.enableShowingSubtitleOffset = function(player) { + self.enableShowingSubtitleOffset = function (player) { player = player || self._currentPlayer; player.enableShowingSubtitleOffset(); }; - self.disableShowingSubtitleOffset = function(player) { + self.disableShowingSubtitleOffset = function (player) { player = player || self._currentPlayer; if (player.disableShowingSubtitleOffset) { player.disableShowingSubtitleOffset(); } }; - self.isShowingSubtitleOffsetEnabled = function(player) { + self.isShowingSubtitleOffsetEnabled = function (player) { player = player || self._currentPlayer; return player.isShowingSubtitleOffsetEnabled(); }; - self.isSubtitleStreamExternal = function(index, player) { - var stream = getSubtitleStream(player, index); + self.isSubtitleStreamExternal = function (index, player) { + const stream = getSubtitleStream(player, index); return stream ? getDeliveryMethod(stream) === 'External' : false; }; @@ -1554,15 +1555,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - self.getPlayerSubtitleOffset = function(player) { + self.getPlayerSubtitleOffset = function (player) { player = player || self._currentPlayer; if (player.getSubtitleOffset) { return player.getSubtitleOffset(); } }; - self.canHandleOffsetOnCurrentSubtitle = function(player) { - var index = self.getSubtitleStreamIndex(player); + self.canHandleOffsetOnCurrentSubtitle = function (player) { + const index = self.getSubtitleStreamIndex(player); return index !== -1 && self.isSubtitleStreamExternal(index, player); }; @@ -1591,7 +1592,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } } - var ticks = getCurrentTicks(player) + offsetTicks; + const ticks = getCurrentTicks(player) + offsetTicks; return this.seek(ticks, player); }; @@ -1601,9 +1602,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); - var currentSrc = (playerData.streamInfo.url || '').toLowerCase(); + const currentSrc = (playerData.streamInfo.url || '').toLowerCase(); if (currentSrc.indexOf('.m3u8') !== -1) { return true; @@ -1613,7 +1614,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.seekable(); } - var isPlayMethodTranscode = self.playMethod(player) === 'Transcode'; + const isPlayMethodTranscode = self.playMethod(player) === 'Transcode'; if (isPlayMethodTranscode) { return false; @@ -1630,37 +1631,35 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla params = params || {}; - var liveStreamId = getPlayerData(player).streamInfo.liveStreamId; - var lastMediaInfoQuery = getPlayerData(player).streamInfo.lastMediaInfoQuery; + const liveStreamId = getPlayerData(player).streamInfo.liveStreamId; + const lastMediaInfoQuery = getPlayerData(player).streamInfo.lastMediaInfoQuery; - var playSessionId = self.playSessionId(player); + const playSessionId = self.playSessionId(player); - var currentItem = self.currentItem(player); + const currentItem = self.currentItem(player); player.getDeviceProfile(currentItem, { - isRetry: params.EnableDirectPlay === false - }).then(function (deviceProfile) { - var audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; - var subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; + const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; + const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; - var currentMediaSource = self.currentMediaSource(player); - var apiClient = connectionManager.getApiClient(currentItem.ServerId); + let currentMediaSource = self.currentMediaSource(player); + const apiClient = connectionManager.getApiClient(currentItem.ServerId); if (ticks) { ticks = parseInt(ticks); } - var maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); + const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); - var currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions(); + const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions(); getPlaybackInfo(player, apiClient, currentItem, deviceProfile, maxBitrate, ticks, true, currentMediaSource.Id, audioStreamIndex, subtitleStreamIndex, liveStreamId, params.EnableDirectPlay, params.EnableDirectStream, params.AllowVideoStreamCopy, params.AllowAudioStreamCopy).then(function (result) { if (validatePlaybackInfoResult(self, result)) { currentMediaSource = result.MediaSources[0]; - var streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks); + const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks); streamInfo.fullscreen = currentPlayOptions.fullscreen; streamInfo.lastMediaInfoQuery = lastMediaInfoQuery; @@ -1680,14 +1679,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function changeStreamToUrl(apiClient, player, playSessionId, streamInfo, newPositionTicks) { - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = true; if (playerData.streamInfo && playSessionId) { apiClient.stopActiveEncodings(playSessionId).then(function () { // Stop the first transcoding afterwards because the player may still send requests to the original url - var afterSetSrc = function () { + const afterSetSrc = function () { apiClient.stopActiveEncodings(playSessionId); }; setSrcIntoPlayer(apiClient, player, streamInfo).then(afterSetSrc, afterSetSrc); @@ -1699,7 +1698,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla function setSrcIntoPlayer(apiClient, player, streamInfo) { return player.play(streamInfo).then(function () { - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = false; playerData.streamInfo = streamInfo; @@ -1708,7 +1707,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla sendProgressUpdate(player, 'timeupdate'); }, function (e) { - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.isChangingStream = false; onPlaybackError.call(player, e, { @@ -1726,12 +1725,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); } - var firstItem = items[0]; - var promise; + const firstItem = items[0]; + let promise; - var serverId = firstItem.ServerId; + const serverId = firstItem.ServerId; - var queryOptions = options.queryOptions || {}; + const queryOptions = options.queryOptions || {}; if (firstItem.Type === 'Program') { promise = getItemsForPlayback(serverId, { @@ -1759,9 +1758,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla SortBy: options.shuffle ? 'Random' : 'SortName', MediaTypes: 'Photo,Video' }).then(function (result) { - var items = result.Items; + const items = result.Items; - var index = items.map(function (i) { + let index = items.map(function (i) { return i.Id; }).indexOf(firstItem.Id); @@ -1782,7 +1781,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla SortBy: options.shuffle ? 'Random' : 'SortName', MediaTypes: 'Photo,Video', Limit: 1000 - }); } else if (firstItem.Type === 'MusicGenre') { promise = getItemsForPlayback(serverId, { @@ -1794,18 +1792,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); } else if (firstItem.IsFolder) { promise = getItemsForPlayback(serverId, mergePlaybackQueries({ - ParentId: firstItem.Id, Filters: 'IsNotFolder', Recursive: true, // These are pre-sorted SortBy: options.shuffle ? 'Random' : (['BoxSet'].indexOf(firstItem.Type) === -1 ? 'SortName' : null), MediaTypes: 'Audio,Video' - }, queryOptions)); } else if (firstItem.Type === 'Episode' && items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) { promise = new Promise(function (resolve, reject) { - var apiClient = connectionManager.getApiClient(firstItem.ServerId); + const apiClient = connectionManager.getApiClient(firstItem.ServerId); apiClient.getCurrentUser().then(function (user) { if (!user.Configuration.EnableNextEpisodeAutoPlay || !firstItem.SeriesId) { @@ -1818,9 +1814,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla IsMissing: false, UserId: apiClient.getCurrentUserId(), Fields: 'Chapters' - }).then(function (episodesResult) { - var foundItem = false; + let foundItem = false; episodesResult.Items = episodesResult.Items.filter(function (e) { if (foundItem) { return true; @@ -1875,9 +1870,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return getItemsForPlayback(options.serverId, { - Ids: options.ids.join(',') - }).then(function (result) { return translateItemsForPlayback(result.Items, options).then(function (items) { return playWithIntros(items, options); @@ -1893,7 +1886,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (!player.name) { throw new Error('player name cannot be null'); } - var state = playerStates[player.name]; + let state = playerStates[player.name]; if (!state) { playerStates[player.name] = {}; @@ -1917,7 +1910,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla item = item || self.currentItem(player); mediaSource = mediaSource || self.currentMediaSource(player); - var state = { + const state = { PlayState: {} }; @@ -1975,13 +1968,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var mediaSource = self.currentMediaSource(player); + const mediaSource = self.currentMediaSource(player); if (mediaSource && mediaSource.RunTimeTicks) { return mediaSource.RunTimeTicks; } - var playerDuration = player.duration(); + let playerDuration = player.duration(); if (playerDuration) { playerDuration *= 10000; @@ -1995,9 +1988,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var playerTime = Math.floor(10000 * (player || self._currentPlayer).currentTime()); + let playerTime = Math.floor(10000 * (player).currentTime()); - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; if (streamInfo) { playerTime += getPlayerData(player).streamInfo.transcodingOffsetTicks || 0; } @@ -2009,8 +2002,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.getCurrentTicks = getCurrentTicks; function playOther(items, options, user) { - var playStartIndex = options.startIndex || 0; - var player = getPlayer(items[playStartIndex], options); + const playStartIndex = options.startIndex || 0; + const player = getPlayer(items[playStartIndex], options); loading.hide(); @@ -2020,8 +2013,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function playWithIntros(items, options, user) { - var playStartIndex = options.startIndex || 0; - var firstItem = items[playStartIndex]; + let playStartIndex = options.startIndex || 0; + let firstItem = items[playStartIndex]; // If index was bad, reset it if (!firstItem) { @@ -2039,11 +2032,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return playOther(items, options, user); } - var apiClient = connectionManager.getApiClient(firstItem.ServerId); + const apiClient = connectionManager.getApiClient(firstItem.ServerId); return getIntros(firstItem, apiClient, options).then(function (introsResult) { - var introItems = introsResult.Items; - var introPlayOptions; + const introItems = introsResult.Items; + let introPlayOptions; firstItem.playOptions = truncatePlayOptions(options); @@ -2099,9 +2092,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } // TODO: This should be the media type requested, not the original media type - var mediaType = item.MediaType; + const mediaType = item.MediaType; - var onBitrateDetectionFailure = function () { + const onBitrateDetectionFailure = function () { return playAfterBitrateDetect(getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn); }; @@ -2109,7 +2102,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return onBitrateDetectionFailure(); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); apiClient.getEndpointInfo().then(function (endpointInfo) { if ((mediaType === 'Video' || mediaType === 'Audio') && appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType)) { return apiClient.detectBitrate().then(function (bitrate) { @@ -2125,7 +2118,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onInterceptorRejection() { - var player = self._currentPlayer; + const player = self._currentPlayer; if (player) { destroyPlayer(player); @@ -2143,7 +2136,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla function runInterceptors(item, playOptions) { return new Promise(function (resolve, reject) { - var interceptors = pluginManager.ofType('preplayintercept'); + const interceptors = pluginManager.ofType('preplayintercept'); interceptors.sort(function (a, b) { return (a.order || 0) - (b.order || 0); @@ -2156,7 +2149,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla loading.hide(); - var options = Object.assign({}, playOptions); + const options = Object.assign({}, playOptions); options.mediaType = item.MediaType; options.item = item; @@ -2171,7 +2164,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var interceptor = interceptors[index]; + const interceptor = interceptors[index]; interceptor.intercept(options).then(function () { runNextPrePlay(interceptors, index + 1, options, resolve, reject); @@ -2194,12 +2187,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn) { - var startPosition = playOptions.startPositionTicks; + const startPosition = playOptions.startPositionTicks; - var player = getPlayer(item, playOptions); - var activePlayer = self._currentPlayer; + const player = getPlayer(item, playOptions); + const activePlayer = self._currentPlayer; - var promise; + let promise; if (activePlayer) { // TODO: if changing players within the same playlist, this will cause nextItem to be null @@ -2211,7 +2204,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (!isServerItem(item) || item.MediaType === 'Book') { return promise.then(function () { - var streamInfo = createStreamInfoFromUrlItem(item); + const streamInfo = createStreamInfoFromUrlItem(item); streamInfo.fullscreen = playOptions.fullscreen; getPlayerData(player).isChangingStream = false; return player.play(streamInfo).then(function () { @@ -2226,13 +2219,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return Promise.all([promise, player.getDeviceProfile(item)]).then(function (responses) { - var deviceProfile = responses[1]; + const deviceProfile = responses[1]; - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); - var mediaSourceId = playOptions.mediaSourceId; - var audioStreamIndex = playOptions.audioStreamIndex; - var subtitleStreamIndex = playOptions.subtitleStreamIndex; + const mediaSourceId = playOptions.mediaSourceId; + const audioStreamIndex = playOptions.audioStreamIndex; + const subtitleStreamIndex = playOptions.subtitleStreamIndex; if (player && !enableLocalPlaylistManagement(player)) { return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, maxBitrate, apiClient, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playOptions.startIndex); @@ -2242,7 +2235,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla playOptions.items = null; return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) { - var streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); + const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); streamInfo.fullscreen = playOptions.fullscreen; @@ -2270,14 +2263,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.getPlaybackInfo = function (item, options) { options = options || {}; - var startPosition = options.startPositionTicks || 0; - var mediaType = options.mediaType || item.MediaType; - var player = getPlayer(item, options); - var apiClient = connectionManager.getApiClient(item.ServerId); + const startPosition = options.startPositionTicks || 0; + const mediaType = options.mediaType || item.MediaType; + const player = getPlayer(item, options); + const apiClient = connectionManager.getApiClient(item.ServerId); // Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate return apiClient.getEndpointInfo().then(function () { - var maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); + const maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); return player.getDeviceProfile(item).then(function (deviceProfile) { return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) { @@ -2289,15 +2282,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.getPlaybackMediaSources = function (item, options) { options = options || {}; - var startPosition = options.startPositionTicks || 0; - var mediaType = options.mediaType || item.MediaType; + const startPosition = options.startPositionTicks || 0; + const mediaType = options.mediaType || item.MediaType; // TODO: Remove the true forceLocalPlayer hack - var player = getPlayer(item, options, true); - var apiClient = connectionManager.getApiClient(item.ServerId); + const player = getPlayer(item, options, true); + const apiClient = connectionManager.getApiClient(item.ServerId); // Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate return apiClient.getEndpointInfo().then(function () { - var maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); + const maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType); return player.getDeviceProfile(item).then(function (deviceProfile) { return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, false, null, null, null, null).then(function (playbackInfoResult) { @@ -2308,16 +2301,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }; function createStreamInfo(apiClient, type, item, mediaSource, startPosition) { - var mediaUrl; - var contentType; - var transcodingOffsetTicks = 0; - var playerStartPositionTicks = startPosition; - var liveStreamId = mediaSource.LiveStreamId; + let mediaUrl; + let contentType; + let transcodingOffsetTicks = 0; + const playerStartPositionTicks = startPosition; + const liveStreamId = mediaSource.LiveStreamId; - var playMethod = 'Transcode'; + let playMethod = 'Transcode'; - var mediaSourceContainer = (mediaSource.Container || '').toLowerCase(); - var directOptions; + const mediaSourceContainer = (mediaSource.Container || '').toLowerCase(); + let directOptions; if (type === 'Video' || type === 'Audio') { contentType = getMimeType(type.toLowerCase(), mediaSourceContainer); @@ -2346,7 +2339,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla directOptions.LiveStreamId = mediaSource.LiveStreamId; } - var prefix = type === 'Video' ? 'Videos' : 'Audio'; + const prefix = type === 'Video' ? 'Videos' : 'Audio'; mediaUrl = apiClient.getUrl(prefix + '/' + item.Id + '/stream.' + mediaSourceContainer, directOptions); playMethod = 'DirectStream'; @@ -2375,7 +2368,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla playMethod = 'DirectPlay'; } - var resultInfo = { + const resultInfo = { url: mediaUrl, mimeType: contentType, transcodingOffsetTicks: transcodingOffsetTicks, @@ -2392,7 +2385,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla title: item.Name }; - var backdropUrl = backdropImageUrl(apiClient, item, {}); + const backdropUrl = backdropImageUrl(apiClient, item, {}); if (backdropUrl) { resultInfo.backdropUrl = backdropUrl; } @@ -2401,19 +2394,19 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getTextTracks(apiClient, item, mediaSource) { - var subtitleStreams = mediaSource.MediaStreams.filter(function (s) { + const subtitleStreams = mediaSource.MediaStreams.filter(function (s) { return s.Type === 'Subtitle'; }); - var textStreams = subtitleStreams.filter(function (s) { + const textStreams = subtitleStreams.filter(function (s) { return s.DeliveryMethod === 'External'; }); - var tracks = []; + const tracks = []; - for (var i = 0, length = textStreams.length; i < length; i++) { - var textStream = textStreams[i]; - var textStreamUrl; + for (let i = 0, length = textStreams.length; i < length; i++) { + const textStream = textStreams[i]; + let textStreamUrl; if (itemHelper.isLocalItem(item)) { textStreamUrl = textStream.Path; @@ -2460,7 +2453,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function getPlayer(item, playOptions, forceLocalPlayers) { - var serverItem = isServerItem(item); + const serverItem = isServerItem(item); return getAutomaticPlayers(self, forceLocalPlayers).filter(function (p) { if (p.canPlayMediaType(item.MediaType)) { if (serverItem) { @@ -2483,11 +2476,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.setCurrentPlaylistItem(playlistItemId); } - var newItem; - var newItemIndex; - var playlist = self._playQueueManager.getPlaylist(); + let newItem; + let newItemIndex; + const playlist = self._playQueueManager.getPlaylist(); - for (var i = 0, length = playlist.length; i < length; i++) { + for (let i = 0, length = playlist.length; i < length; i++) { if (playlist[i].PlaylistItemId === playlistItemId) { newItem = playlist[i]; newItemIndex = i; @@ -2496,7 +2489,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (newItem) { - var newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); playInternal(newItem, newItemPlayOptions, function () { setPlaylistState(newItem.PlaylistItemId, newItemIndex); @@ -2514,18 +2507,19 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.removeFromPlaylist(playlistItemIds); } - var removeResult = self._playQueueManager.removeFromPlaylist(playlistItemIds); + const removeResult = self._playQueueManager.removeFromPlaylist(playlistItemIds); if (removeResult.result === 'empty') { return self.stop(player); } - var isCurrentIndex = removeResult.isCurrentIndex; + const isCurrentIndex = removeResult.isCurrentIndex; events.trigger(player, 'playlistitemremove', [ { playlistItemIds: playlistItemIds - }]); + } + ]); if (isCurrentIndex) { return self.setCurrentPlaylistItem(self._playQueueManager.getPlaylist()[0].PlaylistItemId, player); @@ -2540,7 +2534,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.movePlaylistItem(playlistItemId, newIndex); } - var moveResult = self._playQueueManager.movePlaylistItem(playlistItemId, newIndex); + const moveResult = self._playQueueManager.movePlaylistItem(playlistItemId, newIndex); if (moveResult.result === 'noop') { return; @@ -2550,7 +2544,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla { playlistItemId: moveResult.playlistItemId, newIndex: moveResult.newIndex - }]); + } + ]); }; self.getCurrentPlaylistIndex = function (player) { @@ -2587,12 +2582,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.nextTrack(); } - var newItemInfo = self._playQueueManager.getNextItemInfo(); + const newItemInfo = self._playQueueManager.getNextItemInfo(); if (newItemInfo) { console.debug('playing next track'); - var newItemPlayOptions = newItemInfo.item.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItemInfo.item.playOptions || getDefaultPlayOptions(); playInternal(newItemInfo.item, newItemPlayOptions, function () { setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index); @@ -2606,13 +2601,13 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.previousTrack(); } - var newIndex = self.getCurrentPlaylistIndex(player) - 1; + const newIndex = self.getCurrentPlaylistIndex(player) - 1; if (newIndex >= 0) { - var playlist = self._playQueueManager.getPlaylist(); - var newItem = playlist[newIndex]; + const playlist = self._playQueueManager.getPlaylist(); + const newItem = playlist[newIndex]; if (newItem) { - var newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); + const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions(); newItemPlayOptions.startPositionTicks = 0; playInternal(newItem, newItemPlayOptions, function () { @@ -2648,9 +2643,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return getItemsForPlayback(options.serverId, { - Ids: options.ids.join(',') - }).then(function (result) { return translateItemsForPlayback(result.Items, options).then(function (items) { // TODO: Handle options.startIndex for photos @@ -2678,10 +2671,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return; } - var queueDirectToPlayer = player && !enableLocalPlaylistManagement(player); + const queueDirectToPlayer = player && !enableLocalPlaylistManagement(player); if (queueDirectToPlayer) { - var apiClient = connectionManager.getApiClient(items[0].ServerId); + const apiClient = connectionManager.getApiClient(items[0].ServerId); player.getDeviceProfile(items[0]).then(function (profile) { setStreamUrls(items, profile, self.getMaxStreamingBitrate(player), apiClient, 0).then(function () { @@ -2705,7 +2698,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlayerProgressInterval() { - var player = this; + const player = this; sendProgressUpdate(player, 'timeupdate'); } @@ -2729,7 +2722,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla setCurrentPlayerInternal(player); - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.streamInfo = streamInfo; @@ -2744,10 +2737,10 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self._playNextAfterEnded = true; - var isFirstItem = playOptions.isFirstItem; - var fullscreen = playOptions.fullscreen; + const isFirstItem = playOptions.isFirstItem; + const fullscreen = playOptions.fullscreen; - var state = self.getPlayerState(player, streamInfo.item, streamInfo.mediaSource); + const state = self.getPlayerState(player, streamInfo.item, streamInfo.mediaSource); reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart'); @@ -2763,22 +2756,22 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackStartedFromSelfManagingPlayer(e, item, mediaSource) { - var player = this; + const player = this; setCurrentPlayerInternal(player); - var playOptions = item.playOptions || getDefaultPlayOptions(); - var isFirstItem = playOptions.isFirstItem; - var fullscreen = playOptions.fullscreen; + const playOptions = item.playOptions || getDefaultPlayOptions(); + const isFirstItem = playOptions.isFirstItem; + const fullscreen = playOptions.fullscreen; playOptions.isFirstItem = false; - var playerData = getPlayerData(player); + const playerData = getPlayerData(player); playerData.streamInfo = {}; - var streamInfo = playerData.streamInfo; + const streamInfo = playerData.streamInfo; streamInfo.playbackStartTimeTicks = new Date().getTime() * 10000; - var state = self.getPlayerState(player, item, mediaSource); + const state = self.getPlayerState(player, item, mediaSource); reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart'); @@ -2794,15 +2787,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackStoppedFromSelfManagingPlayer(e, playerStopInfo) { - var player = this; + const player = this; stopPlaybackProgressTimer(player); - var state = self.getPlayerState(player, playerStopInfo.item, playerStopInfo.mediaSource); + const state = self.getPlayerState(player, playerStopInfo.item, playerStopInfo.mediaSource); - var nextItem = playerStopInfo.nextItem; - var nextMediaType = playerStopInfo.nextMediaType; + const nextItem = playerStopInfo.nextItem; + const nextMediaType = playerStopInfo.nextMediaType; - var playbackStopInfo = { + const playbackStopInfo = { player: player, state: state, nextItem: (nextItem ? nextItem.item : null), @@ -2811,7 +2804,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla state.NextMediaType = nextMediaType; - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; // only used internally as a safeguard to avoid reporting other events to the server after playback stopped streamInfo.ended = true; @@ -2827,8 +2820,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(player, 'playbackstop', [state]); events.trigger(self, 'playbackstop', [playbackStopInfo]); - var nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); - var newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; + const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); + const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; if (newPlayer !== player) { destroyPlayer(player); @@ -2838,7 +2831,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) { // mediadecodeerror, medianotsupported, network, servererror - if (streamInfo.mediaSource.SupportsTranscoding && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy)) { return true; } @@ -2847,46 +2839,44 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackError(e, error) { - var player = this; + const player = this; error = error || {}; // network // mediadecodeerror // medianotsupported - var errorType = error.type; + const errorType = error.type; console.debug('playbackmanager playback error type: ' + (errorType || '')); - var streamInfo = error.streamInfo || getPlayerData(player).streamInfo; + const streamInfo = error.streamInfo || getPlayerData(player).streamInfo; if (streamInfo) { - var currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1; - var currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1; + const currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1; + const currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1; // Auto switch to transcoding if (enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy)) { - var startTime = getCurrentTicks(player) || streamInfo.playerStartPositionTicks; + const startTime = getCurrentTicks(player) || streamInfo.playerStartPositionTicks; changeStream(player, startTime, { - // force transcoding EnableDirectPlay: false, EnableDirectStream: false, AllowVideoStreamCopy: false, AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null - }); return; } } - var displayErrorCode = 'NoCompatibleStream'; + const displayErrorCode = 'NoCompatibleStream'; onPlaybackStopped.call(player, e, displayErrorCode); } function onPlaybackStopped(e, displayErrorCode) { - var player = this; + const player = this; if (getPlayerData(player).isChangingStream) { return; @@ -2895,15 +2885,15 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla stopPlaybackProgressTimer(player); // User clicked stop or content ended - var state = self.getPlayerState(player); - var data = getPlayerData(player); - var streamInfo = data.streamInfo; + const state = self.getPlayerState(player); + const data = getPlayerData(player); + const streamInfo = data.streamInfo; - var nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null; + const nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null; - var nextMediaType = (nextItem ? nextItem.item.MediaType : null); + const nextMediaType = (nextItem ? nextItem.item.MediaType : null); - var playbackStopInfo = { + const playbackStopInfo = { player: player, state: state, nextItem: (nextItem ? nextItem.item : null), @@ -2932,8 +2922,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla events.trigger(player, 'playbackstop', [state]); events.trigger(self, 'playbackstop', [playbackStopInfo]); - var nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); - var newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; + const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions(); + const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null; if (newPlayer !== player) { destroyPlayer(player); @@ -2951,12 +2941,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackChanging(activePlayer, newPlayer, newItem) { - var state = self.getPlayerState(activePlayer); + const state = self.getPlayerState(activePlayer); - var serverId = self.currentItem(activePlayer).ServerId; + const serverId = self.currentItem(activePlayer).ServerId; // User started playing something new while existing content is playing - var promise; + let promise; stopPlaybackProgressTimer(activePlayer); unbindStopped(activePlayer); @@ -2993,47 +2983,47 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } function onPlaybackTimeUpdate(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'timeupdate'); } function onPlaybackPause(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'pause'); } function onPlaybackUnpause(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'unpause'); } function onPlaybackVolumeChange(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'volumechange'); } function onRepeatModeChange(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'repeatmodechange'); } function onShuffleQueueModeChange() { - var player = this; + const player = this; sendProgressUpdate(player, 'shufflequeuemodechange'); } function onPlaylistItemMove(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemmove', true); } function onPlaylistItemRemove(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemremove', true); } function onPlaylistItemAdd(e) { - var player = this; + const player = this; sendProgressUpdate(player, 'playlistitemadd', true); } @@ -3110,12 +3100,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla throw new Error('player cannot be null'); } - var state = self.getPlayerState(player); + const state = self.getPlayerState(player); if (state.NowPlayingItem) { - var serverId = state.NowPlayingItem.ServerId; + const serverId = state.NowPlayingItem.ServerId; - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; if (streamInfo && streamInfo.started && !streamInfo.ended) { reportPlayback(self, state, player, reportPlaylist, serverId, 'reportPlaybackProgress', progressEventName); @@ -3134,7 +3124,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla streamInfo.lastMediaInfoQuery = new Date().getTime(); - var apiClient = connectionManager.getApiClient(serverId); + const apiClient = connectionManager.getApiClient(serverId); if (!apiClient.isMinServerVersion('3.2.70.7')) { return; @@ -3144,12 +3134,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla mediaSource.MediaStreams = info.MediaStreams; events.trigger(player, 'mediastreamschange'); }, function () { - }); } self.onAppClose = function () { - var player = this._currentPlayer; + const player = this._currentPlayer; // Try to report playback stopped before the app closes if (player && this.isPlaying(player)) { @@ -3158,89 +3147,80 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - self.playbackStartTime = function (player) { - player = player || this._currentPlayer; + self.playbackStartTime = function (player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) { return player.playbackStartTime(); } - var streamInfo = getPlayerData(player).streamInfo; + const streamInfo = getPlayerData(player).streamInfo; return streamInfo ? streamInfo.playbackStartTimeTicks : null; }; if (apphost.supports('remotecontrol')) { - require(['serverNotifications'], function (serverNotifications) { + import('serverNotifications').then(({ default: serverNotifications }) => { events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self)); events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self)); }); } } - PlaybackManager.prototype.getCurrentPlayer = function () { + getCurrentPlayer() { return this._currentPlayer; - }; + } - PlaybackManager.prototype.currentTime = function (player) { - player = player || this._currentPlayer; + currentTime(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) { return player.currentTime(); } return this.getCurrentTicks(player); - }; - - PlaybackManager.prototype.nextItem = function (player) { - player = player || this._currentPlayer; + } + nextItem(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.nextItem(); } - var nextItem = this._playQueueManager.getNextItemInfo(); + const nextItem = this._playQueueManager.getNextItemInfo(); if (!nextItem || !nextItem.item) { return Promise.reject(); } - var apiClient = connectionManager.getApiClient(nextItem.item.ServerId); + const apiClient = connectionManager.getApiClient(nextItem.item.ServerId); return apiClient.getItem(apiClient.getCurrentUserId(), nextItem.item.Id); - }; + } - PlaybackManager.prototype.canQueue = function (item) { + canQueue(item) { if (item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { return this.canQueueMediaType('Audio'); } return this.canQueueMediaType(item.MediaType); - }; + } - PlaybackManager.prototype.canQueueMediaType = function (mediaType) { + canQueueMediaType(mediaType) { if (this._currentPlayer) { return this._currentPlayer.canPlayMediaType(mediaType); } return false; - }; - - PlaybackManager.prototype.isMuted = function (player) { - player = player || this._currentPlayer; + } + isMuted(player = this._currentPlayer) { if (player) { return player.isMuted(); } return false; - }; - - PlaybackManager.prototype.setMute = function (mute, player) { - player = player || this._currentPlayer; + } + setMute(mute, player = this._currentPlayer) { if (player) { player.setMute(mute); } - }; + } - PlaybackManager.prototype.toggleMute = function (mute, player) { - player = player || this._currentPlayer; + toggleMute(mute, player = this._currentPlayer) { if (player) { if (player.toggleMute) { player.toggleMute(); @@ -3248,29 +3228,28 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla player.setMute(!player.isMuted()); } } - }; + } - PlaybackManager.prototype.toggleDisplayMirroring = function () { + toggleDisplayMirroring() { this.enableDisplayMirroring(!this.enableDisplayMirroring()); - }; + } - PlaybackManager.prototype.enableDisplayMirroring = function (enabled) { + enableDisplayMirroring(enabled) { if (enabled != null) { - var val = enabled ? '1' : '0'; + const val = enabled ? '1' : '0'; appSettings.set('displaymirror', val); return; } return (appSettings.get('displaymirror') || '') !== '0'; - }; + } - PlaybackManager.prototype.nextChapter = function (player) { - player = player || this._currentPlayer; - var item = this.currentItem(player); + nextChapter(player = this._currentPlayer) { + const item = this.currentItem(player); - var ticks = this.getCurrentTicks(player); + const ticks = this.getCurrentTicks(player); - var nextChapter = (item.Chapters || []).filter(function (i) { + const nextChapter = (item.Chapters || []).filter(function (i) { return i.StartPositionTicks > ticks; })[0]; @@ -3279,13 +3258,12 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { this.nextTrack(player); } - }; + } - PlaybackManager.prototype.previousChapter = function (player) { - player = player || this._currentPlayer; - var item = this.currentItem(player); + previousChapter(player = this._currentPlayer) { + const item = this.currentItem(player); - var ticks = this.getCurrentTicks(player); + let ticks = this.getCurrentTicks(player); // Go back 10 seconds ticks -= 100000000; @@ -3295,7 +3273,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla ticks = Math.max(ticks, 0); } - var previousChapters = (item.Chapters || []).filter(function (i) { + const previousChapters = (item.Chapters || []).filter(function (i) { return i.StartPositionTicks <= ticks; }); @@ -3304,63 +3282,55 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } else { this.previousTrack(player); } - }; - - PlaybackManager.prototype.fastForward = function (player) { - player = player || this._currentPlayer; + } + fastForward(player = this._currentPlayer) { if (player.fastForward != null) { player.fastForward(userSettings.skipForwardLength()); return; } // Go back 15 seconds - var offsetTicks = userSettings.skipForwardLength() * 10000; + const offsetTicks = userSettings.skipForwardLength() * 10000; this.seekRelative(offsetTicks, player); - }; - - PlaybackManager.prototype.rewind = function (player) { - player = player || this._currentPlayer; + } + rewind(player = this._currentPlayer) { if (player.rewind != null) { player.rewind(userSettings.skipBackLength()); return; } // Go back 15 seconds - var offsetTicks = 0 - (userSettings.skipBackLength() * 10000); + const offsetTicks = 0 - (userSettings.skipBackLength() * 10000); this.seekRelative(offsetTicks, player); - }; + } - PlaybackManager.prototype.seekPercent = function (percent, player) { - player = player || this._currentPlayer; - - var ticks = this.duration(player) || 0; + seekPercent(percent, player = this._currentPlayer) { + let ticks = this.duration(player) || 0; percent /= 100; ticks *= percent; this.seek(parseInt(ticks), player); - }; + } - PlaybackManager.prototype.seekMs = function (ms, player) { - player = player || this._currentPlayer; - - var ticks = ms * 10000; + seekMs(ms, player = this._currentPlayer) { + const ticks = ms * 10000; this.seek(ticks, player); - }; + } - PlaybackManager.prototype.playTrailers = function (item) { - var player = this._currentPlayer; + playTrailers(item) { + const player = this._currentPlayer; if (player && player.playTrailers) { return player.playTrailers(item); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); - var instance = this; + const instance = this; if (item.LocalTrailerCount) { return apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(function (result) { @@ -3369,7 +3339,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }); }); } else { - var remoteTrailers = item.RemoteTrailers || []; + const remoteTrailers = item.RemoteTrailers || []; if (!remoteTrailers.length) { return Promise.reject(); @@ -3387,17 +3357,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla }) }); } - }; + } - PlaybackManager.prototype.getSubtitleUrl = function (textStream, serverId) { - var apiClient = connectionManager.getApiClient(serverId); + getSubtitleUrl(textStream, serverId) { + const apiClient = connectionManager.getApiClient(serverId); return !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl; - }; + } - PlaybackManager.prototype.stop = function (player) { + stop(player) { player = player || this._currentPlayer; - if (player) { if (enableLocalPlaylistManagement(player)) { this._playNextAfterEnded = false; @@ -3408,11 +3377,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return Promise.resolve(); - }; - - PlaybackManager.prototype.getBufferedRanges = function (player) { - player = player || this._currentPlayer; + } + getBufferedRanges(player = this._currentPlayer) { if (player) { if (player.getBufferedRanges) { return player.getBufferedRanges(); @@ -3420,11 +3387,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } return []; - }; - - PlaybackManager.prototype.playPause = function (player) { - player = player || this._currentPlayer; + } + playPause(player = this._currentPlayer) { if (player) { if (player.playPause) { return player.playPause(); @@ -3436,115 +3401,105 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return this.pause(player); } } - }; - - PlaybackManager.prototype.paused = function (player) { - player = player || this._currentPlayer; + } + paused(player = this._currentPlayer) { if (player) { return player.paused(); } - }; - - PlaybackManager.prototype.pause = function (player) { - player = player || this._currentPlayer; + } + pause(player = this._currentPlayer) { if (player) { player.pause(); } - }; - - PlaybackManager.prototype.unpause = function (player) { - player = player || this._currentPlayer; + } + unpause(player = this._currentPlayer) { if (player) { player.unpause(); } - }; + } - PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) { + setPlaybackRate(value, player = this._currentPlayer) { if (player && player.setPlaybackRate) { player.setPlaybackRate(value); } - }; + } - PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) { + getPlaybackRate(player = this._currentPlayer) { if (player && player.getPlaybackRate) { return player.getPlaybackRate(); } return null; - }; + } - PlaybackManager.prototype.instantMix = function (item, player) { - player = player || this._currentPlayer; + instantMix(item, player = this._currentPlayer) { if (player && player.instantMix) { return player.instantMix(item); } - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); - var options = {}; + const options = {}; options.UserId = apiClient.getCurrentUserId(); options.Limit = 200; - var instance = this; + const instance = this; apiClient.getInstantMixFromItem(item.Id, options).then(function (result) { instance.play({ items: result.Items }); }); - }; + } - PlaybackManager.prototype.shuffle = function (shuffleItem, player) { - player = player || this._currentPlayer; + shuffle(shuffleItem, player = this._currentPlayer) { if (player && player.shuffle) { return player.shuffle(shuffleItem); } return this.play({ items: [shuffleItem], shuffle: true }); - }; + } - PlaybackManager.prototype.audioTracks = function (player) { - player = player || this._currentPlayer; + audioTracks(player = this._currentPlayer) { if (player.audioTracks) { - var result = player.audioTracks(); + const result = player.audioTracks(); if (result) { return result; } } - var mediaSource = this.currentMediaSource(player); + const mediaSource = this.currentMediaSource(player); - var mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = (mediaSource || {}).MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Audio'; }); - }; + } - PlaybackManager.prototype.subtitleTracks = function (player) { - player = player || this._currentPlayer; + subtitleTracks(player = this._currentPlayer) { if (player.subtitleTracks) { - var result = player.subtitleTracks(); + const result = player.subtitleTracks(); if (result) { return result; } } - var mediaSource = this.currentMediaSource(player); + const mediaSource = this.currentMediaSource(player); - var mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = (mediaSource || {}).MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Subtitle'; }); - }; + } - PlaybackManager.prototype.getSupportedCommands = function (player) { + getSupportedCommands(player) { player = player || this._currentPlayer || { isLocalPlayer: true }; if (player.isLocalPlayer) { - var list = [ + const list = [ 'GoHome', 'GoToSettings', 'VolumeUp', @@ -3590,45 +3545,45 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return list; } - var info = this.getPlayerInfo(); + const info = this.getPlayerInfo(); return info ? info.supportedCommands : []; - }; + } - PlaybackManager.prototype.setRepeatMode = function (value, player = this._currentPlayer) { + setRepeatMode(value, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.setRepeatMode(value); } this._playQueueManager.setRepeatMode(value); events.trigger(player, 'repeatmodechange'); - }; + } - PlaybackManager.prototype.getRepeatMode = function (player = this._currentPlayer) { + getRepeatMode(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.getRepeatMode(); } return this._playQueueManager.getRepeatMode(); - }; + } - PlaybackManager.prototype.setQueueShuffleMode = function (value, player = this._currentPlayer) { + setQueueShuffleMode(value, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.setQueueShuffleMode(value); } this._playQueueManager.setShuffleMode(value); events.trigger(player, 'shufflequeuemodechange'); - }; + } - PlaybackManager.prototype.getQueueShuffleMode = function (player = this._currentPlayer) { + getQueueShuffleMode(player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.getQueueShuffleMode(); } return this._playQueueManager.getShuffleMode(); - }; + } - PlaybackManager.prototype.toggleQueueShuffleMode = function (player = this._currentPlayer) { + toggleQueueShuffleMode(player = this._currentPlayer) { let currentvalue; if (player && !enableLocalPlaylistManagement(player)) { currentvalue = player.getQueueShuffleMode(); @@ -3646,23 +3601,23 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla this._playQueueManager.toggleShuffleMode(); } events.trigger(player, 'shufflequeuemodechange'); - }; + } - PlaybackManager.prototype.clearQueue = function (clearCurrentItem = false, player = this._currentPlayer) { + clearQueue(clearCurrentItem = false, player = this._currentPlayer) { if (player && !enableLocalPlaylistManagement(player)) { return player.clearQueue(clearCurrentItem); } this._playQueueManager.clearPlaylist(clearCurrentItem); events.trigger(player, 'playlistitemremove'); - }; + } - PlaybackManager.prototype.trySetActiveDeviceName = function (name) { + trySetActiveDeviceName(name) { name = normalizeName(name); - var instance = this; + const instance = this; instance.getTargets().then(function (result) { - var target = result.filter(function (p) { + const target = result.filter(function (p) { return normalizeName(p.name) === name; })[0]; @@ -3670,50 +3625,49 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla instance.trySetActivePlayer(target.playerName, target); } }); - }; + } - PlaybackManager.prototype.displayContent = function (options, player) { - player = player || this._currentPlayer; + displayContent(options, player = this._currentPlayer) { if (player && player.displayContent) { player.displayContent(options); } - }; + } - PlaybackManager.prototype.beginPlayerUpdates = function (player) { + beginPlayerUpdates(player) { if (player.beginPlayerUpdates) { player.beginPlayerUpdates(); } - }; + } - PlaybackManager.prototype.endPlayerUpdates = function (player) { + endPlayerUpdates(player) { if (player.endPlayerUpdates) { player.endPlayerUpdates(); } - }; + } - PlaybackManager.prototype.setDefaultPlayerActive = function () { + setDefaultPlayerActive() { this.setActivePlayer('localplayer'); - }; + } - PlaybackManager.prototype.removeActivePlayer = function (name) { - var playerInfo = this.getPlayerInfo(); + removeActivePlayer(name) { + const playerInfo = this.getPlayerInfo(); if (playerInfo) { if (playerInfo.name === name) { this.setDefaultPlayerActive(); } } - }; + } - PlaybackManager.prototype.removeActiveTarget = function (id) { - var playerInfo = this.getPlayerInfo(); + removeActiveTarget(id) { + const playerInfo = this.getPlayerInfo(); if (playerInfo) { if (playerInfo.id === id) { this.setDefaultPlayerActive(); } } - }; + } - PlaybackManager.prototype.sendCommand = function (cmd, player) { + sendCommand(cmd, player) { console.debug('MediaController received command: ' + cmd.Name); switch (cmd.Name) { case 'SetRepeatMode': @@ -3764,7 +3718,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } break; } - }; + } +} - return new PlaybackManager(); -}); +export default new PlaybackManager(); diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index f330e54156..ca5c475829 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -104,7 +104,7 @@ define(['dialogHelper', 'globalize', 'layoutManager', 'mediaInfo', 'apphost', 'c var apiClient = connectionManager.getApiClient(serverId); apiClient.getLiveTvProgram(programId, apiClient.getCurrentUserId()).then(function (item) { - playbackManager.play({ + playbackManager.default.play({ ids: [item.ChannelId], serverId: serverId }); diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 29e77debd1..b5ac4c9a8b 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -1,5 +1,8 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) { 'use strict'; + + playbackManager = playbackManager.default || playbackManager; + var showMuteButton = true; var showVolumeSlider = true; diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 56327312f6..60c458e234 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -5,6 +5,8 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) { 'use strict'; + browser = browser.default || browser; + /** * Name of transition event. */ diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index e825e8e0a2..203d88535f 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -1,6 +1,8 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, layoutManager, template, css) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + var player; var subtitleSyncSlider; var subtitleSyncTextField; diff --git a/src/components/themeMediaPlayer.js b/src/components/themeMediaPlayer.js index dd04f384aa..60f0986884 100644 --- a/src/components/themeMediaPlayer.js +++ b/src/components/themeMediaPlayer.js @@ -1,6 +1,8 @@ define(['playbackManager', 'userSettings', 'connectionManager'], function (playbackManager, userSettings, connectionManager) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + var currentOwnerId; var currentThemeIds = []; diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index de5c4cf186..5bc9386053 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -1,6 +1,7 @@ define(['dialogHelper', 'dom', 'layoutManager', 'connectionManager', 'globalize', 'loading', 'browser', 'focusManager', 'scrollHelper', 'material-icons', 'formDialogStyle', 'emby-button', 'emby-itemscontainer', 'cardStyle'], function (dialogHelper, dom, layoutManager, connectionManager, globalize, loading, browser, focusManager, scrollHelper) { 'use strict'; + browser = browser.default || browser; loading = loading.default || loading; var enableFocusTransform = !browser.slow && !browser.edge; diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index 1dbe71c632..5eb2a2ffcc 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -1,6 +1,8 @@ define(['dom', 'playbackManager', 'connectionManager', 'events', 'mediaInfo', 'layoutManager', 'focusManager', 'globalize', 'itemHelper', 'css!./upnextdialog', 'emby-button', 'flexStyles'], function (dom, playbackManager, connectionManager, events, mediaInfo, layoutManager, focusManager, globalize, itemHelper) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + var transitionEndEventName = dom.whichTransitionEvent(); function seriesImageUrl(item, options) { diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index d1b0df6fbb..9d64130cdb 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -1,12 +1,12 @@ -define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewContainer'], function (browser, dom, layoutManager) { - 'use strict'; +import 'css!components/viewManager/viewContainer'; +/* eslint-disable indent */ function setControllerClass(view, options) { if (options.controllerFactory) { return Promise.resolve(); } - var controllerUrl = view.getAttribute('data-controller'); + let controllerUrl = view.getAttribute('data-controller'); if (controllerUrl) { if (controllerUrl.indexOf('__plugin/') === 0) { @@ -14,7 +14,7 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta } controllerUrl = Dashboard.getConfigurationResourceUrl(controllerUrl); - return getRequirePromise([controllerUrl]).then(function (ControllerFactory) { + return import(controllerUrl).then((ControllerFactory) => { options.controllerFactory = ControllerFactory; }); } @@ -22,94 +22,85 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta return Promise.resolve(); } - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); - } - - function loadView(options) { + export function loadView(options) { if (!options.cancel) { - var selected = selectedPageIndex; - var previousAnimatable = selected === -1 ? null : allPages[selected]; - var pageIndex = selected + 1; + const selected = selectedPageIndex; + const previousAnimatable = selected === -1 ? null : allPages[selected]; + let pageIndex = selected + 1; if (pageIndex >= pageContainerCount) { pageIndex = 0; } - var isPluginpage = options.url.toLowerCase().indexOf('/configurationpage') !== -1; - var newViewInfo = normalizeNewView(options, isPluginpage); - var newView = newViewInfo.elem; - var modulesToLoad = []; + const isPluginpage = options.url.toLowerCase().indexOf('/configurationpage') !== -1; + const newViewInfo = normalizeNewView(options, isPluginpage); + const newView = newViewInfo.elem; - return new Promise(function (resolve) { - require(modulesToLoad, function () { - var currentPage = allPages[pageIndex]; + return new Promise((resolve) => { + const currentPage = allPages[pageIndex]; - if (currentPage) { - triggerDestroy(currentPage); - } + if (currentPage) { + triggerDestroy(currentPage); + } - var view = newView; + let view = newView; - if (typeof view == 'string') { - view = document.createElement('div'); - view.innerHTML = newView; - } + if (typeof view == 'string') { + view = document.createElement('div'); + view.innerHTML = newView; + } - view.classList.add('mainAnimatedPage'); + view.classList.add('mainAnimatedPage'); - if (currentPage) { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - mainAnimatedPages.removeChild(currentPage); - } else { - mainAnimatedPages.replaceChild(view, currentPage); - } + if (currentPage) { + if (newViewInfo.hasScript && window.$) { + mainAnimatedPages.removeChild(currentPage); + view = $(view).appendTo(mainAnimatedPages)[0]; } else { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - } else { - mainAnimatedPages.appendChild(view); - } + mainAnimatedPages.replaceChild(view, currentPage); + } + } else { + if (newViewInfo.hasScript && window.$) { + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.appendChild(view); + } + } + + if (options.type) { + view.setAttribute('data-type', options.type); + } + + const properties = []; + + if (options.fullscreen) { + properties.push('fullscreen'); + } + + if (properties.length) { + view.setAttribute('data-properties', properties.join(',')); + } + + allPages[pageIndex] = view; + setControllerClass(view, options).then(() => { + if (onBeforeChange) { + onBeforeChange(view, false, options); } - if (options.type) { - view.setAttribute('data-type', options.type); + beforeAnimate(allPages, pageIndex, selected); + selectedPageIndex = pageIndex; + currentUrls[pageIndex] = options.url; + + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, pageIndex); } - var properties = []; - - if (options.fullscreen) { - properties.push('fullscreen'); + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; } - if (properties.length) { - view.setAttribute('data-properties', properties.join(',')); - } - - allPages[pageIndex] = view; - setControllerClass(view, options).then(function () { - if (onBeforeChange) { - onBeforeChange(view, false, options); - } - - beforeAnimate(allPages, pageIndex, selected); - selectedPageIndex = pageIndex; - currentUrls[pageIndex] = options.url; - - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, pageIndex); - } - - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - resolve(view); - }); + resolve(view); }); }); } @@ -125,28 +116,28 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta html = replaceAll(html, '<\/script>--\x3e', '<\/script>'); } - var wrapper = document.createElement('div'); + const wrapper = document.createElement('div'); wrapper.innerHTML = html; return wrapper.querySelector('div[data-role="page"]'); } function normalizeNewView(options, isPluginpage) { - var viewHtml = options.view; + const viewHtml = options.view; if (viewHtml.indexOf('data-role="page"') === -1) { return viewHtml; } - var hasScript = viewHtml.indexOf(' { if (onBeforeChange) { onBeforeChange(view, true, options); } @@ -228,25 +219,28 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta view.dispatchEvent(new CustomEvent('viewdestroy', {})); } - function reset() { + export function reset() { allPages = []; currentUrls = []; mainAnimatedPages.innerHTML = ''; selectedPageIndex = -1; } - var onBeforeChange; - var mainAnimatedPages = document.querySelector('.mainAnimatedPages'); - var allPages = []; - var currentUrls = []; - var pageContainerCount = 3; - var selectedPageIndex = -1; + let onBeforeChange; + const mainAnimatedPages = document.querySelector('.mainAnimatedPages'); + let allPages = []; + let currentUrls = []; + const pageContainerCount = 3; + let selectedPageIndex = -1; reset(); mainAnimatedPages.classList.remove('hide'); - return { - loadView: loadView, - tryRestoreView: tryRestoreView, - reset: reset, - setOnBeforeChange: setOnBeforeChange - }; -}); + +/* eslint-enable indent */ + +export default { + loadView: loadView, + tryRestoreView: tryRestoreView, + reset: reset, + setOnBeforeChange: setOnBeforeChange +}; + diff --git a/src/controllers/list.js b/src/controllers/list.js index 7ccab39a8d..68e81f05dd 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -1,6 +1,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager', 'cardBuilder', 'loading', 'connectionManager', 'alphaNumericShortcuts', 'scroller', 'playbackManager', 'alphaPicker', 'emby-itemscontainer', 'emby-scroller'], function (globalize, listView, layoutManager, userSettings, focusManager, cardBuilder, loading, connectionManager, AlphaNumericShortcuts, scroller, playbackManager, AlphaPicker) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; loading = loading.default || loading; function getInitialLiveTvQuery(instance, params) { diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 4ffe7888cf..a633d654cd 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -1,6 +1,8 @@ define(['events', 'layoutManager', 'inputManager', 'userSettings', 'libraryMenu', 'mainTabsManager', 'cardBuilder', 'dom', 'imageLoader', 'playbackManager', 'globalize', 'emby-scroller', 'emby-itemscontainer', 'emby-tabs', 'emby-button'], function (events, layoutManager, inputManager, userSettings, libraryMenu, mainTabsManager, cardBuilder, dom, imageLoader, playbackManager, globalize) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + function enableScrollX() { return !layoutManager.desktop; } diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index d9c246b406..4733c617f3 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -1,6 +1,8 @@ define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, dom) { "use strict"; + browser = browser.default || browser; + return function (options) { function getTouches(e) { return e.changedTouches || e.targetTouches || e.touches; diff --git a/src/libraries/screensavermanager.js b/src/libraries/screensavermanager.js index b9d7082850..557b31e0f4 100644 --- a/src/libraries/screensavermanager.js +++ b/src/libraries/screensavermanager.js @@ -1,6 +1,8 @@ define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) { "use strict"; + playbackManager = playbackManager.default || playbackManager; + function getMinIdleTime() { // Returns the minimum amount of idle time required before the screen saver can be displayed //time units used Millisecond diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 645a8ea85c..c0cb3e557c 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -1,6 +1,8 @@ define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'scrollStyles'], function (browser, layoutManager, dom, focusManager, ResizeObserver) { 'use strict'; + browser = browser.default || browser; + /** * Return type of the value. * diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index a676f7a2b3..2741a6f0e3 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -1,6 +1,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', 'globalize', 'events', 'require', 'castSenderApiLoader'], function (appSettings, userSettings, playbackManager, connectionManager, globalize, events, require, castSenderApiLoader) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js var currentResolve; var currentReject; diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js index 89a18a96cc..fb1f745df3 100644 --- a/src/plugins/sessionPlayer/plugin.js +++ b/src/plugins/sessionPlayer/plugin.js @@ -1,6 +1,8 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'], function (playbackManager, events, serverNotifications, connectionManager) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + function getActivePlayerId() { var info = playbackManager.getPlayerInfo(); return info ? info.id : null; diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 382ffbed1a..5befcb1df5 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -1,6 +1,8 @@ define(['browser'], function (browser) { 'use strict'; + browser = browser.default || browser; + function canPlayH264(videoTestElement) { return !!(videoTestElement.canPlayType && videoTestElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')); } diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index d2a68cd6e3..bbe01276ba 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -1,6 +1,9 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncPlayManager', 'groupSelectionMenu', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncPlayManager, groupSelectionMenu, browser, globalize, imageHelper) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + browser = browser.default || browser; + function renderHeader() { var html = ''; html += '
'; diff --git a/src/scripts/mouseManager.js b/src/scripts/mouseManager.js index d801d82393..40253fb91c 100644 --- a/src/scripts/mouseManager.js +++ b/src/scripts/mouseManager.js @@ -1,10 +1,15 @@ -define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'dom'], function (inputManager, focusManager, browser, layoutManager, events, dom) { - 'use strict'; +import inputManager from 'inputManager'; +import focusManager from 'focusManager'; +import browser from 'browser'; +import layoutManager from 'layoutManager'; +import events from 'events'; +import dom from 'dom'; +/* eslint-disable indent */ - var self = {}; + const self = {}; - var lastMouseInputTime = new Date().getTime(); - var isMouseIdle; + let lastMouseInputTime = new Date().getTime(); + let isMouseIdle; function mouseIdleTime() { return new Date().getTime() - lastMouseInputTime; @@ -15,14 +20,14 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } function removeIdleClasses() { - var classList = document.body.classList; + const classList = document.body.classList; classList.remove('mouseIdle'); classList.remove('mouseIdle-tv'); } function addIdleClasses() { - var classList = document.body.classList; + const classList = document.body.classList; classList.add('mouseIdle'); @@ -31,7 +36,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } } - function showCursor() { + export function showCursor() { if (isMouseIdle) { isMouseIdle = false; removeIdleClasses(); @@ -39,7 +44,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } } - function hideCursor() { + export function hideCursor() { if (!isMouseIdle) { isMouseIdle = true; addIdleClasses(); @@ -47,17 +52,17 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } } - var lastPointerMoveData; + let lastPointerMoveData; function onPointerMove(e) { - var eventX = e.screenX; - var eventY = e.screenY; + const eventX = e.screenX; + const eventY = e.screenY; // if coord don't exist how could it move if (typeof eventX === 'undefined' && typeof eventY === 'undefined') { return; } - var obj = lastPointerMoveData; + const obj = lastPointerMoveData; if (!obj) { lastPointerMoveData = { x: eventX, @@ -81,11 +86,11 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } function onPointerEnter(e) { - var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); + const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); if (pointerType === 'mouse') { if (!isMouseIdle) { - var parent = focusManager.focusableParent(e.target); + const parent = focusManager.focusableParent(e.target); if (parent) { focusManager.focus(parent); } @@ -115,7 +120,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } } - var mouseInterval; + let mouseInterval; function startMouseInterval() { if (!mouseInterval) { mouseInterval = setInterval(onMouseInterval, 5000); @@ -123,7 +128,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd } function stopMouseInterval() { - var interval = mouseInterval; + const interval = mouseInterval; if (interval) { clearInterval(interval); @@ -167,8 +172,10 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd events.on(layoutManager, 'modechange', initMouse); - self.hideCursor = hideCursor; - self.showCursor = showCursor; +/* eslint-enable indent */ + +export default { + hideCursor, + showCursor +}; - return self; -}); diff --git a/src/scripts/routes.js b/src/scripts/routes.js index 1d735e3018..4094a2552f 100644 --- a/src/scripts/routes.js +++ b/src/scripts/routes.js @@ -1,18 +1,21 @@ -define([ - 'jQuery', - 'emby-button', - 'emby-input', - 'scripts/livetvcomponents', - 'paper-icon-button-light', - 'emby-itemscontainer', - 'emby-collapse', - 'emby-select', - 'livetvcss', - 'emby-checkbox', - 'emby-slider', - 'listViewStyle', - 'dashboardcss', - 'detailtablecss'], function () { +import 'emby-button'; +import 'emby-input'; +import 'scripts/livetvcomponents'; +import 'paper-icon-button-light'; +import 'emby-itemscontainer'; +import 'emby-collapse'; +import 'emby-select'; +import 'livetvcss'; +import 'emby-checkbox'; +import 'emby-slider'; +import 'listViewStyle'; +import 'dashboardcss'; +import 'detailtablecss'; + +/* eslint-disable indent */ + + console.groupCollapsed('defining core routes'); + function defineRoute(newRoute) { var path = newRoute.alias ? newRoute.alias : newRoute.path; console.debug('defining route: ' + path); @@ -20,8 +23,6 @@ define([ Emby.Page.addRoute(path, newRoute); } - console.debug('defining core routes'); - defineRoute({ alias: '/addserver.html', path: '/controllers/session/addServer/index.html', @@ -30,6 +31,7 @@ define([ startup: true, controller: 'session/addServer/index' }); + defineRoute({ alias: '/selectserver.html', path: '/controllers/session/selectServer/index.html', @@ -39,6 +41,7 @@ define([ controller: 'session/selectServer/index', type: 'selectserver' }); + defineRoute({ alias: '/login.html', path: '/controllers/session/login/index.html', @@ -48,6 +51,7 @@ define([ controller: 'session/login/index', type: 'login' }); + defineRoute({ alias: '/forgotpassword.html', path: '/controllers/session/forgotPassword/index.html', @@ -55,6 +59,7 @@ define([ startup: true, controller: 'session/forgotPassword/index' }); + defineRoute({ alias: '/forgotpasswordpin.html', path: '/controllers/session/redeemPassword/index.html', @@ -68,42 +73,41 @@ define([ alias: '/mypreferencesmenu.html', path: '/controllers/user/menu/index.html', autoFocus: false, - transition: 'fade', controller: 'user/menu/index' }); + defineRoute({ alias: '/myprofile.html', path: '/controllers/user/profile/index.html', autoFocus: false, - transition: 'fade', controller: 'user/profile/index' }); + defineRoute({ alias: '/mypreferencesdisplay.html', path: '/controllers/user/display/index.html', autoFocus: false, - transition: 'fade', controller: 'user/display/index' }); + defineRoute({ alias: '/mypreferenceshome.html', path: '/controllers/user/home/index.html', autoFocus: false, - transition: 'fade', controller: 'user/home/index' }); + defineRoute({ alias: '/mypreferencesplayback.html', path: '/controllers/user/playback/index.html', autoFocus: false, - transition: 'fade', controller: 'user/playback/index' }); + defineRoute({ alias: '/mypreferencessubtitles.html', path: '/controllers/user/subtitles/index.html', autoFocus: false, - transition: 'fade', controller: 'user/subtitles/index' }); @@ -113,42 +117,49 @@ define([ roles: 'admin', controller: 'dashboard/dashboard' }); + defineRoute({ path: '/dashboardgeneral.html', controller: 'dashboard/general', autoFocus: false, roles: 'admin' }); + defineRoute({ path: '/networking.html', autoFocus: false, roles: 'admin', controller: 'dashboard/networking' }); + defineRoute({ path: '/devices.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/devices' }); + defineRoute({ path: '/device.html', autoFocus: false, roles: 'admin', controller: 'dashboard/devices/device' }); + defineRoute({ path: '/dlnaprofile.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profile' }); + defineRoute({ path: '/dlnaprofiles.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/profiles' }); + defineRoute({ alias: '/addplugin.html', path: '/controllers/dashboard/plugins/add/index.html', @@ -156,52 +167,61 @@ define([ roles: 'admin', controller: 'dashboard/plugins/add/index' }); + defineRoute({ path: '/library.html', autoFocus: false, roles: 'admin', controller: 'dashboard/mediaLibrary' }); + defineRoute({ path: '/librarydisplay.html', autoFocus: false, roles: 'admin', controller: 'dashboard/librarydisplay' }); + defineRoute({ path: '/dlnasettings.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dlna/settings' }); + defineRoute({ path: '/edititemmetadata.html', controller: 'edititemmetadata', autoFocus: false }); + defineRoute({ path: '/encodingsettings.html', autoFocus: false, roles: 'admin', controller: 'dashboard/encodingsettings' }); + defineRoute({ path: '/log.html', roles: 'admin', controller: 'dashboard/logs' }); + defineRoute({ path: '/metadataimages.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadataImages' }); + defineRoute({ path: '/metadatanfo.html', autoFocus: false, roles: 'admin', controller: 'dashboard/metadatanfo' }); + defineRoute({ alias: '/notificationsetting.html', path: '/controllers/dashboard/notifications/notification/index.html', @@ -209,6 +229,7 @@ define([ roles: 'admin', controller: 'dashboard/notifications/notification/index' }); + defineRoute({ alias: '/notificationsettings.html', path: '/controllers/dashboard/notifications/notifications/index.html', @@ -216,12 +237,14 @@ define([ autoFocus: false, roles: 'admin' }); + defineRoute({ path: '/playbackconfiguration.html', autoFocus: false, roles: 'admin', controller: 'dashboard/playback' }); + defineRoute({ alias: '/availableplugins.html', path: '/controllers/dashboard/plugins/available/index.html', @@ -229,6 +252,7 @@ define([ roles: 'admin', controller: 'dashboard/plugins/available/index' }); + defineRoute({ alias: '/repositories.html', path: '/controllers/dashboard/plugins/repositories/index.html', @@ -241,67 +265,72 @@ define([ path: '/home.html', autoFocus: false, controller: 'home', - transition: 'fade', type: 'home' }); + defineRoute({ path: '/search.html', controller: 'searchpage' }); + defineRoute({ path: '/list.html', autoFocus: false, - controller: 'list', - transition: 'fade' + controller: 'list' }); + defineRoute({ alias: '/details', path: '/controllers/itemDetails/index.html', controller: 'itemDetails/index', - autoFocus: false, - transition: 'fade' + autoFocus: false }); + defineRoute({ path: '/livetv.html', controller: 'livetv/livetvsuggested', - autoFocus: false, - transition: 'fade' + autoFocus: false }); + defineRoute({ path: '/livetvguideprovider.html', autoFocus: false, roles: 'admin', controller: 'livetvguideprovider' }); + defineRoute({ path: '/livetvsettings.html', autoFocus: false, controller: 'livetvsettings' }); + defineRoute({ path: '/livetvstatus.html', autoFocus: false, roles: 'admin', controller: 'livetvstatus' }); + defineRoute({ path: '/livetvtuner.html', autoFocus: false, roles: 'admin', controller: 'livetvtuner' }); + defineRoute({ path: '/movies.html', autoFocus: false, - controller: 'movies/moviesrecommended', - transition: 'fade' + controller: 'movies/moviesrecommended' }); + defineRoute({ path: '/music.html', controller: 'music/musicrecommended', - autoFocus: false, - transition: 'fade' + autoFocus: false }); + defineRoute({ alias: '/installedplugins.html', path: '/controllers/dashboard/plugins/installed/index.html', @@ -309,41 +338,46 @@ define([ roles: 'admin', controller: 'dashboard/plugins/installed/index' }); + defineRoute({ path: '/scheduledtask.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtask' }); + defineRoute({ path: '/scheduledtasks.html', autoFocus: false, roles: 'admin', controller: 'dashboard/scheduledtasks/scheduledtasks' }); + defineRoute({ path: '/serveractivity.html', autoFocus: false, roles: 'admin', controller: 'dashboard/serveractivity' }); + defineRoute({ path: '/apikeys.html', autoFocus: false, roles: 'admin', controller: 'dashboard/apikeys' }); + defineRoute({ path: '/streamingsettings.html', autoFocus: false, roles: 'admin', controller: 'dashboard/streaming' }); + defineRoute({ path: '/tv.html', autoFocus: false, - controller: 'shows/tvrecommended', - transition: 'fade' + controller: 'shows/tvrecommended' }); defineRoute({ @@ -352,29 +386,34 @@ define([ roles: 'admin', controller: 'dashboard/users/useredit' }); + defineRoute({ path: '/userlibraryaccess.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userlibraryaccess' }); + defineRoute({ path: '/usernew.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/usernew' }); + defineRoute({ path: '/userparentalcontrol.html', autoFocus: false, roles: 'admin', controller: 'dashboard/users/userparentalcontrol' }); + defineRoute({ path: '/userpassword.html', autoFocus: false, controller: 'dashboard/users/userpasswordpage' }); + defineRoute({ path: '/userprofiles.html', autoFocus: false, @@ -389,6 +428,7 @@ define([ anonymous: true, controller: 'wizard/remote/index' }); + defineRoute({ alias: '/wizardfinish.html', path: '/controllers/wizard/finish/index.html', @@ -396,12 +436,14 @@ define([ anonymous: true, controller: 'wizard/finish/index' }); + defineRoute({ path: '/wizardlibrary.html', autoFocus: false, anonymous: true, controller: 'dashboard/mediaLibrary' }); + defineRoute({ alias: '/wizardsettings.html', path: '/controllers/wizard/settings/index.html', @@ -409,6 +451,7 @@ define([ anonymous: true, controller: 'wizard/settings/index' }); + defineRoute({ alias: '/wizardstart.html', path: '/controllers/wizard/start/index.html', @@ -416,6 +459,7 @@ define([ anonymous: true, controller: 'wizard/start/index' }); + defineRoute({ alias: '/wizarduser.html', path: '/controllers/wizard/user/index.html', @@ -427,7 +471,6 @@ define([ defineRoute({ alias: '/video', path: '/controllers/playback/video/index.html', - transition: 'fade', controller: 'playback/video/index', autoFocus: false, type: 'video-osd', @@ -435,16 +478,17 @@ define([ fullscreen: true, enableMediaControl: false }); + defineRoute({ alias: '/queue', path: '/controllers/playback/queue/index.html', controller: 'playback/queue/index', autoFocus: false, - transition: 'fade', fullscreen: true, supportsThemeMedia: true, enableMediaControl: false }); + defineRoute({ path: '/configurationpage', autoFocus: false, @@ -458,9 +502,13 @@ define([ isDefaultRoute: true, autoFocus: false }); + defineRoute({ path: '/index.html', autoFocus: false, isDefaultRoute: true }); -}); + + console.groupEnd('defining core routes'); + +/* eslint-enable indent */ diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 331a75329c..e5fb0bcd61 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -1,6 +1,8 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncPlayManager, events, inputManager, focusManager, appRouter) { 'use strict'; + playbackManager = playbackManager.default || playbackManager; + var serverNotifications = {}; function notifyApp() { diff --git a/src/strings/cs.json b/src/strings/cs.json index 5b29e83d79..3ca8676c1b 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -2,7 +2,7 @@ "AccessRestrictedTryAgainLater": "Přístup je v současné době omezen. Prosím zkuste to znovu později.", "Actor": "Herec", "Add": "Přidat", - "AddItemToCollectionHelp": "Přidat položky do kolekce jejich vyhledáním a použitím pravého tlačítka myši nebo klepnutím na tlačítko menu - přidat do sbírky.", + "AddItemToCollectionHelp": "Přidat položky do kolekce jejich vyhledáním a použitím pravého tlačítka myši nebo kliknutím na příslušnou položku v nabídce.", "AddToCollection": "Přidat do kolekce", "AddToPlayQueue": "Přidat do fronty k přehrání", "AddToPlaylist": "Přidat do playlistu", @@ -144,7 +144,7 @@ "Desktop": "PC", "DeviceAccessHelp": "Platí pouze pro zařízení, která mohou být jednoznačně identifikována. Těmto zařízením nebude bráněno v přístupu. Filtrování přístupu uživatelských zařízení bude bránit v užívání nových zařízení, dokud nebudou schváleny.", "DirectPlaying": "Přímé přehrání", - "DirectStreamHelp1": "Médium je kompatibilní se zařízením, pokud jde o rozlišení a typ média (H.264, AC3, atd.), ale je v nekompatibilním kontejneru (.mkv, .avi, .wmv, atd.). Video bude za běhu přebaleno, než bude streamováno do zařízení.", + "DirectStreamHelp1": "Médium je kompatibilní se zařízením, pokud jde o rozlišení a typ média (H.264, AC3, atd.), ale je v nekompatibilním kontejneru (.mkv, .avi, .wmv, atd.). Video bude za běhu přebaleno, než bude odesláno do zařízení.", "DirectStreaming": "Přímé streamování", "Director": "Režisér", "Disc": "Disk", @@ -235,7 +235,7 @@ "HeaderAlert": "Upozornění", "HeaderApiKey": "Klíč Api", "HeaderApiKeys": "Klíče API", - "HeaderApiKeysHelp": "Externí aplikace musí mít klíč k API, aby mohly komunikovat se serverem Jellyfin. Klíče jsou vydávány přihlášením k účtu Jellyfin nebo ruční žádostí o klíč.", + "HeaderApiKeysHelp": "Externí aplikace musí mít klíč k API, aby mohly komunikovat se serverem. Klíče jsou vydávány přihlášením k běžnému uživatelskému účtu nebo ruční žádostí o klíč.", "HeaderApp": "Aplikace", "HeaderAudioBooks": "Audio knihy", "HeaderAudioSettings": "Nastavení zvuku", @@ -341,7 +341,7 @@ "HeaderPreferredMetadataLanguage": "Preferovaný jazyk metadat", "HeaderProfile": "Profil", "HeaderProfileInformation": "Informace o profilu", - "HeaderProfileServerSettingsHelp": "Tyto hodnoty určují, jak se server Jellyfin bude zobrazovat v zařízení.", + "HeaderProfileServerSettingsHelp": "Tyto hodnoty určují, jak se server bude zobrazovat klientům.", "HeaderRecentlyPlayed": "Naposledy přehráváno", "HeaderRecordingOptions": "Nastavení nahrávání", "HeaderRecordingPostProcessing": "Následné zpracování nahrávek", @@ -358,13 +358,13 @@ "HeaderSecondsValue": "{0} sekund", "HeaderSelectCertificatePath": "Vyber cestu k certifikátu", "HeaderSelectMetadataPath": "Vyberte cestu k metadatům", - "HeaderSelectMetadataPathHelp": "Výběr nebo zadání cesty, kde chcete uložit metadata. Složka musí být zapisovatelná.", + "HeaderSelectMetadataPathHelp": "Procházejte nebo zadejte cestu, kde chcete uložit metadata. Složka musí být zapisovatelná.", "HeaderSelectPath": "Vybrat složku", "HeaderSelectServer": "Vyber Server", "HeaderSelectServerCachePath": "Vyber složku pro vyrovnávací paměť serveru", "HeaderSelectServerCachePathHelp": "Vyberte nebo zadejte složku vyrovnávací paměti souborů. Složka musí být zapisovatelná.", "HeaderSelectTranscodingPath": "Zvolte dočasnou složku pro překódovávání médií", - "HeaderSelectTranscodingPathHelp": "Vyberte nebo zadejte složku pro dočasné soubory překódování. Složka musí být zapisovatelná.", + "HeaderSelectTranscodingPathHelp": "Vyberte nebo zadejte složku pro soubory překódování. Složka musí být zapisovatelná.", "HeaderSendMessage": "Poslat zprávu", "HeaderSeries": "Seriál", "HeaderSeriesOptions": "Nastavení seriálu", @@ -410,8 +410,8 @@ "Home": "Domů", "Identify": "Identifikovat", "Images": "Obrázky", - "ImportFavoriteChannelsHelp": "Pokud je povoleno, jen kanály označené jako oblíbené budou importována na zařízení tuneru.", - "ImportMissingEpisodesHelp": "Informace o chybějících epizodách budou importovány do databáze Jellyfin a zobrazí se u sezón a seriálů. Skenování knihovny se tím může značně prodloužit.", + "ImportFavoriteChannelsHelp": "Jen kanály označené jako oblíbené na zařízení tuneru budou importovány.", + "ImportMissingEpisodesHelp": "Informace o chybějících epizodách budou importovány do databáze a zobrazí se u sezón a seriálů. Skenování knihovny se tím může značně prodloužit.", "InstallingPackage": "Instalace {0} (Verze {1})", "InstantMix": "Okamžité míchání", "ItemCount": "{0} položek", @@ -441,14 +441,14 @@ "LabelAppName": "Název aplikace", "LabelAppNameExample": "Příklad: Sickbeard, Sonarr", "LabelArtists": "Umělci:", - "LabelArtistsHelp": "Odděl pomocí ;", + "LabelArtistsHelp": "Více interpretů se odděluje pomocí středníku.", "LabelAudio": "Zvuk", "LabelAudioLanguagePreference": "Preferovaný jazyk zvuku:", "LabelBindToLocalNetworkAddress": "Vázat na místní síťovou adresu:", - "LabelBindToLocalNetworkAddressHelp": "Volitelné. Změní místní IP adresu, na kterou se váže server HTTP. Pokud je ponecháno prázdné, server bude svázán se všemi dostupnými adresami. Změna této hodnoty vyžaduje restartování serveru Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Změní místní IP adresu serveru HTTP. Pokud je ponecháno prázdné, server bude svázán se všemi dostupnými adresami. Změna této hodnoty vyžaduje restartování serveru Jellyfin.", "LabelBirthDate": "Datum narození:", "LabelBirthYear": "Rok narození:", - "LabelBlastMessageInterval": "Doba zobrazení zprávy (v sekundách)", + "LabelBlastMessageInterval": "Doba zobrazení zprávy", "LabelBlastMessageIntervalHelp": "Určuje dobu trvání v sekundách mezi zobrazením aktuálních zpráv.", "LabelCachePath": "Složka pro cache:", "LabelCachePathHelp": "Zadejte vlastní umístění pro serverové dočasné soubory, jako jsou obrázky. Ponechte prázdné, pokud chcete použít výchozí nastavení serveru.", @@ -461,7 +461,7 @@ "LabelCriticRating": "Hodnocení kritiků:", "LabelCurrentPassword": "Aktuální heslo:", "LabelCustomCss": "Vlastní CSS:", - "LabelCustomCssHelp": "Aplikovat vaše vlastní styly do webového rozhraní.", + "LabelCustomCssHelp": "Aplikovat vaše vlastní styly webového rozhraní.", "LabelCustomDeviceDisplayName": "Jméno pro zobrazení:", "LabelCustomDeviceDisplayNameHelp": "Nahradit vlastním názvem zobrazení nebo ponechte prázdné, aby název byl určen zařízením.", "LabelCustomRating": "Vlastní hodnocení:", @@ -495,16 +495,16 @@ "LabelEnableAutomaticPortMapHelp": "Automaticky zpřístupní port na vašem routeru pomocí technologie UPnP. Nemusí fungovat u některých routerů. Změny se projeví až po restartování serveru.", "LabelEnableBlastAliveMessages": "Vytroubit zprávu do světa", "LabelEnableBlastAliveMessagesHelp": "Tuto možnost povolte, pokud není server zjistitelný jinými UPnP zařízeními v síti.", - "LabelEnableDlnaClientDiscoveryInterval": "Čas pro vyhledání klienta (sekund)", + "LabelEnableDlnaClientDiscoveryInterval": "Interval pro vyhledání klienta", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Určuje interval mezi vyhledáváním SSDP, které Jellyfin provádí.", "LabelEnableDlnaDebugLogging": "Povolit DLNA protokolování (pro ladění)", "LabelEnableDlnaDebugLoggingHelp": "Vytváří velké soubory se záznamy a doporučuje se používat pouze pro potřeby odstraňování problémů.", "LabelEnableDlnaPlayTo": "Povolit DLNA přehrávání", - "LabelEnableDlnaPlayToHelp": "Umí detekovat zařízení v rámci vaší sítě a nabízí možnost jeho dálkového ovládání.", + "LabelEnableDlnaPlayToHelp": "Umí detekovat zařízení v rámci vaší sítě a nabízí možnost jejich dálkového ovládání.", "LabelEnableDlnaServer": "Povolit DLNA server", "LabelEnableDlnaServerHelp": "Umožňuje zařízením UPnP v síti procházet a přehrávat obsah.", "LabelEnableRealtimeMonitor": "Povolit sledování v reálném čase", - "LabelEnableRealtimeMonitorHelp": "Změny budou zpracovány okamžitě, v podporovaných souborových systémech.", + "LabelEnableRealtimeMonitorHelp": "V podporovaných souborových systémech budou změny zpracovány okamžitě.", "LabelEnableSingleImageInDidlLimit": "Limit na jednotlivé vložení obrázku", "LabelEnableSingleImageInDidlLimitHelp": "Některá zařízení nebudou videa zobrazovat správně, pokud je více obrazů uloženo v DIDL.", "LabelEndDate": "Datum ukončení:", @@ -521,14 +521,14 @@ "LabelFormat": "Formát:", "LabelFriendlyName": "Přívětivý název:", "LabelGroupMoviesIntoCollections": "Seskupit filmy do kolekcí", - "LabelGroupMoviesIntoCollectionsHelp": "Při zobrazení seznamů filmu, budou filmy patřící do kolekce, zobrazeny jako jedna položka.", + "LabelGroupMoviesIntoCollectionsHelp": "Při zobrazení seznamu filmů budou filmy v kolekci zobrazeny jako jedna položka.", "LabelH264Crf": "H264 kódování CRF:", "LabelEncoderPreset": "Přednastavení H264 kódování:", "LabelHardwareAccelerationType": "Hardwarová akcelerace:", "LabelHardwareAccelerationTypeHelp": "Hardwarová akcelerace vyžaduje další konfiguraci.", "LabelHomeScreenSectionValue": "Sekce domovské obrazovky {0}:", "LabelHttpsPort": "Lokální HTTPS port:", - "LabelHttpsPortHelp": "Číslo portu TCP, ke kterému by se měl HTTPS server Jellyfin připojit.", + "LabelHttpsPortHelp": "Číslo portu TCP serveru HTTPS.", "LabelIconMaxHeight": "Maximální výška ikon:", "LabelIconMaxHeightHelp": "Maximální rozlišení ikon nabízené prostřednictvím upnp:icon.", "LabelIconMaxWidth": "Maximální šířka ikon:", @@ -552,7 +552,7 @@ "LabelLanguage": "Jazyk:", "LabelLineup": "V pořadí:", "LabelLocalHttpServerPortNumber": "Lokální HTTP port:", - "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP, ke kterému by se měl HTTP server Jellyfin připojit.", + "LabelLocalHttpServerPortNumberHelp": "Číslo portu TCP HTTP serveru.", "LabelLockItemToPreventChanges": "Uzamknout položku pro zabránění budoucích změn", "LabelLoginDisclaimer": "Zřeknutí se zodpovědnosti při přihlášení:", "LabelLoginDisclaimerHelp": "Zpráva, která se zobrazí v dolní části přihlašovací stránky.", @@ -576,7 +576,7 @@ "LabelMetadataReaders": "Čtečky metadat:", "LabelMetadataReadersHelp": "Seřaďte své preferované lokální zdroje metadat dle priority. První nalezená data budou načtena.", "LabelMetadataSavers": "Střadatelé metadat:", - "LabelMetadataSaversHelp": "Vyberte formáty souborů pro uložení metadat.", + "LabelMetadataSaversHelp": "Vyberte formáty souborů, které chcete použít pro ukládání metadat.", "LabelMethod": "Metoda:", "LabelMinBackdropDownloadWidth": "Maximální šířka pro stažení pozadí:", "LabelMinResumeDuration": "Minimální doba trvání:", @@ -592,7 +592,7 @@ "LabelMovieCategories": "Filmové kategorie:", "LabelMoviePrefix": "Předpona filmu:", "LabelMoviePrefixHelp": "Pokud je v názvech filmů použita předpona, zadejte ji sem, aby ji server mohl správně zpracovat.", - "LabelMovieRecordingPath": "Složka pro nahrávání filmů (volitelné):", + "LabelMovieRecordingPath": "Umístění pro nahrávání filmů:", "LabelMusicStreamingTranscodingBitrate": "Datový tok pro překódování hudby:", "LabelMusicStreamingTranscodingBitrateHelp": "Zadejte maximální datový tok pro streamování hudby.", "LabelName": "Jméno:", @@ -605,7 +605,7 @@ "LabelNumber": "Číslo:", "LabelNumberOfGuideDays": "Počet dnů programového průvodce ke stažení:", "LabelNumberOfGuideDaysHelp": "Stažení více dnů televizního průvodce umožňuje naplánovat nahrávání na delší dobu dopředu, ale trvá déle. Automatické nastavení určí počet podle počtu kanálů.", - "LabelOptionalNetworkPath": "(Nepovinné) Sdílená síťová složka:", + "LabelOptionalNetworkPath": "Sdílená síťová složka:", "LabelOriginalAspectRatio": "Původní poměr stran:", "LabelOriginalTitle": "Originální název:", "LabelOverview": "Přehled:", @@ -645,7 +645,7 @@ "LabelRefreshMode": "Mód obnovy:", "LabelReleaseDate": "Datum vydání:", "LabelRemoteClientBitrateLimit": "Datový tok streamování do Internetu (Mbps):", - "LabelRuntimeMinutes": "Délka (v minutách):", + "LabelRuntimeMinutes": "Délka:", "LabelSaveLocalMetadata": "Uložit přebaly a metadata do složky s médii", "LabelSaveLocalMetadataHelp": "Povolíte-li uložení přebalů a metadat do složky s médii bude možné je jednoduše upravovat.", "LabelScheduledTaskLastRan": "Poslední spuštění {0}, zabralo {1}.", @@ -657,7 +657,7 @@ "LabelSelectVersionToInstall": "Vyber verzi k instalaci:", "LabelSendNotificationToUsers": "Odeslat oznámení pro:", "LabelSerialNumber": "Sériové číslo", - "LabelSeriesRecordingPath": "Složka pro nahrávání seriálů (volitelné):", + "LabelSeriesRecordingPath": "Umístění pro nahrávání seriálů:", "LabelServerHostHelp": "192.168.1.100:8096 nebo https://mujserver.cz", "LabelSkipBackLength": "Délka posunu zpět:", "LabelSkipForwardLength": "Délka posunu vpřed:", @@ -717,7 +717,7 @@ "LabelYoureDone": "Hotovo!", "LabelZipCode": "PSČ:", "LabelffmpegPath": "FFmpeg - cesta:", - "LabelffmpegPathHelp": "Cesta k souboru aplikace ffmpeg, nebo složka obsahující aplikaci ffmpeg.", + "LabelffmpegPathHelp": "Cesta k souboru aplikace ffmpeg nebo složka obsahující aplikaci ffmpeg.", "Large": "Velký", "LatestFromLibrary": "Nejnovější {0}", "LearnHowYouCanContribute": "Zjistěte, jak můžete přispět.", @@ -798,7 +798,7 @@ "MessageUnsetContentHelp": "Obsah je zobrazen pomocí prostých složek. Pro dosažení nejlepších výsledků pomocí správce metadat nastavte typy obsahu pod-složek.", "MessageYouHaveVersionInstalled": "V současné době máte instalovánu verzi {0}.", "MetadataManager": "Manažer metadat", - "MetadataSettingChangeHelp": "Změna nastavení metadat bude mít vliv na nový obsah, který bude přidáván. Chcete-li aktualizovat stávající obsah, otevřte obrazovku s detailem a klepněte na tlačítko Aktualizovat, nebo proveďte hromadnou aktualizaci pomocí správce metadat.", + "MetadataSettingChangeHelp": "Změna nastavení metadat bude mít vliv na obsah, který bude nově přidán v budoucnu. Chcete-li aktualizovat stávající obsah, otevřete obrazovku s podrobnostmi a klikněte na tlačítko Aktualizovat, nebo proveďte hromadnou aktualizaci pomocí správce metadat.", "MinutesAfter": "minut po", "MinutesBefore": "minut předem", "Mobile": "Mobilní", @@ -852,7 +852,7 @@ "OptionAuto": "Automaticky", "OptionAutomatic": "Automaticky", "OptionAutomaticallyGroupSeries": "Automatické sloučení k seriálu, které jsou ve více složkách", - "OptionAutomaticallyGroupSeriesHelp": "Pokud je povoleno, budou díly seriálu uložené ve více adresářích v této knihovně, automaticky sloučeny k jednomu seriálu.", + "OptionAutomaticallyGroupSeriesHelp": "Seriály uložené ve více složkách v této knihovně budou automaticky sloučeny do jednoho seriálu.", "OptionBlockBooks": "Knihy", "OptionBlockChannelContent": "Obsah internetového kanálu", "OptionBlockLiveTvChannels": "Televizní kanály", @@ -871,7 +871,7 @@ "OptionDatePlayed": "Datum přehrání", "OptionDescending": "Sestupně", "OptionDisableUser": "Zablokovat tohoto uživatele", - "OptionDisableUserHelp": "Pokud není povoleno, server nedovolí tomuto uživateli žádné připojení. Existující připojení bude okamžitě přerušeno.", + "OptionDisableUserHelp": "Server nedovolí tomuto uživateli žádné připojení. Existující připojení bude okamžitě přerušeno.", "OptionDislikes": "Nelíbí se", "OptionDisplayFolderView": "Zobrazit složku s originálním zobrazením složek médií", "OptionDisplayFolderViewHelp": "Zobrazte složky vedle vašich ostatních knihoven médií. To může být užitečné, pokud si přejete mít prosté zobrazení složky.", @@ -879,7 +879,7 @@ "OptionDownloadBackImage": "Zadek", "OptionDownloadDiscImage": "Disk", "OptionDownloadImagesInAdvance": "Stáhnout obrázky pokročilejším způsobem", - "OptionDownloadImagesInAdvanceHelp": "Ve výchozím nastavení se většina obrázků stahuje pouze na žádost aplikace Jellyfin. Povolením této možnosti dojde ke stažení všech obrázků předem současně s importem nových médií. Může způsobit výrazně delší skenování knihoven.", + "OptionDownloadImagesInAdvanceHelp": "Ve výchozím nastavení se většina obrázků stahuje pouze na žádost klienta. Povolením této možnosti dojde ke stažení všech obrázků předem současně s importem nových médií. Může způsobit výrazně delší skenování knihoven.", "OptionDownloadMenuImage": "Nabídka", "OptionDownloadPrimaryImage": "Primární", "OptionDownloadThumbImage": "Miniatura", @@ -911,7 +911,7 @@ "OptionHlsSegmentedSubtitles": "Segmentované titulky HLS", "OptionHomeVideos": "Fotky", "OptionIgnoreTranscodeByteRangeRequests": "Ignorovat požadavky na překódování rozsahy bajtů", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Pokud je povoleno, budou tyto žádosti nadále plněny, ale budou ignorovány hlavičky bytových rozsahů.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Tyto žádosti budou nadále plněny, ale budou ignorovány hlavičky bajtových rozsahů.", "OptionImdbRating": "Hodnocení IMDb", "OptionLikes": "Líbí se", "OptionMissingEpisode": "Chybějící episody", @@ -923,9 +923,9 @@ "OptionOnInterval": "V intervalu", "OptionParentalRating": "Rodičovské hodnocení", "OptionPlainStorageFolders": "Zobrazit všechny složky jako obyčejné složky pro ukládání", - "OptionPlainStorageFoldersHelp": "Je-li povoleno, všechny složky jsou zastoupeny v DIDL jako \"object.container.storageFolder\" místo specifičtějšího typu, jako je například \"object.container.person.musicArtist\".", + "OptionPlainStorageFoldersHelp": "Všechny složky jsou prezentovány v DIDL jako \"object.container.storageFolder\" místo konkrétnějšího typu, například \"object.container.person.musicArtist\".", "OptionPlainVideoItems": "Zobrazit všechna videa jako s obyčejné video položky", - "OptionPlainVideoItemsHelp": "Je-li povoleno, všechna videa jsou prezentovány v DIDL jako \"object.item.videoItem\" místo specifičtějšího typu, jako je například \"object.item.videoItem.movie\".", + "OptionPlainVideoItemsHelp": "Všechna videa jsou prezentována v DIDL jako \"object.item.videoItem\" místo konkrétnějšího typu, například \"object.item.videoItem.movie\".", "OptionPlayCount": "Počet přehrání", "OptionPlayed": "Shlédnuto", "OptionPremiereDate": "Datum premiéry", @@ -1008,7 +1008,7 @@ "RecordingScheduled": "Plán nahrávání.", "Recordings": "Nahrávky", "Refresh": "Obnovit", - "RefreshDialogHelp": "Metadata se aktualizují na základě nastavení a internetových služeb, které jsou povoleny na nástěnce serveru Jellyfin.", + "RefreshDialogHelp": "Metadata se aktualizují na základě nastavení a internetových služeb, které jsou povoleny na nástěnce.", "RefreshMetadata": "Obnovit metadata", "RefreshQueued": "Obnovení zařazeno.", "ReleaseDate": "Datum vydání", @@ -1205,7 +1205,7 @@ "Depressed": "Vytlačené", "Descending": "Klesající", "DetectingDevices": "Hledání zařízení", - "DirectStreamHelp2": "Přímé streamování souboru používá velmi malý výkon bez ztráty kvality videa.", + "DirectStreamHelp2": "Přímé streamování souboru vyžaduje velmi malý výkon téměř bez ztráty kvality videa.", "Directors": "Režiséři", "Disabled": "Vypnuto", "DisplayInMyMedia": "Zobrazit na domovské obrazovce", @@ -1237,7 +1237,7 @@ "HeaderFavoriteVideos": "Oblíbená videa", "HeaderFetcherSettings": "Nastavení načítání", "HeaderImageOptions": "Volby obrázku", - "HeaderKodiMetadataHelp": "Chcete-li povolit nebo zakázat Nfo metadata, upravte nastavení knihovny v sekci ukládání metadat.", + "HeaderKodiMetadataHelp": "Chcete-li povolit nebo zakázat metadata v souborech NFO, upravte nastavení knihovny v sekci ukládání metadat.", "HeaderLiveTV": "Televize", "HeaderLiveTv": "Televize", "HeaderLiveTvTunerSetup": "Nastavení televizního tuneru", @@ -1305,7 +1305,7 @@ "MediaInfoStreamTypeAudio": "Audio", "MediaInfoStreamTypeData": "Data", "MediaInfoStreamTypeVideo": "Video", - "AuthProviderHelp": "Vyberte poskytovatele ověřování, který bude použit k ověření hesla tohoto uživatele.", + "AuthProviderHelp": "Vyberte poskytovatele ověření, který bude použit k ověření hesla tohoto uživatele.", "HeaderFavoriteMovies": "Oblíbená videa", "HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteEpisodes": "Oblíbené epizody", @@ -1313,7 +1313,7 @@ "HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteSongs": "Oblíbená hudba", "LabelAuthProvider": "Poskytovatel ověření:", - "LabelServerNameHelp": "Tento název bude použit k identifikaci serveru a bude výchozí pro název počítače serveru.", + "LabelServerNameHelp": "Tento název bude použit k identifikaci serveru a ve výchozím nastavení bude použit název hostitele serveru.", "LabelPasswordResetProvider": "Poskytovatel obnovy hesla:", "LabelServerName": "Název serveru:", "LabelTranscodePath": "Cesta pro překódování:", @@ -1339,7 +1339,7 @@ "OnlyImageFormats": "Pouze obrazové formáty (VOBSUB, PGS, SUB, atd.)", "Option3D": "3D", "OptionAlbum": "Album", - "OptionAllowMediaPlaybackTranscodingHelp": "Omezení přístupu k překódování může způsobit selhání přehrávání v aplikacích Jellyfin kvůli nepodporovaným formátům médií.", + "OptionAllowMediaPlaybackTranscodingHelp": "Omezení přístupu k překódování může způsobit selhání přehrávání v klientech kvůli nepodporovaným formátům médií.", "OptionAllowSyncTranscoding": "Povolit stahování a synchronizaci médií, které vyžaduje překódování", "OptionBluray": "Blu-ray", "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", @@ -1357,7 +1357,7 @@ "OptionProtocolHls": "Přímý přenos z internetu", "OptionProtocolHttp": "HTTP", "OptionRequirePerfectSubtitleMatchHelp": "Vyžadování dokonalé shody filtruje titulky tak, aby obsahovaly pouze ty, které byly testovány a ověřeny s vaším přesným videosouborem. Zrušení zaškrtnutí tohoto políčka zvýší pravděpodobnost stahování titulků, ale zvýší pravděpodobnost chybného nebo nesprávného textu titulků.", - "PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit, když tento uživatel o něj požádá", + "PasswordResetProviderHelp": "Zvolte poskytovatele resetování hesla, který bude použit při žádosti tohoto uživatele o resetování hesla.", "MessagePluginInstalled": "Zásuvný modul byl úspěšně nainstalován. Server Jellyfin bude nutné restartovat, aby se změny projevily.", "PreferEmbeddedTitlesOverFileNames": "Preferovat vložené názvy nad názvy souborů", "PreferEmbeddedTitlesOverFileNamesHelp": "Toto určuje výchozí název zobrazení, pokud nejsou k dispozici žádná metadata z internetu nebo místní metadata.", @@ -1499,7 +1499,7 @@ "LabelStable": "Stabilní", "LabelChromecastVersion": "Verze Chromecastu", "ApiKeysCaption": "Seznam povolených API klíčů", - "LabelEnableHttpsHelp": "Umožní serveru naslouchat na určeném portu HTTPS. K fungování je nutné nakonfigurovat i platný certifikát.", + "LabelEnableHttpsHelp": "Naslouchání na uvedeném portu HTTPS. K fungování je nutné nakonfigurovat i platný certifikát.", "LabelEnableHttps": "Povolit HTTPS", "HeaderServerAddressSettings": "Nastavení adresy serveru", "HeaderRemoteAccessSettings": "Nastavení vzdáleného přístupu", @@ -1543,7 +1543,7 @@ "EnableDetailsBanner": "Obrázek detailu", "ShowMore": "Zobrazit více", "ShowLess": "Zobrazit méně", - "EnableBlurHashHelp": "Nenačtené obrázky budou zobrazeny pomocí neurčitých zástupných obrázků", + "EnableBlurHashHelp": "Obrázky, které se ještě načítají, budou zobrazeny pomocí jedinečných zástupných obrázků.", "EnableBlurHash": "Povolit zástupné obrázky", "ButtonCast": "Přehrát v zařízení", "ButtonSyncPlay": "SyncPlay", diff --git a/src/strings/de.json b/src/strings/de.json index b5c52588c9..db1ef10305 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -158,8 +158,8 @@ "DetectingDevices": "Suche Geräte", "DeviceAccessHelp": "Dies wird nur auf Geräte angewandt die eindeutig identifiziert werden können und verhindert nicht den Web-Zugriff. Gefilterter Zugriff auf Geräte verhindert die Nutzung neuer Geräte solange, bis der Zugriff für diese freigegeben wird.", "DirectPlaying": "Direktes Abspielen", - "DirectStreamHelp1": "Das Medium ist mit dem Abspielgerät kompatibel bzgl. Auflösung und Codecs (H.264, AC3, etc.), besitzt jedoch ein inkompatibles Containerformat (mkv, avi, wmv, etc.). Das Video wird in Echtzeit neuverpackt bevor es zum Abspielgerät gestreamt wird.", - "DirectStreamHelp2": "Direktes Streaming von Dateien benötigt sehr wenig Rechenleistung ohne Verlust der Videoqualität.", + "DirectStreamHelp1": "Das Medium ist mit dem Abspielgerät kompatibel bzgl. Auflösung und Codecs (H.264, AC3, etc.), besitzt jedoch ein inkompatibles Containerformat (mkv, avi, wmv, etc.). Das Video wird in Echtzeit neu verpackt bevor es zum Abspielgerät gesendet wird.", + "DirectStreamHelp2": "Direkt Stream benötigt sehr wenig Rechenleistung mit minimalem Verlust der Videoqualität.", "DirectStreaming": "Direktes Streaming", "Director": "Regisseur", "Directors": "Regisseure", @@ -263,7 +263,7 @@ "HeaderAllowMediaDeletionFrom": "Erlaube Medienlöschung von", "HeaderApiKey": "API-Schlüssel", "HeaderApiKeys": "API-Schlüssel", - "HeaderApiKeysHelp": "Externe Applikationen benötigen einen API-Schlüssel, um mit dem Jellyfin-Server zu kommunizieren. API-Schlüssel werden beim Anmelden mit einem Jellyfin-Konto oder durch eine manuelle Freigabe vergeben.", + "HeaderApiKeysHelp": "Externe Applikationen benötigen einen API-Schlüssel, um mit dem Server zu kommunizieren. API-Schlüssel werden beim Anmelden mit einem normalen Benutzerkonto oder durch eine manuelle Freigabe vergeben.", "HeaderAppearsOn": "Erscheint auf", "HeaderAudioBooks": "Hörbücher", "HeaderAudioSettings": "Audioeinstellungen", @@ -333,7 +333,7 @@ "HeaderItems": "Inhalte", "HeaderKeepRecording": "Aufnahme behalten", "HeaderKeepSeries": "Serie behalten", - "HeaderKodiMetadataHelp": "Jellyfin bietet native Unterstützung von NFO Metadatendateien. Um NFO Metadaten zu aktivieren oder deaktivieren, verwende den \"Metadaten\" Tab um die Optionen für deinen Medientypen zu konfigurieren.", + "HeaderKodiMetadataHelp": "Um NFO Metadaten zu aktivieren oder deaktivieren, bearbeite eine Bibliothek und mache den Metadaten-Speicherer Abschnitt ausfindig.", "HeaderLatestEpisodes": "Neueste Episoden", "HeaderLatestMedia": "Neueste Medien", "HeaderLatestMovies": "Neueste Filme", @@ -381,7 +381,7 @@ "HeaderPreferredMetadataLanguage": "Bevorzugte Sprache der Metadaten", "HeaderProfile": "Profil", "HeaderProfileInformation": "Profil Infomationen", - "HeaderProfileServerSettingsHelp": "Diese Werte geben an, wie Jellyfin Server sich Ihren Geräten präsentiert.", + "HeaderProfileServerSettingsHelp": "Diese Werte geben an, wie der Server sich Ihren Clients präsentiert.", "HeaderRecentlyPlayed": "Zuletzt gesehen", "HeaderRecordingOptions": "Aufnahmeeinstellungen", "HeaderRecordingPostProcessing": "Aufnahme Nachbearbeitung", @@ -399,13 +399,13 @@ "HeaderSecondsValue": "{0} Sekunden", "HeaderSelectCertificatePath": "Wählen Sie einen Zertifikat Ordner", "HeaderSelectMetadataPath": "Wähle Metadaten Pfad", - "HeaderSelectMetadataPathHelp": "Suche oder gib den Pfad für die Speicherung von Metadaten an. Das Verzeichnis muss beschreibbar sein.", + "HeaderSelectMetadataPathHelp": "Suche oder gib den Pfad für Metadaten an. Das Verzeichnis muss beschreibbar sein.", "HeaderSelectPath": "Verzeichnis Wählen", "HeaderSelectServer": "Wähle Server", "HeaderSelectServerCachePath": "Wähle Server Cache Pfad", "HeaderSelectServerCachePathHelp": "Suche oder gib den Pfad für die Speicherung von Server Cache Dateien an. Das Verzeichnis muss beschreibbar sein.", "HeaderSelectTranscodingPath": "Wähle Pfad für temporäre Transkodierdateien", - "HeaderSelectTranscodingPathHelp": "Suche oder gib den Pfad für die Speicherung von temporären Transkodierdateien an. Das Verzeichnis muss beschreibbar sein.", + "HeaderSelectTranscodingPathHelp": "Suche oder gib den Pfad für die Speicherung Transkodierdateien an. Das Verzeichnis muss beschreibbar sein.", "HeaderSendMessage": "Nachricht senden", "HeaderSeries": "Serien", "HeaderSeriesOptions": "Serienoptionen", @@ -452,8 +452,8 @@ "HttpsRequiresCert": "Um https für externe Verbindungen zu erzwingen, benötigst du ein vertrauenswürdiges SSL-Zertifikat, z.B. von Let's Encrypt. Bitte stelle entweder ein Zertifikat bereit, oder deaktiviere sichere Verbindungen.", "Identify": "Identifizieren", "Images": "Bilder", - "ImportFavoriteChannelsHelp": "Wenn aktiviert, werden nur auf dem Tuner favorisierte Kanäle importiert.", - "ImportMissingEpisodesHelp": "Wenn aktiviert, werden Informationen über fehlende Episoden in Deine Jellyfin Datenbank importiert und innerhalb von Staffeln angezeigt. Dies kann zu deutlich längeren Bibliothek Scans führen.", + "ImportFavoriteChannelsHelp": "Nur auf dem Tuner favorisierte Kanäle werden importiert.", + "ImportMissingEpisodesHelp": "Informationen über fehlende Episoden werden in deine Datenbank importiert und innerhalb von Staffeln angezeigt. Dies kann zu deutlich längeren Bibliothek Scans führen.", "InstallingPackage": "Installiere {0} (Version {1})", "InstantMix": "Schnellmix", "ItemCount": "{0} Einträge", @@ -485,14 +485,14 @@ "LabelAppName": "App Name", "LabelAppNameExample": "Beispiel: Sickbeard, Sonarr", "LabelArtists": "Interpreten:", - "LabelArtistsHelp": "Trenne mehrere Einträge durch ;", + "LabelArtistsHelp": "Trenne mehrere Künstler durch ein Semikolon.", "LabelAudioLanguagePreference": "Bevorzugte Audiosprache:", "LabelAutomaticallyRefreshInternetMetadataEvery": "Aktualisiere Metadaten automatisch aus dem Internet:", "LabelBindToLocalNetworkAddress": "Binde an lokale Netzwerkadresse:", - "LabelBindToLocalNetworkAddressHelp": "Optional. Überschreibt die lokale IP Adresse des HTTP Servers. Wenn leer, wird der Server an alle verfügbaren Adressen gebunden. Änderungen benötigen einen Neustart des Jellyfin Servers.", + "LabelBindToLocalNetworkAddressHelp": "Überschreibt die lokale IP Adresse für den HTTP Server. Wenn leer, wird der Server an alle verfügbaren Adressen gebunden. Änderungen benötigen einen Neustart des Jellyfin Servers.", "LabelBirthDate": "Geburtsdatum:", "LabelBirthYear": "Geburtsjahr:", - "LabelBlastMessageInterval": "Alive Meldungsintervall (Sekunden)", + "LabelBlastMessageInterval": "Alive Meldungsintervall", "LabelBlastMessageIntervalHelp": "Legt die Dauer in Sekunden zwischen den Server-Alive-Meldungen fest.", "LabelBlockContentWithTags": "Blockiere Inhalte mit Tags:", "LabelBurnSubtitles": "Untertitel einbrennen:", @@ -511,7 +511,7 @@ "LabelCustomCertificatePath": "Benutzerdefinierter SSL-Zertifikatspfad:", "LabelCustomCertificatePathHelp": "Pfad zu einer PKCS #12 Datei die ein Zertifikat und einen privaten Schlüssel enthält, um TLS Unterstützung für eine eigene Domain zu aktivieren.", "LabelCustomCss": "Benutzerdefiniertes CSS:", - "LabelCustomCssHelp": "Wende dein eigenes benutzerdefiniertes Styling auf die Weboberfläche an.", + "LabelCustomCssHelp": "Wende deine eigenen benutzerdefinierte Styles auf die Weboberfläche an.", "LabelCustomDeviceDisplayName": "Angezeigter Name:", "LabelCustomDeviceDisplayNameHelp": "Lege einen individuellen Anzeigenamen fest oder lasse das Feld leer, um den vom gerät übermittelten Namen zu nutzen.", "LabelCustomRating": "Eigene Bewertung:", @@ -547,12 +547,12 @@ "LabelEnableAutomaticPortMapHelp": "Leitet automatisch die öffentlichen Ports des Routers an die lokalen Ports des Servers mit Hilfe von UPnP weiter. Dies kann mit einigen Router-Modellen nicht funktionieren. Die Änderungen werden erst nach einem Neustart des Server aktiv.", "LabelEnableBlastAliveMessages": "Erzeuge Alive Meldungen", "LabelEnableBlastAliveMessagesHelp": "Aktiviere dies, wenn der Server nicht zuverlässig von anderen UPnP Geräten in ihrem Netzwerk erkannt wird.", - "LabelEnableDlnaClientDiscoveryInterval": "Client-Entdeckungs Intervall (Sekunden)", + "LabelEnableDlnaClientDiscoveryInterval": "Client-Entdeckungs Intervall", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Ermittelt die Zeit in Sekunden zwischen SSDP Suchanfragen die durch Jellyfin ausgeführt wurden.", "LabelEnableDlnaDebugLogging": "Aktiviere DLNA Debug Logging", "LabelEnableDlnaDebugLoggingHelp": "Erzeugt große Logdateien und sollte nur zur Fehlerbehebung benutzt werden.", "LabelEnableDlnaPlayTo": "Aktiviere DLNA Play To", - "LabelEnableDlnaPlayToHelp": "Jellyfin kann Geräte in Ihrem Netzwerk erkennen und bietet die Möglichkeit, diese fernzusteuern.", + "LabelEnableDlnaPlayToHelp": "Jellyfin kann Geräte in Ihrem Netzwerk erkennen und bietet die Möglichkeit diese fernzusteuern.", "LabelEnableDlnaServer": "DLNA-Server aktivieren", "LabelEnableDlnaServerHelp": "Erlaubt UPnP Geräten in Ihrem Netzwerk den Zugriff und die Wiedergabe von Inhalten.", "LabelEnableHardwareDecodingFor": "Aktiviere Hardware-Decoding für:", @@ -572,16 +572,16 @@ "LabelFont": "Schriftart:", "LabelForgotPasswordUsernameHelp": "Bitte gib deinen Benutzernamen ein, falls du dich daran erinnerst.", "LabelFriendlyName": "Benutzerfreundlicher Name:", - "LabelServerNameHelp": "Dieser Name wird benutzt um den Server zu identifizieren, standardmäßig wird der Server-/Computername verwendet.", + "LabelServerNameHelp": "Dieser Name wird benutzt, um den Server zu identifizieren, standardmäßig wird der Hostname des Servers verwendet.", "LabelGroupMoviesIntoCollections": "Gruppiere Filme in Collections", - "LabelGroupMoviesIntoCollectionsHelp": "Wenn Filmlisten angezeigt werden, dann werden Filme, die zu einer Collection gehören, als ein gruppiertes Element angezeigt.", + "LabelGroupMoviesIntoCollectionsHelp": "Wenn Filmlisten angezeigt werden, werden Filme in einer Sammlung als ein gruppiertes Element angezeigt.", "LabelEncoderPreset": "H264 Encoding Voreinstellung:", "LabelHardwareAccelerationType": "Hardware Beschleunigung:", "LabelHardwareAccelerationTypeHelp": "Hardwarebeschleunigung benötigt zusätzliche Konfiguration.", "LabelHomeNetworkQuality": "Heimnetzwerkqualität:", "LabelHomeScreenSectionValue": "Startseitenbereich {0}:", "LabelHttpsPort": "Lokale HTTPS-Portnummer:", - "LabelHttpsPortHelp": "Die TCP-Portnummer, die der HTTPS-Server von Jellyfin verwenden soll.", + "LabelHttpsPortHelp": "Die TCP-Portnummer für den HTTPS-Server.", "LabelIconMaxHeight": "Maximale Iconhöhe:", "LabelIconMaxHeightHelp": "Maximale Auflösung für durch UPnP übermittelte Icons:icon.", "LabelIconMaxWidth": "Maximale Iconbreite:", @@ -609,7 +609,7 @@ "LabelLanguage": "Sprache:", "LabelLineup": "TV Programm:", "LabelLocalHttpServerPortNumber": "Lokale HTTP Portnummer:", - "LabelLocalHttpServerPortNumberHelp": "Die TCP-Portnummer, die der HTTP-Server von Jellyfin verwenden soll.", + "LabelLocalHttpServerPortNumberHelp": "Die TCP-Portnummer für den HTTP-Server.", "LabelLockItemToPreventChanges": "Sperre diesen Eintrag um zukünftige Änderungen zu verhindern", "LabelLoginDisclaimer": "Anmeldung Haftungsausschluss:", "LabelLoginDisclaimerHelp": "Diese Nachricht wird am unteren Ende des Anmeldebildschirms angezeigt.", @@ -634,7 +634,7 @@ "LabelMetadataReaders": "Metadatenleser:", "LabelMetadataReadersHelp": "Lege deine bevorzugte lokale Metadatenquelle fest und ordne sie nach Prioritäten. Die erste Datei die gefunden wird, wird verwendet.", "LabelMetadataSavers": "Metadaten-Speicherer:", - "LabelMetadataSaversHelp": "Wähle das Dateiformat, in dem deine Metadaten gespeichert werden sollen.", + "LabelMetadataSaversHelp": "Wähle die Dateiformate, die beim Speichern deiner Metadaten verwendet werden sollen.", "LabelMethod": "Methode:", "LabelMinBackdropDownloadWidth": "Minimale Breite für zu herunterladende Hintergründe:", "LabelMinResumeDuration": "Minimale Dauer für Wiederaufnahme:", @@ -650,9 +650,9 @@ "LabelMovieCategories": "Filmkategorien:", "LabelMoviePrefix": "Filmpräfix:", "LabelMoviePrefixHelp": "Wenn ein Präfix in Filmtiteln angewendet wird, gib es hier ein damit der Server es korrekt behandeln kann.", - "LabelMovieRecordingPath": "Film Aufnahmepfad (Optional):", + "LabelMovieRecordingPath": "Film Aufnahmepfad:", "LabelMusicStreamingTranscodingBitrate": "Musik-Transkodierung Bitrate:", - "LabelMusicStreamingTranscodingBitrateHelp": "Wähle die maximale Bitrate für das streamen von Musik.", + "LabelMusicStreamingTranscodingBitrateHelp": "Wähle die maximale Bitrate für das Streamen von Musik.", "LabelNewName": "Neuer Name:", "LabelNewPassword": "Neues Passwort:", "LabelNewPasswordConfirm": "Neues Passwort wiederholen:", @@ -662,7 +662,7 @@ "LabelNumber": "Nummer:", "LabelNumberOfGuideDays": "Anzahl von Tagen für die Programminformationen geladen werden sollen:", "LabelNumberOfGuideDaysHelp": "Das laden von zusätzlichen Programmdaten bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Aber es wird länger dauern alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", - "LabelOptionalNetworkPath": "(Optionaler) Gemeinsamer Netzwerkordner:", + "LabelOptionalNetworkPath": "Geteilter Netzwerkordner:", "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Jellyfin Apps auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen. Beispielsweise {0} oder {1}.", "LabelOriginalAspectRatio": "Original Seitenverhältnis:", "LabelOriginalTitle": "Original Titel:", @@ -705,7 +705,7 @@ "LabelReleaseDate": "Veröffentlichungsdatum:", "LabelRemoteClientBitrateLimit": "Limit für die Internet Streaming Datenrate (Mbps):", "LabelRemoteClientBitrateLimitHelp": "Ein optionales Bitratenlimit pro Stream für alle Geräte außerhalb des Netzwerkes. Dies ist nützlich um zu verhindern, dass Geräte eine höhere Datenrate verwenden als die Internetverbindung erlaubt. Es kann zu erhöhter CPU-Last auf deinem Server kommen, da ggf. Videos in Echtzeit in eine niedrigere Bitrate transkodiert werden müssen.", - "LabelRuntimeMinutes": "Laufzeit (Minuten):", + "LabelRuntimeMinutes": "Laufzeit:", "LabelSaveLocalMetadata": "Speichere Bildmaterial und Metadaten in den Medienverzeichnissen", "LabelSaveLocalMetadataHelp": "Durch die Speicherung von Bildmaterial und Metadaten direkt in den Medienverzeichnissen, befinden sich diese an einem Ort wo sie sehr leicht bearbeitet werden können.", "LabelScheduledTaskLastRan": "Zuletzt ausgeführt vor: {0}. Benötigte Zeit: {1}.", @@ -717,7 +717,7 @@ "LabelSelectVersionToInstall": "Wähle die Version für die Installation:", "LabelSendNotificationToUsers": "Sende die Benachrichtigung an:", "LabelSerialNumber": "Seriennummer", - "LabelSeriesRecordingPath": "Serien Aufnahmepfad (optional):", + "LabelSeriesRecordingPath": "Serien Aufnahmepfad:", "LabelServerHost": "Adresse:", "LabelServerHostHelp": "192.168.1.100 oder https://myserver.com", "LabelSimultaneousConnectionLimit": "Paralleler Streamlimit:", @@ -783,7 +783,7 @@ "LabelYoureDone": "Du bist fertig!", "LabelZipCode": "PLZ:", "LabelffmpegPath": "FFmpeg Verzeichnis:", - "LabelffmpegPathHelp": "Verzeichnis zur runtergeladenen FFmpeg Applikation oder zum Ordner, der FFMpeg enthält.", + "LabelffmpegPathHelp": "Verzeichnis zur FFmpeg Applikationsdatei oder zum Ordner, der FFmpeg enthält.", "LanNetworksHelp": "Komma separierte Liste von IP Adressen oder IP Masken die als lokale Netzwerke behandelt werden sollen um Bandbreitenlimitationen auszusetzen. Wenn befüllt werden alle anderen IP Adressen als externe Netzwerke behandelt und unterliegen den Bandbreitenlimitationen für externe Verbindungen. Wenn leer, wird nur das SubNetz des Servers als Lokales Netz gesetzt.", "Large": "Groß", "LatestFromLibrary": "Neueste {0}", @@ -864,7 +864,7 @@ "MessageYouHaveVersionInstalled": "Du hast momentan Version {0} installiert.", "Metadata": "Metadaten", "MetadataManager": "Metadaten-Manager", - "MetadataSettingChangeHelp": "Das Verändern der Metadata-Einstellungen hat nur Einfluss auf neu hinzugefügte Inhalte. Um eine Aktualisierung bereits hinzugefügter Inhalte durchzuführen, öffnen Sie bitte die Detail Ansicht und klicken die Aktualisieren Schaltfläche. Die Aktualisierung mehrerer Inhalte kann im Metadata Manager durchgeführt werden.", + "MetadataSettingChangeHelp": "Das Verändern der Metadata-Einstellungen hat nur Einfluss auf neu hinzugefügte Inhalte. Um eine Aktualisierung bereits hinzugefügter Inhalte durchzuführen, öffnen Sie bitte die Detailansicht und klicken die Aktualisieren-Schaltfläche. Die Aktualisierung mehrerer Inhalte kann im Metadata Manager durchgeführt werden.", "MinutesAfter": "Minuten nach", "MinutesBefore": "Minuten vor", "Mobile": "Smartphone", @@ -910,7 +910,7 @@ "OptionAllowLinkSharingHelp": "Es werden nur Web-Seiten mit Medieninformationen geteilt. Medien hingenen werden niemals öffentlich geteilt. Die geteilten Inhalte sind nur begrenzt zugänglich werden nach {0} Tagen ungültig.", "OptionAllowManageLiveTv": "Erlaube Live-TV Aufnahmeplanung", "OptionAllowMediaPlayback": "Erlaube Medienwiedergabe", - "OptionAllowMediaPlaybackTranscodingHelp": "Das Einschränken des Transcoding-Zugriffes kann bei nicht unterstützten Medienformaten Abspielfehler in Jellyfin Apps hervorrufen.", + "OptionAllowMediaPlaybackTranscodingHelp": "Das Einschränken des Transcoding-Zugriffes kann durch nicht unterstützte Medienformate Abspielfehler in Clients hervorrufen.", "OptionAllowRemoteControlOthers": "Erlaube Fernsteuerung anderer Benutzer", "OptionAllowRemoteSharedDevices": "Erlaube Fernsteuerung geteilter Geräte", "OptionAllowRemoteSharedDevicesHelp": "DLNA-Geräte werden als gemeinsam genutzt betrachtet, bis ein Benutzer die Steuerung übernimmt.", @@ -921,7 +921,7 @@ "OptionArtist": "Interpret", "OptionAscending": "Aufsteigend", "OptionAutomaticallyGroupSeries": "Vermische Serieninhalte, die in verschiedenen Ordnern abgelegt sind", - "OptionAutomaticallyGroupSeriesHelp": "Wenn aktiviert, werden Inhalte einer Serie in verschiedenen Ordnern innerhalb einer Bibliothek als eine Serie angezeigt.", + "OptionAutomaticallyGroupSeriesHelp": "Inhalte einer Serie in verschiedenen Ordnern werden innerhalb einer Bibliothek als eine Serie angezeigt.", "OptionBlockBooks": "Bücher", "OptionBlockChannelContent": "Internet Channelinhalte", "OptionBlockLiveTvChannels": "Live-TV Kanäle", @@ -940,7 +940,7 @@ "OptionDatePlayed": "Gesehen am", "OptionDescending": "Absteigend", "OptionDisableUser": "Sperre diesen Benutzer", - "OptionDisableUserHelp": "Wenn deaktiviert wird der Server keine Verbindung von diesem Benutzer erlauben. Bestehende Verbindungen werden sofort beendet.", + "OptionDisableUserHelp": "Der Server keine Verbindung von diesem Benutzer erlauben. Bestehende Verbindungen werden sofort beendet.", "OptionDislikes": "Mag ich nicht", "OptionDisplayFolderView": "Darstellung in Verzeichnisansicht zeigt Medien Verzechnisse", "OptionDisplayFolderViewHelp": "Zeige eine Verzeichnisansicht neben deinen Bibliotheken an. Dies kann praktisch sein, wenn man nur Verzeichnisansichten verwendet.", @@ -948,7 +948,7 @@ "OptionDownloadBackImage": "Zurück", "OptionDownloadDiscImage": "Disk", "OptionDownloadImagesInAdvance": "Bilder vorab herunterladen", - "OptionDownloadImagesInAdvanceHelp": "Grundsätzlich werden die meisten Bilder erst dann runter geladen, wenn eine Jellyfin-App diese anfragt. Schalten Sie diese Option ein um alle Bilder im Voraus herunterzuladen, wenn neue Medien importiert wurden. Diese Einstellung kann zu signifikant längeren Bibliothekscans führen.", + "OptionDownloadImagesInAdvanceHelp": "Standardmäßig werden die meisten Bilder erst dann heruntergeladen, wenn ein Client diese anfragt. Schalten Sie diese Option ein um alle Bilder im Voraus herunterzuladen, wenn neue Medien importiert werden. Diese Einstellung kann zu signifikant längeren Bibliothekscans führen.", "OptionDownloadMenuImage": "Menü", "OptionDownloadPrimaryImage": "Primär", "OptionDvd": "DVD", @@ -978,7 +978,7 @@ "OptionHlsSegmentedSubtitles": "HLS segmentierte Untertitel", "OptionHomeVideos": "Fotos", "OptionIgnoreTranscodeByteRangeRequests": "Ignoriere Anfragen für Transkodierbytebereiche", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Falls aktiviert, werden diese Anfragen berücksichtigt aber Byte-Range-Header ignoriert werden.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Diese Anfragen werden berücksichtigt, aber den Byte-Range-Header ignorieren.", "OptionImdbRating": "IMDb Bewertung", "OptionLikes": "Mag ich", "OptionMissingEpisode": "Fehlende Episoden", @@ -989,9 +989,9 @@ "OptionOnInterval": "Nach einem Intervall", "OptionParentalRating": "Altersfreigabe", "OptionPlainStorageFolders": "Zeige alle Verzeichnisse als reine Speicherorte an", - "OptionPlainStorageFoldersHelp": "Falls aktiviert, werden alle Verzeichnisse in DIDL als \"object.container.storageFolder\" angezeigt, anstatt eines spezifischen Typs wie beispielsweise \"object.container.person.musicArtist\".", + "OptionPlainStorageFoldersHelp": "Alle Verzeichnisse werden in DIDL als \"object.container.storageFolder\" angezeigt, anstatt eines spezifischen Typs wie beispielsweise \"object.container.person.musicArtist\".", "OptionPlainVideoItems": "Zeige alle Videos als reine Videodateien an", - "OptionPlainVideoItemsHelp": "Falls aktiviert, werden alle Videos in DIDL als \"object.item.videoItem\" angezeigt, anstatt eines spezifischen Typs wie beispielsweise \"object.item.videoItem.movie\".", + "OptionPlainVideoItemsHelp": "Alle Videos werden in DIDL als \"object.item.videoItem\" angezeigt, anstatt eines spezifischen Typs wie beispielsweise \"object.item.videoItem.movie\".", "OptionPlayCount": "Zähler", "OptionPlayed": "Gesehen", "OptionPremiereDate": "Premiere", @@ -1075,7 +1075,7 @@ "RecordingScheduled": "Aufnahme geplant.", "Recordings": "Aufnahmen", "Refresh": "Aktualisieren", - "RefreshDialogHelp": "Metadaten werden auf Basis der Einstellungen und Internet Services in den Jellyfin Server Einstellungen aktualisiert.", + "RefreshDialogHelp": "Metadaten werden auf Basis der Einstellungen und Internet Services, die im Dashboard aktiviert sind, aktualisiert.", "RefreshMetadata": "Aktualisiere Metadaten", "RefreshQueued": "Aktualisierung eingereiht.", "ReleaseDate": "Veröffentlichungsdatum", @@ -1369,7 +1369,7 @@ "TagsValue": "Markierungen: {0}", "Thumb": "Miniaturansicht", "Whitelist": "Erlaubt", - "AuthProviderHelp": "Wähle einen Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzes verwendet werden soll.", + "AuthProviderHelp": "Wähle einen Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzers verwendet werden soll.", "Features": "Funktionen", "HeaderFavoriteBooks": "Lieblingsbücher", "HeaderFavoriteMovies": "Lieblingsfilme", @@ -1396,7 +1396,7 @@ "OptionDownloadBoxImage": "Box", "OptionLoginAttemptsBeforeLockout": "Legt fest, wie viele falsche Anmeldeversuche durchgeführt werden können, bevor es zur Sperrung kommt.", "OptionLoginAttemptsBeforeLockoutHelp": "Null (0) bedeutet den Standardwert von drei Versuchen für normale, sowie fünf für Administrator-Benutzer zu übernehmen. Ein Wert von -1 deaktiviert die Funktion.", - "PasswordResetProviderHelp": "Wählen Sie einen Password Reset Provider, der verwendet werden soll, wenn dieser Benutzer ein Passwort zurücksetzen möchte", + "PasswordResetProviderHelp": "Wählen Sie einen Password Reset Provider, der verwendet werden soll, wenn dieser Benutzer ein Passwort zurücksetzen möchte.", "Box": "Box", "HeaderHome": "Startseite", "LabelAudioCodec": "Audiocodec:", @@ -1414,7 +1414,7 @@ "LabelTranscodingFramerate": "Transcodierrate:", "LabelAudioSampleRate": "Audio-Abtastrate:", "LabelBaseUrl": "Basis URL:", - "LabelBaseUrlHelp": "Fügt ein benutzerdefiniertes Unterverzeichnis zur Server-URL hinzu, zum Beispiel: http://example.com/<baseurl>", + "LabelBaseUrlHelp": "Füge ein benutzerdefiniertes Unterverzeichnis zur Server-URL hinzu, zum Beispiel: http://example.com/<baseurl>", "LabelFolder": "Ordner:", "LabelPasswordResetProvider": "Anbieter zum Zurücksetzen des Passwortes:", "LabelPlayMethod": "Spielmethode:", @@ -1504,7 +1504,7 @@ "SaveChanges": "Änderungen speichern", "LabelRequireHttpsHelp": "Wenn dies ausgewählt ist, leitet der Server alle Anfragen über HTTP an HTTPS weiter. Dies hat keinen Effekt, falls der Server nicht auf HTTPS hört.", "LabelRequireHttps": "Erfordere HTTPS", - "LabelEnableHttpsHelp": "Erlaubt es dem Server, den konfigurierten HTTPS-Port zu beobachten. Damit dies geschehen kann, muss ein gültiges Zertifikat konfiguriert sein.", + "LabelEnableHttpsHelp": "Beobachtet den konfigurierten HTTPS-Port. Damit dies geschehen kann, muss ein gültiges Zertifikat bereitgestellt werden.", "LabelEnableHttps": "Aktiviere HTTPS", "HeaderServerAddressSettings": "Server-Adresseinstellungen", "HeaderRemoteAccessSettings": "Fernzugriffs-Einstellungen", @@ -1543,7 +1543,7 @@ "EnableDetailsBanner": "Detailbanner", "ShowMore": "Mehr anzeigen", "ShowLess": "Weniger anzeigen", - "EnableBlurHashHelp": "Bilder, die noch nicht fertig geladen wurden, werden mit einem verschwommenen Platzhalter dargestellt", + "EnableBlurHashHelp": "Bilder, die noch nicht fertig geladen wurden, werden mit einem verschwommenen Platzhalter dargestellt.", "EnableBlurHash": "Verschwommene Platzhalter für Bilder erlauben", "EnableFasterAnimations": "Schnellere Animationen", "EnableDecodingColorDepth10Vp9": "Aktiviere 10-Bit-Hardware-Dekodierung für VP9", @@ -1564,5 +1564,8 @@ "Writers": "Autoren", "ClearQueue": "Wiedergabeliste leeren", "StopPlayback": "Wiedergabe anhalten", - "ViewAlbumArtist": "Zeige Albumkünstler" + "ViewAlbumArtist": "Zeige Albumkünstler", + "PreviousTrack": "Zum Vorherigen springen", + "NextTrack": "Zum Nächsten springen", + "LabelUnstable": "Instabil" } diff --git a/src/strings/fr.json b/src/strings/fr.json index cb75eba888..a4fbc078ef 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -172,7 +172,7 @@ "DeviceAccessHelp": "Ceci ne s'applique qu'aux appareils qui peuvent être identifiés de manière unique et n'empêchera pas l'accès par navigateur. Le filtrage de l'accès aux appareil par utilisateur empêchera l'utilisation de nouveaux appareils jusqu'à ce qu'ils soient approuvés ici.", "DirectPlaying": "Lecture directe", "DirectStreamHelp1": "Le média est compatible avec l'appareil en ce qui concerne la résolution et le type de média (H.264, AC3, etc), mais se trouve dans un conteneur de fichiers incompatible (mkv, avi, wmv, etc). La vidéo sera rempaquetée à la volée avant d'être diffusée à l'appareil.", - "DirectStreamHelp2": "Le streaming en direct d'un fichier utilise très peu de puissance de traitement sans perte de qualité vidéo.", + "DirectStreamHelp2": "Le streaming en direct utilise très peu de puissance de traitement avec une perte minime de qualité vidéo.", "DirectStreaming": "Streaming direct", "Director": "Réalisateur(trice)", "Directors": "Réalisateurs", @@ -277,7 +277,7 @@ "HeaderAllowMediaDeletionFrom": "Autoriser la suppression de médias à partir de", "HeaderApiKey": "Clé API", "HeaderApiKeys": "Clés API", - "HeaderApiKeysHelp": "Les applications externes ont besoin d'une clé d'API pour communiquer avec le serveur Jellyfin. Les clés sont distribuées lors d'une connexion avec un compte Jellyfin, ou bien en accordant manuellement une clé à une application.", + "HeaderApiKeysHelp": "Les applications externes ont besoin d'une clé d'API pour communiquer avec le serveur. Les clés sont distribuées lors d'une connexion avec un compte normal ou en accordant manuellement une clé à une application.", "HeaderApp": "Application", "HeaderAppearsOn": "Apparait dans", "HeaderAudioBooks": "Livres audios", @@ -397,7 +397,7 @@ "HeaderPreferredMetadataLanguage": "Langue de métadonnées préférée", "HeaderProfile": "Profil", "HeaderProfileInformation": "Information de profil", - "HeaderProfileServerSettingsHelp": "Ces valeurs contrôlent la façon dont le serveur Jellyfin se présentera aux appareils.", + "HeaderProfileServerSettingsHelp": "Ces valeurs contrôlent la façon dont le serveur se présentera aux clients.", "HeaderRecentlyPlayed": "Lus récemment", "HeaderRecordingOptions": "Options d'enregistrement", "HeaderRecordingPostProcessing": "Traitement des enregistrements", @@ -421,7 +421,7 @@ "HeaderSelectServerCachePath": "Sélectionner le chemin d'accès du cache de serveur", "HeaderSelectServerCachePathHelp": "Parcourir ou saisir le chemin d'accès à utiliser pour les fichiers cache du serveur. Le dossier doit être accessible en écriture.", "HeaderSelectTranscodingPath": "Sélectionner le chemin d'accès du dossier temporaire de transcodage", - "HeaderSelectTranscodingPathHelp": "Parcourir ou saisir le chemin d'accès à utiliser pour les fichiers de transcodage temporaires. Le dossier doit être accessible en écriture.", + "HeaderSelectTranscodingPathHelp": "Parcourir ou saisir le chemin d'accès à utiliser pour les fichiers de transcodage. Le dossier doit être accessible en écriture.", "HeaderSendMessage": "Envoyer un message", "HeaderSeries": "Séries", "HeaderSeriesOptions": "Options de la série", @@ -471,8 +471,8 @@ "Home": "Accueil", "HttpsRequiresCert": "Pour activer les connexions sécurisées, vous devrez fournir un certificat SSL vérifié, comme ceux fournis par Let's Encrypt. Veuillez fournir un certificat ou désactiver les connexions sécurisées.", "Identify": "Identifier", - "ImportFavoriteChannelsHelp": "Activez cette option pour n'importer que les chaînes ajoutées aux favoris sur le tuner.", - "ImportMissingEpisodesHelp": "Les informations à propos des épisodes manquants seront importées dans votre base de donnée Jellyfin et affichées dans les saisons et séries. Cela peut accroître significativement la durée d'actualisation de la médiathèque.", + "ImportFavoriteChannelsHelp": "Seules les chaînes ajoutées aux favoris sur le tuner seront importées.", + "ImportMissingEpisodesHelp": "Les informations à propos des épisodes manquants seront importées dans votre base de données et affichées dans les saisons et séries. Cela peut accroître significativement la durée d'actualisation de la médiathèque.", "InstallingPackage": "Installation de {0} (version {1})", "InstantMix": "Mix instantané", "ItemCount": "{0} éléments", @@ -505,14 +505,14 @@ "LabelAppName": "Nom de l'application", "LabelAppNameExample": "Exemple: Sickbeard, Sonarr", "LabelArtists": "Artistes :", - "LabelArtistsHelp": "Séparer les différents éléments par ;", + "LabelArtistsHelp": "Séparer les différents éléments par un point-virgule.", "LabelAudioLanguagePreference": "Langue audio préférée :", "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualiser automatiquement les métadonnées depuis internet :", "LabelBindToLocalNetworkAddress": "Lier à l'adresse de réseau local :", - "LabelBindToLocalNetworkAddressHelp": "Optionnel. Remplace l'adresse IP locale à laquelle se lie le serveur HTTP. Sans paramètre, le serveur va se lier à toutes les adresses disponibles. La modification de cette valeur nécessite le redémarrage du serveur Jellyfin.", + "LabelBindToLocalNetworkAddressHelp": "Remplace l'adresse IP locale du serveur HTTP. Sans paramètre, le serveur va se lier à toutes les adresses disponibles. La modification de cette valeur nécessite le redémarrage du serveur Jellyfin.", "LabelBirthDate": "Date de naissance :", "LabelBirthYear": "Année de naissance :", - "LabelBlastMessageInterval": "Intervalle des messages de présence (secondes)", + "LabelBlastMessageInterval": "Intervalle des messages de présence", "LabelBlastMessageIntervalHelp": "Détermine la durée en secondes entre les messages de présence.", "LabelBlockContentWithTags": "Bloquer les éléments avec les étiquettes :", "LabelBurnSubtitles": "Graver les sous-titres :", @@ -569,7 +569,7 @@ "LabelEnableAutomaticPortMapHelp": "Mapper automatiquement les ports publics vers des ports locaux via UPnP. Cela peut ne pas fonctionner avec certains modèles de routeurs. La modification de ce paramètre ne prendra effet qu'après redémarrage du serveur.", "LabelEnableBlastAliveMessages": "Diffuser des message de présence", "LabelEnableBlastAliveMessagesHelp": "Activer cette option si le serveur n'est pas détecté de manière fiable par les autres appareils UPnP sur votre réseau.", - "LabelEnableDlnaClientDiscoveryInterval": "Intervalle de découverte des clients (secondes)", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalle de découverte des clients", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Détermine la durée en secondes entre les recherches SSDP exécutées par Jellyfin.", "LabelEnableDlnaDebugLogging": "Activer le débogage DLNA dans le journal d'événements", "LabelEnableDlnaDebugLoggingHelp": "Génère de gros fichiers de journal d'événements et ne devrait être utilisé que pour des diagnostics d'erreur.", @@ -579,7 +579,7 @@ "LabelEnableDlnaServerHelp": "Autorise les appareils UPnP de votre réseau à parcourir et à lire le contenu.", "LabelEnableHardwareDecodingFor": "Activer le décodage matériel pour :", "LabelEnableRealtimeMonitor": "Activer la surveillance en temps réel", - "LabelEnableRealtimeMonitorHelp": "Les modifications des fichiers seront traitées immédiatement, sur les systèmes de fichiers qui le permettent.", + "LabelEnableRealtimeMonitorHelp": "Les modifications des fichiers seront traitées immédiatement sur les systèmes de fichiers qui le permettent.", "LabelEnableSingleImageInDidlLimit": "Limiter à une seule image intégrée", "LabelEnableSingleImageInDidlLimitHelp": "Quelques périphériques ne fourniront pas un rendu correct si plusieurs images sont intégrées dans Didl.", "LabelEndDate": "Date de fin :", @@ -595,9 +595,9 @@ "LabelForgotPasswordUsernameHelp": "Saisissez votre nom d'utilisateur, si vous vous en souvenez.", "LabelFormat": "Format :", "LabelFriendlyName": "Nom d'affichage :", - "LabelServerNameHelp": "Ce nom sera utilisé pour identifier le serveur. La valeur par défaut est le nom d'ordinateur du serveur.", + "LabelServerNameHelp": "Ce nom sera utilisé pour identifier le serveur. La valeur par défaut est le nom d'hôte du serveur.", "LabelGroupMoviesIntoCollections": "Grouper les films en collections", - "LabelGroupMoviesIntoCollectionsHelp": "Dans l'affichage des listes de films, les films faisant partie d'une collection seront affichés comme un élément groupé.", + "LabelGroupMoviesIntoCollectionsHelp": "Dans l'affichage des listes de films, les films d'une collection seront affichés comme un élément groupé.", "LabelH264Crf": "CRF d'encodage H264 :", "LabelEncoderPreset": "Profil d'encodage H264 :", "LabelHardwareAccelerationType": "Accélération matérielle :", @@ -605,7 +605,7 @@ "LabelHomeNetworkQuality": "Qualité du réseau local :", "LabelHomeScreenSectionValue": "Section {0} de l'accueil :", "LabelHttpsPort": "Numéro de port HTTPS local :", - "LabelHttpsPortHelp": "Le port TCP que le serveur HTTPS de Jellyfin doit utiliser.", + "LabelHttpsPortHelp": "Le numéro de port TCP pour le serveur HTTPS.", "LabelIconMaxHeight": "Hauteur maximum des icônes :", "LabelIconMaxHeightHelp": "Résolution maximum des icônes exposée par upnp:icon.", "LabelIconMaxWidth": "Largeur maximum des icônes :", @@ -633,7 +633,7 @@ "LabelLanguage": "Langue :", "LabelLineup": "Programmation :", "LabelLocalHttpServerPortNumber": "Numéro de port HTTP local :", - "LabelLocalHttpServerPortNumberHelp": "Le port TCP que le serveur HTTP de Jellyfin doit utiliser.", + "LabelLocalHttpServerPortNumberHelp": "Le numéro de port TCP pour le serveur HTTP.", "LabelLockItemToPreventChanges": "Verrouiller cet élément pour éviter de futures modifications", "LabelLoginDisclaimer": "Avertissement sur la page d'accueil :", "LabelLoginDisclaimerHelp": "Le slogan sera affiché en bas de la page de connexion.", @@ -659,7 +659,7 @@ "LabelMetadataReaders": "Lecteurs de métadonnées :", "LabelMetadataReadersHelp": "Classez vos sources locales de métadonnées préférées dans l'ordre de priorité. Le premier fichier trouvé sera lu.", "LabelMetadataSavers": "Enregistreurs de métadonnées :", - "LabelMetadataSaversHelp": "Sélectionnez un format de fichier pour l'enregistrement des métadonnées.", + "LabelMetadataSaversHelp": "Sélectionnez un format de fichier qui sera utilisé pour l'enregistrement des métadonnées.", "LabelMethod": "Méthode :", "LabelMinBackdropDownloadWidth": "Largeur minimum d'image d'arrière-plan à télécharger :", "LabelMinResumeDuration": "Temps de reprise minimum :", @@ -675,7 +675,7 @@ "LabelMovieCategories": "Catégories de films :", "LabelMoviePrefix": "Préfixe de film :", "LabelMoviePrefixHelp": "Si un préfixe est appliqué aux titres de film, précisez-le ici afin que le serveur puisse le gérer convenablement.", - "LabelMovieRecordingPath": "Chemin d'enregistrement des films (optionnel) :", + "LabelMovieRecordingPath": "Chemin d'enregistrement des films :", "LabelMusicStreamingTranscodingBitrate": "Débit du transcodage de la musique :", "LabelMusicStreamingTranscodingBitrateHelp": "Spécifiez le débit maximum pendant la diffusion de musique.", "LabelName": "Nom :", @@ -688,7 +688,7 @@ "LabelNumber": "Numéro :", "LabelNumberOfGuideDays": "Nombre de jours de données du guide à télécharger :", "LabelNumberOfGuideDaysHelp": "Télécharger plus de journées du guide permet de programmer des enregistrements plus longtemps à l'avance et de visualiser plus de contenus, mais prendra également plus de temps. Automatique permettra une sélection automatique basée sur le nombre de chaînes.", - "LabelOptionalNetworkPath": "(Optionnel) Dossier réseau partagé :", + "LabelOptionalNetworkPath": "Dossier réseau partagé :", "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner le chemin d'accès au dossier réseau peut permettre aux applications Jellyfin sur d'autres appareils d'avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", "LabelOriginalAspectRatio": "Ratio d'aspect original :", "LabelOriginalTitle": "Titre original :", @@ -733,7 +733,7 @@ "LabelReleaseDate": "Date de sortie :", "LabelRemoteClientBitrateLimit": "Limite de débit de streaming Internet (Mbps) :", "LabelRemoteClientBitrateLimitHelp": "Une limite de débit optionnelle par streaming pour les connexions hors du réseau local. Utile pour éviter que les appareils ne demandent un débit supérieur à ce que votre connexion internet peu fournir. Cela peut augmenter la charge du processeur de votre serveur pour transcoder les vidéos à la volée à un débit plus faible.", - "LabelRuntimeMinutes": "Durée (minutes) :", + "LabelRuntimeMinutes": "Durée :", "LabelSaveLocalMetadata": "Enregistrer les illustrations dans les dossiers des médias", "LabelSaveLocalMetadataHelp": "L'enregistrement des illustrations dans les dossiers des médias les placera à un endroit où elles seront facilement modifiables.", "LabelScheduledTaskLastRan": "Dernière exécution {0}, durée {1}.", @@ -745,7 +745,7 @@ "LabelSelectVersionToInstall": "Sélectionner la version à installer :", "LabelSendNotificationToUsers": "Envoyer la notification à :", "LabelSerialNumber": "Numéro de série", - "LabelSeriesRecordingPath": "Chemin d'enregistrement des séries (optionnel) :", + "LabelSeriesRecordingPath": "Chemin d'enregistrement des séries :", "LabelServerHost": "Nom d'hôte :", "LabelServerHostHelp": "192.168.1.1:8096 ou https://monserveur.com", "LabelSimultaneousConnectionLimit": "Limite de flux simultanée :", @@ -819,7 +819,7 @@ "LabelYoureDone": "Vous avez terminé !", "LabelZipCode": "Code postal :", "LabelffmpegPath": "Chemin vers FFmpeg :", - "LabelffmpegPathHelp": "Le chemin d'accès vers l'application FFmpeg, ou un dossier contenant FFmpeg.", + "LabelffmpegPathHelp": "Le chemin d'accès vers l'application FFmpeg ou un dossier contenant FFmpeg.", "LanNetworksHelp": "Liste des adresses IP ou des entrées IP/masque de réseau séparées par des virgules pour les réseaux qui seront considérés comme locaux lors de l'application des restrictions de bande passante. Si elle est définie, toutes les autres adresses IP seront considérées sur le réseau externe et seront soumises aux restrictions de bande passante externe. Si elle est vide, seul le sous-réseau du serveur est considéré comme se trouvant sur le réseau local.", "Large": "Grand", "LatestFromLibrary": "{0}, ajouts récents", @@ -954,7 +954,7 @@ "OptionAllowLinkSharingHelp": "Seules les pages Web contenant des informations de médias sont partagés. Les fichiers multimédias ne sont jamais partagés publiquement. Les partages sont limités dans le temps et expirent après {0} jours.", "OptionAllowManageLiveTv": "Autoriser la gestion des enregistrements de TV en direct", "OptionAllowMediaPlayback": "Autoriser la lecture de média", - "OptionAllowMediaPlaybackTranscodingHelp": "Limiter l'accès au transcodage peut entraîner des échecs de lecture dans les applications Jellyfin en raison de formats de média non pris en charge.", + "OptionAllowMediaPlaybackTranscodingHelp": "Limiter l'accès au transcodage peut entraîner des échecs de lecture dans les clients en raison de formats de média non pris en charge.", "OptionAllowRemoteControlOthers": "Autoriser le contrôle à distance des autres utilisateurs", "OptionAllowRemoteSharedDevices": "Autoriser le contrôle à distance des appareils partagés", "OptionAllowRemoteSharedDevicesHelp": "Les appareils DLNA sont considérés comme partagés tant qu'un utilisateur ne commence pas à les contrôler.", @@ -986,7 +986,7 @@ "OptionDatePlayed": "Date de lecture", "OptionDescending": "Décroissant", "OptionDisableUser": "Désactiver cet utilisateur", - "OptionDisableUserHelp": "Si désactivé, le serveur n'autorisera pas de connexion de cet utilisateur. Les connexions existantes seront interrompues.", + "OptionDisableUserHelp": "Le serveur n'autorisera pas de connexion de cet utilisateur. Les connexions existantes seront interrompues.", "OptionDislikes": "Pas aimés", "OptionDisplayFolderView": "Afficher une vue de dossiers pour montrer les dossiers multimédia en intégralité", "OptionDisplayFolderViewHelp": "Afficher les dossier au côté de votre médiathèque. Cela peut être utile si vous souhaitez avoir une vue complète des dossiers.", @@ -995,7 +995,7 @@ "OptionDownloadBoxImage": "Boîtier", "OptionDownloadDiscImage": "Disque", "OptionDownloadImagesInAdvance": "Télécharger les images en avance", - "OptionDownloadImagesInAdvanceHelp": "Par défaut, la plupart des images sont téléchargées seulement lorsqu'une application Jellyfin le demande. Sélectionnez cette option pour télécharger toutes les images à l'avance, lorsqu'un nouveau média est importé. Cela peut allonger significativement la durée d'actualisation de la médiathèque.", + "OptionDownloadImagesInAdvanceHelp": "Par défaut, la plupart des images sont téléchargées seulement lorsqu'un client le demande. Sélectionnez cette option pour télécharger toutes les images à l'avance, lorsqu'un nouveau média est importé. Cela peut allonger significativement la durée d'actualisation de la médiathèque.", "OptionDownloadPrimaryImage": "Principal", "OptionDownloadThumbImage": "Vignette", "OptionDvd": "DVD", @@ -1026,7 +1026,7 @@ "OptionHlsSegmentedSubtitles": "Sous-titres segmentés HLS", "OptionHomeVideos": "Photos", "OptionIgnoreTranscodeByteRangeRequests": "Ignore les requêtes de transcodage de plage d'octets", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si l'option est activée, ces requêtes seront honorées mais l'en-tête de plage d'octets sera ignoré.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Ces requêtes seront honorées mais l'en-tête de plage d'octets sera ignoré.", "OptionImdbRating": "Note IMDb", "OptionLikes": "Aimés", "OptionMax": "Maximum", @@ -1039,9 +1039,9 @@ "OptionOnInterval": "Par intervalle", "OptionParentalRating": "Classification parentale", "OptionPlainStorageFolders": "Afficher tous les dossiers en tant que simples dossiers de stockage", - "OptionPlainStorageFoldersHelp": "Tous les répertoires seront affichés dans le DIDL en tant que \"object.container.storageFolder\" au lieu de formats plus spécifiques comme, par exemple \"object.container.person.musicArtist\".", + "OptionPlainStorageFoldersHelp": "Tous les répertoires seront affichés dans le DIDL en tant que \"object.container.storageFolder\" au lieu de formats plus spécifiques comme, par exemple \"object.container.person.musicArtist\".", "OptionPlainVideoItems": "Afficher les vidéos en tant que simples éléments vidéos", - "OptionPlainVideoItemsHelp": "Si activé, toutes les vidéos seront affichées dans le DIDL en tant que \"object.item.videoItem\" au lieu de formats plus spécifiques comme, par exemple \"object.item.videoItem.movie\".", + "OptionPlainVideoItemsHelp": "Toutes les vidéos seront affichées dans le DIDL en tant que \"object.item.videoItem\" au lieu de formats plus spécifiques comme, par exemple \"object.item.videoItem.movie\".", "OptionPlayCount": "Nombre de lectures", "OptionPlayed": "Lu", "OptionPremiereDate": "Date de la première", @@ -1128,7 +1128,7 @@ "RecordingScheduled": "Enregistrement planifié.", "Recordings": "Enregistrements", "Refresh": "Actualiser", - "RefreshDialogHelp": "Les métadonnées sont actualisées en fonction des paramètres et des services Internet qui sont activés dans le tableau de bord du serveur Jellyfin.", + "RefreshDialogHelp": "Les métadonnées sont actualisées en fonction des paramètres et des services Internet qui sont activés dans le tableau de bord.", "RefreshMetadata": "Actualiser les métadonnées", "RefreshQueued": "Actualisation mise en file d'attente.", "ReleaseDate": "Date de sortie", @@ -1386,7 +1386,7 @@ "MediaInfoStreamTypeSubtitle": "Sous-titres", "MediaInfoStreamTypeVideo": "Video", "AuthProviderHelp": "Sélectionner un fournisseur d'authentification pour authentifier le mot de passe de cet utilisateur.", - "PasswordResetProviderHelp": "Choisissez un Fournisseur de réinitialisation de mot de passe à utiliser lorsqu'un utilisateur demande la réinitialisation de son mot de passe", + "PasswordResetProviderHelp": "Choisissez un fournisseur de réinitialisation de mot de passe à utiliser lorsqu'un utilisateur demande la réinitialisation de son mot de passe.", "HeaderHome": "Accueil", "LabelUserLoginAttemptsBeforeLockout": "Tentatives de connexion échouées avant que l'utilisateur ne soit verrouillé :", "DashboardOperatingSystem": "Système d'Exploitation: {0}", @@ -1497,7 +1497,7 @@ "HeaderFavoritePlaylists": "Listes de lecture favorites", "TabDVR": "DVR", "LabelChromecastVersion": "Version de Chromecast", - "LabelEnableHttpsHelp": "Autorise le serveur à écouter les requêtes HTTPS sur le port configuré. Un certificat valide doit être configuré pour permettre ce mode de fonctionnement.", + "LabelEnableHttpsHelp": "Écouter les requêtes HTTPS sur le port configuré. Un certificat valide doit être fourni pour permettre ce mode de fonctionnement.", "LabelEnableHttps": "Activer HTTPS", "HeaderServerAddressSettings": "Paramètres adresses serveur", "HeaderRemoteAccessSettings": "Paramètres d'accès distant", @@ -1543,7 +1543,7 @@ "MessageSyncPlayErrorAccessingGroups": "Une erreur s'est produite pendant l'accès à la liste de groupes.", "ShowMore": "Voir plus", "ShowLess": "Voir moins", - "EnableBlurHashHelp": "Les images qui sont encore en cours de chargement seront remplacées par une image générique floue", + "EnableBlurHashHelp": "Les images qui sont encore en cours de chargement seront remplacées par une image générique floue.", "EnableBlurHash": "Utilise des images génériques floues à la place des images", "ButtonCast": "Diffuser", "ButtonSyncPlay": "SyncPlay", diff --git a/src/strings/hu.json b/src/strings/hu.json index 31a26935ef..b097531d06 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -278,13 +278,13 @@ "LabelMetadataPath": "Metaadat útvonal:", "LabelMetadataReaders": "Metaadat olvasók:", "LabelMetadataSavers": "Metaadat mentés:", - "LabelMetadataSaversHelp": "A metaadat letöltésének formátuma.", + "LabelMetadataSaversHelp": "A metaadatok mentési fájlformátuma.", "LabelName": "Név:", "LabelNewPassword": "Új jelszó:", "LabelNewPasswordConfirm": "Új jelszó megerősítése:", "LabelNext": "Következő", "LabelNotificationEnabled": "Értesítés engedélyezése", - "LabelOptionalNetworkPath": "(Opcionális) Megosztott hálózati mappa:", + "LabelOptionalNetworkPath": "Megosztott hálózati mappa:", "LabelOriginalAspectRatio": "Eredeti képarány:", "LabelOriginalTitle": "Eredeti cím:", "LabelOverview": "Tartalom:", @@ -303,7 +303,7 @@ "LabelProfileVideoCodecs": "Videó kódekek:", "LabelRefreshMode": "Frissítési mód:", "LabelReleaseDate": "Megjelenés dátuma:", - "LabelRuntimeMinutes": "Játékidő (perc):", + "LabelRuntimeMinutes": "Játékidő:", "LabelSeasonNumber": "Évad száma:", "LabelSelectFolderGroups": "Automatikusan csoportosítsa a következő mappák tartalmát olyan nézetekre, mint a Filmek, a Zene és a TV:", "LabelSelectFolderGroupsHelp": "A ki nem választott mappák önmagukban, saját nézetben jelennek meg.", @@ -459,7 +459,7 @@ "RecommendationStarring": "Főszerepben: {0}", "Record": "Felvétel", "Refresh": "Frissítés", - "RefreshDialogHelp": "A metaadatok frissítése a Jellyfin Server vezérlőpultjában engedélyezett beállítások és internetszolgáltatások alapján történik.", + "RefreshDialogHelp": "A metaadatok frissítése a vezérlőpultban engedélyezett beállítások és internetszolgáltatások alapján történik.", "RefreshMetadata": "Metaadat frissítése", "ReleaseDate": "Megjelenés dátuma", "RememberMe": "Emlékezz rám", @@ -655,7 +655,7 @@ "DetectingDevices": "Eszközök észlelése", "DirectPlaying": "Közvetlen lejátszás", "DirectStreamHelp1": "Az adathordozó kompatibilis a készülékkel a felbontás és a médiatípus (H.264, AC3, stb.) tekintetében, de nem kompatibilis a fájltárolóban (.mkv, .avi, .wmv, stb.). A videófelvétel újra csomagolásra kerül, mielőtt azt a készülékre továbbítaná.", - "DirectStreamHelp2": "A fájl közvetlen közvetítése (Direct Streaming) nagyon kevés feldolgozási erőforrást használ, ennek ellenére a videó nem veszít a minőségéből.", + "DirectStreamHelp2": "A fájl közvetlen közvetítése (Direct Streaming) nagyon kevés feldolgozási erőforrást használ, és a videóban is minimális a minőségvesztés.", "DirectStreaming": "Közvetlen streaming", "Disabled": "Tiltva", "Disc": "Lemez", @@ -747,7 +747,7 @@ "HeaderInstantMix": "Azonnali keverés", "HeaderItems": "Elemek", "HeaderKeepRecording": "Felvétel készítése", - "HeaderKodiMetadataHelp": "Az Nfo metaadatok engedélyezéséhez vagy letiltásához szerkeszd a könyvtárat a Jellyfin Médiatár beállításaiban és keresd meg a metaadat letöltő részt.", + "HeaderKodiMetadataHelp": "Az Nfo metaadatok engedélyezéséhez vagy letiltásához szerkeszd a könyvtárat és keresd meg a metaadat letöltő részt.", "HeaderLatestMusic": "Legújabb Zene", "HeaderLatestRecordings": "Legújabb Felvételek", "HeaderLiveTV": "Élő TV", @@ -763,7 +763,7 @@ "HeaderPhotoAlbums": "Fényképalbumok", "HeaderPlaybackError": "Lejátszási hiba", "HeaderProfileInformation": "Profil információ", - "HeaderProfileServerSettingsHelp": "Ezek az értékek szabályozzák, hogy a Jellyfin Szerver hogyan jelenik meg az eszközökön.", + "HeaderProfileServerSettingsHelp": "Ezek az értékek szabályozzák, hogy a Szerver hogyan jelenik meg a kliensek számára.", "HeaderRecordingOptions": "Felvétel beállítások", "HeaderRecordingPostProcessing": "Felvétel utáni feldolgozás", "HeaderRemoveMediaFolder": "Média mappa eltávolítása", @@ -777,7 +777,7 @@ "HeaderSelectServerCachePath": "Válaszd ki a szerver gyorsítótár útvonalát", "HeaderSelectServerCachePathHelp": "Tallózd ki vagy írd be a szerver gyorsítótár fájljainak elérési útját. A mappának írhatónak kell lennie.", "HeaderSelectTranscodingPath": "Válaszd ki az Átkódolás ideiglenes útvonalát", - "HeaderSelectTranscodingPathHelp": "Tallózd ki vagy add meg az átmeneti fájlok átkódolásához használt útvonalat. A mappának írhatónak kell lennie.", + "HeaderSelectTranscodingPathHelp": "Tallózd ki vagy add meg a fájlok átkódolásához használt útvonalat. A mappának írhatónak kell lennie.", "HeaderSeriesOptions": "Sorozatok beállításai", "LabelTag": "Címke:", "MediaInfoCodecTag": "Kódek címke", @@ -814,7 +814,7 @@ "Hide": "Elrejtés", "Horizontal": "Vízszintes", "HttpsRequiresCert": "A biztonságos kapcsolatok engedélyezéséhez megbízható SSL-tanúsítványt kell használni, mint például a Let's Encrypt. Kérlek add meg a tanúsítványt, vagy tiltsd le a biztonságos kapcsolatokat.", - "ImportMissingEpisodesHelp": "Ha engedélyezve van, a hiányzó epizódokra vonatkozó információk a Jellyfin adatbázisába kerülnek importálásra és megjelenítésre kerülnek az évadokban és sorozatokban. Ez jelentősen hosszabb könyvtárvizsgálatot okozhat.", + "ImportMissingEpisodesHelp": "A hiányzó epizódokra vonatkozó információk a Jellyfin adatbázisába kerülnek importálásra és megjelenítésre kerülnek az évadokban és sorozatokban. Ez jelentősen hosszabb könyvtárvizsgálatot okozhat.", "InstantMix": "Azonnali keverés", "ItemCount": "{0} elem", "Items": "Elemek", @@ -836,9 +836,9 @@ "LabelAppNameExample": "Például: Sickbeard, Sonarr", "LabelAutomaticallyRefreshInternetMetadataEvery": "A metaadatok automatikus frissítése az internetről:", "LabelBindToLocalNetworkAddress": "Kötés a helyi hálózati címhez:", - "LabelBindToLocalNetworkAddressHelp": "Opcionális. A helyi IP cím felülbírálása a http szerverhez való csatlakozáshoz. Ha üres marad, a szerver minden elérhető címhez kötődik. Az érték megváltoztatásához a Jellyfin Szerver újraindítása szükséges.", + "LabelBindToLocalNetworkAddressHelp": "A helyi IP cím felülbírálása a HTTP szerverhez való csatlakozáshoz. Ha üres marad, a szerver minden elérhető címhez kötődik. Az érték megváltoztatásához a Jellyfin Szerver újraindítása szükséges.", "LabelBirthDate": "Születési dátum:", - "LabelBlastMessageInterval": "Élő üzenetintervallum (másodperc)", + "LabelBlastMessageInterval": "Élő üzenetintervallum", "LabelBlastMessageIntervalHelp": "Meghatározza másodpercben az üzenetek közötti időtartamot.", "LabelBlockContentWithTags": "Blokkolja a címkével ellátott elemeket:", "LabelCache": "Gyorsítótár:", @@ -860,15 +860,15 @@ "LabelDisplayLanguage": "Megjelenítési nyelv:", "LabelDisplayLanguageHelp": "A Jellyfin fordítása egy folyamatos projekt.", "LabelDisplayMode": "Megjelenítési mód:", - "LabelArtistsHelp": "Ha több van használd a következő elválasztót ;", + "LabelArtistsHelp": "Ha több előadót adsz meg, pontosvesszővel válaszd el őket.", "LabelEnableAutomaticPortMapHelp": "A szerver az UPnP segítségével a routeren megpróbálja automatikusan átirányítani a nyilvános portot a helyi portra. Előfordulhat, hogy egyes router modellek, vagy hálózati konfigurációk esetén ez nem működik. A módosítások újraindítás után lépnek életbe.", "LabelEnableBlastAliveMessagesHelp": "Engedélyezd ezt ha a szerver nem észleli megbízhatóan a hálózat más UPnP-eszközeit.", - "LabelEnableDlnaClientDiscoveryInterval": "Kliens felderítési intervallum (másodperc)", + "LabelEnableDlnaClientDiscoveryInterval": "Kliens felderítési intervallum", "LabelEnableDlnaClientDiscoveryIntervalHelp": "A Jellyfin által végrehajtott SSDP keresések időtartamát határozza meg másodpercben.", "LabelEnableDlnaDebugLogging": "DLNA hibakeresési naplózás engedélyezése", "LabelEnableDlnaDebugLoggingHelp": "Ez nagy naplófájlokat hoz létre és csak hibaelhárítás céljából használható.", "LabelEnableDlnaPlayTo": "DLNA Play To engedélyezése", - "LabelEnableDlnaPlayToHelp": "Felismerheti a hálózaton belüli eszközöket, és lehetővé teszi azok távvezérlését.", + "LabelEnableDlnaPlayToHelp": "Felismerheti a hálózaton belüli eszközöket, és lehetővé teszi azok vezérlését.", "LabelEnableDlnaServer": "DLNA szerver engedélyezése", "LabelEnableDlnaServerHelp": "Lehetővé teszi a hálózaton található UPnP eszközöknek, hogy böngésszenek és lejátszanak tartalmat.", "LabelEnableSingleImageInDidlLimit": "Korlátozás egyetlen beágyazott képre", @@ -881,8 +881,8 @@ "LabelFont": "Betűtípus:", "LabelFormat": "Formátum:", "LabelFriendlyName": "Könnyen megjegyezhető név:", - "LabelServerNameHelp": "Ez a név kerül a Szerver azonosítására és alapértelmezetten a számítógép neve kerül felhasználásra.", - "LabelGroupMoviesIntoCollectionsHelp": "A filmlisták megjelenítésekor a gyűjteményhez tartozó filmek egy csoportos elemként jelennek meg.", + "LabelServerNameHelp": "Ez a név kerül a Szerver azonosítására és alapértelmezetten a hoszt neve kerül felhasználásra.", + "LabelGroupMoviesIntoCollectionsHelp": "A filmlisták megjelenítésekor a gyűjteményben lévő filmek egy csoportos elemként jelennek meg.", "LabelH264Crf": "H264 enkóder CRF:", "LabelHomeNetworkQuality": "Otthoni hálózat minősége:", "LabelHttpsPort": "Helyi HTTPS port száma:", @@ -921,7 +921,7 @@ "LabelMovieCategories": "Film kategóriák:", "LabelMoviePrefix": "Film előtag:", "LabelMoviePrefixHelp": "Ha a filmcímekhez előtagot használsz, írd be ide, hogy a szerver megfelelően kezelje.", - "LabelMovieRecordingPath": "Filmfelvételi útvonal (opcionális):", + "LabelMovieRecordingPath": "Filmfelvételi útvonal:", "LabelMusicStreamingTranscodingBitrate": "Zene átkódolási bitráta:", "LabelNewName": "Új név:", "LabelNewsCategories": "Hírek kategóriái:", @@ -949,7 +949,7 @@ "LabelScheduledTaskLastRan": "Utoljára futtatva: {0}, Időtartam: {1}.", "LabelScreensaver": "Képernyővédő:", "LabelSerialNumber": "Sorozatszám", - "LabelSeriesRecordingPath": "Sorozatfelvétel útvonala (opcionális):", + "LabelSeriesRecordingPath": "Sorozatfelvétel útvonala:", "LabelServerHost": "Kiszolgáló:", "LabelVersion": "Verzió:", "MessageAreYouSureDeleteSubtitles": "Biztosan törölni szeretnéd ezt a feliratfájlt?", @@ -988,7 +988,7 @@ "LabelValue": "Érték:", "LabelZipCode": "Irányítószám:", "LabelffmpegPath": "FFmpeg útvonal:", - "LabelffmpegPathHelp": "Az ffmpeg alkalmazásfájl elérési útja, vagy az őt tartalmazó mappa.", + "LabelffmpegPathHelp": "Az ffmpeg alkalmazásfájl elérési útja vagy az őt tartalmazó mappa.", "Large": "Nagy", "LearnHowYouCanContribute": "Ismerd meg, hogyan járulhatsz hozzá.", "LeaveBlankToNotSetAPassword": "Ha nem szeretnél jelszót beállítani, hagyd ezt a mezőt üresen.", @@ -1083,7 +1083,7 @@ "OptionAllowContentDownloading": "Média letöltésének és szinkronizálásának engedélyezése", "OptionAllowLinkSharingHelp": "Csak a médiaadatokat tartalmazó weboldalak oszthatók meg. A médiafájlok soha nem oszthatók meg nyilvánosan. A megosztás időlimithez van kötve, és lejár {0} nap elteltével.", "OptionAllowManageLiveTv": "Élő TV felvételkezelés engedélyezése", - "OptionAllowMediaPlaybackTranscodingHelp": "Az átkódoláshoz való hozzáférés korlátozása lejátszási hibákat okozhat a Jellyfin alkalmazásokban a nem támogatott médiaformátumok miatt.", + "OptionAllowMediaPlaybackTranscodingHelp": "Az átkódoláshoz való hozzáférés korlátozása lejátszási hibákat okozhat a kliens alkalmazásokban a nem támogatott médiaformátumok miatt.", "OptionAllowRemoteSharedDevicesHelp": "A DLNA eszközöket mindaddig megosztottnak tekintjük, amíg a felhasználó meg nem kezdi azok irányítását.", "OptionAllowSyncTranscoding": "Engedélyezze a média letöltését és szinkronizálását, amely átkódolást igényel", "OptionAllowVideoPlaybackRemuxing": "Olyan videólejátszás engedélyezése, amely átalakítást igényel újrakódolás nélkül", @@ -1092,7 +1092,7 @@ "OptionAuto": "Auto", "OptionAutomatic": "Auto", "OptionAutomaticallyGroupSeries": "A több mappában elosztott sorozat automatikus összevonása", - "OptionAutomaticallyGroupSeriesHelp": "Ha engedélyezve van, a több mappában elosztott sorozat automatikusan egyesül egy sorozatba.", + "OptionAutomaticallyGroupSeriesHelp": "A több mappában elosztott sorozat automatikusan egyesül egy sorozatba.", "OptionBlockBooks": "Könyvek", "OptionBlockLiveTvChannels": "Élő TV csatornák", "OptionBlockMusic": "Zene", @@ -1101,11 +1101,11 @@ "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", "OptionContinuing": "Folytatva", "OptionDateAddedImportTime": "Használja a könyvtárba beolvasási dátumot", - "OptionDisableUserHelp": "Ha letiltod, a szerver nem engedélyezi a felhasználó csatlakozását. A meglévő kapcsolatok azonnal megszűnnek.", + "OptionDisableUserHelp": "A szerver nem engedélyezi a felhasználó csatlakozását. A meglévő kapcsolatok azonnal megszűnnek.", "OptionDisplayFolderView": "Az egyszerű média mappák mappanézetének megjelenítése", "OptionDisplayFolderViewHelp": "Jelenítse meg a mappákat a többi médiakönyvtár mellett. Ez hasznos lehet, ha egyszerű mappa nézeteket szeretnél látni.", "OptionDownloadImagesInAdvance": "Képek előzetes letöltése", - "OptionDownloadImagesInAdvanceHelp": "Alapértelmezés szerint a legtöbb kép csak akkor töltődik le, ha azt egy Jellyfin alkalmazás kéri. Engedélyezd ezt az opciót az összes kép előzetes letöltéséhez, mikor új médiát importál. Ez jelentősen hosszabb könyvtár vizsgálatot eredményezhet.", + "OptionDownloadImagesInAdvanceHelp": "Alapértelmezés szerint a legtöbb kép csak akkor töltődik le, ha azt egy kliens kéri. Engedélyezd ezt az opciót az összes kép előzetes letöltéséhez, mikor új médiát importál. Ez jelentősen hosszabb könyvtár vizsgálatot eredményezhet.", "OptionDownloadPrimaryImage": "Elsődleges", "OptionDvd": "DVD", "OptionEmbedSubtitles": "Beágyazva tárolóba", @@ -1126,7 +1126,7 @@ "OptionEveryday": "Minden nap", "OptionHideUserFromLoginHelp": "Hasznos a privát vagy a rejtett rendszergazdák számára. A felhasználónak kézzel kell bejelentkeznie a felhasználónevének és jelszavának megadásával.", "OptionIgnoreTranscodeByteRangeRequests": "Figyelmen kívül hagyja a transzkód bájt tartomány kéréseket", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Ha engedélyezve van, ezeket a kéréseket tiszteletben tartja, viszont figyelmen kívül hagyja a bájt tartomány fejlécét.", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Ezeket a kéréseket tiszteletben tartja, viszont figyelmen kívül hagyja a bájt tartomány fejlécét.", "OptionIsHD": "HD", "OptionIsSD": "SD", "OptionMax": "Max", @@ -1266,7 +1266,7 @@ "LabelMaxResumePercentageHelp": "A címeket teljesen lejátszottnak tekintjük, ha ezen idő után fejezed be.", "LabelMaxStreamingBitrateHelp": "Adj meg egy maximum bitrátát a streameléshez.", "LabelMinResumePercentageHelp": "A címeket nem lejátszottnak tekintjük, ha ez alatt az idő alatt fejezed be.", - "LabelMusicStreamingTranscodingBitrateHelp": "Határozz meg egy streamelési max bitrátát a zenékhez.", + "LabelMusicStreamingTranscodingBitrateHelp": "Határozz meg egy streamelési maximális bitrátát a zenékhez.", "DashboardVersionNumber": "Verzió: {0}", "DashboardServerName": "Szerver: {0}", "LabelWeb": "Web:", @@ -1299,7 +1299,7 @@ "Guide": "Műsorújság", "H264CrfHelp": "A Constant Rate Factor (CRF) az alapértelmezett minőségi beállítás az x264 enkóderhez. Az értékek 0 és 51 között állíthatók, ahol az alacsonyabb érték jobb minőséget eredményez (nagyobb fájl méret mellett). Az ajánlott érték 18 és 28 között van. Az x264 alapértelmezett beállítása 23, ez lehet kiindulási alap.", "HeaderAddScheduledTaskTrigger": "Vezérlő Hozzáadása", - "HeaderApiKeysHelp": "A külső alkalmazásoknak egy API kulcsra van szükésge, hogy kommunikáljanak a Jellyfin szerverrel. A kulcsokat egy Jellyfin fiókkal történő belépéssel lehet megkapni, vagy kézileg felvenni egy alkalmazáshoz tartozó kulcsot.", + "HeaderApiKeysHelp": "A külső alkalmazásoknak egy API kulcsra van szükésge, hogy kommunikáljanak a Szerverrel. A kulcsokat egy normális fiókkal történő belépéssel lehet megkapni, vagy kézileg felvenni egy alkalmazáshoz tartozó kulcsot.", "HeaderBranding": "Személyes arculat", "HeaderContinueListening": "Folyamatban lévő zenék", "HeaderDeleteTaskTrigger": "Feladatvezérlő törlése", @@ -1312,7 +1312,7 @@ "HeaderGuideProviders": "TV műsorújság Szolgáltatók", "HeaderHome": "Kezdőlap", "HeaderUpcomingOnTV": "Következő TV műsorok", - "ImportFavoriteChannelsHelp": "Ha engedélyezve van, csak a tuner eszközön kedvencként megjelölt csatornák kerülnek importálásra.", + "ImportFavoriteChannelsHelp": "Csak a tuner eszközön kedvencként megjelölt csatornák kerülnek importálásra.", "LabelAlbumArtHelp": "A használandó PN érték az albumborítók esetében, mely a upnp:albumArtURI dlna:profileID tulajdonságában szerepel. Néhány eszköz meghatározott értéket vár el, függetlenül a kép méretétől.", "LabelAlbumArtMaxHeight": "Albumborító maximális magasság:", "LabelAlbumArtMaxHeightHelp": "Albumborító maximális magasság mely upnp:albumArtURI kiajánlásra kerül.", @@ -1329,7 +1329,7 @@ "LabelEmbedAlbumArtDidl": "Albumborító beágyazása a Didl-be", "LabelEmbedAlbumArtDidlHelp": "Néhány eszköz ezt a megoldást részesíti előnyben az albumborítók esetében. Mások esetlegesen lejátszási hibát jeleznek, ha ez az opció engedélyezve van.", "LabelEnableBlastAliveMessages": "Blast alive üzenetek", - "LabelHttpsPortHelp": "A TCP port száma, melyen a Jellyfin HTTPS szervere figyel.", + "LabelHttpsPortHelp": "A TCP port száma, melyen a HTTPS szerver figyel.", "LabelIconMaxHeight": "Ikon maximális magasság:", "LabelIconMaxHeightHelp": "Ikon maximális magasság, mely az upnp:icon keresztül kiajánlásra kerül.", "LabelIconMaxWidth": "Ikon maximális szélesség:", @@ -1338,7 +1338,7 @@ "LabelKeepUpTo": "Őrizd meg:", "LabelKodiMetadataUser": "Mentsd el a következő felhasználó megtekintési adatát az NFO-ba:", "LabelKodiMetadataUserHelp": "A kiválasztott felhasználó megtekintési adata elmentésre kerül az NFO fájlokba, melyet azután más alkalmazások használhatnak.", - "LabelLocalHttpServerPortNumberHelp": "A TCP port száma, melyen a Jellyfin HTTP szerver figyel.", + "LabelLocalHttpServerPortNumberHelp": "A TCP port száma, melyen a HTTP szerver figyel.", "UserAgentHelp": "Adj meg egy egyedi HTTP user-agent fejlécet.", "XmlDocumentAttributeListHelp": "Ezek a tulajdonságok minden XML válaszüzenet gyökér elemére alkalmazásra kerülnek.", "Thumb": "Miniatűr", @@ -1429,8 +1429,8 @@ "PasswordResetProviderHelp": "Válassz egy jelszó-visszaállítási szolgáltatót, amelyet akkor kell használni, amikor a felhasználó jelszó-visszaállítást kér", "OptionResElement": "res elem", "OptionReportByteRangeSeekingWhenTranscodingHelp": "Erre olyan készülékek esetében van szükség, amelyek időigénye nem nagyon jó.", - "OptionPlainVideoItemsHelp": "Ha engedélyezve van, akkor az összes videót a DIDL-ben \"object.item.videoItem\" -ként ábrázolja, nem pedig egy specifikusabb típusként, például \"object.item.videoItem.movie\" .", - "OptionPlainStorageFoldersHelp": "Ha engedélyezve van, akkor az összes mappa a DIDL-ben \"object.container.storageFolder\" lesz, nem pedig egy specifikusabb típusként, például \"object.container.person.musicArtist\".", + "OptionPlainVideoItemsHelp": "Az összes videót a DIDL-ben \"object.item.videoItem\" -ként ábrázolja, nem pedig egy specifikusabb típusként, például \"object.item.videoItem.movie\" .", + "OptionPlainStorageFoldersHelp": "Az összes mappa a DIDL-ben \"object.container.storageFolder\" lesz, nem pedig egy specifikusabb típusként, például \"object.container.person.musicArtist\".", "OptionHlsSegmentedSubtitles": "HLS szegmentált feliratok", "OptionEquals": "Egyenlő", "OptionForceRemoteSourceTranscoding": "A távoli médiaforrások (például az élő TV) átkódolásának kényszerítése", @@ -1499,7 +1499,7 @@ "LabelNightly": "Éjszakai", "LabelStable": "Stabil", "LabelChromecastVersion": "Chromecast verzió", - "LabelEnableHttpsHelp": "Engedélyezi a kiszolgálónak a kommunikációt HTTPS protokollon keresztül. Érvényes tanúsítványt is be kell állítani az érvénybe léptetéshez.", + "LabelEnableHttpsHelp": "Figyelés a megadott HTTPS porton. Érvényes tanúsítványt is be kell állítani az érvénybe léptetéshez.", "LabelRequireHttpsHelp": "Bekapcsolást követően minden egyes HTTP-kérést átirányít HTTPS protokollra. A már meglévő HTTPS kéréseket nem módosítja.", "LabelRequireHttps": "HTTPS megkövetelése", "LabelEnableHttps": "HTTPS engedélyezése", @@ -1564,5 +1564,8 @@ "ClearQueue": "Sor ürítése", "StopPlayback": "Lejátszás leállítása", "ViewAlbumArtist": "Album előadójának megtekintése", - "ButtonPlayer": "Lejátszó" + "ButtonPlayer": "Lejátszó", + "PreviousTrack": "Ugrás az előzőhöz", + "NextTrack": "Ugrás a következőre", + "LabelUnstable": "Instabil" } diff --git a/src/strings/ja.json b/src/strings/ja.json index 11a2050bb7..53d3650cef 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -375,7 +375,7 @@ "HeaderItems": "アイテム", "HeaderKeepRecording": "録画を続ける", "HeaderKeepSeries": "シリーズを続ける", - "HeaderKodiMetadataHelp": "NFOメタデータを有効または無効にするには、Jellyfinライブラリ設定でライブラリを編集し、メタデータ保存機能セクションを見つけます。", + "HeaderKodiMetadataHelp": "NFOメタデータを有効または無効にするには、ライブラリを編集し「メタデータサーバー」の項目にて変更できます。", "HeaderLatestEpisodes": "最新のエピソード", "HeaderLatestMedia": "最新のメディア", "HeaderLatestMovies": "最新のムービー", @@ -423,7 +423,7 @@ "HeaderPreferredMetadataLanguage": "優先するメタデータ言語", "HeaderProfile": "プロファイル", "HeaderProfileInformation": "プロファイル情報", - "HeaderProfileServerSettingsHelp": "これらの値はJellyfinサーバーがそれ自体をデバイスに提示する方法を制御します。", + "HeaderProfileServerSettingsHelp": "これらの設定はサーバーがクライアントに提示する方法を示しています。", "HeaderRecentlyPlayed": "最近再生した", "HeaderRecordingOptions": "録画設定", "HeaderRecordingPostProcessing": "録画後の処理", @@ -441,13 +441,13 @@ "HeaderSecondsValue": "{0} 秒", "HeaderSelectCertificatePath": "証明書のパスを選択", "HeaderSelectMetadataPath": "メタデータのパスを選択", - "HeaderSelectMetadataPathHelp": "メタデータを保存するパスを参照または入力します。 フォルダは書き込み可能でなければなりません。", + "HeaderSelectMetadataPathHelp": "メタデータの保存先を参照またはパスを入力してください。 フォルダは書き込み可能でなければなりません。", "HeaderSelectPath": "パスの選択", "HeaderSelectServer": "サーバーの選択", "HeaderSelectServerCachePath": "サーバーキャッシュのパスを選択", "HeaderSelectServerCachePathHelp": "サーバーキャッシュファイルに使用するパスを参照または入力します。 フォルダは書き込み可能でなければなりません。", "HeaderSelectTranscodingPath": "トランスコーディング用の一時パスの選択", - "HeaderSelectTranscodingPathHelp": "一時ファイルのトランスコードに使用するパスを参照または入力します。 フォルダは書き込み可能でなければなりません。", + "HeaderSelectTranscodingPathHelp": "トランスコードファイルの保存先を参照またはパスを入力してください。 フォルダは書き込み可能でなければなりません。", "HeaderSendMessage": "メッセージの送信", "HeaderSeries": "シリーズ", "HeaderSeriesOptions": "シリーズオプション", @@ -633,7 +633,7 @@ "OneChannel": "1チャンネル", "TabDevices": "デバイス", "ValueContainer": "コンテナ: {0}", - "ImportFavoriteChannelsHelp": "有効にすると、チューナーのデバイスのお気に入りのチャンネルのみインポートされます。", + "ImportFavoriteChannelsHelp": "チューナーでのお気に入りのチャンネルのみインポートされます。", "MusicAlbum": "ミュージックアルバム", "OptionDownloadLogoImage": "ロゴ", "OptionEnableAccessToAllChannels": "すべてのチャンネルへのアクセスを有効化", @@ -657,9 +657,9 @@ "LabelCriticRating": "評論家の評価:", "LabelCurrentPassword": "現在のパスワード:", "LabelCustomCss": "カスタムCSS:", - "LabelCustomCssHelp": "ウェブインターフェースにカスタムスタイリングを適応する。", + "LabelCustomCssHelp": "ウェブインターフェースにカスタムスタイルを適応する。", "LabelCustomDeviceDisplayName": "表示名:", - "LabelEnableDlnaClientDiscoveryInterval": "クライアント探索間隔 (秒)", + "LabelEnableDlnaClientDiscoveryInterval": "クライアント探索間隔", "LabelParentalRating": "個人評価:", "LabelPassword": "パスワード:", "LabelPasswordConfirm": "パスワード (確認):", @@ -682,7 +682,7 @@ "LabelBirthDate": "誕生日:", "LabelBitrate": "ビットレート:", "LabelBirthYear": "生年:", - "LabelBlastMessageInterval": "アライブメッセージ間隔 (秒)", + "LabelBlastMessageInterval": "アライブメッセージ間隔", "LabelCache": "キャッシュ:", "LabelDisplayMode": "表示モード:", "LabelDisplayOrder": "表示順:", @@ -1106,7 +1106,7 @@ "ValueTimeLimitSingleHour": "タイムリミット: 1 時間", "Wednesday": "水曜日", "LabelPreferredDisplayLanguage": "優先する表示言語:", - "ImportMissingEpisodesHelp": "有効にすると、所有してないエピソードの情報がJellyfinデータベースにインポートされ、シーズンとシリーズに表示されます。これは、ライブラリスキャンに莫大な時間が掛かる可能性があります。", + "ImportMissingEpisodesHelp": "所有してないエピソードの情報がデータベースにインポートされ、シーズンとシリーズ内に表示されます。ライブラリの読み込み時間が非常に長くなる可能性があります。", "LabelBindToLocalNetworkAddress": "ローカルネットワークアドレスにバインド:", "LabelDownMixAudioScale": "ダウンミキシング時の音声ブースト:", "HeaderNavigation": "ナビゲーション", @@ -1117,7 +1117,7 @@ "LabelDroppedFrames": "ドロップフレーム:", "LabelDisplayMissingEpisodesWithinSeasons": "シーズン中の見つからなかったエピソードを表示", "LabelCustomDeviceDisplayNameHelp": "任意の表示名を提供するか、空白のままにしてデバイスネームで報告する。", - "LabelArtistsHelp": "分けますと使用;", + "LabelArtistsHelp": "複数のアーティストは「;」で分ける。", "Identify": "識別する", "TabRecordings": "録画", "Recordings": "録画", @@ -1132,7 +1132,7 @@ "LabelEveryXMinutes": "毎:", "LabelEnableSingleImageInDidlLimit": "単一の埋め込み画像に制限", "LabelEnableBlastAliveMessages": "アライブメッセージを配信する", - "LabelDateAddedBehaviorHelp": "メタデータある場合、これらのオプションの前にメタデータ使います。", + "LabelDateAddedBehaviorHelp": "メタデータがある場合、これらのオプションの前に優先します。", "AskAdminToCreateLibrary": "管理者にライブラリを作成する依頼をしてください。", "AllowFfmpegThrottling": "トランスコードをスロットルする", "Episode": "エピソード", @@ -1174,7 +1174,7 @@ "LabelEmbedAlbumArtDidlHelp": "一部のデバイスでは、アルバムアートを取得するためにこの方法が好まれています。その他のデバイスでは、このオプションを有効にしても再生できない場合があります。", "LabelDownMixAudioScaleHelp": "ダウンミックス時にオーディオの音量を増幅します。値が 1 の場合、元の音量を維持します。", "LabelEnableHttps": "HTTPS を有効にする", - "LabelEnableDlnaPlayToHelp": "ネットワーク内のデバイスを検出し、それらをリモートコントロールできるようにします。", + "LabelEnableDlnaPlayToHelp": "ネットワーク内のデバイスを検出し、それらをリモートで操作できるようにします。", "LabelEnableDlnaPlayTo": "DLNA 再生を有効にする", "LabelEnableDlnaDebugLoggingHelp": "巨大なログファイルを作成します。トラブルシューティングでの必要な際にだけ使用してください。", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Jellyfin が実行する SSDP 検索の間隔を決めます(秒単位)。", diff --git a/yarn.lock b/yarn.lock index 94d714872d..17c2b176ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5262,10 +5262,10 @@ gulp-sourcemaps@^2.6.5: strip-bom-string "1.X" through2 "2.X" -gulp-terser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/gulp-terser/-/gulp-terser-1.2.1.tgz#d5b0ee7ebb1107c1a7bb92449629b07a1951b896" - integrity sha512-wFWfO6hqPwHbzyulA67ZiC2mFXQO4bPno82cvL/V6qZsFXvYxKeeFuLSNsv+i/POhyfNJLkDrcye4rRxkvJUAA== +gulp-terser@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/gulp-terser/-/gulp-terser-1.3.0.tgz#6423fdb7dd15cc376e28063b5271271a928084bd" + integrity sha512-EvizE1LJLfOh3/EmpJoq9iqYziObOkTzFgN4KvxfB0ICp3+W5H+MOO9B7Xq5Iuu9N+RKByNJLmqR+Ph13U1vtQ== dependencies: plugin-error "^1.0.1" terser ">=4"