From 6c18b655e0b4823e4b7a6377cc1ebc4fbc27ab12 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 1 Apr 2020 17:53:14 +0200 Subject: [PATCH 001/181] Implement syncplay frontend --- src/assets/css/videoosd.css | 2 +- src/components/htmlaudioplayer/plugin.js | 42 ++ src/components/htmlvideoplayer/plugin.js | 25 + src/components/playback/playbackmanager.js | 21 + src/components/playerstats/playerstats.js | 31 +- src/components/serverNotifications.js | 6 +- src/components/syncplay/groupSelectionMenu.js | 140 ++++ src/components/syncplay/syncplaymanager.js | 600 ++++++++++++++++++ src/scripts/librarymenu.js | 36 +- src/scripts/site.js | 1 + src/strings/en-us.json | 15 + 11 files changed, 915 insertions(+), 4 deletions(-) create mode 100644 src/components/syncplay/groupSelectionMenu.js create mode 100644 src/components/syncplay/syncplaymanager.js diff --git a/src/assets/css/videoosd.css b/src/assets/css/videoosd.css index f4f198325b..50cb41021b 100644 --- a/src/assets/css/videoosd.css +++ b/src/assets/css/videoosd.css @@ -30,7 +30,7 @@ opacity: 0; } -.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) { +.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) { display: none; } diff --git a/src/components/htmlaudioplayer/plugin.js b/src/components/htmlaudioplayer/plugin.js index c3a5484aca..2580481051 100644 --- a/src/components/htmlaudioplayer/plugin.js +++ b/src/components/htmlaudioplayer/plugin.js @@ -171,6 +171,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp elem.addEventListener('pause', onPause); elem.addEventListener('playing', onPlaying); elem.addEventListener('play', onPlay); + elem.addEventListener('waiting', onWaiting); } function unBindEvents(elem) { @@ -180,6 +181,7 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp elem.removeEventListener('pause', onPause); elem.removeEventListener('playing', onPlaying); elem.removeEventListener('play', onPlay); + elem.removeEventListener('waiting', onWaiting); } self.stop = function (destroyPlayer) { @@ -294,6 +296,10 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp events.trigger(self, 'pause'); } + function onWaiting() { + events.trigger(self, 'waiting'); + } + function onError() { var errorCode = this.error ? (this.error.code || 0) : 0; @@ -450,6 +456,21 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp return false; }; + HtmlAudioPlayer.prototype.setPlaybackRate = function (value) { + var mediaElement = this._mediaElement; + if (mediaElement) { + mediaElement.playbackRate = value; + } + }; + + HtmlAudioPlayer.prototype.getPlaybackRate = function () { + var mediaElement = this._mediaElement; + if (mediaElement) { + return mediaElement.playbackRate; + } + return null; + }; + HtmlAudioPlayer.prototype.setVolume = function (val) { var mediaElement = this._mediaElement; if (mediaElement) { @@ -493,5 +514,26 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; + var supportedFeatures; + + function getSupportedFeatures() { + var list = []; + var audio = document.createElement('audio'); + + if (typeof audio.playbackRate === "number") { + list.push("PlaybackRate"); + } + + return list; + } + + HtmlAudioPlayer.prototype.supports = function (feature) { + if (!supportedFeatures) { + supportedFeatures = getSupportedFeatures(); + } + + return supportedFeatures.indexOf(feature) !== -1; + }; + return HtmlAudioPlayer; }); diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index f87fd19462..064e4155ee 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -785,6 +785,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa videoElement.removeEventListener('play', onPlay); videoElement.removeEventListener('click', onClick); videoElement.removeEventListener('dblclick', onDblClick); + videoElement.removeEventListener('waiting', onWaiting); videoElement.parentNode.removeChild(videoElement); } @@ -915,6 +916,10 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa events.trigger(self, 'pause'); } + function onWaiting() { + events.trigger(self, 'waiting'); + } + function onError() { var errorCode = this.error ? (this.error.code || 0) : 0; var errorMessage = this.error ? (this.error.message || '') : ''; @@ -1348,6 +1353,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa videoElement.addEventListener('play', onPlay); videoElement.addEventListener('click', onClick); videoElement.addEventListener('dblclick', onDblClick); + videoElement.addEventListener('waiting', onWaiting); document.body.insertBefore(dlg, document.body.firstChild); videoDialog = dlg; @@ -1436,6 +1442,10 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa list.push('AirPlay'); } + if (typeof video.playbackRate === "number") { + list.push("PlaybackRate"); + } + list.push('SetBrightness'); list.push('SetAspectRatio'); @@ -1656,6 +1666,21 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return false; }; + HtmlVideoPlayer.prototype.setPlaybackRate = function (value) { + var mediaElement = this._mediaElement; + if (mediaElement) { + mediaElement.playbackRate = value; + } + }; + + HtmlVideoPlayer.prototype.getPlaybackRate = function () { + var mediaElement = this._mediaElement; + if (mediaElement) { + return mediaElement.playbackRate; + } + return null; + }; + HtmlVideoPlayer.prototype.setVolume = function (val) { var mediaElement = this._mediaElement; if (mediaElement) { diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index ee85f9acb1..923092cd3d 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3777,6 +3777,24 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; + PlaybackManager.prototype.setPlaybackRate = function (value, player) { + player = player || this._currentPlayer; + + if (player) { + player.setPlaybackRate(value); + } + }; + + PlaybackManager.prototype.getPlaybackRate = function (player) { + player = player || this._currentPlayer; + + if (player) { + return player.getPlaybackRate(val); + } + + return null; + }; + PlaybackManager.prototype.instantMix = function (item, player) { player = player || this._currentPlayer; @@ -3887,6 +3905,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (player.supports('SetAspectRatio')) { list.push('SetAspectRatio'); } + if (player.supports('PlaybackRate')) { + list.push('PlaybackRate'); + } } return list; diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index c0fb369c6c..ad4ce960bf 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -1,4 +1,4 @@ -define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, playMethodHelper, layoutManager, serverNotifications) { +define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplayManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, syncplayManager, playMethodHelper, layoutManager, serverNotifications) { 'use strict'; function init(instance) { @@ -327,6 +327,28 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth return sessionStats; } + function getSyncplayStats() { + var syncStats = []; + var stats = syncplayManager.getStats(); + + syncStats.push({ + label: globalize.translate("LabelSyncplayTimeDiff"), + value: stats.TimeDiff + "ms" + }); + + syncStats.push({ + label: globalize.translate("LabelSyncplayPlaybackDiff"), + value: stats.PlaybackDiff + "ms" + }); + + syncStats.push({ + label: globalize.translate("LabelSyncplaySyncMethod"), + value: stats.SyncMethod + }); + + return syncStats; + } + function getStats(instance, player) { var statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); @@ -383,6 +405,13 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'playMeth name: 'Original Media Info' }); + if (syncplayManager.isSyncplayEnabled()) { + categories.push({ + stats: getSyncplayStats(), + name: 'Syncplay Info' + }); + } + return Promise.resolve(categories); }); } diff --git a/src/components/serverNotifications.js b/src/components/serverNotifications.js index e60f98475b..9776c88bd3 100644 --- a/src/components/serverNotifications.js +++ b/src/components/serverNotifications.js @@ -1,4 +1,4 @@ -define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, events, inputManager, focusManager, appRouter) { +define(['connectionManager', 'playbackManager', 'syncplayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncplayManager, events, inputManager, focusManager, appRouter) { 'use strict'; var serverNotifications = {}; @@ -187,6 +187,10 @@ define(['connectionManager', 'playbackManager', 'events', 'inputManager', 'focus events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]); } } + } else if (msg.MessageType === "SyncplayCommand") { + syncplayManager.processCommand(msg.Data, apiClient); + } else if (msg.MessageType === "SyncplayGroupUpdate") { + syncplayManager.processGroupUpdate(msg.Data, apiClient); } else { events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]); } diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js new file mode 100644 index 0000000000..01141741c1 --- /dev/null +++ b/src/components/syncplay/groupSelectionMenu.js @@ -0,0 +1,140 @@ +define(['events', 'loading', 'connectionManager', 'playbackManager', 'syncplayManager', 'globalize', 'datetime'], function (events, loading, connectionManager, playbackManager, syncplayManager, globalize, datetime) { + 'use strict'; + + function getActivePlayerId() { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; + } + + function emptyCallback() { + // avoid console logs about uncaught promises + } + + function showNewJoinGroupSelection(button) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + sessionId = sessionId ? sessionId : "none"; + + loading.show(); + + apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { + response.json().then(function (groups) { + var inSession = sessionId !== "none"; + + var menuItems = groups.map(function (group) { + var name = datetime.getDisplayRunningTime(group.PositionTicks); + if (!inSession) { + name = group.PlayingItemName + } + return { + name: name, + icon: "group", + id: group.GroupId, + selected: false, + secondaryText: group.Partecipants.join(", ") + } + }); + + if (inSession) { + menuItems.push({ + name: globalize.translate('LabelSyncplayNewGroup'), + icon: "add", + id: "new-group", + selected: true, + secondaryText: globalize.translate('LabelSyncplayNewGroupDescription') + }); + } + + if (menuItems.length === 0) { + require(['toast'], function (alert) { + alert({ + title: globalize.translate('MessageSyncplayNoGroupsAvailable'), + text: globalize.translate('MessageSyncplayNoGroupsAvailable') + }); + }); + loading.hide(); + return; + } + + require(['actionsheet'], function (actionsheet) { + + loading.hide(); + + var menuOptions = { + title: globalize.translate('HeaderSyncplaySelectGroup'), + items: menuItems, + positionTo: button, + resolveOnClick: true, + border: true + }; + + actionsheet.show(menuOptions).then(function (id) { + + if (id == "new-group") { + apiClient.sendSyncplayCommand(sessionId, "NewGroup"); + } else { + apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + GroupId: id + }); + } + }, emptyCallback); + }); + }); + }).catch(function (error) { + loading.hide(); + console.error(error); + }); + } + + function showLeaveGroupSelection(button) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + + loading.show(); + + var menuItems = [{ + name: globalize.translate('LabelSyncplayLeaveGroup'), + icon: "meeting_room", + id: "leave-group", + selected: true, + secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription') + }]; + + require(['actionsheet'], function (actionsheet) { + + loading.hide(); + + var menuOptions = { + title: globalize.translate('HeaderSyncplayEnabled'), + items: menuItems, + positionTo: button, + resolveOnClick: true, + border: true + }; + + actionsheet.show(menuOptions).then(function (id) { + if (id == "leave-group") { + apiClient.sendSyncplayCommand(sessionId, "LeaveGroup"); + } + }, emptyCallback); + }); + } + + function showGroupSelection(button) { + if (syncplayEnabled) { + showLeaveGroupSelection(button); + } else { + showNewJoinGroupSelection(button); + } + } + + var syncplayEnabled = false; + + events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { + syncplayEnabled = enabled; + }); + + return { + show: showGroupSelection + }; +}); diff --git a/src/components/syncplay/syncplaymanager.js b/src/components/syncplay/syncplaymanager.js new file mode 100644 index 0000000000..bde47a9d44 --- /dev/null +++ b/src/components/syncplay/syncplaymanager.js @@ -0,0 +1,600 @@ +define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager'], function (events, globalize, loading, connectionManager, playbackManager) { + 'use strict'; + + function waitForEvent(emitter, eventType) { + return new Promise(function (resolve) { + var callback = function () { + events.off(emitter, eventType, callback); + resolve(arguments); + } + events.on(emitter, eventType, callback); + }); + } + + function displaySyncplayUpdate(message) { + require(['toast'], function (alert) { + alert({ + title: message.Header, + text: message.Text + }); + }); + } + + function getActivePlayerId() { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; + } + + function millisecondsToTicks(milliseconds) { + return milliseconds * 10000; + } + + function ticksToMilliseconds(ticks) { + return ticks / 10000; + } + + function SyncplayManager() { + + var self = this; + + function onPlayerChange() { + bindToPlayer(playbackManager.getCurrentPlayer()); + events.trigger(self, "PlayerChange", [self.currentPlayer]); + } + + function onPlayPauseStateChanged(e) { + events.trigger(self, "PlayPauseStateChange", [self.currentPlayer]); + } + + self.playbackRateSupported = false; + self.syncEnabled = false; + self.maxAcceptedDelaySpeedToSync = 50; // milliseconds + self.maxAcceptedDelaySkipToSync = 300; // milliseconds + self.syncMethodThreshold = 2000; // milliseconds + self.speedUpToSyncTime = 1000; // milliseconds + self.playbackDiffMillis = 0; // used for stats + self.syncMethod = "None"; // used for stats + + function onTimeUpdate(e) { + events.trigger(self, "TimeUpdate", [e]); + + if (self.lastCommand && self.lastCommand.Command === 'Play' && !self.isBuffering()) { + var currentTime = new Date(); + var playAtTime = self.lastCommand.When; + + var state = playbackManager.getPlayerState().PlayState; + // Estimate PositionTicks on server + var ServerPositionTicks = self.lastCommand.PositionTicks + ((currentTime - playAtTime) - self.timeDiff) * 10000; + // Measure delay that needs to be recovered + // diff might be caused by the player internally starting the playback + var diff = ServerPositionTicks - state.PositionTicks; + var diffMillis = diff / 10000; + + self.playbackDiffMillis = diffMillis; + + // console.debug("Syncplay onTimeUpdate", diffMillis, state.PositionTicks, ServerPositionTicks); + + if (self.syncEnabled) { + var absDiffMillis = Math.abs(diffMillis); + // TODO: SpeedToSync sounds bad on songs + if (self.playbackRateSupported && absDiffMillis > self.maxAcceptedDelaySpeedToSync && absDiffMillis < self.syncMethodThreshold) { + // SpeedToSync method + var speed = 1 + diffMillis / self.speedUpToSyncTime; + + self.currentPlayer.setPlaybackRate(speed); + self.syncEnabled = false; + self.showSyncIcon("SpeedToSync (x" + speed + ")"); + + self.syncTimeout = setTimeout(() => { + self.currentPlayer.setPlaybackRate(1); + self.syncEnabled = true; + self.clearSyncIcon(); + }, self.speedUpToSyncTime); + } else if (absDiffMillis > self.maxAcceptedDelaySkipToSync) { + // SkipToSync method + playbackManager.syncplay_seek(ServerPositionTicks); + self.syncEnabled = false; + self.showSyncIcon("SkipToSync"); + + self.syncTimeout = setTimeout(() => { + self.syncEnabled = true; + self.clearSyncIcon(); + }, self.syncMethodThreshold / 2); + } + } + } + } + + self.lastPlaybackWaiting = null; // used to determine if player's buffering + self.minBufferingThresholdMillis = 1000; + + // TODO: implement group wait + function onPlaying() { + self.lastPlaybackWaiting = null; + events.trigger(self, "PlayerPlaying"); + } + + // TODO: implement group wait + function onWaiting() { + if (!self.lastPlaybackWaiting) { + self.lastPlaybackWaiting = new Date(); + } + events.trigger(self, "PlayerWaiting"); + } + + self.isBuffering = function () { + if (self.lastPlaybackWaiting === null) return false; + return (new Date() - self.lastPlaybackWaiting) > self.minBufferingThresholdMillis; + } + + function bindToPlayer(player) { + if (player !== self.currentPlayer) { + releaseCurrentPlayer(); + self.currentPlayer = player; + if (!player) return; + } + events.on(player, "pause", onPlayPauseStateChanged); + events.on(player, "unpause", onPlayPauseStateChanged); + events.on(player, "timeupdate", onTimeUpdate); + events.on(player, "playing", onPlaying); + events.on(player, "waiting", onWaiting); + self.playbackRateSupported = player.supports("PlaybackRate"); + } + + function releaseCurrentPlayer() { + var player = self.currentPlayer; + if (player) { + events.off(player, "pause", onPlayPauseStateChanged); + events.off(player, "unpause", onPlayPauseStateChanged); + events.off(player, "timeupdate", onTimeUpdate); + events.off(player, "playing", onPlaying); + events.off(player, "waiting", onWaiting); + if (self.playbackRateSupported) { + player.setPlaybackRate(1); + } + self.currentPlayer = null; + self.playbackRateSupported = false; + } + } + + self.currentPlayer = null; + + events.on(playbackManager, "playerchange", onPlayerChange); + bindToPlayer(playbackManager.getCurrentPlayer()); + + self.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled + self.syncplayReady = false; // Syncplay is ready after first ping to server + + self.processGroupUpdate = function (cmd, apiClient) { + switch (cmd.Type) { + case 'PrepareSession': + var serverId = apiClient.serverInfo().Id; + playbackManager.play({ + ids: cmd.Data.ItemIds, + startPositionTicks: cmd.Data.StartPositionTicks, + mediaSourceId: cmd.Data.MediaSourceId, + audioStreamIndex: cmd.Data.AudioStreamIndex, + subtitleStreamIndex: cmd.Data.SubtitleStreamIndex, + startIndex: cmd.Data.StartIndex, + serverId: serverId + }).then(function () { + waitForEvent(self, "PlayerChange").then(function () { + playbackManager.pause(); + var sessionId = getActivePlayerId(); + if (!sessionId) { + console.error("Missing sessionId!"); + displaySyncplayUpdate({ + Text: "Failed to enable Syncplay!" + }); + return; + } + // Sometimes JoinGroup fails, maybe because server hasn't been updated yet + setTimeout(() => { + apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + GroupId: cmd.GroupId + }); + }, 500); + }); + }); + break; + case 'UserJoined': + displaySyncplayUpdate({ + Text: globalize.translate('MessageSyncplayUserJoined', cmd.Data), + }); + break; + case 'UserLeft': + displaySyncplayUpdate({ + Text: globalize.translate('MessageSyncplayUserLeft', cmd.Data), + }); + break; + case 'GroupJoined': + displaySyncplayUpdate({ + Text: globalize.translate('MessageSyncplayEnabled'), + }); + // Enable Syncplay + self.syncplayEnabledAt = new Date(cmd.Data); + self.syncplayReady = false; + events.trigger(self, "SyncplayEnabled", [true]); + waitForEvent(self, "SyncplayReady").then(function () { + self.processCommand(self.queuedCommand, apiClient); + self.queuedCommand = null; + }); + self.injectPlaybackManager(); + self.startPing(); + break; + case 'NotInGroup': + case 'GroupLeft': + displaySyncplayUpdate({ + Text: globalize.translate('MessageSyncplayDisabled'), + }); + // Disable Syncplay + self.syncplayEnabledAt = null; + self.syncplayReady = false; + events.trigger(self, "SyncplayEnabled", [false]); + self.restorePlaybackManager(); + self.stopPing(); + break; + case 'GroupWait': + displaySyncplayUpdate({ + Text: globalize.translate('MessageSyncplayGroupWait', cmd.Data), + }); + break; + case 'KeepAlive': + break; + default: + console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); + break; + } + } + + self.lastCommand = null; + self.queuedCommand = null; + + self.processCommand = function (cmd, apiClient) { + if (cmd === null) return; + + if (!self.isSyncplayEnabled()) { + console.debug("Syncplay processCommand: ignoring command", cmd); + return; + } + + if (!self.syncplayReady) { + console.debug("Syncplay processCommand: queued command", cmd); + self.queuedCommand = cmd; + return; + } + + cmd.When = new Date(cmd.When); + + if (cmd.When < self.syncplayEnabledAt) { + console.debug("Syncplay processCommand: ignoring old command", cmd); + return; + } + + // Check if new command differs from last one + if (self.lastCommand && + self.lastCommand.When === cmd.When && + self.lastCommand.PositionTicks === cmd.PositionTicks && + self.Command === cmd.Command + ) { + console.debug("Syncplay processCommand: ignoring duplicate command", cmd); + return; + } + + self.lastCommand = cmd; + console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks); + + switch (cmd.Command) { + case 'Play': + self.schedulePlay(cmd.When, cmd.PositionTicks); + break; + case 'Pause': + self.schedulePause(cmd.When, cmd.PositionTicks); + break; + case 'Seek': + self.scheduleSeek(cmd.When, cmd.PositionTicks); + break; + default: + console.error('processSyncplayCommand does not recognize: ' + cmd.Type); + break; + } + } + + self.scheduledCommand = null; + self.syncTimeout = null; + + self.schedulePlay = function (playAtTime, positionTicks) { + self.clearScheduledCommand(); + var currentTime = new Date(); + var playAtTimeLocal = self.serverDateToLocal(playAtTime); + + if (playAtTimeLocal > currentTime) { + var playTimeout = (playAtTimeLocal - currentTime) - self.playerDelay; + playbackManager.syncplay_seek(positionTicks); + + self.scheduledCommand = setTimeout(() => { + playbackManager.syncplay_unpause(); + + self.syncTimeout = setTimeout(() => { + self.syncEnabled = true + }, self.syncMethodThreshold / 2); + + }, playTimeout); + + // console.debug("Syncplay schedulePlay:", playTimeout); + } else { + // Group playback already started + var serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; + playbackManager.syncplay_unpause(); + playbackManager.syncplay_seek(serverPositionTicks); + + self.syncTimeout = setTimeout(() => { + self.syncEnabled = true + }, self.syncMethodThreshold / 2); + } + } + + self.schedulePause = function (pauseAtTime, positionTicks) { + self.clearScheduledCommand(); + var currentTime = new Date(); + var pauseAtTimeLocal = self.serverDateToLocal(pauseAtTime); + + if (pauseAtTimeLocal > currentTime) { + var pauseTimeout = (pauseAtTimeLocal - currentTime) - self.playerDelay; + + self.scheduledCommand = setTimeout(() => { + playbackManager.syncplay_pause(); + setTimeout(() => { + playbackManager.syncplay_seek(positionTicks); + }, 800); + + }, pauseTimeout); + } else { + playbackManager.syncplay_pause(); + setTimeout(() => { + playbackManager.syncplay_seek(positionTicks); + }, 800); + } + } + + self.scheduleSeek = function (seekAtTime, positionTicks) { + self.schedulePause(seekAtTime, positionTicks); + } + + self.clearScheduledCommand = function () { + clearTimeout(self.scheduledCommand); + clearTimeout(self.syncTimeout); + + self.syncEnabled = false; + if (self.currentPlayer) { + self.currentPlayer.setPlaybackRate(1); + } + self.clearSyncIcon(); + } + + self.injectPlaybackManager = function () { + if (!self.isSyncplayEnabled()) return; + if (playbackManager.syncplayEnabled) return; + + playbackManager.syncplay_unpause = playbackManager.unpause; + playbackManager.syncplay_pause = playbackManager.pause; + playbackManager.syncplay_seek = playbackManager.seek; + + playbackManager.unpause = self.playRequest; + playbackManager.pause = self.pauseRequest; + playbackManager.seek = self.seekRequest; + playbackManager.syncplayEnabled = true; + } + + self.restorePlaybackManager = function () { + if (self.isSyncplayEnabled()) return; + if (!playbackManager.syncplayEnabled) return; + + playbackManager.unpause = playbackManager.syncplay_unpause; + playbackManager.pause = playbackManager.syncplay_pause; + playbackManager.seek = playbackManager.syncplay_seek; + playbackManager.syncplayEnabled = false; + } + + self.playRequest = function (player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "PlayRequest"); + } + + self.pauseRequest = function (player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "PauseRequest"); + // Pause locally as well, to give the user some little control + playbackManager.syncplay_pause(); + } + + self.seekRequest = function (PositionTicks, player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "SeekRequest", { + PositionTicks: PositionTicks + }); + } + + self.pingIntervalTimeoutGreedy = 1000; + self.pingIntervalTimeoutLowProfile = 60000; + self.greedyPingCount = 3; + + self.pingStop = true; + self.pingIntervalTimeout = self.pingIntervalTimeoutGreedy; + self.pingInterval = null; + self.initTimeDiff = 0; // number of pings + self.timeDiff = 0; // local time minus server time + self.roundTripDuration = 0; + self.notifySyncplayReady = false; + + self.updateTimeDiff = function (pingStartTime, pingEndTime, serverTime) { + self.roundTripDuration = (pingEndTime - pingStartTime); + // The faster the response, the closer we are to the real timeDiff value + // localTime = pingStartTime + roundTripDuration / 2 + // newTimeDiff = localTime - serverTime + var newTimeDiff = (pingStartTime - serverTime) + (self.roundTripDuration / 2); + + // Initial setup + if (self.initTimeDiff === 0) { + self.timeDiff = newTimeDiff; + self.initTimeDiff++ + return; + } + + // As response time gets better, absolute value should decrease + var distanceFromZero = Math.abs(newTimeDiff); + var oldDistanceFromZero = Math.abs(self.timeDiff); + if (distanceFromZero < oldDistanceFromZero) { + self.timeDiff = newTimeDiff; + } + + // Avoid overloading server + if (self.initTimeDiff >= self.greedyPingCount) { + self.pingIntervalTimeout = self.pingIntervalTimeoutLowProfile; + } else { + self.initTimeDiff++; + } + + // console.debug("Syncplay updateTimeDiff:", serverTime, self.timeDiff, self.roundTripDuration, newTimeDiff); + } + + self.requestPing = function () { + if (self.pingInterval === null && !self.pingStop) { + self.pingInterval = setTimeout(() => { + self.pingInterval = null; + + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + + var pingStartTime = new Date(); + apiClient.sendSyncplayCommand(sessionId, "GetUtcTime").then(function (response) { + var pingEndTime = new Date(); + response.text().then(function (utcTime) { + var serverTime = new Date(utcTime); + self.updateTimeDiff(pingStartTime, pingEndTime, serverTime); + + // Alert user that ping is high + if (Math.abs(self.roundTripDuration) >= 1000) { + events.trigger(self, "SyncplayError", [true]); + } else { + events.trigger(self, "SyncplayError", [false]); + } + + // Notify server of ping + apiClient.sendSyncplayCommand(sessionId, "KeepAlive", { + Ping: (self.roundTripDuration / 2) + self.playerDelay + }); + + if (self.notifySyncplayReady) { + self.syncplayReady = true; + events.trigger(self, "SyncplayReady"); + self.notifySyncplayReady = false; + } + + self.requestPing(); + }); + }); + + }, self.pingIntervalTimeout); + } + } + + self.startPing = function () { + self.notifySyncplayReady = true; + self.pingStop = false; + self.initTimeDiff = self.initTimeDiff > self.greedyPingCount ? 1 : self.initTimeDiff; + self.pingIntervalTimeout = self.pingIntervalTimeoutGreedy; + + self.requestPing(); + } + + self.stopPing = function () { + self.pingStop = true; + if (self.pingInterval !== null) { + clearTimeout(self.pingInterval); + self.pingInterval = null; + } + } + + self.serverDateToLocal = function (server) { + // local - server = diff + return new Date(server.getTime() + self.timeDiff); + } + + self.localDateToServer = function (local) { + // local - server = diff + return new Date(local.getTime() - self.timeDiff); + } + + // THIS FEATURE IS CURRENTLY DISABLED + // Mainly because SpeedToSync seems to do the job + // Also because the delay is unreliable and different every time + self.playerDelay = 0; + self.playerDelayMeasured = true; // disable this feature + self.measurePlayerDelay = function (positionTicks) { + if (self.playerDelayMeasured) { + playbackManager.syncplay_seek(positionTicks); + } else { + // Measure playerDelay by issuing a play command + // followed by a pause command after one second + // PositionTicks should be at 1 second minus two times the player delay + loading.show(); + self.currentPlayer.setPlaybackRate(1); + playbackManager.syncplay_seek(0); + // Wait for player to seek + setTimeout(() => { + playbackManager.syncplay_unpause(); + // Play one second of media + setTimeout(() => { + playbackManager.syncplay_pause(); + // Wait for state to get update + setTimeout(() => { + var state = playbackManager.getPlayerState().PlayState; + var delayTicks = millisecondsToTicks(1000) - state.PositionTicks; + var delayMillis = ticksToMilliseconds(delayTicks); + self.playerDelay = delayMillis / 2; + // Make sure delay is not negative + self.playerDelay = self.playerDelay > 0 ? self.playerDelay : 0; + self.playerDelayMeasured = true; + // console.debug("Syncplay PlayerDelay:", self.playerDelay); + // Restore player + setTimeout(() => { + playbackManager.syncplay_seek(positionTicks); + loading.hide(); + }, 800); + }, 1000); + }, 1000); + }, 2000); + } + } + + // Stats + self.isSyncplayEnabled = function () { + return self.syncplayEnabledAt !== null ? true : false; + } + + self.getStats = function () { + return { + TimeDiff: self.timeDiff, + PlaybackDiff: self.playbackDiffMillis, + SyncMethod: self.syncMethod + } + } + + // UI + self.showSyncIcon = function (syncMethod) { + self.syncMethod = syncMethod; + events.trigger(self, "SyncplayError", [true]); + } + + self.clearSyncIcon = function () { + self.syncMethod = "None"; + events.trigger(self, "SyncplayError", [false]); + } + } + + return new SyncplayManager(); +}); diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js index 81a381bff2..8afe5f10f1 100644 --- a/src/scripts/librarymenu.js +++ b/src/scripts/librarymenu.js @@ -1,4 +1,4 @@ -define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, browser, globalize, imageHelper) { +define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncplayManager', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncplayManager, browser, globalize, imageHelper) { 'use strict'; function renderHeader() { @@ -12,6 +12,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' html += ''; html += '
'; html += ''; + html += ''; html += ''; html += ''; html += ''; @@ -30,6 +31,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' headerCastButton = skinHeader.querySelector('.headerCastButton'); headerAudioPlayerButton = skinHeader.querySelector('.headerAudioPlayerButton'); headerSearchButton = skinHeader.querySelector('.headerSearchButton'); + headerSyncButton = skinHeader.querySelector('.headerSyncButton'); lazyLoadViewMenuBarImages(); bindMenuEvents(); @@ -93,6 +95,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } } + headerSyncButton.classList.remove("hide"); + requiresUserRefresh = false; } @@ -147,6 +151,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } headerAudioPlayerButton.addEventListener('click', showAudioPlayer); + headerSyncButton.addEventListener('click', onSyncButtonClicked); if (layoutManager.mobile) { initHeadRoom(skinHeader); @@ -177,6 +182,32 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' }); } + function onSyncButtonClicked() { + var btn = this; + + require(["groupSelectionMenu"], function (groupSelectionMenu) { + groupSelectionMenu.show(btn); + }); + } + + function updateSyncplayIcon(event, enabled) { + var icon = headerSyncButton.querySelector("i"); + if (enabled) { + icon.innerHTML = "sync"; + } else { + icon.innerHTML = "sync_disabled"; + } + } + + function updateSyncplayErrorIcon(event, show_error) { + var icon = headerSyncButton.querySelector("i"); + if (show_error) { + icon.innerHTML = "sync_problem"; + } else { + icon.innerHTML = "sync"; + } + } + function getItemHref(item, context) { return appRouter.getRouteUrl(item, { context: context @@ -799,6 +830,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' var headerCastButton; var headerSearchButton; var headerAudioPlayerButton; + var headerSyncButton; var enableLibraryNavDrawer = layoutManager.desktop; var skinHeader = document.querySelector('.skinHeader'); var requiresUserRefresh = true; @@ -931,6 +963,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' updateUserInHeader(); }); events.on(playbackManager, 'playerchange', updateCastIcon); + events.on(syncplayManager, 'SyncplayEnabled', updateSyncplayIcon); + events.on(syncplayManager, 'SyncplayError', updateSyncplayErrorIcon); loadNavDrawer(); return LibraryMenu; }); diff --git a/src/scripts/site.js b/src/scripts/site.js index c00169d224..3a2ba13156 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -817,6 +817,7 @@ var AppInfo = {}; define('playbackSettings', [componentsPath + '/playbacksettings/playbacksettings'], returnFirstDependency); define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); + define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnFirstDependency); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 9895bbc05c..c7611333f0 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -491,6 +491,8 @@ "HeaderSubtitleProfile": "Subtitle Profile", "HeaderSubtitleProfiles": "Subtitle Profiles", "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", + "HeaderSyncplaySelectGroup": "Join a group", + "HeaderSyncplayEnabled": "Syncplay enabled", "HeaderSystemDlnaProfiles": "System Profiles", "HeaderTags": "Tags", "HeaderTaskTriggers": "Task Triggers", @@ -853,6 +855,13 @@ "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelSubtitles": "Subtitles", "LabelSupportedMediaTypes": "Supported Media Types:", + "LabelSyncplayTimeDiff": "Time difference with server:", + "LabelSyncplayPlaybackDiff": "Playback time difference:", + "LabelSyncplaySyncMethod": "Sync method:", + "LabelSyncplayNewGroup": "New group", + "LabelSyncplayNewGroupDescription": "Create a new group", + "LabelSyncplayLeaveGroup": "Leave group", + "LabelSyncplayLeaveGroupDescription": "Disable Syncplay", "LabelTVHomeScreen": "TV mode home screen:", "LabelTag": "Tag:", "LabelTagline": "Tagline:", @@ -1016,6 +1025,12 @@ "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please ensure it is running and try again.", "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.", "MessageYouHaveVersionInstalled": "You currently have version {0} installed.", + "MessageSyncplayEnabled": "Syncplay enabled.", + "MessageSyncplayDisabled": "Syncplay disabled.", + "MessageSyncplayUserJoined": "{0} joined group.", + "MessageSyncplayUserLeft": "{0} left group.", + "MessageSyncplayGroupWait": "{0} is buffering...", + "MessageSyncplayNoGroupsAvailable": "No groups available.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", From 460c2a1f77dbc805ba364b7f7e932e9d9d79486b Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 1 Apr 2020 19:27:38 +0200 Subject: [PATCH 002/181] Fix lint errors --- src/components/playback/playbackmanager.js | 2 +- src/components/playerstats/playerstats.js | 2 +- src/components/syncplay/groupSelectionMenu.js | 4 +- src/components/syncplay/syncplaymanager.js | 76 +++++++++---------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 923092cd3d..e4ce40cf4e 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3789,7 +3789,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla player = player || this._currentPlayer; if (player) { - return player.getPlaybackRate(val); + return player.getPlaybackRate(); } return null; diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index ad4ce960bf..4bd49ba5c1 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -345,7 +345,7 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay label: globalize.translate("LabelSyncplaySyncMethod"), value: stats.SyncMethod }); - + return syncStats; } diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 01141741c1..8fb0523c30 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -24,7 +24,7 @@ define(['events', 'loading', 'connectionManager', 'playbackManager', 'syncplayMa var menuItems = groups.map(function (group) { var name = datetime.getDisplayRunningTime(group.PositionTicks); if (!inSession) { - name = group.PlayingItemName + name = group.PlayingItemName; } return { name: name, @@ -32,7 +32,7 @@ define(['events', 'loading', 'connectionManager', 'playbackManager', 'syncplayMa id: group.GroupId, selected: false, secondaryText: group.Partecipants.join(", ") - } + }; }); if (inSession) { diff --git a/src/components/syncplay/syncplaymanager.js b/src/components/syncplay/syncplaymanager.js index bde47a9d44..995c140fd5 100644 --- a/src/components/syncplay/syncplaymanager.js +++ b/src/components/syncplay/syncplaymanager.js @@ -6,7 +6,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' var callback = function () { events.off(emitter, eventType, callback); resolve(arguments); - } + }; events.on(emitter, eventType, callback); }); } @@ -80,11 +80,11 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' if (self.playbackRateSupported && absDiffMillis > self.maxAcceptedDelaySpeedToSync && absDiffMillis < self.syncMethodThreshold) { // SpeedToSync method var speed = 1 + diffMillis / self.speedUpToSyncTime; - + self.currentPlayer.setPlaybackRate(speed); self.syncEnabled = false; self.showSyncIcon("SpeedToSync (x" + speed + ")"); - + self.syncTimeout = setTimeout(() => { self.currentPlayer.setPlaybackRate(1); self.syncEnabled = true; @@ -95,7 +95,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' playbackManager.syncplay_seek(ServerPositionTicks); self.syncEnabled = false; self.showSyncIcon("SkipToSync"); - + self.syncTimeout = setTimeout(() => { self.syncEnabled = true; self.clearSyncIcon(); @@ -105,7 +105,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' } } - self.lastPlaybackWaiting = null; // used to determine if player's buffering + self.lastPlaybackWaiting = null; // used to determine if player's buffering self.minBufferingThresholdMillis = 1000; // TODO: implement group wait @@ -119,13 +119,13 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' if (!self.lastPlaybackWaiting) { self.lastPlaybackWaiting = new Date(); } - events.trigger(self, "PlayerWaiting"); + events.trigger(self, "PlayerWaiting"); } self.isBuffering = function () { if (self.lastPlaybackWaiting === null) return false; return (new Date() - self.lastPlaybackWaiting) > self.minBufferingThresholdMillis; - } + }; function bindToPlayer(player) { if (player !== self.currentPlayer) { @@ -165,7 +165,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' self.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled self.syncplayReady = false; // Syncplay is ready after first ping to server - self.processGroupUpdate = function (cmd, apiClient) { + self.processGroupUpdate = function (cmd, apiClient) { switch (cmd.Type) { case 'PrepareSession': var serverId = apiClient.serverInfo().Id; @@ -199,17 +199,17 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' break; case 'UserJoined': displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayUserJoined', cmd.Data), + Text: globalize.translate('MessageSyncplayUserJoined', cmd.Data) }); break; case 'UserLeft': displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayUserLeft', cmd.Data), + Text: globalize.translate('MessageSyncplayUserLeft', cmd.Data) }); break; case 'GroupJoined': displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayEnabled'), + Text: globalize.translate('MessageSyncplayEnabled') }); // Enable Syncplay self.syncplayEnabledAt = new Date(cmd.Data); @@ -225,7 +225,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' case 'NotInGroup': case 'GroupLeft': displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayDisabled'), + Text: globalize.translate('MessageSyncplayDisabled') }); // Disable Syncplay self.syncplayEnabledAt = null; @@ -236,7 +236,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' break; case 'GroupWait': displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayGroupWait', cmd.Data), + Text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) }); break; case 'KeepAlive': @@ -245,7 +245,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); break; } - } + }; self.lastCommand = null; self.queuedCommand = null; @@ -283,7 +283,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' self.lastCommand = cmd; console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks); - + switch (cmd.Command) { case 'Play': self.schedulePlay(cmd.When, cmd.PositionTicks); @@ -298,7 +298,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' console.error('processSyncplayCommand does not recognize: ' + cmd.Type); break; } - } + }; self.scheduledCommand = null; self.syncTimeout = null; @@ -332,7 +332,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' self.syncEnabled = true }, self.syncMethodThreshold / 2); } - } + }; self.schedulePause = function (pauseAtTime, positionTicks) { self.clearScheduledCommand(); @@ -355,11 +355,11 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' playbackManager.syncplay_seek(positionTicks); }, 800); } - } + }; self.scheduleSeek = function (seekAtTime, positionTicks) { self.schedulePause(seekAtTime, positionTicks); - } + }; self.clearScheduledCommand = function () { clearTimeout(self.scheduledCommand); @@ -370,7 +370,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' self.currentPlayer.setPlaybackRate(1); } self.clearSyncIcon(); - } + }; self.injectPlaybackManager = function () { if (!self.isSyncplayEnabled()) return; @@ -379,12 +379,12 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' playbackManager.syncplay_unpause = playbackManager.unpause; playbackManager.syncplay_pause = playbackManager.pause; playbackManager.syncplay_seek = playbackManager.seek; - + playbackManager.unpause = self.playRequest; playbackManager.pause = self.pauseRequest; playbackManager.seek = self.seekRequest; playbackManager.syncplayEnabled = true; - } + }; self.restorePlaybackManager = function () { if (self.isSyncplayEnabled()) return; @@ -394,13 +394,13 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' playbackManager.pause = playbackManager.syncplay_pause; playbackManager.seek = playbackManager.syncplay_seek; playbackManager.syncplayEnabled = false; - } + }; self.playRequest = function (player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); apiClient.sendSyncplayCommand(sessionId, "PlayRequest"); - } + }; self.pauseRequest = function (player) { var apiClient = connectionManager.currentApiClient(); @@ -408,7 +408,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' apiClient.sendSyncplayCommand(sessionId, "PauseRequest"); // Pause locally as well, to give the user some little control playbackManager.syncplay_pause(); - } + }; self.seekRequest = function (PositionTicks, player) { var apiClient = connectionManager.currentApiClient(); @@ -416,7 +416,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' apiClient.sendSyncplayCommand(sessionId, "SeekRequest", { PositionTicks: PositionTicks }); - } + }; self.pingIntervalTimeoutGreedy = 1000; self.pingIntervalTimeoutLowProfile = 60000; @@ -459,7 +459,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' } // console.debug("Syncplay updateTimeDiff:", serverTime, self.timeDiff, self.roundTripDuration, newTimeDiff); - } + }; self.requestPing = function () { if (self.pingInterval === null && !self.pingStop) { @@ -493,14 +493,14 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' events.trigger(self, "SyncplayReady"); self.notifySyncplayReady = false; } - + self.requestPing(); }); }); }, self.pingIntervalTimeout); } - } + }; self.startPing = function () { self.notifySyncplayReady = true; @@ -509,7 +509,7 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' self.pingIntervalTimeout = self.pingIntervalTimeoutGreedy; self.requestPing(); - } + }; self.stopPing = function () { self.pingStop = true; @@ -517,17 +517,17 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' clearTimeout(self.pingInterval); self.pingInterval = null; } - } + }; self.serverDateToLocal = function (server) { // local - server = diff return new Date(server.getTime() + self.timeDiff); - } + }; self.localDateToServer = function (local) { // local - server = diff return new Date(local.getTime() - self.timeDiff); - } + }; // THIS FEATURE IS CURRENTLY DISABLED // Mainly because SpeedToSync seems to do the job @@ -569,12 +569,12 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' }, 1000); }, 2000); } - } + }; // Stats self.isSyncplayEnabled = function () { return self.syncplayEnabledAt !== null ? true : false; - } + }; self.getStats = function () { return { @@ -582,18 +582,18 @@ define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager' PlaybackDiff: self.playbackDiffMillis, SyncMethod: self.syncMethod } - } + }; // UI self.showSyncIcon = function (syncMethod) { self.syncMethod = syncMethod; events.trigger(self, "SyncplayError", [true]); - } + }; self.clearSyncIcon = function () { self.syncMethod = "None"; events.trigger(self, "SyncplayError", [false]); - } + }; } return new SyncplayManager(); From 56ee678fc222bd50ea9e540fa8ffe0698dd67e7c Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 3 Apr 2020 18:49:19 +0200 Subject: [PATCH 003/181] Port to ES6 --- package.json | 4 +- src/components/syncplay/groupSelectionMenu.js | 239 +++--- src/components/syncplay/syncplayManager.js | 704 ++++++++++++++++++ src/components/syncplay/syncplaymanager.js | 600 --------------- src/scripts/site.js | 9 +- 5 files changed, 840 insertions(+), 716 deletions(-) create mode 100644 src/components/syncplay/syncplayManager.js delete mode 100644 src/components/syncplay/syncplaymanager.js diff --git a/package.json b/package.json index ffbbc02954..39330cf600 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,9 @@ "src/scripts/keyboardnavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", - "src/scripts/settings/webSettings.js" + "src/scripts/settings/webSettings.js", + "src/components/syncplay/groupSelectionMenu.js", + "src/components/syncplay/syncplayManager.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 8fb0523c30..671b76d5ea 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -1,111 +1,78 @@ -define(['events', 'loading', 'connectionManager', 'playbackManager', 'syncplayManager', 'globalize', 'datetime'], function (events, loading, connectionManager, playbackManager, syncplayManager, globalize, datetime) { - 'use strict'; +import events from 'events'; +import connectionManager from 'connectionManager'; +import playbackManager from 'playbackManager'; +import syncplayManager from 'syncplayManager'; +import loading from 'loading'; +import datetime from 'datetime'; +import toast from 'toast'; +import actionsheet from 'actionsheet'; +import globalize from 'globalize'; - function getActivePlayerId() { - var info = playbackManager.getPlayerInfo(); - return info ? info.id : null; - } +/** + * Gets active player id. + * @returns {string} The player's id. + */ +function getActivePlayerId() { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; +} - function emptyCallback() { - // avoid console logs about uncaught promises - } +/** + * Used to avoid console logs about uncaught promises + */ +function emptyCallback() { + // avoid console logs about uncaught promises +} - function showNewJoinGroupSelection(button) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - sessionId = sessionId ? sessionId : "none"; +/** + * Used when user needs to join a group. + * @param {HTMLElement} button - Element where to place the menu. + */ +function showNewJoinGroupSelection(button) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + sessionId = sessionId ? sessionId : "none"; - loading.show(); + loading.show(); - apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { - response.json().then(function (groups) { - var inSession = sessionId !== "none"; + apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { + response.json().then(function (groups) { + var inSession = sessionId !== "none"; - var menuItems = groups.map(function (group) { - var name = datetime.getDisplayRunningTime(group.PositionTicks); - if (!inSession) { - name = group.PlayingItemName; - } - return { - name: name, - icon: "group", - id: group.GroupId, - selected: false, - secondaryText: group.Partecipants.join(", ") - }; - }); - - if (inSession) { - menuItems.push({ - name: globalize.translate('LabelSyncplayNewGroup'), - icon: "add", - id: "new-group", - selected: true, - secondaryText: globalize.translate('LabelSyncplayNewGroupDescription') - }); + var menuItems = groups.map(function (group) { + var name = datetime.getDisplayRunningTime(group.PositionTicks); + if (!inSession) { + name = group.PlayingItemName; } - - if (menuItems.length === 0) { - require(['toast'], function (alert) { - alert({ - title: globalize.translate('MessageSyncplayNoGroupsAvailable'), - text: globalize.translate('MessageSyncplayNoGroupsAvailable') - }); - }); - loading.hide(); - return; - } - - require(['actionsheet'], function (actionsheet) { - - loading.hide(); - - var menuOptions = { - title: globalize.translate('HeaderSyncplaySelectGroup'), - items: menuItems, - positionTo: button, - resolveOnClick: true, - border: true - }; - - actionsheet.show(menuOptions).then(function (id) { - - if (id == "new-group") { - apiClient.sendSyncplayCommand(sessionId, "NewGroup"); - } else { - apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { - GroupId: id - }); - } - }, emptyCallback); - }); + return { + name: name, + icon: "group", + id: group.GroupId, + selected: false, + secondaryText: group.Partecipants.join(", ") + }; }); - }).catch(function (error) { - loading.hide(); - console.error(error); - }); - } - function showLeaveGroupSelection(button) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); + if (inSession) { + menuItems.push({ + name: globalize.translate('LabelSyncplayNewGroup'), + icon: "add", + id: "new-group", + selected: true, + secondaryText: globalize.translate('LabelSyncplayNewGroupDescription') + }); + } - loading.show(); - - var menuItems = [{ - name: globalize.translate('LabelSyncplayLeaveGroup'), - icon: "meeting_room", - id: "leave-group", - selected: true, - secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription') - }]; - - require(['actionsheet'], function (actionsheet) { - - loading.hide(); + if (menuItems.length === 0) { + toast({ + text: globalize.translate('MessageSyncplayNoGroupsAvailable') + }); + loading.hide(); + return; + } var menuOptions = { - title: globalize.translate('HeaderSyncplayEnabled'), + title: globalize.translate('HeaderSyncplaySelectGroup'), items: menuItems, positionTo: button, resolveOnClick: true, @@ -113,28 +80,72 @@ define(['events', 'loading', 'connectionManager', 'playbackManager', 'syncplayMa }; actionsheet.show(menuOptions).then(function (id) { - if (id == "leave-group") { - apiClient.sendSyncplayCommand(sessionId, "LeaveGroup"); + if (id == "new-group") { + apiClient.sendSyncplayCommand(sessionId, "NewGroup"); + } else { + apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + GroupId: id + }); } }, emptyCallback); + + loading.hide(); }); - } - - function showGroupSelection(button) { - if (syncplayEnabled) { - showLeaveGroupSelection(button); - } else { - showNewJoinGroupSelection(button); - } - } - - var syncplayEnabled = false; - - events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { - syncplayEnabled = enabled; + }).catch(function (error) { + loading.hide(); + console.error(error); }); +} - return { - show: showGroupSelection +/** + * Used when user has joined a group. + * @param {HTMLElement} button - Element where to place the menu. + */ +function showLeaveGroupSelection(button) { + const apiClient = connectionManager.currentApiClient(); + const sessionId = getActivePlayerId(); + + loading.show(); + + const menuItems = [{ + name: globalize.translate('LabelSyncplayLeaveGroup'), + icon: "meeting_room", + id: "leave-group", + selected: true, + secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription') + }]; + + var menuOptions = { + title: globalize.translate('HeaderSyncplayEnabled'), + items: menuItems, + positionTo: button, + resolveOnClick: true, + border: true }; + + actionsheet.show(menuOptions).then(function (id) { + if (id == "leave-group") { + apiClient.sendSyncplayCommand(sessionId, "LeaveGroup"); + } + }, emptyCallback); + + loading.hide(); +} + +// Register to Syncplay events +let syncplayEnabled = false; +events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { + syncplayEnabled = enabled; }); + +/** + * Shows a menu to handle Syncplay groups. + * @param {HTMLElement} button - Element where to place the menu. + */ +export function show(button) { + if (syncplayEnabled) { + showLeaveGroupSelection(button); + } else { + showNewJoinGroupSelection(button); + } +} diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js new file mode 100644 index 0000000000..bc0fe7bc6c --- /dev/null +++ b/src/components/syncplay/syncplayManager.js @@ -0,0 +1,704 @@ +/* eslint-disable indent */ + +/** + * Module that manages the Syncplay feature. + * @module components/syncplay/syncplayManager + */ + +import events from 'events'; +import connectionManager from 'connectionManager'; +import playbackManager from 'playbackManager'; +import toast from 'toast'; +import globalize from 'globalize'; + +/** + * Waits for an event to be triggered on an object. + * @param {Object} emitter Object on which to listen for events. + * @param {string} eventType Event name to listen for. + * @returns {Promise} A promise that resolves when the event is triggered. + */ +function waitForEvent(emitter, eventType) { + return new Promise((resolve) => { + var callback = () => { + events.off(emitter, eventType, callback); + resolve(arguments); + }; + events.on(emitter, eventType, callback); + }); +} + +/** + * Gets active player id. + * @returns {string} The player's id. + */ +function getActivePlayerId() { + var info = playbackManager.getPlayerInfo(); + return info ? info.id : null; +} + +/** + * Playback synchronization + */ +const MaxAcceptedDelaySpeedToSync = 50; // milliseconds, delay after which SpeedToSync is enabled +const MaxAcceptedDelaySkipToSync = 300; // milliseconds, delay after which SkipToSync is enabled +const SyncMethodThreshold = 2000; // milliseconds, switches between SpeedToSync or SkipToSync +const SpeedUpToSyncTime = 1000; // milliseconds, duration in which the playback is sped up + +/** + * Time estimation + */ +const PingIntervalTimeoutGreedy = 1000; // milliseconds +const PingIntervalTimeoutLowProfile = 60000; // milliseconds +const GreedyPingCount = 3; + +/** + * Class that manages the Syncplay feature. + */ +class SyncplayManager { + constructor() { + this.playbackRateSupported = false; + this.syncEnabled = false; + this.playbackDiffMillis = 0; // used for stats + this.syncMethod = "None"; // used for stats + + this.lastPlaybackWaiting = null; // used to determine if player's buffering + this.minBufferingThresholdMillis = 1000; + + this.currentPlayer = null; + + this.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled + this.syncplayReady = false; // Syncplay is ready after first ping to server + + this.lastCommand = null; + this.queuedCommand = null; + + this.scheduledCommand = null; + this.syncTimeout = null; + + this.pingStop = true; + this.pingIntervalTimeout = PingIntervalTimeoutGreedy; + this.pingInterval = null; + this.initTimeDiff = 0; // number of pings + this.timeDiff = 0; // local time minus server time + this.roundTripDuration = 0; + this.notifySyncplayReady = false; + + events.on(playbackManager, "playerchange", () => { + this.onPlayerChange(); + }); + this.bindToPlayer(playbackManager.getCurrentPlayer()); + } + + /** + * Called when the player changes. + */ + onPlayerChange () { + this.bindToPlayer(playbackManager.getCurrentPlayer()); + events.trigger(this, "PlayerChange", [this.currentPlayer]); + } + + /** + * Called on playback state changes. + * @param {Object} e The playback state change event. + */ + onPlayPauseStateChanged (e) { + events.trigger(this, "PlayPauseStateChange", [this.currentPlayer]); + } + + /** + * Called on playback progress. + * @param {Object} e The time update event. + */ + onTimeUpdate (e) { + events.trigger(this, "TimeUpdate", [e]); + + if (this.lastCommand && this.lastCommand.Command === 'Play' && !this.isBuffering()) { + var currentTime = new Date(); + var playAtTime = this.lastCommand.When; + + var state = playbackManager.getPlayerState().PlayState; + // Estimate PositionTicks on server + var ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) - this.timeDiff) * 10000; + // Measure delay that needs to be recovered + // diff might be caused by the player internally starting the playback + var diff = ServerPositionTicks - state.PositionTicks; + var diffMillis = diff / 10000; + + this.playbackDiffMillis = diffMillis; + + // console.debug("Syncplay onTimeUpdate", diffMillis, state.PositionTicks, ServerPositionTicks); + + if (this.syncEnabled) { + var absDiffMillis = Math.abs(diffMillis); + // TODO: SpeedToSync sounds bad on songs + if (this.playbackRateSupported && absDiffMillis > MaxAcceptedDelaySpeedToSync && absDiffMillis < SyncMethodThreshold) { + // SpeedToSync method + var speed = 1 + diffMillis / SpeedUpToSyncTime; + + this.currentPlayer.setPlaybackRate(speed); + this.syncEnabled = false; + this.showSyncIcon("SpeedToSync (x" + speed + ")"); + + this.syncTimeout = setTimeout(() => { + this.currentPlayer.setPlaybackRate(1); + this.syncEnabled = true; + this.clearSyncIcon(); + }, SpeedUpToSyncTime); + } else if (absDiffMillis > MaxAcceptedDelaySkipToSync) { + // SkipToSync method + playbackManager.syncplay_seek(ServerPositionTicks); + this.syncEnabled = false; + this.showSyncIcon("SkipToSync"); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true; + this.clearSyncIcon(); + }, this.syncMethodThreshold / 2); + } + } + } + } + + /** + * Called when playback is resumed. + */ + onPlaying () { + // TODO: implement group wait + this.lastPlaybackWaiting = null; + events.trigger(this, "PlayerPlaying"); + } + + /** + * Called when playback is buffering. + */ + onWaiting () { + // TODO: implement group wait + if (!this.lastPlaybackWaiting) { + this.lastPlaybackWaiting = new Date(); + } + events.trigger(this, "PlayerWaiting"); + } + + /** + * Gets playback buffering status. + * @returns {boolean} _true_ if player is buffering, _false_ otherwise. + */ + isBuffering () { + if (this.lastPlaybackWaiting === null) return false; + return (new Date() - this.lastPlaybackWaiting) > this.minBufferingThresholdMillis; + } + + /** + * Binds to the player's events. + * @param {Object} player The player. + */ + bindToPlayer (player) { + if (player !== this.currentPlayer) { + this.releaseCurrentPlayer(); + this.currentPlayer = player; + if (!player) return; + } + + // TODO: remove this extra functions + const self = this; + this._onPlayPauseStateChanged = () => { + self.onPlayPauseStateChanged(); + }; + + this._onPlayPauseStateChanged = (e) => { + self.onPlayPauseStateChanged(e); + }; + + this._onTimeUpdate = (e) => { + self.onTimeUpdate(e); + }; + + this._onPlaying = () => { + self.onPlaying(); + }; + + this._onWaiting = () => { + self.onWaiting(); + }; + + events.on(player, "pause", this._onPlayPauseStateChanged); + events.on(player, "unpause", this._onPlayPauseStateChanged); + events.on(player, "timeupdate", this._onTimeUpdate); + events.on(player, "playing", this._onPlaying); + events.on(player, "waiting", this._onWaiting); + this.playbackRateSupported = player.supports("PlaybackRate"); + } + + /** + * Removes the bindings to the current player's events. + */ + releaseCurrentPlayer () { + var player = this.currentPlayer; + if (player) { + events.off(player, "pause", this._onPlayPauseStateChanged); + events.off(player, "unpause", this._onPlayPauseStateChanged); + events.off(player, "timeupdate", this._onTimeUpdate); + events.off(player, "playing", this._onPlaying); + events.off(player, "waiting", this._onWaiting); + if (this.playbackRateSupported) { + player.setPlaybackRate(1); + } + this.currentPlayer = null; + this.playbackRateSupported = false; + } + } + + /** + * Handles a group update from the server. + * @param {Object} cmd The group update. + * @param {Object} apiClient The ApiClient. + */ + processGroupUpdate (cmd, apiClient) { + switch (cmd.Type) { + case 'PrepareSession': + var serverId = apiClient.serverInfo().Id; + playbackManager.play({ + ids: cmd.Data.ItemIds, + startPositionTicks: cmd.Data.StartPositionTicks, + mediaSourceId: cmd.Data.MediaSourceId, + audioStreamIndex: cmd.Data.AudioStreamIndex, + subtitleStreamIndex: cmd.Data.SubtitleStreamIndex, + startIndex: cmd.Data.StartIndex, + serverId: serverId + }).then(() => { + waitForEvent(this, "PlayerChange").then(() => { + playbackManager.pause(); + var sessionId = getActivePlayerId(); + if (!sessionId) { + console.error("Missing sessionId!"); + toast({ + text: "Failed to enable Syncplay!" + }); + return; + } + // Sometimes JoinGroup fails, maybe because server hasn't been updated yet + setTimeout(() => { + apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + GroupId: cmd.GroupId + }); + }, 500); + }); + }); + break; + case 'UserJoined': + toast({ + text: globalize.translate('MessageSyncplayUserJoined', cmd.Data) + }); + break; + case 'UserLeft': + toast({ + text: globalize.translate('MessageSyncplayUserLeft', cmd.Data) + }); + break; + case 'GroupJoined': + toast({ + text: globalize.translate('MessageSyncplayEnabled') + }); + // Enable Syncplay + this.syncplayEnabledAt = new Date(cmd.Data); + this.syncplayReady = false; + events.trigger(this, "SyncplayEnabled", [true]); + waitForEvent(this, "SyncplayReady").then(() => { + this.processCommand(this.queuedCommand, apiClient); + this.queuedCommand = null; + }); + this.injectPlaybackManager(); + this.startPing(); + break; + case 'NotInGroup': + case 'GroupLeft': + toast({ + text: globalize.translate('MessageSyncplayDisabled') + }); + // Disable Syncplay + this.syncplayEnabledAt = null; + this.syncplayReady = false; + events.trigger(this, "SyncplayEnabled", [false]); + this.restorePlaybackManager(); + this.stopPing(); + break; + case 'GroupWait': + toast({ + text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) + }); + break; + case 'KeepAlive': + break; + default: + console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); + break; + } + } + + /** + * Handles a playback command from the server. + * @param {Object} cmd The playback command. + * @param {Object} apiClient The ApiClient. + */ + processCommand (cmd, apiClient) { + if (cmd === null) return; + + if (!this.isSyncplayEnabled()) { + console.debug("Syncplay processCommand: ignoring command", cmd); + return; + } + + if (!this.syncplayReady) { + console.debug("Syncplay processCommand: queued command", cmd); + this.queuedCommand = cmd; + return; + } + + cmd.When = new Date(cmd.When); + + if (cmd.When < this.syncplayEnabledAt) { + console.debug("Syncplay processCommand: ignoring old command", cmd); + return; + } + + // Check if new command differs from last one + if (this.lastCommand && + this.lastCommand.When === cmd.When && + this.lastCommand.PositionTicks === cmd.PositionTicks && + this.Command === cmd.Command + ) { + console.debug("Syncplay processCommand: ignoring duplicate command", cmd); + return; + } + + this.lastCommand = cmd; + console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks); + + switch (cmd.Command) { + case 'Play': + this.schedulePlay(cmd.When, cmd.PositionTicks); + break; + case 'Pause': + this.schedulePause(cmd.When, cmd.PositionTicks); + break; + case 'Seek': + this.scheduleSeek(cmd.When, cmd.PositionTicks); + break; + default: + console.error('processSyncplayCommand does not recognize: ' + cmd.Type); + break; + } + } + + /** + * Schedules a resume playback on the player at the specified clock time. + * @param {Date} playAtTime The server's UTC time at which to resume playback. + * @param {number} positionTicks The PositionTicks from where to resume. + */ + schedulePlay (playAtTime, positionTicks) { + this.clearScheduledCommand(); + var currentTime = new Date(); + var playAtTimeLocal = this.serverDateToLocal(playAtTime); + + if (playAtTimeLocal > currentTime) { + var playTimeout = playAtTimeLocal - currentTime; + playbackManager.syncplay_seek(positionTicks); + + this.scheduledCommand = setTimeout(() => { + playbackManager.syncplay_unpause(); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true + }, this.syncMethodThreshold / 2); + + }, playTimeout); + + // console.debug("Syncplay schedulePlay:", playTimeout); + } else { + // Group playback already started + var serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; + playbackManager.syncplay_unpause(); + playbackManager.syncplay_seek(serverPositionTicks); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true + }, this.syncMethodThreshold / 2); + } + } + + /** + * Schedules a pause playback on the player at the specified clock time. + * @param {Date} pauseAtTime The server's UTC time at which to pause playback. + * @param {number} positionTicks The PositionTicks where player will be paused. + */ + schedulePause (pauseAtTime, positionTicks) { + this.clearScheduledCommand(); + var currentTime = new Date(); + var pauseAtTimeLocal = this.serverDateToLocal(pauseAtTime); + + if (pauseAtTimeLocal > currentTime) { + var pauseTimeout = pauseAtTimeLocal - currentTime; + + this.scheduledCommand = setTimeout(() => { + playbackManager.syncplay_pause(); + setTimeout(() => { + playbackManager.syncplay_seek(positionTicks); + }, 800); + + }, pauseTimeout); + } else { + playbackManager.syncplay_pause(); + setTimeout(() => { + playbackManager.syncplay_seek(positionTicks); + }, 800); + } + } + + /** + * Schedules a seek playback on the player at the specified clock time. + * @param {Date} pauseAtTime The server's UTC time at which to seek playback. + * @param {number} positionTicks The PositionTicks where player will be seeked. + */ + scheduleSeek (seekAtTime, positionTicks) { + this.schedulePause(seekAtTime, positionTicks); + } + + /** + * Clears the current scheduled command. + */ + clearScheduledCommand () { + clearTimeout(this.scheduledCommand); + clearTimeout(this.syncTimeout); + + this.syncEnabled = false; + if (this.currentPlayer) { + this.currentPlayer.setPlaybackRate(1); + } + this.clearSyncIcon(); + } + + /** + * Overrides some PlaybackManager's methods to intercept playback commands. + */ + injectPlaybackManager () { + if (!this.isSyncplayEnabled()) return; + if (playbackManager.syncplayEnabled) return; + + // TODO: make this less hacky + playbackManager.syncplay_unpause = playbackManager.unpause; + playbackManager.syncplay_pause = playbackManager.pause; + playbackManager.syncplay_seek = playbackManager.seek; + + playbackManager.unpause = this.playRequest; + playbackManager.pause = this.pauseRequest; + playbackManager.seek = this.seekRequest; + playbackManager.syncplayEnabled = true; + } + + /** + * Restores original PlaybackManager's methods. + */ + restorePlaybackManager () { + if (this.isSyncplayEnabled()) return; + if (!playbackManager.syncplayEnabled) return; + + playbackManager.unpause = playbackManager.syncplay_unpause; + playbackManager.pause = playbackManager.syncplay_pause; + playbackManager.seek = playbackManager.syncplay_seek; + playbackManager.syncplayEnabled = false; + } + + /** + * Overrides PlaybackManager's unpause method. + */ + playRequest (player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "PlayRequest"); + } + + /** + * Overrides PlaybackManager's pause method. + */ + pauseRequest (player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "PauseRequest"); + // Pause locally as well, to give the user some little control + playbackManager.syncplay_pause(); + } + + /** + * Overrides PlaybackManager's seek method. + */ + seekRequest (PositionTicks, player) { + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + apiClient.sendSyncplayCommand(sessionId, "SeekRequest", { + PositionTicks: PositionTicks + }); + } + + /** + * Computes time difference between this client's time and server's time. + * @param {Date} pingStartTime Local time when ping request started. + * @param {Date} pingEndTime Local time when ping request ended. + * @param {Date} serverTime Server UTC time at ping request. + */ + updateTimeDiff (pingStartTime, pingEndTime, serverTime) { + this.roundTripDuration = (pingEndTime - pingStartTime); + // The faster the response, the closer we are to the real timeDiff value + // localTime = pingStartTime + roundTripDuration / 2 + // newTimeDiff = localTime - serverTime + var newTimeDiff = (pingStartTime - serverTime) + (this.roundTripDuration / 2); + + // Initial setup + if (this.initTimeDiff === 0) { + this.timeDiff = newTimeDiff; + this.initTimeDiff++ + return; + } + + // As response time gets better, absolute value should decrease + var distanceFromZero = Math.abs(newTimeDiff); + var oldDistanceFromZero = Math.abs(this.timeDiff); + if (distanceFromZero < oldDistanceFromZero) { + this.timeDiff = newTimeDiff; + } + + // Avoid overloading server + if (this.initTimeDiff >= GreedyPingCount) { + this.pingIntervalTimeout = PingIntervalTimeoutLowProfile; + } else { + this.initTimeDiff++; + } + + // console.debug("Syncplay updateTimeDiff:", serverTime, this.timeDiff, this.roundTripDuration, newTimeDiff); + } + + /** + * Schedules a ping request to the server. Used to compute time difference between client and server. + */ + requestPing () { + if (this.pingInterval === null && !this.pingStop) { + this.pingInterval = setTimeout(() => { + this.pingInterval = null; + + var apiClient = connectionManager.currentApiClient(); + var sessionId = getActivePlayerId(); + + var pingStartTime = new Date(); + apiClient.sendSyncplayCommand(sessionId, "GetUtcTime").then((response) => { + var pingEndTime = new Date(); + response.text().then((utcTime) => { + var serverTime = new Date(utcTime); + this.updateTimeDiff(pingStartTime, pingEndTime, serverTime); + + // Alert user that ping is high + if (Math.abs(this.roundTripDuration) >= 1000) { + events.trigger(this, "SyncplayError", [true]); + } else { + events.trigger(this, "SyncplayError", [false]); + } + + // Notify server of ping + apiClient.sendSyncplayCommand(sessionId, "KeepAlive", { + Ping: this.roundTripDuration / 2 + }); + + if (this.notifySyncplayReady) { + this.syncplayReady = true; + events.trigger(this, "SyncplayReady"); + this.notifySyncplayReady = false; + } + + this.requestPing(); + }); + }); + + }, this.pingIntervalTimeout); + } + } + + /** + * Starts the keep alive poller. + */ + startPing () { + this.notifySyncplayReady = true; + this.pingStop = false; + this.initTimeDiff = this.initTimeDiff > this.greedyPingCount ? 1 : this.initTimeDiff; + this.pingIntervalTimeout = this.pingIntervalTimeoutGreedy; + + this.requestPing(); + } + + /** + * Stops the keep alive poller. + */ + stopPing () { + this.pingStop = true; + if (this.pingInterval !== null) { + clearTimeout(this.pingInterval); + this.pingInterval = null; + } + } + + /** + * Converts server time to local time. + * @param {Date} server + * @returns {Date} Local time. + */ + serverDateToLocal (server) { + // local - server = diff + return new Date(server.getTime() + this.timeDiff); + } + + /** + * Converts local time to server time. + * @param {Date} local + * @returns {Date} Server time. + */ + localDateToServer (local) { + // local - server = diff + return new Date(local.getTime() - this.timeDiff); + } + + /** + * Gets Syncplay status. + * @returns {boolean} _true_ is user joined a group, _false_ otherwise. + */ + isSyncplayEnabled () { + return this.syncplayEnabledAt !== null ? true : false; + } + + /** + * Gets Syncplay stats. + * @returns {Object} The Syncplay stats. + */ + getStats () { + return { + TimeDiff: this.timeDiff, + PlaybackDiff: this.playbackDiffMillis, + SyncMethod: this.syncMethod + } + } + + /** + * Emits an event to update the Syncplay status icon. + */ + showSyncIcon (syncMethod) { + this.syncMethod = syncMethod; + events.trigger(this, "SyncplayError", [true]); + } + + /** + * Emits an event to clear the Syncplay status icon. + */ + clearSyncIcon () { + this.syncMethod = "None"; + events.trigger(this, "SyncplayError", [false]); + } +} + +/** SyncplayManager singleton. */ +export default new SyncplayManager(); diff --git a/src/components/syncplay/syncplaymanager.js b/src/components/syncplay/syncplaymanager.js deleted file mode 100644 index 995c140fd5..0000000000 --- a/src/components/syncplay/syncplaymanager.js +++ /dev/null @@ -1,600 +0,0 @@ -define(['events', 'globalize', 'loading', 'connectionManager', 'playbackManager'], function (events, globalize, loading, connectionManager, playbackManager) { - 'use strict'; - - function waitForEvent(emitter, eventType) { - return new Promise(function (resolve) { - var callback = function () { - events.off(emitter, eventType, callback); - resolve(arguments); - }; - events.on(emitter, eventType, callback); - }); - } - - function displaySyncplayUpdate(message) { - require(['toast'], function (alert) { - alert({ - title: message.Header, - text: message.Text - }); - }); - } - - function getActivePlayerId() { - var info = playbackManager.getPlayerInfo(); - return info ? info.id : null; - } - - function millisecondsToTicks(milliseconds) { - return milliseconds * 10000; - } - - function ticksToMilliseconds(ticks) { - return ticks / 10000; - } - - function SyncplayManager() { - - var self = this; - - function onPlayerChange() { - bindToPlayer(playbackManager.getCurrentPlayer()); - events.trigger(self, "PlayerChange", [self.currentPlayer]); - } - - function onPlayPauseStateChanged(e) { - events.trigger(self, "PlayPauseStateChange", [self.currentPlayer]); - } - - self.playbackRateSupported = false; - self.syncEnabled = false; - self.maxAcceptedDelaySpeedToSync = 50; // milliseconds - self.maxAcceptedDelaySkipToSync = 300; // milliseconds - self.syncMethodThreshold = 2000; // milliseconds - self.speedUpToSyncTime = 1000; // milliseconds - self.playbackDiffMillis = 0; // used for stats - self.syncMethod = "None"; // used for stats - - function onTimeUpdate(e) { - events.trigger(self, "TimeUpdate", [e]); - - if (self.lastCommand && self.lastCommand.Command === 'Play' && !self.isBuffering()) { - var currentTime = new Date(); - var playAtTime = self.lastCommand.When; - - var state = playbackManager.getPlayerState().PlayState; - // Estimate PositionTicks on server - var ServerPositionTicks = self.lastCommand.PositionTicks + ((currentTime - playAtTime) - self.timeDiff) * 10000; - // Measure delay that needs to be recovered - // diff might be caused by the player internally starting the playback - var diff = ServerPositionTicks - state.PositionTicks; - var diffMillis = diff / 10000; - - self.playbackDiffMillis = diffMillis; - - // console.debug("Syncplay onTimeUpdate", diffMillis, state.PositionTicks, ServerPositionTicks); - - if (self.syncEnabled) { - var absDiffMillis = Math.abs(diffMillis); - // TODO: SpeedToSync sounds bad on songs - if (self.playbackRateSupported && absDiffMillis > self.maxAcceptedDelaySpeedToSync && absDiffMillis < self.syncMethodThreshold) { - // SpeedToSync method - var speed = 1 + diffMillis / self.speedUpToSyncTime; - - self.currentPlayer.setPlaybackRate(speed); - self.syncEnabled = false; - self.showSyncIcon("SpeedToSync (x" + speed + ")"); - - self.syncTimeout = setTimeout(() => { - self.currentPlayer.setPlaybackRate(1); - self.syncEnabled = true; - self.clearSyncIcon(); - }, self.speedUpToSyncTime); - } else if (absDiffMillis > self.maxAcceptedDelaySkipToSync) { - // SkipToSync method - playbackManager.syncplay_seek(ServerPositionTicks); - self.syncEnabled = false; - self.showSyncIcon("SkipToSync"); - - self.syncTimeout = setTimeout(() => { - self.syncEnabled = true; - self.clearSyncIcon(); - }, self.syncMethodThreshold / 2); - } - } - } - } - - self.lastPlaybackWaiting = null; // used to determine if player's buffering - self.minBufferingThresholdMillis = 1000; - - // TODO: implement group wait - function onPlaying() { - self.lastPlaybackWaiting = null; - events.trigger(self, "PlayerPlaying"); - } - - // TODO: implement group wait - function onWaiting() { - if (!self.lastPlaybackWaiting) { - self.lastPlaybackWaiting = new Date(); - } - events.trigger(self, "PlayerWaiting"); - } - - self.isBuffering = function () { - if (self.lastPlaybackWaiting === null) return false; - return (new Date() - self.lastPlaybackWaiting) > self.minBufferingThresholdMillis; - }; - - function bindToPlayer(player) { - if (player !== self.currentPlayer) { - releaseCurrentPlayer(); - self.currentPlayer = player; - if (!player) return; - } - events.on(player, "pause", onPlayPauseStateChanged); - events.on(player, "unpause", onPlayPauseStateChanged); - events.on(player, "timeupdate", onTimeUpdate); - events.on(player, "playing", onPlaying); - events.on(player, "waiting", onWaiting); - self.playbackRateSupported = player.supports("PlaybackRate"); - } - - function releaseCurrentPlayer() { - var player = self.currentPlayer; - if (player) { - events.off(player, "pause", onPlayPauseStateChanged); - events.off(player, "unpause", onPlayPauseStateChanged); - events.off(player, "timeupdate", onTimeUpdate); - events.off(player, "playing", onPlaying); - events.off(player, "waiting", onWaiting); - if (self.playbackRateSupported) { - player.setPlaybackRate(1); - } - self.currentPlayer = null; - self.playbackRateSupported = false; - } - } - - self.currentPlayer = null; - - events.on(playbackManager, "playerchange", onPlayerChange); - bindToPlayer(playbackManager.getCurrentPlayer()); - - self.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled - self.syncplayReady = false; // Syncplay is ready after first ping to server - - self.processGroupUpdate = function (cmd, apiClient) { - switch (cmd.Type) { - case 'PrepareSession': - var serverId = apiClient.serverInfo().Id; - playbackManager.play({ - ids: cmd.Data.ItemIds, - startPositionTicks: cmd.Data.StartPositionTicks, - mediaSourceId: cmd.Data.MediaSourceId, - audioStreamIndex: cmd.Data.AudioStreamIndex, - subtitleStreamIndex: cmd.Data.SubtitleStreamIndex, - startIndex: cmd.Data.StartIndex, - serverId: serverId - }).then(function () { - waitForEvent(self, "PlayerChange").then(function () { - playbackManager.pause(); - var sessionId = getActivePlayerId(); - if (!sessionId) { - console.error("Missing sessionId!"); - displaySyncplayUpdate({ - Text: "Failed to enable Syncplay!" - }); - return; - } - // Sometimes JoinGroup fails, maybe because server hasn't been updated yet - setTimeout(() => { - apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { - GroupId: cmd.GroupId - }); - }, 500); - }); - }); - break; - case 'UserJoined': - displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayUserJoined', cmd.Data) - }); - break; - case 'UserLeft': - displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayUserLeft', cmd.Data) - }); - break; - case 'GroupJoined': - displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayEnabled') - }); - // Enable Syncplay - self.syncplayEnabledAt = new Date(cmd.Data); - self.syncplayReady = false; - events.trigger(self, "SyncplayEnabled", [true]); - waitForEvent(self, "SyncplayReady").then(function () { - self.processCommand(self.queuedCommand, apiClient); - self.queuedCommand = null; - }); - self.injectPlaybackManager(); - self.startPing(); - break; - case 'NotInGroup': - case 'GroupLeft': - displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayDisabled') - }); - // Disable Syncplay - self.syncplayEnabledAt = null; - self.syncplayReady = false; - events.trigger(self, "SyncplayEnabled", [false]); - self.restorePlaybackManager(); - self.stopPing(); - break; - case 'GroupWait': - displaySyncplayUpdate({ - Text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) - }); - break; - case 'KeepAlive': - break; - default: - console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); - break; - } - }; - - self.lastCommand = null; - self.queuedCommand = null; - - self.processCommand = function (cmd, apiClient) { - if (cmd === null) return; - - if (!self.isSyncplayEnabled()) { - console.debug("Syncplay processCommand: ignoring command", cmd); - return; - } - - if (!self.syncplayReady) { - console.debug("Syncplay processCommand: queued command", cmd); - self.queuedCommand = cmd; - return; - } - - cmd.When = new Date(cmd.When); - - if (cmd.When < self.syncplayEnabledAt) { - console.debug("Syncplay processCommand: ignoring old command", cmd); - return; - } - - // Check if new command differs from last one - if (self.lastCommand && - self.lastCommand.When === cmd.When && - self.lastCommand.PositionTicks === cmd.PositionTicks && - self.Command === cmd.Command - ) { - console.debug("Syncplay processCommand: ignoring duplicate command", cmd); - return; - } - - self.lastCommand = cmd; - console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks); - - switch (cmd.Command) { - case 'Play': - self.schedulePlay(cmd.When, cmd.PositionTicks); - break; - case 'Pause': - self.schedulePause(cmd.When, cmd.PositionTicks); - break; - case 'Seek': - self.scheduleSeek(cmd.When, cmd.PositionTicks); - break; - default: - console.error('processSyncplayCommand does not recognize: ' + cmd.Type); - break; - } - }; - - self.scheduledCommand = null; - self.syncTimeout = null; - - self.schedulePlay = function (playAtTime, positionTicks) { - self.clearScheduledCommand(); - var currentTime = new Date(); - var playAtTimeLocal = self.serverDateToLocal(playAtTime); - - if (playAtTimeLocal > currentTime) { - var playTimeout = (playAtTimeLocal - currentTime) - self.playerDelay; - playbackManager.syncplay_seek(positionTicks); - - self.scheduledCommand = setTimeout(() => { - playbackManager.syncplay_unpause(); - - self.syncTimeout = setTimeout(() => { - self.syncEnabled = true - }, self.syncMethodThreshold / 2); - - }, playTimeout); - - // console.debug("Syncplay schedulePlay:", playTimeout); - } else { - // Group playback already started - var serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; - playbackManager.syncplay_unpause(); - playbackManager.syncplay_seek(serverPositionTicks); - - self.syncTimeout = setTimeout(() => { - self.syncEnabled = true - }, self.syncMethodThreshold / 2); - } - }; - - self.schedulePause = function (pauseAtTime, positionTicks) { - self.clearScheduledCommand(); - var currentTime = new Date(); - var pauseAtTimeLocal = self.serverDateToLocal(pauseAtTime); - - if (pauseAtTimeLocal > currentTime) { - var pauseTimeout = (pauseAtTimeLocal - currentTime) - self.playerDelay; - - self.scheduledCommand = setTimeout(() => { - playbackManager.syncplay_pause(); - setTimeout(() => { - playbackManager.syncplay_seek(positionTicks); - }, 800); - - }, pauseTimeout); - } else { - playbackManager.syncplay_pause(); - setTimeout(() => { - playbackManager.syncplay_seek(positionTicks); - }, 800); - } - }; - - self.scheduleSeek = function (seekAtTime, positionTicks) { - self.schedulePause(seekAtTime, positionTicks); - }; - - self.clearScheduledCommand = function () { - clearTimeout(self.scheduledCommand); - clearTimeout(self.syncTimeout); - - self.syncEnabled = false; - if (self.currentPlayer) { - self.currentPlayer.setPlaybackRate(1); - } - self.clearSyncIcon(); - }; - - self.injectPlaybackManager = function () { - if (!self.isSyncplayEnabled()) return; - if (playbackManager.syncplayEnabled) return; - - playbackManager.syncplay_unpause = playbackManager.unpause; - playbackManager.syncplay_pause = playbackManager.pause; - playbackManager.syncplay_seek = playbackManager.seek; - - playbackManager.unpause = self.playRequest; - playbackManager.pause = self.pauseRequest; - playbackManager.seek = self.seekRequest; - playbackManager.syncplayEnabled = true; - }; - - self.restorePlaybackManager = function () { - if (self.isSyncplayEnabled()) return; - if (!playbackManager.syncplayEnabled) return; - - playbackManager.unpause = playbackManager.syncplay_unpause; - playbackManager.pause = playbackManager.syncplay_pause; - playbackManager.seek = playbackManager.syncplay_seek; - playbackManager.syncplayEnabled = false; - }; - - self.playRequest = function (player) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "PlayRequest"); - }; - - self.pauseRequest = function (player) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "PauseRequest"); - // Pause locally as well, to give the user some little control - playbackManager.syncplay_pause(); - }; - - self.seekRequest = function (PositionTicks, player) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "SeekRequest", { - PositionTicks: PositionTicks - }); - }; - - self.pingIntervalTimeoutGreedy = 1000; - self.pingIntervalTimeoutLowProfile = 60000; - self.greedyPingCount = 3; - - self.pingStop = true; - self.pingIntervalTimeout = self.pingIntervalTimeoutGreedy; - self.pingInterval = null; - self.initTimeDiff = 0; // number of pings - self.timeDiff = 0; // local time minus server time - self.roundTripDuration = 0; - self.notifySyncplayReady = false; - - self.updateTimeDiff = function (pingStartTime, pingEndTime, serverTime) { - self.roundTripDuration = (pingEndTime - pingStartTime); - // The faster the response, the closer we are to the real timeDiff value - // localTime = pingStartTime + roundTripDuration / 2 - // newTimeDiff = localTime - serverTime - var newTimeDiff = (pingStartTime - serverTime) + (self.roundTripDuration / 2); - - // Initial setup - if (self.initTimeDiff === 0) { - self.timeDiff = newTimeDiff; - self.initTimeDiff++ - return; - } - - // As response time gets better, absolute value should decrease - var distanceFromZero = Math.abs(newTimeDiff); - var oldDistanceFromZero = Math.abs(self.timeDiff); - if (distanceFromZero < oldDistanceFromZero) { - self.timeDiff = newTimeDiff; - } - - // Avoid overloading server - if (self.initTimeDiff >= self.greedyPingCount) { - self.pingIntervalTimeout = self.pingIntervalTimeoutLowProfile; - } else { - self.initTimeDiff++; - } - - // console.debug("Syncplay updateTimeDiff:", serverTime, self.timeDiff, self.roundTripDuration, newTimeDiff); - }; - - self.requestPing = function () { - if (self.pingInterval === null && !self.pingStop) { - self.pingInterval = setTimeout(() => { - self.pingInterval = null; - - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - - var pingStartTime = new Date(); - apiClient.sendSyncplayCommand(sessionId, "GetUtcTime").then(function (response) { - var pingEndTime = new Date(); - response.text().then(function (utcTime) { - var serverTime = new Date(utcTime); - self.updateTimeDiff(pingStartTime, pingEndTime, serverTime); - - // Alert user that ping is high - if (Math.abs(self.roundTripDuration) >= 1000) { - events.trigger(self, "SyncplayError", [true]); - } else { - events.trigger(self, "SyncplayError", [false]); - } - - // Notify server of ping - apiClient.sendSyncplayCommand(sessionId, "KeepAlive", { - Ping: (self.roundTripDuration / 2) + self.playerDelay - }); - - if (self.notifySyncplayReady) { - self.syncplayReady = true; - events.trigger(self, "SyncplayReady"); - self.notifySyncplayReady = false; - } - - self.requestPing(); - }); - }); - - }, self.pingIntervalTimeout); - } - }; - - self.startPing = function () { - self.notifySyncplayReady = true; - self.pingStop = false; - self.initTimeDiff = self.initTimeDiff > self.greedyPingCount ? 1 : self.initTimeDiff; - self.pingIntervalTimeout = self.pingIntervalTimeoutGreedy; - - self.requestPing(); - }; - - self.stopPing = function () { - self.pingStop = true; - if (self.pingInterval !== null) { - clearTimeout(self.pingInterval); - self.pingInterval = null; - } - }; - - self.serverDateToLocal = function (server) { - // local - server = diff - return new Date(server.getTime() + self.timeDiff); - }; - - self.localDateToServer = function (local) { - // local - server = diff - return new Date(local.getTime() - self.timeDiff); - }; - - // THIS FEATURE IS CURRENTLY DISABLED - // Mainly because SpeedToSync seems to do the job - // Also because the delay is unreliable and different every time - self.playerDelay = 0; - self.playerDelayMeasured = true; // disable this feature - self.measurePlayerDelay = function (positionTicks) { - if (self.playerDelayMeasured) { - playbackManager.syncplay_seek(positionTicks); - } else { - // Measure playerDelay by issuing a play command - // followed by a pause command after one second - // PositionTicks should be at 1 second minus two times the player delay - loading.show(); - self.currentPlayer.setPlaybackRate(1); - playbackManager.syncplay_seek(0); - // Wait for player to seek - setTimeout(() => { - playbackManager.syncplay_unpause(); - // Play one second of media - setTimeout(() => { - playbackManager.syncplay_pause(); - // Wait for state to get update - setTimeout(() => { - var state = playbackManager.getPlayerState().PlayState; - var delayTicks = millisecondsToTicks(1000) - state.PositionTicks; - var delayMillis = ticksToMilliseconds(delayTicks); - self.playerDelay = delayMillis / 2; - // Make sure delay is not negative - self.playerDelay = self.playerDelay > 0 ? self.playerDelay : 0; - self.playerDelayMeasured = true; - // console.debug("Syncplay PlayerDelay:", self.playerDelay); - // Restore player - setTimeout(() => { - playbackManager.syncplay_seek(positionTicks); - loading.hide(); - }, 800); - }, 1000); - }, 1000); - }, 2000); - } - }; - - // Stats - self.isSyncplayEnabled = function () { - return self.syncplayEnabledAt !== null ? true : false; - }; - - self.getStats = function () { - return { - TimeDiff: self.timeDiff, - PlaybackDiff: self.playbackDiffMillis, - SyncMethod: self.syncMethod - } - }; - - // UI - self.showSyncIcon = function (syncMethod) { - self.syncMethod = syncMethod; - events.trigger(self, "SyncplayError", [true]); - }; - - self.clearSyncIcon = function () { - self.syncMethod = "None"; - events.trigger(self, "SyncplayError", [false]); - }; - } - - return new SyncplayManager(); -}); diff --git a/src/scripts/site.js b/src/scripts/site.js index 3a2ba13156..421cdae32e 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -314,6 +314,13 @@ var AppInfo = {}; return obj; } + function returnDefault(obj) { + if (obj.default === null) { + throw new Error("Object has no default!"); + } + return obj.default; + } + function getBowerPath() { return 'libraries'; } @@ -817,7 +824,7 @@ var AppInfo = {}; define('playbackSettings', [componentsPath + '/playbacksettings/playbacksettings'], returnFirstDependency); define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); - define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnFirstDependency); + define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency); From a18bca9d8c4a15383985d7a403b5564b88008921 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 4 Apr 2020 18:20:39 +0200 Subject: [PATCH 004/181] Implement syncplay permissions for a user --- src/components/syncplay/groupSelectionMenu.js | 55 +++++++++++++------ src/components/syncplay/syncplayManager.js | 6 +- src/controllers/useredit.js | 2 + src/scripts/librarymenu.js | 8 ++- src/strings/en-us.json | 6 ++ src/useredit.html | 10 ++++ 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 671b76d5ea..046900d8e2 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -27,18 +27,17 @@ function emptyCallback() { /** * Used when user needs to join a group. * @param {HTMLElement} button - Element where to place the menu. + * @param {Object} user - Current user. + * @param {Object} apiClient - ApiClient. */ -function showNewJoinGroupSelection(button) { - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); +function showNewJoinGroupSelection(button, user, apiClient) { + let sessionId = getActivePlayerId(); sessionId = sessionId ? sessionId : "none"; - - loading.show(); + const inSession = sessionId !== "none"; + const policy = user.localUser ? user.localUser.Policy : {}; apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { - response.json().then(function (groups) { - var inSession = sessionId !== "none"; - + response.json().then(function (groups) { var menuItems = groups.map(function (group) { var name = datetime.getDisplayRunningTime(group.PositionTicks); if (!inSession) { @@ -53,7 +52,7 @@ function showNewJoinGroupSelection(button) { }; }); - if (inSession) { + if (inSession && policy.SyncplayAccess === "CreateAndJoinGroups") { menuItems.push({ name: globalize.translate('LabelSyncplayNewGroup'), icon: "add", @@ -64,9 +63,15 @@ function showNewJoinGroupSelection(button) { } if (menuItems.length === 0) { + if (inSession && policy.SyncplayAccess === "JoinGroups") { + toast({ + text: globalize.translate('MessageSyncplayPermissionRequired') + }); + } else { toast({ text: globalize.translate('MessageSyncplayNoGroupsAvailable') }); + } loading.hide(); return; } @@ -92,20 +97,23 @@ function showNewJoinGroupSelection(button) { loading.hide(); }); }).catch(function (error) { - loading.hide(); console.error(error); + loading.hide(); + toast({ + text: globalize.translate('MessageSyncplayNoGroupsAvailable') + }); }); } /** * Used when user has joined a group. * @param {HTMLElement} button - Element where to place the menu. + * @param {Object} user - Current user. + * @param {Object} apiClient - ApiClient. */ -function showLeaveGroupSelection(button) { - const apiClient = connectionManager.currentApiClient(); +function showLeaveGroupSelection(button, user, apiClient) { const sessionId = getActivePlayerId(); - loading.show(); const menuItems = [{ name: globalize.translate('LabelSyncplayLeaveGroup'), @@ -143,9 +151,20 @@ events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { * @param {HTMLElement} button - Element where to place the menu. */ export function show(button) { - if (syncplayEnabled) { - showLeaveGroupSelection(button); - } else { - showNewJoinGroupSelection(button); - } + loading.show(); + + const apiClient = connectionManager.currentApiClient(); + connectionManager.user(apiClient).then((user) => { + if (syncplayEnabled) { + showLeaveGroupSelection(button, user, apiClient); + } else { + showNewJoinGroupSelection(button, user, apiClient); + } + }).catch((error) => { + console.error(error); + loading.hide(); + toast({ + text: globalize.translate('MessageSyncplayNoGroupsAvailable') + }); + }); } diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index bc0fe7bc6c..8619508e28 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -645,7 +645,7 @@ class SyncplayManager { /** * Converts server time to local time. - * @param {Date} server + * @param {Date} server The time to convert. * @returns {Date} Local time. */ serverDateToLocal (server) { @@ -655,7 +655,7 @@ class SyncplayManager { /** * Converts local time to server time. - * @param {Date} local + * @param {Date} local The time to convert. * @returns {Date} Server time. */ localDateToServer (local) { @@ -665,7 +665,7 @@ class SyncplayManager { /** * Gets Syncplay status. - * @returns {boolean} _true_ is user joined a group, _false_ otherwise. + * @returns {boolean} _true_ if user joined a group, _false_ otherwise. */ isSyncplayEnabled () { return this.syncplayEnabledAt !== null ? true : false; diff --git a/src/controllers/useredit.js b/src/controllers/useredit.js index 21ed60cfa9..cbe5acd36a 100644 --- a/src/controllers/useredit.js +++ b/src/controllers/useredit.js @@ -104,6 +104,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'fnchecked'], function $('#chkEnableSharing', page).checked(user.Policy.EnablePublicSharing); $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); $('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); + $('#selectSyncplayAccess').val(user.Policy.SyncplayAccess); loading.hide(); } @@ -145,6 +146,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'fnchecked'], function }).map(function (c) { return c.getAttribute('data-id'); }); + user.Policy.SyncplayAccess = page.querySelector('#selectSyncplayAccess').value; ApiClient.updateUser(user).then(function () { ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { onSaveComplete(page, user); diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js index 8afe5f10f1..1daa200b8b 100644 --- a/src/scripts/librarymenu.js +++ b/src/scripts/librarymenu.js @@ -86,6 +86,12 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' if (!layoutManager.tv) { headerCastButton.classList.remove('hide'); } + + var policy = user.Policy ? user.Policy : user.localUser.Policy; + + if (headerSyncButton && policy && policy.SyncplayAccess !== "None") { + headerSyncButton.classList.remove("hide"); + } } else { headerHomeButton.classList.add('hide'); headerCastButton.classList.add('hide'); @@ -95,8 +101,6 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } } - headerSyncButton.classList.remove("hide"); - requiresUserRefresh = false; } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c7611333f0..83890426c5 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -862,6 +862,10 @@ "LabelSyncplayNewGroupDescription": "Create a new group", "LabelSyncplayLeaveGroup": "Leave group", "LabelSyncplayLeaveGroupDescription": "Disable Syncplay", + "LabelSyncplayAccessCreateAndJoinGroups": "Allow user to create and join groups", + "LabelSyncplayAccessJoinGroups": "Allow user to join groups", + "LabelSyncplayAccessNone": "Disabled for this user", + "LabelSyncplayAccess": "Syncplay access", "LabelTVHomeScreen": "TV mode home screen:", "LabelTag": "Tag:", "LabelTagline": "Tagline:", @@ -1031,6 +1035,7 @@ "MessageSyncplayUserLeft": "{0} left group.", "MessageSyncplayGroupWait": "{0} is buffering...", "MessageSyncplayNoGroupsAvailable": "No groups available.", + "MessageSyncplayPermissionRequired": "Permission required to create a group.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", @@ -1379,6 +1384,7 @@ "Suggestions": "Suggestions", "Sunday": "Sunday", "Sync": "Sync", + "SyncplayAccessHelp": "Select the level of access this user has to the Syncplay feature. Syncplay enables to sync playback with other users.", "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", "TV": "TV", "TabAccess": "Access", diff --git a/src/useredit.html b/src/useredit.html index 0de3069dc7..5f759c973c 100644 --- a/src/useredit.html +++ b/src/useredit.html @@ -104,6 +104,16 @@
${LabelUserRemoteClientBitrateLimitHelp}
+
+
+ +
${SyncplayAccessHelp}
+
+

${HeaderAllowMediaDeletionFrom}

From 9fabbd5746c90b2115ae5b634159fb79d60abc5f Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 15 Apr 2020 18:09:34 +0200 Subject: [PATCH 005/181] Capture playback permission --- gulpfile.js | 2 +- package.json | 7 +-- src/assets/audio/silence.wav | Bin 0 -> 88244 bytes src/components/syncplay/groupSelectionMenu.js | 11 ++++ .../syncplay/playbackPermissionManager.js | 51 ++++++++++++++++++ src/scripts/site.js | 1 + src/strings/en-us.json | 1 + webpack.dev.js | 4 ++ webpack.prod.js | 4 ++ 9 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/assets/audio/silence.wav create mode 100644 src/components/syncplay/playbackPermissionManager.js diff --git a/gulpfile.js b/gulpfile.js index 6c33167386..ad77d9a677 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -45,7 +45,7 @@ const options = { query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'] }, copy: { - query: ['src/**/*.json', 'src/**/*.ico'] + query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.wav'] }, injectBundle: { query: 'src/index.html' diff --git a/package.json b/package.json index 39330cf600..19d3b05a45 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,9 @@ "src/components/playback/mediasession.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/components/syncplay/playbackPermissionManager.js", + "src/components/syncplay/groupSelectionMenu.js", + "src/components/syncplay/syncplayManager.js", "src/scripts/dfnshelper.js", "src/scripts/dom.js", "src/scripts/filesystem.js", @@ -105,9 +108,7 @@ "src/scripts/keyboardnavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", - "src/scripts/settings/webSettings.js", - "src/components/syncplay/groupSelectionMenu.js", - "src/components/syncplay/syncplayManager.js" + "src/scripts/settings/webSettings.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" diff --git a/src/assets/audio/silence.wav b/src/assets/audio/silence.wav new file mode 100644 index 0000000000000000000000000000000000000000..63f253da845e5177e59bcbc05a49ecb118f5e97a GIT binary patch literal 88244 zcmbWgOR_9Wl3n+*5?RG2hQO&%goFsXYyj*+gu)(s7z77{0kS@s$-C})E4R8w+yu#Z zaw5#l%~ZE;-Ky>pC(eEU>wo+Y|KY#=Z-4mhKmSkv>3{h@{?GsVKmPmQe*5hYKmPll z|J!fB{a-)k|KYcP|J(omU;gWVVa(&NfBlEw{_^AJ&p&=Vx}!h-ILB^Q@blNd{@nDp zCwBk&udZVEAAbMQ9;-c{P0zEvwfX$x-w?0{%y&&Y?rL_pD_ej0d7P!c{Po}c_OCyF zh=3KG{mW0wuE(anu=>Z}{_V%wfBU({SdH1}Is3=oam9nI{PoAfy?_46)SrJ?AzEVK zjFGnoHnP|%k$E(v#rE6Zc$d>>j72UlMEZO@GiS7B1SW_xp093Y&Ytx5`7qXWf!^1KRc@=7$X0usC@3MJ-;}3Etb~ab8+uEay6D^sr|Jifdiq zS9Zu3p45c)Wl99)vCNBj(Q*7(z`W_qli|-Ue#vH?gmW@JQ zswQz`Z9l0do8apQo7r(Rh8lg3%Ph&QnDbcNu~a3HA+t;lpVtyN@tV}Uouk@OpU*Wa z!F3=Ir|gvVu6^FS$u*3zHzldb&Mou$z7fUbFUsnwsAh`{@zBb4FE8y0mjSNKvY%{(PA}?JgfG`-3mA>^W_W++`D*x#B6tc8{KQN5j=ZO_HUlhYI=pe zfBkuHyslbCR{8EI3Xtr|@a!7ejgzuO6f*e7AHgq&MxEk}CH&5M@AgkW?%%qLfMHfr zRWL(@+s5W*fzXTx9PuBU`Qaj5V7kVqU%OZZVWkblDq9qO3bEMm!b&aLtx; z9xwWk-O83@BQSSxVt;F;}&t>(-YS@3#T_cebh zeOgtS;K5pV0I9{-nE+3C#;e_9sja`)%o-!sswx=)z?WyPqv zO48rB(=HafC2YD+)Tu_SA-_AD?(-trHJp`C z{>GJl$zOFgJlS=%8sB~TI#}6s=T`4n&a^;Go!N+)B8ze7u>O9iEgobGj>+#9bd|Get0^NG{d*owV89S;f@4ITOah84FvxU^xB2z!`m~z6g*X(#t?7b5EZ$*mCJ@U7%6HYm*dd07L zSdosb4{SIxcb~K2l|`enn|wwy#)>Ltwf;np8ry*_#~`3vj7{}$-{pv6>%)lG=?pu6 z`t6_p+WUlSr>N&Y)_k>6suxu#BF3>WZ`oEOt{*>Qa`o`ZsLVX?N3=%OY|o-a6WM0> zuzF3c+HO6&3Vt@~C=Rf*NB8`8GUi?9J40(=*GH_D-Ry9s%8=%B$Kl==a{n}S2AiIxtdRI7au2B(q zp$fj^T!n_FoXQT_C-d@}Qo^8rfAe8Q$xay&k+IL-W;-L&d!$nFBW^QbmeA7s6UiIv ztQmKnts^7a7&NU&a~_wE=j&U+tJSbNQ#L@;W8+ zukKSke)fW5H}3Dxi|}()#(G`#m?d)TRmJP1*TT2@Z$?2Lm)?c+E=ulQyH0T5>7`4# z0@FqumA^6&qw!fTJTGr%$TP6zLxfphp2Ub<&sq82FNJxPfvtRt9lQSIR}`@lQ)%9e z+qt%57VI`^{$^Ju@HNZxJdI;{(HVa%7UjEdYO|{;y-?4ezLt?^^sdhW=ZI*>@Wvc$ zH(E~bOqJkXk+PdLMXBw3&YY}cSgGIQHKxzZUhDemj{0US{zbAC%|Cf4!Wc~hWyM;q zN|vSa1In&U;ww{Iv1$TmS#hMQ%tbYMmBslt;w(S;FPCw?W0=s6qc>Z64+&PyFsr>U zBBtK=_KAdCgznmxY4vc~$d5G@4`W&zM(g#b+pV;U(|&ul*3!HHGqHI^)j zDO*>u>+pEhbG2jsi>_Cd1WN46Ia;xj?AczkjA@-F9hB!his$ahmMpW5sEb_B#)&^y z@!6K$(oE-Y>Z zdfzDQzOHJ^Mmk+SypQ|SkC>a2A+trK)}gE!PZ^2fpVC}6{cgvcMQcT6z<&50IkVV{L~hNS zx?R7O-MCkIV#QjI7#(|@+3b|^*@d;iTCJDKFsFQMTdgtTtLs$7o*mu2of}(@oEI&h z^z;6m+KUzNefllmU@(F=M~kKipFOR8vtT^zvkr)um_s}}u~oKM=^moFBeU)K7=G$- zR?C>zmFzTI=EirvQf8mq83%S>PTu!N+jA>Op7mI^x6bTyjAtKZ-~N?eI;)LEBu|`` z%e@1+9mv_DW4BphpZ+>0f92Q;OR;(FYJtzP!?>LFiuHLaYgXQO+;QxC(|$XIl|I9R zmCF_5@ft!9<8Q||670Pnm-TD^Ggq_8XpiwIr@q6TE%W0QJ{7Th^V)kJtDmkz9POH& z?s@GBdUI1A;#ym>WfVp5>C?`#YkxYtyT@s{PX(O0pD!#&%kaH6ifmjql2h3dK`Sp} zlr9%pzt^>p_zCzp5i9w9j>=wrGKG^#@W{4D%dSYPWHst)RM_fbke@!2XV+r<71P!5 zG7#J8#++tLub~%N(Nj<23S$*k)UEvg@|tI_sMC(N`NXKtkFNg9wd~$b=uFW)dCAw} zND*DhSfRB>GExrh5cRv|Hr%)7-ujm5CpTuJXI}N|NVY-i&wXdzJ0L$z!Ee9&uA1}z ztJl0u_fKrbQXW|&Z0iZsr9CE|^@zV2)9_^g{^ zmgn_oJo8WwtO~np%JZ0Tz0brj)|x9j=CL?^4M#bl^WVE$kWA%X7udrZ?<}bx*wLO0 z5H&gDdmA#Z9r$-d9N~(c&SdNTYW`$4@2=4C5IuX|W%#25Mjw<)+AAaZa`v{fEacYQfYk%Km zjXn8)T7KI0-K6}evYTnI2h}HWJ9gBOiW@6kmzif)y}CzDlH>fSC9jG0cXnyh`eoLR zk*)IiYu5TC)f_Emy~<0^)~wegH65>eRlP7-4(_?da*=ITmY;lqY5hU`tTwXWycA8igb_TOFI>)`t@SYf`O_KVJ05yYX@*LgkEkHL^WyoTIZuzjNr zIj>nTDy`7vR`I{`$lmwL{svDCf>_^Je=11Tfg5ZwsYN5ren)qmW22&yo@{LAR+Pmz z-$QWMhA!6hXEcpnbLXywd+%dK8KbdY`(Zb4b=yz%)%kYcqb)pDvvSP!sEV?x^=(&h zCcUiJ%S8FOyzI=kks6wg(zNKPUwbt%`!1sIXL6?Of&-LzSpJIe&WK@NiOOzb4V5z$ zNA;-2jr41O^1d>?np>F;t5*+MHWswBJc@fdZ)K}gMXdO`>+o|Pj$&k^v0$h2j#Xw^ zEpPUid;c~)-m9fo2bee7@#Om~`NQr;a?Z27mo0f<-nGxJ;VO7&SJ&L~jLO$*tWkZ) zr`I~)we<oSukqV zdgCx_u5Ew1ET7Dmq4Byd$GK2jJ!Xpi^&cXrGQkUm#o(@@sWP$|-&g<%$K*pVKL@`b ziP9{YJ(WNh;-&m_35vrmpS^HpRRh*9&vOoPKJ_rc{rmU zD|X2tO!q@wc3)jocgCxgn809`V!iGolSaNwsCZF*?k&oxtPZ60(2^6of-07q5a2Dg z9G63R?}$~6J5#^9N_%Q7p32E0WMsYadbRsRX54AkZsOH6&O%kCFu$=Z=(jA_CphP0 zv28{+`UH&rF9*a_JYvn;uC!n>reeJDHYRgrrqvv$Ih8O*t(!VreNt|gJEKfl`8J)}xlcxEid+>;WEe(UGM+`x-__b&I}e&mH%dS;E)aT(R7JvB69j!`W0kQX?C zJFKQh&VFW;Ssr=4pZk+9f3%^0R;MLruFpQLxC((M9`WV4IM7XMz$+8F&Y^yQ;Tq@vH=cew`L<(R~_weJ?EZ3ab+&e zPkBm5t%1+yL$x}0V}0A%vEJEn(TiEEzw(~_XHCxX_BDq+R$rf=W3|m55aER#*EF-j*_F-v8b8C}Ou6UZoSc5xqyI*s@b%f7RBkHtx-&o2!}Goq z8~XD~usdio7AE60o;`~IRMt#)XZvn%U27}rpVO)}m<@7f4$P7G^w0Qv#powXbo}RM z4%Q&P$)CE=vb|xwqKJ7#PyLSS-Q9-LmEvAT$!ZVI*-d3eHbn6pmp83*?q-m&@0;q8 zT{*)*X1Ax_BWG$K-s9fYpse1CzPg(VTNBflMIyNsX=FwZo_OH3tA*@Ze~b3AfyeRD z?=Flqi!mDyi~e5qLx(*d=Kg6*U-MX0MqEsJklUieL#*PZBSv1!JB$BzZN!W=Zif)1 zER-2vmj(4~RD15)?dn_GS@XKwk=f&jhr8c3p-=Y_z1+8RF?pvHKjT6jJads&t97iP za=IDcbp6L4V}DDt&h*KIn7`#K&HHKSG%*{XQj`218t=wQe9PI*%~qH-{9tuC-iZNi)PSDdc1=1&D;V^&o{zfCSub=UVK z%4=ugVU_V&9rADIWDk$U^&CAd8KpVM3f_kxX3al$%0(>0SvNP@oAyvF)s_LW^XVZK55YUy>+_j`t`H&s4du?9)!XrW$gm=84I; z%!oGCi#-%8pU1A=y!YhIVl(y_)n&#SDsoSVW5Hu1GjjbKjaOYV&%fibe|3fR{;&sI zy{oa>IJ^9reGuNf#R+F+MT_6>f_C-0IVRRBO~m36leOdfD8HrA^%rb-i#5;=Z(X?< zdu>-c?PgrrpP$O11*2O~F1Ak$p9n zHp-fLcprhci22rRk*F;#hLq^UBG!(5b*JaKb6OA8a~7>O*+w-n74)Emu zdKE|U@YYaFiQ33xv`DaThPbBi?_Byx=_1GH%^zpfpqTyc0c^+9Pe|t}&b1@oS|yq% zI@Ku3yvkLGI`S1a#2{|_{r=Uo*t&c4pnv}k&~NKnQGI%9gzTSg7(bEKA^glNPF*uz zpN!u8o84z5SP{3}*a=jLI?s>i%MN=)DD_52j_NXXQFGo}y-%a{{BQkFY|C^k5}``e zH`dtib6gge{J6dtwCPcL{Cm8Gx>%>Zr;RiX2l8N-GCOvnRC15L<0`W^19iPLplV}E z+qXj73v#@$-pm=(s-zgj_0)lJ*ID&;J$L1LM*Xa)kl;sq#h1=u>7pu2uJ25%7EgYd z(bmmrN~vz|`|)}TbKmmPp7(n<888>BE(WqggeuZ(^sb=1m3>~dFr1F$!t+Gc4n{I! zJwGjo7!>VfwJX6(#2n+JTIR^?uui|u7(<-CGZ$i4u|unUs@)w{=ldjwtIo~+GO71z z(u|h*DQ@wLEwyr8h)=Is#Of$U%txzQ52DU6xB24wL~I^pD*n{L-|J6Cqc`{}H?nk} z&C(l}u`?SlV7=Uks2qo~eO9rar`k7<;EZoJ(w_8yrX z-MPK9TU=dz%Fnm=6wl93iZ)eUu8FFc^cb(jYeaBTU%8B+x&6I*XHx#J-fx!m`mF2v z7ca1~3Sk^#)>rpWUB2^i^N_7Ksw^9NX0QGLpo-&@Wi(Bj$9SHDM`&>of)!YG#u&Ec)*??KQ$AF z&;5R~-OREY{?sbFdzavB73{is(a*eh{WrSj^-4c_Fz-D3SS?h&z9TWeMtk3*Xr()In!2kZmjUq(e?Q?OMVK^8F^uM zv8UwTgPH|#z4OAkUn?S;i_rek|Kq6qRs&&VpPXnx{p|M+Y6q*Lrz(wrT~pgK!`k9o ztXO~RqT{J{tK#_P)120JcPlTz+PEUb4F1F&sUUHl+RHa<(ev*=T&BRj@=`QJM8mg zt$6orjp!--sg3yddk?dTHGk6C6D>B3Uu&Qi@n3)7_S1F$A3|owo}g`TspikmdY^V< z*hpmene%zKcMWB#U_}{j{Or^+mU>}BF5P$6rtG?D`sQ`obbtBC8eV1*t12sBR*Eb3 zxrQFFy(?Cc-ah;0t=yz7V+3WL&@0!}Ax>GFPP7AmD(D%hR)77BKjJGFRuwbEKFvIH z>XEDLmG%tkt8SbY7FX-_In|0lc8B!l%NW07Fy3bjc^Kzf*fAr1;BERTw_Uwj9}nfC z997?GnwZvWtflXs)r<9AC!1HZThwqOR_mPPkIC0Cu8ompKd zv%LcCb^nwK#Uy@_4rdlpt^e1#=M}jgbQ#E-c@)W0mEdlb`uyDgtWOn%8fA?2u;Tys z!hbhYEiQw`0~gP%+dJyiB6F2ir<~L$|8SKhJ7^gh^NczNRxx46_rHGIh-m6dceT;s z$0%+!W=%{Ykrj8L=xV+Fvt*u`--rx=f}X={QFJe~Yg*Y!Z&uHG@RM`x5~+4!4_4d_ z27UP*>^`%M*(u0=Ob<1D!bl(68Gb&_h9pAreU=##XDUfwK$hv zSo*EK-nIU)kzD*F(*7!rs zKESk*?3tabz&dPm=FR+x_$qvS#e(Zwnf>vxNQk{?FZwW=8?Oy;TUT2a+-)SC{XN7V zFJLg@Mlj4LQ2k3g?0npB zcXqe8u3$6lj#%v~&TVYI-fYlY`tISiRYu4>V-RCshM4s#QwQZ7E7d_An3=T5=pDEH zaBEh&s5%VnmD$*Wff{Wz9~V&#lEY-Vv5wYl!9YUG|V^iD_LU&sG#Lc&mfZ@N9NUY?l03a!`%CE zaeGdDjK7WUtYb4~7b-LSud zg$3qiEZfbzeKzm<7;E?Pw~<)9M=j=Xx(bv$o@lXsJGJ?po?*|P)KyfaWlh`nS?tbS zec1uKD-=s;^!n}{;^*Aue)wZ?oVpH#o=b@_CpI&B`?&LP=uXgcb@FNk%HnC{vCr;n zjE2*kt5*s6)SvUrU7Xkv{YBkOIjs^})1K<93c_|=6GzA9Z&tFOoshFmbevI%MrWKx zZ63>J*;*|(V+|~?@Ev>l(HC=5gIL8bSH`qxx?eyk^)bGyQcl{n*Wy$E_YR$$gU}oi z6OqQ-Gu|C(*=XMAhTr^;Yipu!`pDnaf{Z`)&#KrdTVx$Bo%j9TvzKZcycf;7>6l3` zS$iJ8@5|C3jHwRqt%}iHd!%o6?9R_?Lkjy9pTsu(8CfjP6OlcmBOj>^?$a1^du&ay zaxS%-{nnI`;=0*TBU@R#Ll{T!UQJ9B`iwu>EQ@uf`NxkrT4(O=vACC!s)_w#H!hEu zRf{V9=`Dn;F;lM3dbwd&_vMV1wd|?a;$&ar>~1c6_}titwG85Xh(S)|<>nGU>@0Q| zz@r($K4-My%6dN*jhn6~I;{DK&1V$vqq{o1`0V`%T40ap+bx=Sx$ECi+x?2>tvW5w zzJ8v3nAg~OAElkCx6Yn2#(PopdZURmRicehV*yOzl=G16HCDdYMK-gxSQ*uK{CC`1 zV;xdM&wpKGJ+=`xtdic#c;D-1b7qzprq&z9?xKj50kV`esx~`4h9b~)QXaC!4dAzw>MXra7?irEjo0F0Ie6y48#$Xo9%U&1PuMo=) z*nOa(xNgoh+VY$_EIO42M=B5tS}YTsA-Zhviq%j#_I%gzcBmq-(4)we+S48$eHZ!O z=lA?P*r#DV=xbup*P?B||Sfs565c70zyEXJ_U?ESZ?uxPF6 zFD=5Ap7z@WEEzvm?9SrXxBNB+Y4L@v7=6{Vz11sD8B1&UE2G(BZ$4rrO~-`QHg;I= z8duyi>gj$)U`K!YCBytS?$)%ich}IaVH-F3xSguGK3AHC^y1x9zmJ!PuH|4U5?Gf9 zv*Tw(=rHcgu~jpaJ8Pu!I?YyJDdPPahhqAsvULbMx$oQPhhR}}VCMlE@s|nga!sj6 zTE{+5Td^ja^;oPm4rh#1*LZWz+73^=bbh)^uduIGachw|k5f0t;PMQk>GZ86*=;s< z%-Q|kf9jV3J$zRjv#}COAzH@8^yEGUK<;?)-aEKB@RbPOSMByMYs`q<4B2n}W}hl4 zZ{e@ncXeWgJjkv#jpQ@Zu~R11nSNk|hc?J$_%3@fVQ(fM{vHlX{ zc~_Q=y;iTPi1n!roO@@4Qu=p0{45q6#br$K-up5vjcw09pVeO#yF0was;b4Z9&ktH z#ofk}-zUDs))gsME^GLj^5Xku*$mv7P+r{c>#Q6NgFa&XvabCy0-F1X!MG!SGto$8 zG5fxoP*1Int1*}U>_B+HCOPaX>tV#_!E&2)atUO3gKw#j&lGxa_=p!&)3Nfyx`js<`xJoUCA9 zuX3l1&>7LN+w4(w@t>e*S&bk`7%UQtz|ceKU1d~HOYco?||Reb(b zhAW!uUf=aSS9KzRC;i|b%w03##u&71jII(3=N)DHiO{nlVy=hgoi9D?H3ZiuXNz@ch*a<8EtTs1eP5u?RsmJVn0V&mD&+cKoZm*p>Zc_BHL&=f z%9fGt0D0)vpf??%CdU6e)ku7GA^5}9v-H5HM!dJo?B zkOL6ZNkwt@E~a%kEW~YWXywW7-UZ-)=iV`CEflk@AL3;cUppU$7+bz#6LyT?aUR+9T=7&TU(_L233_dMEgzd_ z#60!x{Ru4ldC{Us^Txn=vsyKXO!E+%hR?0_DEj59-<=|Hjz z2Tx6JzQ~R_@zVwC(Ok%eQLwL3S#YN8!?`=kRq^PXdmrx&*kK%(jT@mB^p{rRX4h6c zR%x6iFZ9lEgAW^4r04LWrMD7`s@GxD%CfaHviat;TFKJ5H%5=^WA94Sk6Ft;`+f%V zvs3q17shEkj2l_iMd?1yfyZ;{gLBP!jb)cRpYuE0$UaG|JX&pS__?6?ID@}dy1p!U z$7+tvclC1bW#hyvqx`#i>Zk|1zv%O`J#uL#;`g0fFqS>9*!u>j44IFt^lC2UqKxU! zs5^5t**fRG)t`H_E#~11r}M-M19m9*GB=G{r8O)sKaJ?}eQ%cidA71{Je&i!@2Eb% zr(hp0yR2q?3~t2yysJ~I)>!WEUs`)r`sDET1*^Idq*RkleD(;-M<6p#__fcKW zLuZEA%J9C2vtr1$IkU^gqg)atJ)us+J~;o3A@+K$a~koJt@KlGle6+O^p0)?io<+h zrI^u+>=dJCt)bSf?=k`tk8dN- zJ9hsOdh;R6^S85a+hC6MTX)PAI}toJS=Kot1CGY>r-sr|5vK=^!AC@BO4QD*O43b{ zh%JO@Y&Cq(F<$ocaF4aiZYwmknBAu?%j4zi-dztfUgInsGRGtDU6WV+)#mPx^5{O8 zQf*TnEBFigfBOASt!#d(=BcstWA?NP7d&o;iiymS8~nM3_cQea`Ev!f#O>XNN9n|j zg1@Y15v!_>D2*@`W$_yW`IFgq#>|y56`6Bl98WQqPb_`=0n1fD9z*|=p1);TTYTOQ5vbLQrzUEk)k@vLJbEC1|9#rJy_@q=fs z-u$1W|MiDPLPTW5`-#z*QxuL4E`~2zXCxDL250vvU;4u0t_R-BJdf=h?5{82^>#v$ik-N$xzV(4UAnS7-`_wQ zlNIqq5z0mE+AJDxjC79z7FX~}%&Y*}eK6O11fxwgs^$4ClRWRIN?YVAB$n~!`sH@} z&6$eudAg`ai8*EEZ#K5hzSbR_wfpz{YYXJ|AsL8CdGbj9szR?kezGOYcvdA-N4b~v zi+@bSO?<{jxr0mRI&(F~_*=AbXaupp7@raDsz^=We?9ygW^894J7#AieSUk^&$2@7 z&uVH{x#gJgQ(JPy896ckH#+zDE|1lpnOK$UkO`?-1Z z>QXM3<@fq(-WXj68XsZ;IqTG3=&|j3vzIQHHCZ-46 z>&HB{&)4c!HG#xkMDiSqQjWMX`JS3lHMkyT--~U(MYMHeqpMyD7Vl*V48>3;SuMKG zw4X}!DqD19=U#VCVj21SgS4SyL9$-lYH43%Z)b7UV!PWQKGxopv*cHTn z_v`mS_y~#F@qPfs%kT0sREtTBPyfbp^K=oXZdPfLvp0_X4#BNCZHc>`VC=Ww+nxRK zXr!_EJ$Ct646PnZ-*Xt}@j2_o79*>(-v89+)O-q6g`zj=DI71UZlh*=XZB{8vu5g@ zUF*<3`pgN6Hxd!}+0uJ`E5xdWjT`k^7QU|=Lil3O69Gp>)`(TIrCP)1-lxT^wb!`x zbFUJi)0b!HuBpm=Kl_8{v}{&Ful;)mi(gk_^ZlcDv6*!vv`;&~E@l69o$4`4?0fAJ zNBUZK0+&DE@QPY1`xLa!Tz*y`o5hWhiBwMDg2SE}uO zbEZw6-}r_F_Sf5YMafUNu;34S)r^>)`7q1!r)Ou1N_**-9_PDW7{;piKwa6VCau84 zuOGy@d#EQb#be~psN|psm$!PE?^4^zUiIKzuHUzLddn8C?`yhQ=eo?_hb%6~>HBIt z?*87ZtcVd0y%%5BSfTaqQHaE9Z&mC+b7K9~CA0(Xbdcp%6vJoaDh9u*mkwZm+x76R zC-*ojYx01F>b1!3d(^3cUH*6{KvwFiyGJZ%=HKg$%D9>N?8A2^CceeQx?Km;%ajPV zF=H)w@9UcToD$-ySF@s~oyB7EO; zP^8o;kMNQQf4XlZ53+4WQYieP7}@<)DV+O%TJHbMY9~}VKb`*m1U_tGF&%8ge3{>U z3V!{O-|9TxuvIOJ5rk&$b{wqpSoZ^UmOIB@XK!Clp|d})WnJZI$C#SYV%#pqxDb_| z%Lsd!+0Qb=$g^cA+wTl+cf1y_D~BaDr9ZIgMb(+n+byS`%kqu&&92^JrL2`zJ?B-t zzgAq4n?u;pI=DqlU+`V`#nigg7x&x!M44KyrK^wCAMu2@d)r-0!0XX!0N?YwT-&9s zPV>O-KHvN7*Y2EN7@24B=x4Jjwl#Z&9UiHp(VIb6@qD9JFIZ!r5A(Toe0!@dVa7S< z&h}f`bsWy>MOJrB*MHIH&uHorPqwb7=X#hb%6EoBKb}&yuS0hJ~K9+&$I-9LYOJ=Vt)#}yrac<<(9c-Z-_KDJb$MX1R z4=aeVe_z2J*f2Msj+@PW&io$U-(FZ@b#sT|6@fXwH5W3Mrwp?rPG6d`twtX-CH)gEXL=xS&V26pY~o>@(7J1*S^Q;Ahr7XT`fk}?mOs6UsUFWSLPqAvR~Zy?jRK9MoL-rm$ zEw;ATs$@)T6brE5cRQ^)@n}O;5yL!RTsIQ2lyR$^^BcuZyW}z!%vuW9BXh<09$|&= zu=?8$uQFm6A$IMZ_Bm{IrWjEC{!b2A^k<(2xsqoew?p2@@bu;=jfg5VR8qg8$xn0q zRbyRQ-R*uEX2y|q=uCc!vx?&Q^C}VxDAazj-jIcO#6H+mQfv|@mhB)SdG6>4PKcmS z5{#TX=l;%b_)?;OKfsZ7prSx3TUA(mvd*y?5|z<;k3^j7RmFJORPD@!=gu^%ePW6^ zcBsJnZlLqN+ZsBwgTB0n@%lg)ACqR=Iy9^9UIs+1rS1Y`Ky=nm9N3l2FF5n0j@;pM zRmP5#>^^cCXO62l{_m6heUkaAT3DwF9XZC6D#qUKIygIJ%e=nLh?N@}vylh5UKKIx z5A!KAuhgs2VT+$^v4Z10o>^Y~v!WO-i`}=B^Y-`G%2xB#rGj<^$8Nkc=3$J!idGd5 zZPC_s&0c8RuQe?ivG)t>UmMMWTxEGIYt^$@6HWZ}iZ^+}_OiW<5?AvfW{2aAL@zfm z9<%HCt);uzX+qt;(U^D2tZ=VF+etxc?1?YpYjjb)A~V^Xc+ zZJ)||zojPSQD)3sEb=PKsn-m!M>eki)dZ`){oHI~gACQbIp-r@!XaOxiKDXOT76B+ zSZe+5+urA`;H^wksRUo?p=*r(-w4?ekLAYtjfESz88N3Yusa}gw%oN_+slH;%(6Wy zzMs0Nm$p(ye|6-046nVaH>=m*y`Ss6EZvC3vFm&1F22h%F;}nE3en4VIqdIJc^A~3 z!}al=i{m=m&4N`dt~m63t5Og+%J@d1e=0{Kcs~z$@(Ls!cb-gSY!{@uctDsbD#_y3_h(~_N(JFR%+9)K&3<8xi%jE-|v_3WLs?W^b`HZyc ze||%O{^GhtNL>4YX=B+ZW2(!r=?W(sOZN&N2+G1sn>@OdUWu{to{}UB{FIHJ|wmjVYh4B-W zT_!9lczQA_cP}fcsXF6~{ekPY`MW$Wu4}JM$ZDQL-z+eaiEdo>DbPRv{MPu@xwf@%vnu*BtOD+wc$U>?wCpo_>bVG( zL-!d+J+VGRtj~x-XKsjPe}fmls;`{hR~g3U);gEHw{maZE(5Vp>>V48)>Ij>h`LNJ zuVwmLx!DHkU86Sjl@)c}Cm?>}i^nng4S~$Q*QBO+*FRYam0qVoc60~)++D|PHykXR z8$FT65sXYV)v&fWleLXgl$K>w{3;;Kow=`z3cIn)j%WU@VZX=X6;>;*+TZkCUs+Uf zzT>(%#RB`Eer2V578qua>eRD7taE($Lk_-GnLL@HG7?i|U^&KioXjpg+J12}9q;|? zbbVf-jhG0#x%gIPt$d;?mf~DS{GD*`bY;}cxwg;v;3IauE}E@p{@4RXq8&zZwtox8 zx&r|`=%uwBc_v)m$qy0d_m~&?^^BaEE5>q5C83+{vCDOgnZM_qoK~NGfKG8US5C~T zIZ*?hDLQblD&@`kuoMR~W$BGrb^dyHds*?khnFAcwH{tkg>*5R(Ku><$}2)FqLiXr zj?1WM%}LK%3oUz8kH0}!1+ceFKasG$YY=CCwMtJtn?IH1+{F;@br)>rBer-ANgTvo zRpJ^E$!BNS>8@*ISAb@#rRzbJ-K&B#+U%JaQb|$AO*L+ua*;0Sl>2{4g4evMcYC62 z_)G?ix>rVg8S{+^-{tBukssn}pNPxSovjXvzHD7x zvfdg+i8t%2`06V*+4FvI&Yriw@4yeU7wd4ggE;M;MC$w3uvA;IG<#*TY>D$-(fP2h zwB~ilGt5|d_Q-O6uT?5QY^;(o5T~|sDzCRX@lxGv+$b*0&y67FcFh;>^O$|U=bQz3 z!K*g%E?PBj4DSrSkMVdjjfE6+quSNd)v}SbYJ`mPD3jv4K3b_BjKDqS7&OZ^Y{|FG ztWJCX%8F`A+niywY&q&^p04GwmLit{&c$}0o_nvA1zCzU*qOame{+7`0Gr?b3RJ#W ztJN~|8QD{RD#Z7za&M$yvBP_%U9al?A!CW{5>@MyxZ}Wm9cr zsoC~}#s}VDxXe|Bvfip*+3$MMI-eKWqyP8qFRY{**0Pf0{a9Jy5sldIdU%l&eUyRn z7b@0$UTL!~+KY*}QxdE6f`#%HFEKQ}?2d2S*zot4d^d17cLf4P)`lo`&t6>IOl(dY zSGUx_lMi>Y#%ogj>fXne?^v+^82u+YJs7#n#0R*EYPszm%xkvSZut1DOmZOe;0K+y z{W}zXYh9&?czTm@RqSbJtcE9Iqb z=#`tb?(;RzGPiSbefP5RxySdb`01T1=gqmRJ?CA99z=^JvtVSYo#?D+=k2fc*wy+) zGE76?`OjD52ZbK&80C}oJ&5F+3S8soYu;nzg`MqMCrN*GC6D#vP8In+PX@Z**_BNE zMA|*Jn(kig4?nu&KmEAt<=={k)nb;PDl2`cN%OYPctB~6IKM@yxT4 zKI^P?_OS0O#1OX^YrXsKd*%?k{@!#vTyC*FPK+aV#g-y^Wj%44y)+vmoUKn)i6Tzh z;8R6=)|PqAvahSVt$CjjS?4+0m{AWIyj(dh>ZfOInBl9XTe(Ci1M~{-Onf(lE&Srs zQT+QFi%&JApLSp&ON>MFu88LC<{@V`o%{PvJ6e6Ms|CmH@qG=~SI+8t3Sm{f(H)`N z{4=7leb=+=?ko>^@+#W<(NFfbcUGO7<=JvHTq?qycEI|FN{|h6=Pq|(XQM@C?UK8y zSUc9)-X+Xd6%ohZP?K#QZ}j*Dg=%*6d>$ueFza{Mtg5w+Q{b~7XXi#_@40z2s#Sfv zQWrSnTkEcz*2Pdy^IlV>gZw^1Yi-E{V@7d5>#klUZlIN7>O!z%Zq}ixq$S7y; z_fx#bigUB&UU7QmvP(E04qg|_X2IKib2gsez^Z<>AC2U-yuA%-dVC+4VZ!D&-`;;nd($$S(=}x9zwHy+wu9i&2q^q84_g-gx0g> zPm6Cos0CEvTB3nje*E15zsck80p!YOIhPlo)z$c{YU-YLr1^CUDnoBDo_t*${ zRu_vErm)#Mkf^XvPpO*h^gf}!AO?=W`n)5)_1NcSWlKzWH?Mnju1`vgC;TZY{eb^P? z$?)I{Og-P~DB75^?-eip`!RMAbzX(Qu2IN8@l<2u5ER*B|ILXOz(=gUx25jyr(w4v zzxR5a&taZFdFB{9WoRQ+W!>>!C1%BBT-p$!*LQQw-n(L6Wn6B}0vr_QQ)98C8tO+d zB_F@;;Ij7Yw8m+E#vE~rRu;k^rqp-eLlr|6I30P0xEfvScg%{8&2=cgy^3P2#@CH- z*DAUSYhjrOXVhb7%#G(%KpI;XTZ?b^i^Ajm{dv)kb2;peEbOf7k!~)^FoTE3?7A}z z%WOygZ(ge``gfXAfn~p=IJ`aEJjI}~$-!L}`fGvnyFx5htHERTVY{o#;_1q6*Y#em zYXaHq8l#2IJTVzp9=u!KzZK$Nni7!+JYTJv0nTZYHRZ5qH1THT15tJ_wnrH<-XUNL2yb6&0e1Qk1%J^NtXWZ~9dxbniM~xjVXS+uTXxr2yej6`pBIa?sEh_AX;oiX!-IW;7>Qs? zaer3E&8LF!@QjHg?Gyj=d|8egJ7N~(>+ZO9G1XSBFlrUto%;X8bXQ)@tbT~s3>f*4 zV1Hg&b03_6JMIe2%xW)WRqJO&TC3h#A18GgaitWzt1Gz{4QxAaw!^3Q}y$|AgUHw{C?<>PAU+66;iE*tD}epkE8$Iegd<3Z1@2c6Y-S75;i z%q8_fwKxAB#dY_2um;9rlr^-T-i_HAGTRyVJQ>lOG0Vdp=VeTZ{XQ3qS}HdoR6j9t zj~I7n;T>dq-hE`=-0{qe(HZS(W}dR9YV&bfh^hK2{Q5{!)<;jvEPYxV--+WC=X)5oB42bqq|wT^{!{EHf!AV zpVb=Hyk3GjMGUP&^(L+CR`X#AJY z?x%(rcFL|gwgTXKXnI#?c8m_|R|zt#J)TpCQO>Fw;<$U5?gEB1KOw_5bMaw>Wt#b7 zSBLJ;(;HbB-RDuiZ_`ss1JR-k6|c)$s&LM#w$qBZEI%=cZGB>xWTmTxc{Wn9^lhCR z)hE|!V0M=Qxbmp`FuEwJZ?zHbNDji{E)l8)RzCME|DWDaQ&sc4J8>7EBJdqk{B>CWAe+ac(;^$L*Nf5AycdTG57uz}addL?>OP&8Uh|DXLG~`DsUY z&_?vEwVQL#RNvyuZU5z_aJ3JmH4hfed>cf`M^RMXH9-KHs5j3-f{(7aOwJS>ALDdZ(z(b zV;{`O1G(#-EPNMFnUTYu>oC67Q>&=bu~R)>{LJbDn?&;D!d>-K znfcUNdyGF#VwcA^4Kk}oB6-I9bJO3RLM_`&8@qW`v$JOXOoj0-W))Z_>i|UE4&9QW z{E1V>Ri-+Xi_U^T9cbZO%&l1d(*&>f1Nj$^K0Q}$n2#socCVJ$83OqQIrns4H_CIh z9i!&4Y%+Vt?}#t!ayn*32*aSeXBpos@H(Ix_qdGcb9`WJ?`DUoCb;OD`)lXcwvd>7#0@*A^5pdLX4O=Pk zz80T**`Y4jcl%FTF<+{IXl@6=1N^3$Y{wnzjS0LWe&RAaoYU5Oq<3}d?-ud4%B+6a z7vgDDYoch6tKg}u<$2L~#`^OLS0pf*uQ7(rRagGn!+M!{kC^TH?-kGJ;u8x-!_Gw> z((>B+veB+(rWL>a2AB6ezVWF{voPz^h*{K%ou5_anNfM(=*H@7_iEh}dM~4b-}~di zUQwpBQ+AB980*c$C@w#@@GSfFw=6!h1nPOw!n*^&a(3Q~g?st}57(IPbe=h#Kl!|z zRz=omadG4i@!wsoxYz69D;9U7)@vVBw{w6?%|6%JjpV4#WFCtvL{HDn&gbqJUCFZ% zl-HEc-g2^$ok3lKwJT^^(O$MfH!msx+dQzJzn>F7X3X2|iLFydy>rXscFgN?6=&&m z$Hj!!Rc8uM{UB3WX51q`4M+p)%f=()c#-qxe%6$eMk>`$dER$LL}U&`*)@>(o;Ze( zk-YorxJ+`C;$~Z1W_2rz#b{!`d;J}g5fGITvx|3U$0OeTx$%gp3>gXQQtiduEIwKz z^C@Zb?@{ZW?}*(zzO!}x%$_?wpYICBF%9Ze#%MfDv#v0!{fo8Z1hhq{-?tU-c3J~> z?Epu9mWywraG?zA@Y`K*JFBv?Y`ptSp#0T4&0Tk#fA8O2ROhZITDgRcs%9#8CQhF7 zF(6mZb3`#~&k@SS7Z0mIODdaLIepIN#a(h4H`pPL?5%I8cP;Ddo@@Pfey)F?1Dm-S zgJ9|b&OD7-@cDQuS8MEWpKFYD_J>N#9oIXWHJ(K^9IfE>83FMz^Z9N=%*LN5)^^qB zOb@J@lOnzE8aQ`zwVkc97N2pclZ*D=qcFcaclmo_pvv~%LfhkpkqTq9UZRce1K{hf zT=eRwYf2e2Qr`Ql%2_kc@~Y+Gxf#0St%z%0%OZ6oL#QRjtmJZbtv$zPMKu>O5tRAH zbl8$anL~k5-<#08Un|$d+HsT>i`RB#YoJG>cbL9>o0j?R}nAMF#pVoy7{J= ztFX}41L||GA-1k+M$%r;T=t)5FYBu%xp8M*8(r0hyxRDQO7|12MsxAxcxw4c#?N|9 znXUlc`$05h(^$0_CZd#KbJY9tG~L}rHoH%;N3a^4+Lkk~{Kna?REekJ!mmFmZmVI( zqtzcXdh$N8Xyil`)ihaA7slbekde;I=V(2Sb7e42p6jvPzi`)z?Kc9^8_UL>iviBd z%2Q1$2CuTNrdpzY3#WH9i|^w`;JN@x6yTdM@j{+F{>y=uXzR z$H>z?@hta7F{OH@*=yl>#`sfU-qd@u&o$t}t~DSEIcvAC=rio!SoU0v)I~SWxPDGqcJgRS!KpI2(@>+-0i!p2tQcudDph*y`~svzwINZesYiPmedET)ay%@^55bvqeWSJUHcKiO1s%&EN8!_Iy>Bi5eD Sj#^Qp#u#I*axXE^2mT*$^iC20 literal 0 HcmV?d00001 diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 046900d8e2..1b490db803 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -7,6 +7,7 @@ import datetime from 'datetime'; import toast from 'toast'; import actionsheet from 'actionsheet'; import globalize from 'globalize'; +import playbackPermissionManager from 'playbackPermissionManager'; /** * Gets active player id. @@ -153,6 +154,16 @@ events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { export function show(button) { loading.show(); + // TODO: should feature be disabled if playback permission is missing? + playbackPermissionManager.check().then(() => { + console.debug("Playback is allowed."); + }).catch((error) => { + console.error("Playback not allowed!", error); + toast({ + text: globalize.translate("MessageSyncplayPlaybackPermissionRequired") + }); + }); + const apiClient = connectionManager.currentApiClient(); connectionManager.user(apiClient).then((user) => { if (syncplayEnabled) { diff --git a/src/components/syncplay/playbackPermissionManager.js b/src/components/syncplay/playbackPermissionManager.js new file mode 100644 index 0000000000..df16545b39 --- /dev/null +++ b/src/components/syncplay/playbackPermissionManager.js @@ -0,0 +1,51 @@ +/** + * Creates an audio element that plays a silent sound. + * @returns {HTMLMediaElement} The audio element. + */ +function createTestMediaElement () { + + const elem = document.createElement('audio'); + elem.classList.add('testMediaPlayerAudio'); + elem.classList.add('hide'); + + document.body.appendChild(elem); + + elem.volume = 1; // Volume should not be zero to trigger proper permissions + elem.src = "assets/audio/silence.wav"; // Silent sound + + return elem; +} + +/** + * Destroys a media element. + * @param {HTMLMediaElement} elem The element to destroy. + */ +function destroyTestMediaElement (elem) { + elem.pause(); + elem.remove(); +} + +/** + * Class that manages the playback permission. + */ +class PlaybackPermissionManager { + /** + * Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction). + * @returns {Promise} Promise that resolves succesfully if playback permission is allowed. + */ + check () { + return new Promise((resolve, reject) => { + const media = createTestMediaElement(); + media.play().then(() => { + resolve(); + }).catch((error) => { + reject(error); + }).finally(() => { + destroyTestMediaElement(media); + }); + }); + } +} + +/** PlaybackPermissionManager singleton. */ +export default new PlaybackPermissionManager(); diff --git a/src/scripts/site.js b/src/scripts/site.js index 421cdae32e..3954b153a4 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -825,6 +825,7 @@ var AppInfo = {}; define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault); + define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 83890426c5..136a015430 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1036,6 +1036,7 @@ "MessageSyncplayGroupWait": "{0} is buffering...", "MessageSyncplayNoGroupsAvailable": "No groups available.", "MessageSyncplayPermissionRequired": "Permission required to create a group.", + "MessageSyncplayPlaybackPermissionRequired": "Playback permission required.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", diff --git a/webpack.dev.js b/webpack.dev.js index 76a1a7a752..d8879fe808 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -44,6 +44,10 @@ module.exports = merge(common, { use: [ 'file-loader' ] + }, + { + test: /\.(wav)$/i, + use: ["file-loader"] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index f5c7accd04..cc4c57b9f4 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -37,6 +37,10 @@ module.exports = merge(common, { use: [ 'file-loader' ] + }, + { + test: /\.(wav)$/i, + use: ["file-loader"] } ] } From 06e6c99c03f70023e40e35bd2804e3916e2ec0de Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 15 Apr 2020 18:15:28 +0200 Subject: [PATCH 006/181] Disable syncing after several attempts Refactor syncplay manager Attempt fixing Safari issues (timeupdate event not firing) --- src/components/syncplay/groupSelectionMenu.js | 29 +- src/components/syncplay/syncplayManager.js | 428 +++++++++++++----- src/workers/syncplay/syncplay.worker.js | 90 ++++ webpack.dev.js | 4 + webpack.prod.js | 4 + 5 files changed, 437 insertions(+), 118 deletions(-) create mode 100644 src/workers/syncplay/syncplay.worker.js diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 1b490db803..c907d338a4 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -13,7 +13,7 @@ import playbackPermissionManager from 'playbackPermissionManager'; * Gets active player id. * @returns {string} The player's id. */ -function getActivePlayerId() { +function getActivePlayerId () { var info = playbackManager.getPlayerInfo(); return info ? info.id : null; } @@ -21,7 +21,7 @@ function getActivePlayerId() { /** * Used to avoid console logs about uncaught promises */ -function emptyCallback() { +function emptyCallback () { // avoid console logs about uncaught promises } @@ -31,15 +31,23 @@ function emptyCallback() { * @param {Object} user - Current user. * @param {Object} apiClient - ApiClient. */ -function showNewJoinGroupSelection(button, user, apiClient) { +function showNewJoinGroupSelection (button, user, apiClient) { let sessionId = getActivePlayerId(); sessionId = sessionId ? sessionId : "none"; const inSession = sessionId !== "none"; const policy = user.localUser ? user.localUser.Policy : {}; + let playingItemId; + try { + const playState = playbackManager.getPlayerState(); + playingItemId = playState.NowPlayingItem.Id; + } catch (error) { + playingItemId = ""; + } apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { response.json().then(function (groups) { var menuItems = groups.map(function (group) { + // TODO: update running time if group is playing? var name = datetime.getDisplayRunningTime(group.PositionTicks); if (!inSession) { name = group.PlayingItemName; @@ -90,7 +98,8 @@ function showNewJoinGroupSelection(button, user, apiClient) { apiClient.sendSyncplayCommand(sessionId, "NewGroup"); } else { apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { - GroupId: id + GroupId: id, + PlayingItemId: playingItemId }); } }, emptyCallback); @@ -112,8 +121,16 @@ function showNewJoinGroupSelection(button, user, apiClient) { * @param {Object} user - Current user. * @param {Object} apiClient - ApiClient. */ -function showLeaveGroupSelection(button, user, apiClient) { +function showLeaveGroupSelection (button, user, apiClient) { const sessionId = getActivePlayerId(); + if (!sessionId) { + syncplayManager.signalError(); + toast({ + // TODO: translate + text: "Syncplay error occured." + }); + return; + } const menuItems = [{ @@ -151,7 +168,7 @@ events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { * Shows a menu to handle Syncplay groups. * @param {HTMLElement} button - Element where to place the menu. */ -export function show(button) { +export function show (button) { loading.show(); // TODO: should feature be disabled if playback permission is missing? diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index 8619508e28..86b7ec34af 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -32,7 +32,7 @@ function waitForEvent(emitter, eventType) { * @returns {string} The player's id. */ function getActivePlayerId() { - var info = playbackManager.getPlayerInfo(); + var info = playbackManager.getPlayerInfo(); return info ? info.id : null; } @@ -42,7 +42,9 @@ function getActivePlayerId() { const MaxAcceptedDelaySpeedToSync = 50; // milliseconds, delay after which SpeedToSync is enabled const MaxAcceptedDelaySkipToSync = 300; // milliseconds, delay after which SkipToSync is enabled const SyncMethodThreshold = 2000; // milliseconds, switches between SpeedToSync or SkipToSync -const SpeedUpToSyncTime = 1000; // milliseconds, duration in which the playback is sped up +const SpeedToSyncTime = 1000; // milliseconds, duration in which the playback is sped up +const MaxAttemptsSpeedToSync = 3; // attempts before disabling SpeedToSync +const MaxAttemptsSync = 5; // attempts before disabling syncing at all /** * Time estimation @@ -60,6 +62,9 @@ class SyncplayManager { this.syncEnabled = false; this.playbackDiffMillis = 0; // used for stats this.syncMethod = "None"; // used for stats + this.syncAttempts = 0; + this.lastSyncTime = new Date(); + this.syncWatcherTimeout = null; // interval that watches playback time and syncs it this.lastPlaybackWaiting = null; // used to determine if player's buffering this.minBufferingThresholdMillis = 1000; @@ -86,7 +91,16 @@ class SyncplayManager { events.on(playbackManager, "playerchange", () => { this.onPlayerChange(); }); + + events.on(playbackManager, "playbackstart", (player, state) => { + events.trigger(this, 'PlaybackStart', [player, state]); + }); + this.bindToPlayer(playbackManager.getCurrentPlayer()); + + events.on(this, "TimeUpdate", (event) => { + this.syncPlaybackTime(); + }); } /** @@ -110,53 +124,9 @@ class SyncplayManager { * @param {Object} e The time update event. */ onTimeUpdate (e) { + // NOTICE: this event is unreliable, at least in Safari + // which just stops firing the event after a while. events.trigger(this, "TimeUpdate", [e]); - - if (this.lastCommand && this.lastCommand.Command === 'Play' && !this.isBuffering()) { - var currentTime = new Date(); - var playAtTime = this.lastCommand.When; - - var state = playbackManager.getPlayerState().PlayState; - // Estimate PositionTicks on server - var ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) - this.timeDiff) * 10000; - // Measure delay that needs to be recovered - // diff might be caused by the player internally starting the playback - var diff = ServerPositionTicks - state.PositionTicks; - var diffMillis = diff / 10000; - - this.playbackDiffMillis = diffMillis; - - // console.debug("Syncplay onTimeUpdate", diffMillis, state.PositionTicks, ServerPositionTicks); - - if (this.syncEnabled) { - var absDiffMillis = Math.abs(diffMillis); - // TODO: SpeedToSync sounds bad on songs - if (this.playbackRateSupported && absDiffMillis > MaxAcceptedDelaySpeedToSync && absDiffMillis < SyncMethodThreshold) { - // SpeedToSync method - var speed = 1 + diffMillis / SpeedUpToSyncTime; - - this.currentPlayer.setPlaybackRate(speed); - this.syncEnabled = false; - this.showSyncIcon("SpeedToSync (x" + speed + ")"); - - this.syncTimeout = setTimeout(() => { - this.currentPlayer.setPlaybackRate(1); - this.syncEnabled = true; - this.clearSyncIcon(); - }, SpeedUpToSyncTime); - } else if (absDiffMillis > MaxAcceptedDelaySkipToSync) { - // SkipToSync method - playbackManager.syncplay_seek(ServerPositionTicks); - this.syncEnabled = false; - this.showSyncIcon("SkipToSync"); - - this.syncTimeout = setTimeout(() => { - this.syncEnabled = true; - this.clearSyncIcon(); - }, this.syncMethodThreshold / 2); - } - } - } } /** @@ -256,34 +226,7 @@ class SyncplayManager { processGroupUpdate (cmd, apiClient) { switch (cmd.Type) { case 'PrepareSession': - var serverId = apiClient.serverInfo().Id; - playbackManager.play({ - ids: cmd.Data.ItemIds, - startPositionTicks: cmd.Data.StartPositionTicks, - mediaSourceId: cmd.Data.MediaSourceId, - audioStreamIndex: cmd.Data.AudioStreamIndex, - subtitleStreamIndex: cmd.Data.SubtitleStreamIndex, - startIndex: cmd.Data.StartIndex, - serverId: serverId - }).then(() => { - waitForEvent(this, "PlayerChange").then(() => { - playbackManager.pause(); - var sessionId = getActivePlayerId(); - if (!sessionId) { - console.error("Missing sessionId!"); - toast({ - text: "Failed to enable Syncplay!" - }); - return; - } - // Sometimes JoinGroup fails, maybe because server hasn't been updated yet - setTimeout(() => { - apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { - GroupId: cmd.GroupId - }); - }, 500); - }); - }); + this.prepareSession(apiClient, cmd.GroupId, cmd.Data); break; case 'UserJoined': toast({ @@ -296,31 +239,12 @@ class SyncplayManager { }); break; case 'GroupJoined': - toast({ - text: globalize.translate('MessageSyncplayEnabled') - }); - // Enable Syncplay - this.syncplayEnabledAt = new Date(cmd.Data); - this.syncplayReady = false; - events.trigger(this, "SyncplayEnabled", [true]); - waitForEvent(this, "SyncplayReady").then(() => { - this.processCommand(this.queuedCommand, apiClient); - this.queuedCommand = null; - }); - this.injectPlaybackManager(); - this.startPing(); + const enabledAt = new Date(cmd.Data); + this.enableSyncplay(apiClient, enabledAt, true); break; case 'NotInGroup': case 'GroupLeft': - toast({ - text: globalize.translate('MessageSyncplayDisabled') - }); - // Disable Syncplay - this.syncplayEnabledAt = null; - this.syncplayReady = false; - events.trigger(this, "SyncplayEnabled", [false]); - this.restorePlaybackManager(); - this.stopPing(); + this.disableSyncplay(true); break; case 'GroupWait': toast({ @@ -355,8 +279,9 @@ class SyncplayManager { } cmd.When = new Date(cmd.When); + cmd.EmittedAt = new Date(cmd.EmitttedAt); - if (cmd.When < this.syncplayEnabledAt) { + if (cmd.EmitttedAt < this.syncplayEnabledAt) { console.debug("Syncplay processCommand: ignoring old command", cmd); return; } @@ -390,6 +315,114 @@ class SyncplayManager { } } + /** + * Prepares this client to join a group by loading the required content. + * @param {Object} apiClient The ApiClient. + * @param {string} groupId The group to join. + * @param {Object} sessionData Info about the content to load. + */ + prepareSession (apiClient, groupId, sessionData) { + var serverId = apiClient.serverInfo().Id; + playbackManager.play({ + ids: sessionData.ItemIds, + startPositionTicks: sessionData.StartPositionTicks, + mediaSourceId: sessionData.MediaSourceId, + audioStreamIndex: sessionData.AudioStreamIndex, + subtitleStreamIndex: sessionData.SubtitleStreamIndex, + startIndex: sessionData.StartIndex, + serverId: serverId + }).then(() => { + // TODO: switch to PlaybackStart maybe? + waitForEvent(this, "PlayerChange").then(() => { + playbackManager.pause(); + var sessionId = getActivePlayerId(); + if (!sessionId) { + console.error("Missing sessionId!"); + toast({ + // TODO: translate + text: "Failed to enable Syncplay! Missing session id." + }); + return; + } + // Get playing item id + let playingItemId; + try { + const playState = playbackManager.getPlayerState(); + playingItemId = playState.NowPlayingItem.Id; + } catch (error) { + playingItemId = ""; + } + // Sometimes JoinGroup fails, maybe because server hasn't been updated yet + setTimeout(() => { + apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + GroupId: groupId, + PlayingItemId: playingItemId + }); + }, 500); + }); + }).catch((error) => { + console.error(error); + toast({ + // TODO: translate + text: "Failed to enable Syncplay! Media error." + }); + }); + } + + /** + * Enables Syncplay. + * @param {Object} apiClient The ApiClient. + * @param {Date} enabledAt When Syncplay has been enabled. Server side date. + * @param {boolean} showMessage Display message. + */ + enableSyncplay (apiClient, enabledAt, showMessage = false) { + this.syncplayEnabledAt = enabledAt; + this.syncplayReady = false; + events.trigger(this, "SyncplayEnabled", [true]); + waitForEvent(this, "SyncplayReady").then(() => { + this.processCommand(this.queuedCommand, apiClient); + this.queuedCommand = null; + }); + this.injectPlaybackManager(); + this.startPing(); + + if (showMessage) { + toast({ + text: globalize.translate('MessageSyncplayEnabled') + }); + } + } + + /** + * Disables Syncplay. + * @param {boolean} showMessage Display message. + */ + disableSyncplay (showMessage = false) { + this.syncplayEnabledAt = null; + this.syncplayReady = false; + this.lastCommand = null; + this.queuedCommand = null; + this.syncEnabled = false; + events.trigger(this, "SyncplayEnabled", [false]); + this.restorePlaybackManager(); + this.stopPing(); + this.stopSyncWatcher(); + + if (showMessage) { + toast({ + text: globalize.translate('MessageSyncplayDisabled') + }); + } + } + + /** + * Gets Syncplay status. + * @returns {boolean} _true_ if user joined a group, _false_ otherwise. + */ + isSyncplayEnabled () { + return this.syncplayEnabledAt !== null ? true : false; + } + /** * Schedules a resume playback on the player at the specified clock time. * @param {Date} playAtTime The server's UTC time at which to resume playback. @@ -408,8 +441,9 @@ class SyncplayManager { playbackManager.syncplay_unpause(); this.syncTimeout = setTimeout(() => { - this.syncEnabled = true - }, this.syncMethodThreshold / 2); + this.syncEnabled = true; + this.startSyncWatcher(); + }, SyncMethodThreshold / 2); }, playTimeout); @@ -421,8 +455,9 @@ class SyncplayManager { playbackManager.syncplay_seek(serverPositionTicks); this.syncTimeout = setTimeout(() => { - this.syncEnabled = true - }, this.syncMethodThreshold / 2); + this.syncEnabled = true; + this.startSyncWatcher(); + }, SyncMethodThreshold / 2); } } @@ -471,6 +506,7 @@ class SyncplayManager { clearTimeout(this.syncTimeout); this.syncEnabled = false; + this.stopSyncWatcher(); if (this.currentPlayer) { this.currentPlayer.setPlaybackRate(1); } @@ -587,6 +623,15 @@ class SyncplayManager { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); + if (!sessionId) { + this.signalError(); + toast({ + // TODO: translate + text: "Syncplay error occured." + }); + return; + } + var pingStartTime = new Date(); apiClient.sendSyncplayCommand(sessionId, "GetUtcTime").then((response) => { var pingEndTime = new Date(); @@ -614,6 +659,13 @@ class SyncplayManager { this.requestPing(); }); + }).catch((error) => { + console.error(error); + this.signalError(); + toast({ + // TODO: translate + text: "Syncplay error occured." + }); }); }, this.pingIntervalTimeout); @@ -627,7 +679,7 @@ class SyncplayManager { this.notifySyncplayReady = true; this.pingStop = false; this.initTimeDiff = this.initTimeDiff > this.greedyPingCount ? 1 : this.initTimeDiff; - this.pingIntervalTimeout = this.pingIntervalTimeoutGreedy; + this.pingIntervalTimeout = PingIntervalTimeoutGreedy; this.requestPing(); } @@ -643,6 +695,159 @@ class SyncplayManager { } } + /** + * Attempts to sync playback time with estimated server time. + * + * When sync is enabled, the following will be checked: + * - check if local playback time is close enough to the server playback time + * If it is not, then a playback time sync will be attempted. + * Two methods of syncing are available: + * - SpeedToSync: speeds up the media for some time to catch up (default is one second) + * - SkipToSync: seeks the media to the estimated correct time + * SpeedToSync aims to reduce the delay as much as possible, whereas SkipToSync is less pretentious. + */ + syncPlaybackTime () { + // Attempt to sync only when media is playing. + if (!this.lastCommand || this.lastCommand.Command !== 'Play' || this.isBuffering()) return; + + const currentTime = new Date(); + + // Avoid overloading the browser + const elapsed = currentTime - this.lastSyncTime; + if (elapsed < SyncMethodThreshold / 2) return; + this.lastSyncTime = currentTime; + this.notifySyncWatcher(); + + const playAtTime = this.lastCommand.When; + + const CurrentPositionTicks = playbackManager.currentTime(); + // Estimate PositionTicks on server + const ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) - this.timeDiff) * 10000; + // Measure delay that needs to be recovered + // diff might be caused by the player internally starting the playback + const diff = ServerPositionTicks - CurrentPositionTicks; + const diffMillis = diff / 10000; + + this.playbackDiffMillis = diffMillis; + + // console.debug("Syncplay onTimeUpdate", diffMillis, CurrentPositionTicks, ServerPositionTicks); + + if (this.syncEnabled) { + const absDiffMillis = Math.abs(diffMillis); + // TODO: SpeedToSync sounds bad on songs + // TODO: SpeedToSync is failing on Safari (Mojave); even if playbackRate is supported, some delay seems to exist + if (this.playbackRateSupported && absDiffMillis > MaxAcceptedDelaySpeedToSync && absDiffMillis < SyncMethodThreshold) { + // Disable SpeedToSync if it keeps failing + if (this.syncAttempts > MaxAttemptsSpeedToSync) { + this.playbackRateSupported = false; + } + // SpeedToSync method + const speed = 1 + diffMillis / SpeedToSyncTime; + + this.currentPlayer.setPlaybackRate(speed); + this.syncEnabled = false; + this.syncAttempts++; + this.showSyncIcon("SpeedToSync (x" + speed + ")"); + + this.syncTimeout = setTimeout(() => { + this.currentPlayer.setPlaybackRate(1); + this.syncEnabled = true; + this.clearSyncIcon(); + }, SpeedToSyncTime); + } else if (absDiffMillis > MaxAcceptedDelaySkipToSync) { + // Disable SkipToSync if it keeps failing + if (this.syncAttempts > MaxAttemptsSync) { + this.syncEnabled = false; + this.showSyncIcon("Sync disabled (too many attempts)"); + } + // SkipToSync method + playbackManager.syncplay_seek(ServerPositionTicks); + this.syncEnabled = false; + this.syncAttempts++; + this.showSyncIcon("SkipToSync (" + this.syncAttempts + ")"); + + this.syncTimeout = setTimeout(() => { + this.syncEnabled = true; + this.clearSyncIcon(); + }, SyncMethodThreshold / 2); + } else { + // Playback is synced + if (this.syncAttempts > 0) { + // console.debug("Playback has been synced after", this.syncAttempts, "attempts."); + } + this.syncAttempts = 0; + } + } + } + + /** + * Signals the worker to start watching sync. Also creates the worker if needed. + * + * This additional fail-safe has been added because on Safari the timeupdate event fails after a while. + */ + startSyncWatcher () { + // SPOILER ALERT: this idea fails too on Safari... Keeping it here for future investigations + return; + if (window.Worker) { + // Start worker if needed + if (!this.worker) { + this.worker = new Worker("workers/syncplay/syncplay.worker.js"); + this.worker.onmessage = (event) => { + const message = event.data; + switch (message.type) { + case "TriggerSync": + // TODO: player state might not reflect the real playback position, + // thus calling syncPlaybackTime outside a timeupdate event might not really sync to the right point + this.syncPlaybackTime(); + break; + default: + console.error("Syncplay: unknown message from worker:", message.type); + break; + } + }; + this.worker.onerror = (event) => { + console.error("Syncplay: worker error", event); + }; + this.worker.onmessageerror = (event) => { + console.error("Syncplay: worker message error", event); + }; + } + // Start watcher + this.worker.postMessage({ + type: "StartSyncWatcher", + data: { + interval: SyncMethodThreshold / 2, + threshold: SyncMethodThreshold + } + }); + } else { + console.debug("Syncplay: workers not supported."); + } + } + + /** + * Signals the worker to stop watching sync. + */ + stopSyncWatcher () { + if (this.worker) { + this.worker.postMessage({ + type: "StopSyncWatcher" + }); + } + } + + /** + * Signals new state to worker. + */ + notifySyncWatcher () { + if (this.worker) { + this.worker.postMessage({ + type: "UpdateLastSyncTime", + data: this.lastSyncTime + }); + } + } + /** * Converts server time to local time. * @param {Date} server The time to convert. @@ -663,14 +868,6 @@ class SyncplayManager { return new Date(local.getTime() - this.timeDiff); } - /** - * Gets Syncplay status. - * @returns {boolean} _true_ if user joined a group, _false_ otherwise. - */ - isSyncplayEnabled () { - return this.syncplayEnabledAt !== null ? true : false; - } - /** * Gets Syncplay stats. * @returns {Object} The Syncplay stats. @@ -698,6 +895,13 @@ class SyncplayManager { this.syncMethod = "None"; events.trigger(this, "SyncplayError", [false]); } + + /** + * Signals an error state, which disables and resets Syncplay for a new session. + */ + signalError () { + this.disableSyncplay(); + } } /** SyncplayManager singleton. */ diff --git a/src/workers/syncplay/syncplay.worker.js b/src/workers/syncplay/syncplay.worker.js new file mode 100644 index 0000000000..bd8d2bd60f --- /dev/null +++ b/src/workers/syncplay/syncplay.worker.js @@ -0,0 +1,90 @@ +var SyncTimeThreshold = 2000; // milliseconds, overwritten by startSyncWatcher +var SyncWatcherInterval = 1000; // milliseconds, overwritten by startSyncWatcher +var lastSyncTime = new Date(); // internal state +var syncWatcher; // holds value from setInterval + +/** + * Sends a message to the UI worker. + * @param {string} type + * @param {*} data + */ +function sendMessage (type, data) { + postMessage({ + type: type, + data: data + }); + +} + +/** + * Updates the state. + * @param {Date} syncTime The new state. + */ +function updateLastSyncTime (syncTime) { + lastSyncTime = syncTime; +} + +/** + * Starts sync watcher. + * @param {Object} options Additional options to configure the watcher, like _interval_ and _threshold_. + */ +function startSyncWatcher(options) { + stopSyncWatcher(); + if (options) { + if (options.interval) { + SyncWatcherInterval = options.interval; + } + if (options.threshold) { + SyncTimeThreshold = options.threshold; + } + } + syncWatcher = setInterval(syncWatcherCallback, SyncWatcherInterval); +} + +/** + * Stops sync watcher. + */ +function stopSyncWatcher () { + if (syncWatcher) { + clearInterval(syncWatcher); + syncWatcher = null; + } +} + +/** + * Oversees playback sync and makes sure that it gets called regularly. + */ +function syncWatcherCallback () { + const currentTime = new Date(); + const elapsed = currentTime - lastSyncTime; + if (elapsed > SyncTimeThreshold) { + sendMessage("TriggerSync"); + } +} + +/** + * Handles messages from UI worker. + * @param {MessageEvent} event The message to handle. + */ +function handleMessage (event) { + const message = event.data; + switch (message.type) { + case "UpdateLastSyncTime": + updateLastSyncTime(message.data); + break; + case "StartSyncWatcher": + startSyncWatcher(message.data); + break; + case "StopSyncWatcher": + stopSyncWatcher(); + break; + default: + console.error("Unknown message type:", message.type); + break; + } +} + +// Listen for messages +addEventListener("message", function (event) { + handleMessage(event); +}); diff --git a/webpack.dev.js b/webpack.dev.js index d8879fe808..544d32d63e 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -48,6 +48,10 @@ module.exports = merge(common, { { test: /\.(wav)$/i, use: ["file-loader"] + }, + { + test: /\.worker.js$/, + use: ["worker"] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index cc4c57b9f4..7b5f2ff6c0 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -41,6 +41,10 @@ module.exports = merge(common, { { test: /\.(wav)$/i, use: ["file-loader"] + }, + { + test: /\.worker.js$/, + use: ["worker"] } ] } From 5342b90a56a7aeeacbf9edaa464ab74e9ecf8aca Mon Sep 17 00:00:00 2001 From: gion Date: Thu, 16 Apr 2020 16:05:04 +0200 Subject: [PATCH 007/181] Implement NTP like time sync --- src/components/playerstats/playerstats.js | 4 +- src/components/syncplay/syncplayManager.js | 176 +++-------------- src/components/syncplay/timeSyncManager.js | 209 +++++++++++++++++++++ src/scripts/site.js | 1 + src/strings/en-us.json | 2 +- 5 files changed, 236 insertions(+), 156 deletions(-) create mode 100644 src/components/syncplay/timeSyncManager.js diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 4bd49ba5c1..404baab7eb 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -332,8 +332,8 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay var stats = syncplayManager.getStats(); syncStats.push({ - label: globalize.translate("LabelSyncplayTimeDiff"), - value: stats.TimeDiff + "ms" + label: globalize.translate("LabelSyncplayTimeOffset"), + value: stats.TimeOffset + "ms" }); syncStats.push({ diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index 86b7ec34af..de1424b89f 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -8,6 +8,7 @@ import events from 'events'; import connectionManager from 'connectionManager'; import playbackManager from 'playbackManager'; +import timeSyncManager from 'timeSyncManager'; import toast from 'toast'; import globalize from 'globalize'; @@ -80,11 +81,7 @@ class SyncplayManager { this.scheduledCommand = null; this.syncTimeout = null; - this.pingStop = true; - this.pingIntervalTimeout = PingIntervalTimeoutGreedy; - this.pingInterval = null; - this.initTimeDiff = 0; // number of pings - this.timeDiff = 0; // local time minus server time + this.timeOffsetWithServer = 0; // server time minus local time this.roundTripDuration = 0; this.notifySyncplayReady = false; @@ -101,6 +98,17 @@ class SyncplayManager { events.on(this, "TimeUpdate", (event) => { this.syncPlaybackTime(); }); + + events.on(timeSyncManager, "Update", (event, timeOffset, ping) => { + this.timeOffsetWithServer = timeOffset; + this.roundTripDuration = ping * 2; + + if (this.notifySyncplayReady) { + this.syncplayReady = true; + events.trigger(this, "SyncplayReady"); + this.notifySyncplayReady = false; + } + }); } /** @@ -377,14 +385,17 @@ class SyncplayManager { */ enableSyncplay (apiClient, enabledAt, showMessage = false) { this.syncplayEnabledAt = enabledAt; - this.syncplayReady = false; + this.injectPlaybackManager(); events.trigger(this, "SyncplayEnabled", [true]); + waitForEvent(this, "SyncplayReady").then(() => { this.processCommand(this.queuedCommand, apiClient); this.queuedCommand = null; }); - this.injectPlaybackManager(); - this.startPing(); + this.syncplayReady = false; + this.notifySyncplayReady = true; + + timeSyncManager.forceUpdate(); if (showMessage) { toast({ @@ -405,7 +416,6 @@ class SyncplayManager { this.syncEnabled = false; events.trigger(this, "SyncplayEnabled", [false]); this.restorePlaybackManager(); - this.stopPing(); this.stopSyncWatcher(); if (showMessage) { @@ -431,7 +441,7 @@ class SyncplayManager { schedulePlay (playAtTime, positionTicks) { this.clearScheduledCommand(); var currentTime = new Date(); - var playAtTimeLocal = this.serverDateToLocal(playAtTime); + var playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime); if (playAtTimeLocal > currentTime) { var playTimeout = playAtTimeLocal - currentTime; @@ -469,7 +479,7 @@ class SyncplayManager { schedulePause (pauseAtTime, positionTicks) { this.clearScheduledCommand(); var currentTime = new Date(); - var pauseAtTimeLocal = this.serverDateToLocal(pauseAtTime); + var pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime); if (pauseAtTimeLocal > currentTime) { var pauseTimeout = pauseAtTimeLocal - currentTime; @@ -575,126 +585,6 @@ class SyncplayManager { }); } - /** - * Computes time difference between this client's time and server's time. - * @param {Date} pingStartTime Local time when ping request started. - * @param {Date} pingEndTime Local time when ping request ended. - * @param {Date} serverTime Server UTC time at ping request. - */ - updateTimeDiff (pingStartTime, pingEndTime, serverTime) { - this.roundTripDuration = (pingEndTime - pingStartTime); - // The faster the response, the closer we are to the real timeDiff value - // localTime = pingStartTime + roundTripDuration / 2 - // newTimeDiff = localTime - serverTime - var newTimeDiff = (pingStartTime - serverTime) + (this.roundTripDuration / 2); - - // Initial setup - if (this.initTimeDiff === 0) { - this.timeDiff = newTimeDiff; - this.initTimeDiff++ - return; - } - - // As response time gets better, absolute value should decrease - var distanceFromZero = Math.abs(newTimeDiff); - var oldDistanceFromZero = Math.abs(this.timeDiff); - if (distanceFromZero < oldDistanceFromZero) { - this.timeDiff = newTimeDiff; - } - - // Avoid overloading server - if (this.initTimeDiff >= GreedyPingCount) { - this.pingIntervalTimeout = PingIntervalTimeoutLowProfile; - } else { - this.initTimeDiff++; - } - - // console.debug("Syncplay updateTimeDiff:", serverTime, this.timeDiff, this.roundTripDuration, newTimeDiff); - } - - /** - * Schedules a ping request to the server. Used to compute time difference between client and server. - */ - requestPing () { - if (this.pingInterval === null && !this.pingStop) { - this.pingInterval = setTimeout(() => { - this.pingInterval = null; - - var apiClient = connectionManager.currentApiClient(); - var sessionId = getActivePlayerId(); - - if (!sessionId) { - this.signalError(); - toast({ - // TODO: translate - text: "Syncplay error occured." - }); - return; - } - - var pingStartTime = new Date(); - apiClient.sendSyncplayCommand(sessionId, "GetUtcTime").then((response) => { - var pingEndTime = new Date(); - response.text().then((utcTime) => { - var serverTime = new Date(utcTime); - this.updateTimeDiff(pingStartTime, pingEndTime, serverTime); - - // Alert user that ping is high - if (Math.abs(this.roundTripDuration) >= 1000) { - events.trigger(this, "SyncplayError", [true]); - } else { - events.trigger(this, "SyncplayError", [false]); - } - - // Notify server of ping - apiClient.sendSyncplayCommand(sessionId, "KeepAlive", { - Ping: this.roundTripDuration / 2 - }); - - if (this.notifySyncplayReady) { - this.syncplayReady = true; - events.trigger(this, "SyncplayReady"); - this.notifySyncplayReady = false; - } - - this.requestPing(); - }); - }).catch((error) => { - console.error(error); - this.signalError(); - toast({ - // TODO: translate - text: "Syncplay error occured." - }); - }); - - }, this.pingIntervalTimeout); - } - } - - /** - * Starts the keep alive poller. - */ - startPing () { - this.notifySyncplayReady = true; - this.pingStop = false; - this.initTimeDiff = this.initTimeDiff > this.greedyPingCount ? 1 : this.initTimeDiff; - this.pingIntervalTimeout = PingIntervalTimeoutGreedy; - - this.requestPing(); - } - - /** - * Stops the keep alive poller. - */ - stopPing () { - this.pingStop = true; - if (this.pingInterval !== null) { - clearTimeout(this.pingInterval); - this.pingInterval = null; - } - } - /** * Attempts to sync playback time with estimated server time. * @@ -722,7 +612,7 @@ class SyncplayManager { const CurrentPositionTicks = playbackManager.currentTime(); // Estimate PositionTicks on server - const ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) - this.timeDiff) * 10000; + const ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000; // Measure delay that needs to be recovered // diff might be caused by the player internally starting the playback const diff = ServerPositionTicks - CurrentPositionTicks; @@ -848,33 +738,13 @@ class SyncplayManager { } } - /** - * Converts server time to local time. - * @param {Date} server The time to convert. - * @returns {Date} Local time. - */ - serverDateToLocal (server) { - // local - server = diff - return new Date(server.getTime() + this.timeDiff); - } - - /** - * Converts local time to server time. - * @param {Date} local The time to convert. - * @returns {Date} Server time. - */ - localDateToServer (local) { - // local - server = diff - return new Date(local.getTime() - this.timeDiff); - } - /** * Gets Syncplay stats. * @returns {Object} The Syncplay stats. */ getStats () { return { - TimeDiff: this.timeDiff, + TimeOffset: this.timeOffsetWithServer, PlaybackDiff: this.playbackDiffMillis, SyncMethod: this.syncMethod } diff --git a/src/components/syncplay/timeSyncManager.js b/src/components/syncplay/timeSyncManager.js new file mode 100644 index 0000000000..e526aa5fa4 --- /dev/null +++ b/src/components/syncplay/timeSyncManager.js @@ -0,0 +1,209 @@ +/* eslint-disable indent */ + +/** + * Module that manages time syncing with server. + * @module components/syncplay/timeSyncManager + */ + +import events from 'events'; +import connectionManager from 'connectionManager'; + +/** + * Time estimation + */ +const NumberOfTrackedMeasurements = 8; +const PollingIntervalGreedy = 1000; // milliseconds +const PollingIntervalLowProfile = 60000; // milliseconds +const GreedyPingCount = 3; + +/** + * Class that stores measurement data. + */ +class Measurement { + /** + * Creates a new measurement. + * @param {Date} t0 Client's timestamp of the request transmission + * @param {Date} t1 Server's timestamp of the request reception + * @param {Date} t2 Server's timestamp of the response transmission + * @param {Date} t3 Client's timestamp of the response reception + */ + constructor(t0, t1, t2, t3) { + this.t0 = t0.getTime(); + this.t1 = t1.getTime(); + this.t2 = t2.getTime(); + this.t3 = t3.getTime(); + } + + /** + * Time offset from server. + */ + getOffset () { + return ((this.t1 - this.t0) + (this.t2 - this.t3)) / 2; + } + + /** + * Get round-trip delay. + */ + getDelay () { + return (this.t3 - this.t0) - (this.t2 - this.t1); + } + + /** + * Get ping time. + */ + getPing () { + return this.getDelay() / 2; + } +} + +/** + * Class that manages time syncing with server. + */ +class TimeSyncManager { + constructor() { + this.pingStop = true; + this.pollingInterval = PollingIntervalGreedy; + this.poller = null; + this.pings = 0; // number of pings + this.measurement = null; // current time sync + this.measurements = []; + + this.startPing(); + } + + /** + * Gets status of time sync. + * @returns {boolean} _true_ if a measurement has been done, _false_ otherwise. + */ + isReady() { + return this.measurement ? true : false; + } + + /** + * Gets time offset with server. + * @returns {number} The time offset. + */ + getTimeOffset () { + return this.measurement ? this.measurement.getOffset() : 0; + } + + /** + * Gets ping time to server. + * @returns {number} The ping time. + */ + getPing () { + return this.measurement ? this.measurement.getPing() : 0; + } + + /** + * Updates time offset between server and client. + * @param {Measurement} measurement The new measurement. + */ + updateTimeOffset(measurement) { + this.measurements.push(measurement); + if (this.measurements.length > NumberOfTrackedMeasurements) { + this.measurements.shift(); + } + + // Pick measurement with minimum delay + const sortedMeasurements = this.measurements.slice(0); + sortedMeasurements.sort((a, b) => a.getDelay() - b.getDelay()); + this.measurement = sortedMeasurements[0]; + } + + /** + * Schedules a ping request to the server. Triggers time offset update. + */ + requestPing() { + if (!this.poller) { + this.poller = setTimeout(() => { + this.poller = null; + const apiClient = connectionManager.currentApiClient(); + const t0 = new Date(); // pingStartTime + apiClient.getServerTime().then((response) => { + const t3 = new Date(); // pingEndTime + response.json().then((data) => { + const t1 = new Date(data.RequestReceptionTime); // request received + const t2 = new Date(data.ResponseTransmissionTime); // response sent + + const measurement = new Measurement(t0, t1, t2, t3); + this.updateTimeOffset(measurement); + + // Avoid overloading server + if (this.pings >= GreedyPingCount) { + this.pollingInterval = PollingIntervalLowProfile; + } else { + this.pings++; + } + + events.trigger(this, "Update", [this.getTimeOffset(), this.getPing()]); + }); + }).catch((error) => { + console.error(error); + events.trigger(this, "Error", [error]); + }).finally(() => { + this.requestPing(); + }); + + }, this.pollingInterval); + } + } + + /** + * Drops accumulated measurements. + */ + resetMeasurements () { + this.measurement = null; + this.measurements = []; + } + + /** + * Starts the time poller. + */ + startPing() { + this.requestPing(); + } + + /** + * Stops the time poller. + */ + stopPing() { + if (this.poller) { + clearTimeout(this.poller); + this.poller = null; + } + } + + /** + * Resets poller into greedy mode. + */ + forceUpdate() { + this.stopPing(); + this.pollingInterval = PollingIntervalGreedy; + this.pings = 0; + this.startPing(); + } + + /** + * Converts server time to local time. + * @param {Date} server The time to convert. + * @returns {Date} Local time. + */ + serverDateToLocal(server) { + // server - local = offset + return new Date(server.getTime() + this.getTimeOffset()); + } + + /** + * Converts local time to server time. + * @param {Date} local The time to convert. + * @returns {Date} Server time. + */ + localDateToServer(local) { + // server - local = offset + return new Date(local.getTime() - this.getTimeOffset()); + } +} + +/** TimeSyncManager singleton. */ +export default new TimeSyncManager(); diff --git a/src/scripts/site.js b/src/scripts/site.js index 3954b153a4..ecfeb27349 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -824,6 +824,7 @@ var AppInfo = {}; define('playbackSettings', [componentsPath + '/playbacksettings/playbacksettings'], returnFirstDependency); define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); + define('timeSyncManager', [componentsPath + '/syncplay/timeSyncManager'], returnDefault); define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault); define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 136a015430..43b5f14148 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -855,7 +855,7 @@ "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelSubtitles": "Subtitles", "LabelSupportedMediaTypes": "Supported Media Types:", - "LabelSyncplayTimeDiff": "Time difference with server:", + "LabelSyncplayTimeOffset": "Time offset with server:", "LabelSyncplayPlaybackDiff": "Playback time difference:", "LabelSyncplaySyncMethod": "Sync method:", "LabelSyncplayNewGroup": "New group", From 9839dcd02ad8d46ef19ad6b24b859552e816dd65 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 13:42:46 +0200 Subject: [PATCH 008/181] Update session ping --- package.json | 1 + src/components/syncplay/syncplayManager.js | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 19d3b05a45..33a7d2b004 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "src/components/scrollManager.js", "src/components/syncplay/playbackPermissionManager.js", "src/components/syncplay/groupSelectionMenu.js", + "src/components/syncplay/timeSyncManager.js", "src/components/syncplay/syncplayManager.js", "src/scripts/dfnshelper.js", "src/scripts/dom.js", diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index de1424b89f..167f79c2ab 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -99,7 +99,7 @@ class SyncplayManager { this.syncPlaybackTime(); }); - events.on(timeSyncManager, "Update", (event, timeOffset, ping) => { + events.on(timeSyncManager, "Update", (event, timeOffset, ping) => { this.timeOffsetWithServer = timeOffset; this.roundTripDuration = ping * 2; @@ -108,6 +108,25 @@ class SyncplayManager { events.trigger(this, "SyncplayReady"); this.notifySyncplayReady = false; } + + // Report ping + if (this.syncEnabled) { + const apiClient = connectionManager.currentApiClient(); + const sessionId = getActivePlayerId(); + + if (!sessionId) { + this.signalError(); + toast({ + // TODO: translate + text: "Syncplay error occured." + }); + return; + } + + apiClient.sendSyncplayCommand(sessionId, "UpdatePing", { + Ping: ping + }); + } }); } From 4eedbe57427d43a96ca78f2ca27b4ff043ec8a1c Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 19:41:02 +0200 Subject: [PATCH 009/181] Fix sign in date conversion --- src/components/syncplay/timeSyncManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/syncplay/timeSyncManager.js b/src/components/syncplay/timeSyncManager.js index e526aa5fa4..74c98820c2 100644 --- a/src/components/syncplay/timeSyncManager.js +++ b/src/components/syncplay/timeSyncManager.js @@ -191,7 +191,7 @@ class TimeSyncManager { */ serverDateToLocal(server) { // server - local = offset - return new Date(server.getTime() + this.getTimeOffset()); + return new Date(server.getTime() - this.getTimeOffset()); } /** @@ -201,7 +201,7 @@ class TimeSyncManager { */ localDateToServer(local) { // server - local = offset - return new Date(local.getTime() - this.getTimeOffset()); + return new Date(local.getTime() + this.getTimeOffset()); } } From 71f72f836c508acb758bd18f06d43e4d9bf5857a Mon Sep 17 00:00:00 2001 From: gion Date: Mon, 20 Apr 2020 20:03:27 +0200 Subject: [PATCH 010/181] Remove syncplay worker This was an attempt to fix issues on Safari --- src/components/syncplay/syncplayManager.js | 78 ------------------- src/workers/syncplay/syncplay.worker.js | 90 ---------------------- webpack.dev.js | 4 - webpack.prod.js | 4 - 4 files changed, 176 deletions(-) delete mode 100644 src/workers/syncplay/syncplay.worker.js diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index 167f79c2ab..0ca7c13149 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -89,10 +89,6 @@ class SyncplayManager { this.onPlayerChange(); }); - events.on(playbackManager, "playbackstart", (player, state) => { - events.trigger(this, 'PlaybackStart', [player, state]); - }); - this.bindToPlayer(playbackManager.getCurrentPlayer()); events.on(this, "TimeUpdate", (event) => { @@ -359,7 +355,6 @@ class SyncplayManager { startIndex: sessionData.StartIndex, serverId: serverId }).then(() => { - // TODO: switch to PlaybackStart maybe? waitForEvent(this, "PlayerChange").then(() => { playbackManager.pause(); var sessionId = getActivePlayerId(); @@ -435,7 +430,6 @@ class SyncplayManager { this.syncEnabled = false; events.trigger(this, "SyncplayEnabled", [false]); this.restorePlaybackManager(); - this.stopSyncWatcher(); if (showMessage) { toast({ @@ -471,7 +465,6 @@ class SyncplayManager { this.syncTimeout = setTimeout(() => { this.syncEnabled = true; - this.startSyncWatcher(); }, SyncMethodThreshold / 2); }, playTimeout); @@ -485,7 +478,6 @@ class SyncplayManager { this.syncTimeout = setTimeout(() => { this.syncEnabled = true; - this.startSyncWatcher(); }, SyncMethodThreshold / 2); } } @@ -535,7 +527,6 @@ class SyncplayManager { clearTimeout(this.syncTimeout); this.syncEnabled = false; - this.stopSyncWatcher(); if (this.currentPlayer) { this.currentPlayer.setPlaybackRate(1); } @@ -625,7 +616,6 @@ class SyncplayManager { const elapsed = currentTime - this.lastSyncTime; if (elapsed < SyncMethodThreshold / 2) return; this.lastSyncTime = currentTime; - this.notifySyncWatcher(); const playAtTime = this.lastCommand.When; @@ -689,74 +679,6 @@ class SyncplayManager { } } - /** - * Signals the worker to start watching sync. Also creates the worker if needed. - * - * This additional fail-safe has been added because on Safari the timeupdate event fails after a while. - */ - startSyncWatcher () { - // SPOILER ALERT: this idea fails too on Safari... Keeping it here for future investigations - return; - if (window.Worker) { - // Start worker if needed - if (!this.worker) { - this.worker = new Worker("workers/syncplay/syncplay.worker.js"); - this.worker.onmessage = (event) => { - const message = event.data; - switch (message.type) { - case "TriggerSync": - // TODO: player state might not reflect the real playback position, - // thus calling syncPlaybackTime outside a timeupdate event might not really sync to the right point - this.syncPlaybackTime(); - break; - default: - console.error("Syncplay: unknown message from worker:", message.type); - break; - } - }; - this.worker.onerror = (event) => { - console.error("Syncplay: worker error", event); - }; - this.worker.onmessageerror = (event) => { - console.error("Syncplay: worker message error", event); - }; - } - // Start watcher - this.worker.postMessage({ - type: "StartSyncWatcher", - data: { - interval: SyncMethodThreshold / 2, - threshold: SyncMethodThreshold - } - }); - } else { - console.debug("Syncplay: workers not supported."); - } - } - - /** - * Signals the worker to stop watching sync. - */ - stopSyncWatcher () { - if (this.worker) { - this.worker.postMessage({ - type: "StopSyncWatcher" - }); - } - } - - /** - * Signals new state to worker. - */ - notifySyncWatcher () { - if (this.worker) { - this.worker.postMessage({ - type: "UpdateLastSyncTime", - data: this.lastSyncTime - }); - } - } - /** * Gets Syncplay stats. * @returns {Object} The Syncplay stats. diff --git a/src/workers/syncplay/syncplay.worker.js b/src/workers/syncplay/syncplay.worker.js deleted file mode 100644 index bd8d2bd60f..0000000000 --- a/src/workers/syncplay/syncplay.worker.js +++ /dev/null @@ -1,90 +0,0 @@ -var SyncTimeThreshold = 2000; // milliseconds, overwritten by startSyncWatcher -var SyncWatcherInterval = 1000; // milliseconds, overwritten by startSyncWatcher -var lastSyncTime = new Date(); // internal state -var syncWatcher; // holds value from setInterval - -/** - * Sends a message to the UI worker. - * @param {string} type - * @param {*} data - */ -function sendMessage (type, data) { - postMessage({ - type: type, - data: data - }); - -} - -/** - * Updates the state. - * @param {Date} syncTime The new state. - */ -function updateLastSyncTime (syncTime) { - lastSyncTime = syncTime; -} - -/** - * Starts sync watcher. - * @param {Object} options Additional options to configure the watcher, like _interval_ and _threshold_. - */ -function startSyncWatcher(options) { - stopSyncWatcher(); - if (options) { - if (options.interval) { - SyncWatcherInterval = options.interval; - } - if (options.threshold) { - SyncTimeThreshold = options.threshold; - } - } - syncWatcher = setInterval(syncWatcherCallback, SyncWatcherInterval); -} - -/** - * Stops sync watcher. - */ -function stopSyncWatcher () { - if (syncWatcher) { - clearInterval(syncWatcher); - syncWatcher = null; - } -} - -/** - * Oversees playback sync and makes sure that it gets called regularly. - */ -function syncWatcherCallback () { - const currentTime = new Date(); - const elapsed = currentTime - lastSyncTime; - if (elapsed > SyncTimeThreshold) { - sendMessage("TriggerSync"); - } -} - -/** - * Handles messages from UI worker. - * @param {MessageEvent} event The message to handle. - */ -function handleMessage (event) { - const message = event.data; - switch (message.type) { - case "UpdateLastSyncTime": - updateLastSyncTime(message.data); - break; - case "StartSyncWatcher": - startSyncWatcher(message.data); - break; - case "StopSyncWatcher": - stopSyncWatcher(); - break; - default: - console.error("Unknown message type:", message.type); - break; - } -} - -// Listen for messages -addEventListener("message", function (event) { - handleMessage(event); -}); diff --git a/webpack.dev.js b/webpack.dev.js index 544d32d63e..d8879fe808 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -48,10 +48,6 @@ module.exports = merge(common, { { test: /\.(wav)$/i, use: ["file-loader"] - }, - { - test: /\.worker.js$/, - use: ["worker"] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index 7b5f2ff6c0..cc4c57b9f4 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -41,10 +41,6 @@ module.exports = merge(common, { { test: /\.(wav)$/i, use: ["file-loader"] - }, - { - test: /\.worker.js$/, - use: ["worker"] } ] } From a2ba96ab820c5b3e8d3050e77952d48eee2be776 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 22 Apr 2020 22:48:26 +0200 Subject: [PATCH 011/181] Handle error messages --- src/components/syncplay/groupSelectionMenu.js | 4 ++-- src/components/syncplay/syncplayManager.js | 20 ++++++++++++++++++- src/strings/en-us.json | 7 +++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index c907d338a4..be54d92214 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -57,7 +57,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { icon: "group", id: group.GroupId, selected: false, - secondaryText: group.Partecipants.join(", ") + secondaryText: group.Participants.join(", ") }; }); @@ -74,7 +74,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { if (menuItems.length === 0) { if (inSession && policy.SyncplayAccess === "JoinGroups") { toast({ - text: globalize.translate('MessageSyncplayPermissionRequired') + text: globalize.translate('MessageSyncplayCreateGroupDenied') }); } else { toast({ diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index 0ca7c13149..0de232108b 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -274,7 +274,25 @@ class SyncplayManager { text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) }); break; - case 'KeepAlive': + case 'GroupNotJoined': + toast({ + text: globalize.translate('MessageSyncplayGroupNotJoined', cmd.Data) + }); + break; + case 'CreateGroupDenied': + toast({ + text: globalize.translate('MessageSyncplayCreateGroupDenied', cmd.Data) + }); + break; + case 'JoinGroupDenied': + toast({ + text: globalize.translate('MessageSyncplayJoinGroupDenied', cmd.Data) + }); + break; + case 'LibraryAccessDenied': + toast({ + text: globalize.translate('MessageSyncplayLibraryAccessDenied', cmd.Data) + }); break; default: console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 43b5f14148..fb72e8881c 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1034,9 +1034,12 @@ "MessageSyncplayUserJoined": "{0} joined group.", "MessageSyncplayUserLeft": "{0} left group.", "MessageSyncplayGroupWait": "{0} is buffering...", - "MessageSyncplayNoGroupsAvailable": "No groups available.", - "MessageSyncplayPermissionRequired": "Permission required to create a group.", + "MessageSyncplayNoGroupsAvailable": "No groups available. Start playing something first.", "MessageSyncplayPlaybackPermissionRequired": "Playback permission required.", + "MessageSyncplayGroupNotJoined": "Failed to join requested group.", + "MessageSyncplayCreateGroupDenied": "Permission required to create a group.", + "MessageSyncplayJoinGroupDenied": "Permission required to use Syncplay.", + "MessageSyncplayLibraryAccessDenied": "Access to this content is restricted.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", From 11f6217bb22072b3196b1406ad2b321f01ea3839 Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 5 May 2020 12:01:43 +0200 Subject: [PATCH 012/181] Fix code issues --- gulpfile.js | 2 +- src/assets/audio/silence.mp3 | Bin 0 -> 4890 bytes src/assets/audio/silence.wav | Bin 88244 -> 0 bytes src/components/htmlaudioplayer/plugin.js | 4 +- src/components/htmlvideoplayer/plugin.js | 4 +- src/components/playback/playbackmanager.js | 19 +- src/components/playerstats/playerstats.js | 10 +- src/components/serverNotifications.js | 4 +- src/components/syncplay/groupSelectionMenu.js | 77 ++-- .../syncplay/playbackPermissionManager.js | 4 +- src/components/syncplay/syncplayManager.js | 356 +++++++++++------- src/components/syncplay/timeSyncManager.js | 40 +- src/scripts/librarymenu.js | 36 +- src/scripts/site.js | 5 +- src/strings/en-us.json | 13 +- webpack.dev.js | 4 +- webpack.prod.js | 4 +- 17 files changed, 340 insertions(+), 242 deletions(-) create mode 100644 src/assets/audio/silence.mp3 delete mode 100644 src/assets/audio/silence.wav diff --git a/gulpfile.js b/gulpfile.js index ad77d9a677..538497d4d0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -45,7 +45,7 @@ const options = { query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'] }, copy: { - query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.wav'] + query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3'] }, injectBundle: { query: 'src/index.html' diff --git a/src/assets/audio/silence.mp3 b/src/assets/audio/silence.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..29dbef21856b7e607cab432056a8fa10315b496b GIT binary patch literal 4890 zcmd^?dr(wW9LLXkeKcmi5QiKgf>i+va1=&`l&2ssmzRQ~IJBh2q(cfi4$8`^VHm#2 zVH|Q%gjT^oCDM3ZaZ>UD>H?xck`=7DlMPT^VsGc}VXr^D?6O$>)tzDQ-QPL;``qvE z{C>Z4V0#bLpck*G_cyG=wh8#p4k5E?Xw)c6OIur8dwVA*Cl?ntH#bjDKR>_V;PCM9 z=;+wk*!cK_goMPzEnBv1-=3D1mY$xOnR(<$c6RoO6Q@s~K6|#Lq@=8@qN1Xvrna`W zuCAe>;o-x!wzl?mjYiYe#V`!ZVwQW5XRz;F$2rcc8A zKk$FKv%M2*5LJalTCCU_p~Y1Qjby>q_eRT6K5&uz1!l0FZ27B$OyTEp4=hdud%2#o z2*q;&9x!8&FA$=v49SZa@4$1E|JwBrU4oa?mP#9w?U-Uq-sK>o1~CB_tTts;k=>gE zEUJC24!ccu34i)vef`>;&5Ba-$I$AXIe4KF74P;Eum0}!^q`UxhZX~CLHKQZ$1D5FCcdWP z*M|!a$9cr->0cj%anlU|ugt=Yo0RW1#EhR{F?mEQ7+}{d73M()Rs9#KpA|AG4x6#K zL0{k2#)T=&=(A>G2XAO^x^>xtk#`I9bWpTZq|1Y)L*xM$fah1fQt{6GM&HL0apK13 zR<;*m<2Dec)hgDMj%i7rJ_n#!hK=y&Ir=)qdL%?o zplSK01TK3ToBVHPr3Zx^odv9H-{-4&4)@wz9&}Q40nxKUrVF28Jrqoz#)+)BxVe*!Zs_{yY}F*Ru6CNe;K1I@N&q_J9e8hN z+-xh6^P`JAN3Z9Joc@Dw0*v)h;7*l$%-#2^;{1l`W2%ciAH{;99(A67(oO%Qs(2xX zqU)&XoSu*&^9^`pRh@C(3%oYGk_wCpS{b=5bc*ZfvBG}PE~k$ZJswD0g>gpX2Ar}_ z?@al^Y8kW?-0|Jz=o(E+y3GZ8Fy|YQHTI9(khBWl4CjGv1p4j1F!nc%@Aa@HsleLj zy(yFt_8pvWDJ0+4AYFemz!5DbzL>7w@9|UT;iTD@-nv$nO02q1ChT&Yv~m3uXNg0e zKo9OaH?QJ+TTvBbVx7XSz^a-uT+Fioj6E5faJ}+~fTN+VNJt(;kUBM>hc`o?y%j5K z@`0gMrBr<=_MO=a0(YWD3h-#U7ez`<4hL}3m=jk4_SEC9ec@7r^ZvVl0BFH&0z_dJo z944xGWxz+N&=k<<^Pu^l4bRcX4X@8UvY+~q^)apoy43EG{Hd-4mit@p2i~=sHg%@O zT%M!XgX`xw(Gy}?KOpGSk(;Qx*DaDN;567M_IPyPn!oDb;$ literal 0 HcmV?d00001 diff --git a/src/assets/audio/silence.wav b/src/assets/audio/silence.wav deleted file mode 100644 index 63f253da845e5177e59bcbc05a49ecb118f5e97a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88244 zcmbWgOR_9Wl3n+*5?RG2hQO&%goFsXYyj*+gu)(s7z77{0kS@s$-C})E4R8w+yu#Z zaw5#l%~ZE;-Ky>pC(eEU>wo+Y|KY#=Z-4mhKmSkv>3{h@{?GsVKmPmQe*5hYKmPll z|J!fB{a-)k|KYcP|J(omU;gWVVa(&NfBlEw{_^AJ&p&=Vx}!h-ILB^Q@blNd{@nDp zCwBk&udZVEAAbMQ9;-c{P0zEvwfX$x-w?0{%y&&Y?rL_pD_ej0d7P!c{Po}c_OCyF zh=3KG{mW0wuE(anu=>Z}{_V%wfBU({SdH1}Is3=oam9nI{PoAfy?_46)SrJ?AzEVK zjFGnoHnP|%k$E(v#rE6Zc$d>>j72UlMEZO@GiS7B1SW_xp093Y&Ytx5`7qXWf!^1KRc@=7$X0usC@3MJ-;}3Etb~ab8+uEay6D^sr|Jifdiq zS9Zu3p45c)Wl99)vCNBj(Q*7(z`W_qli|-Ue#vH?gmW@JQ zswQz`Z9l0do8apQo7r(Rh8lg3%Ph&QnDbcNu~a3HA+t;lpVtyN@tV}Uouk@OpU*Wa z!F3=Ir|gvVu6^FS$u*3zHzldb&Mou$z7fUbFUsnwsAh`{@zBb4FE8y0mjSNKvY%{(PA}?JgfG`-3mA>^W_W++`D*x#B6tc8{KQN5j=ZO_HUlhYI=pe zfBkuHyslbCR{8EI3Xtr|@a!7ejgzuO6f*e7AHgq&MxEk}CH&5M@AgkW?%%qLfMHfr zRWL(@+s5W*fzXTx9PuBU`Qaj5V7kVqU%OZZVWkblDq9qO3bEMm!b&aLtx; z9xwWk-O83@BQSSxVt;F;}&t>(-YS@3#T_cebh zeOgtS;K5pV0I9{-nE+3C#;e_9sja`)%o-!sswx=)z?WyPqv zO48rB(=HafC2YD+)Tu_SA-_AD?(-trHJp`C z{>GJl$zOFgJlS=%8sB~TI#}6s=T`4n&a^;Go!N+)B8ze7u>O9iEgobGj>+#9bd|Get0^NG{d*owV89S;f@4ITOah84FvxU^xB2z!`m~z6g*X(#t?7b5EZ$*mCJ@U7%6HYm*dd07L zSdosb4{SIxcb~K2l|`enn|wwy#)>Ltwf;np8ry*_#~`3vj7{}$-{pv6>%)lG=?pu6 z`t6_p+WUlSr>N&Y)_k>6suxu#BF3>WZ`oEOt{*>Qa`o`ZsLVX?N3=%OY|o-a6WM0> zuzF3c+HO6&3Vt@~C=Rf*NB8`8GUi?9J40(=*GH_D-Ry9s%8=%B$Kl==a{n}S2AiIxtdRI7au2B(q zp$fj^T!n_FoXQT_C-d@}Qo^8rfAe8Q$xay&k+IL-W;-L&d!$nFBW^QbmeA7s6UiIv ztQmKnts^7a7&NU&a~_wE=j&U+tJSbNQ#L@;W8+ zukKSke)fW5H}3Dxi|}()#(G`#m?d)TRmJP1*TT2@Z$?2Lm)?c+E=ulQyH0T5>7`4# z0@FqumA^6&qw!fTJTGr%$TP6zLxfphp2Ub<&sq82FNJxPfvtRt9lQSIR}`@lQ)%9e z+qt%57VI`^{$^Ju@HNZxJdI;{(HVa%7UjEdYO|{;y-?4ezLt?^^sdhW=ZI*>@Wvc$ zH(E~bOqJkXk+PdLMXBw3&YY}cSgGIQHKxzZUhDemj{0US{zbAC%|Cf4!Wc~hWyM;q zN|vSa1In&U;ww{Iv1$TmS#hMQ%tbYMmBslt;w(S;FPCw?W0=s6qc>Z64+&PyFsr>U zBBtK=_KAdCgznmxY4vc~$d5G@4`W&zM(g#b+pV;U(|&ul*3!HHGqHI^)j zDO*>u>+pEhbG2jsi>_Cd1WN46Ia;xj?AczkjA@-F9hB!his$ahmMpW5sEb_B#)&^y z@!6K$(oE-Y>Z zdfzDQzOHJ^Mmk+SypQ|SkC>a2A+trK)}gE!PZ^2fpVC}6{cgvcMQcT6z<&50IkVV{L~hNS zx?R7O-MCkIV#QjI7#(|@+3b|^*@d;iTCJDKFsFQMTdgtTtLs$7o*mu2of}(@oEI&h z^z;6m+KUzNefllmU@(F=M~kKipFOR8vtT^zvkr)um_s}}u~oKM=^moFBeU)K7=G$- zR?C>zmFzTI=EirvQf8mq83%S>PTu!N+jA>Op7mI^x6bTyjAtKZ-~N?eI;)LEBu|`` z%e@1+9mv_DW4BphpZ+>0f92Q;OR;(FYJtzP!?>LFiuHLaYgXQO+;QxC(|$XIl|I9R zmCF_5@ft!9<8Q||670Pnm-TD^Ggq_8XpiwIr@q6TE%W0QJ{7Th^V)kJtDmkz9POH& z?s@GBdUI1A;#ym>WfVp5>C?`#YkxYtyT@s{PX(O0pD!#&%kaH6ifmjql2h3dK`Sp} zlr9%pzt^>p_zCzp5i9w9j>=wrGKG^#@W{4D%dSYPWHst)RM_fbke@!2XV+r<71P!5 zG7#J8#++tLub~%N(Nj<23S$*k)UEvg@|tI_sMC(N`NXKtkFNg9wd~$b=uFW)dCAw} zND*DhSfRB>GExrh5cRv|Hr%)7-ujm5CpTuJXI}N|NVY-i&wXdzJ0L$z!Ee9&uA1}z ztJl0u_fKrbQXW|&Z0iZsr9CE|^@zV2)9_^g{^ zmgn_oJo8WwtO~np%JZ0Tz0brj)|x9j=CL?^4M#bl^WVE$kWA%X7udrZ?<}bx*wLO0 z5H&gDdmA#Z9r$-d9N~(c&SdNTYW`$4@2=4C5IuX|W%#25Mjw<)+AAaZa`v{fEacYQfYk%Km zjXn8)T7KI0-K6}evYTnI2h}HWJ9gBOiW@6kmzif)y}CzDlH>fSC9jG0cXnyh`eoLR zk*)IiYu5TC)f_Emy~<0^)~wegH65>eRlP7-4(_?da*=ITmY;lqY5hU`tTwXWycA8igb_TOFI>)`t@SYf`O_KVJ05yYX@*LgkEkHL^WyoTIZuzjNr zIj>nTDy`7vR`I{`$lmwL{svDCf>_^Je=11Tfg5ZwsYN5ren)qmW22&yo@{LAR+Pmz z-$QWMhA!6hXEcpnbLXywd+%dK8KbdY`(Zb4b=yz%)%kYcqb)pDvvSP!sEV?x^=(&h zCcUiJ%S8FOyzI=kks6wg(zNKPUwbt%`!1sIXL6?Of&-LzSpJIe&WK@NiOOzb4V5z$ zNA;-2jr41O^1d>?np>F;t5*+MHWswBJc@fdZ)K}gMXdO`>+o|Pj$&k^v0$h2j#Xw^ zEpPUid;c~)-m9fo2bee7@#Om~`NQr;a?Z27mo0f<-nGxJ;VO7&SJ&L~jLO$*tWkZ) zr`I~)we<oSukqV zdgCx_u5Ew1ET7Dmq4Byd$GK2jJ!Xpi^&cXrGQkUm#o(@@sWP$|-&g<%$K*pVKL@`b ziP9{YJ(WNh;-&m_35vrmpS^HpRRh*9&vOoPKJ_rc{rmU zD|X2tO!q@wc3)jocgCxgn809`V!iGolSaNwsCZF*?k&oxtPZ60(2^6of-07q5a2Dg z9G63R?}$~6J5#^9N_%Q7p32E0WMsYadbRsRX54AkZsOH6&O%kCFu$=Z=(jA_CphP0 zv28{+`UH&rF9*a_JYvn;uC!n>reeJDHYRgrrqvv$Ih8O*t(!VreNt|gJEKfl`8J)}xlcxEid+>;WEe(UGM+`x-__b&I}e&mH%dS;E)aT(R7JvB69j!`W0kQX?C zJFKQh&VFW;Ssr=4pZk+9f3%^0R;MLruFpQLxC((M9`WV4IM7XMz$+8F&Y^yQ;Tq@vH=cew`L<(R~_weJ?EZ3ab+&e zPkBm5t%1+yL$x}0V}0A%vEJEn(TiEEzw(~_XHCxX_BDq+R$rf=W3|m55aER#*EF-j*_F-v8b8C}Ou6UZoSc5xqyI*s@b%f7RBkHtx-&o2!}Goq z8~XD~usdio7AE60o;`~IRMt#)XZvn%U27}rpVO)}m<@7f4$P7G^w0Qv#powXbo}RM z4%Q&P$)CE=vb|xwqKJ7#PyLSS-Q9-LmEvAT$!ZVI*-d3eHbn6pmp83*?q-m&@0;q8 zT{*)*X1Ax_BWG$K-s9fYpse1CzPg(VTNBflMIyNsX=FwZo_OH3tA*@Ze~b3AfyeRD z?=Flqi!mDyi~e5qLx(*d=Kg6*U-MX0MqEsJklUieL#*PZBSv1!JB$BzZN!W=Zif)1 zER-2vmj(4~RD15)?dn_GS@XKwk=f&jhr8c3p-=Y_z1+8RF?pvHKjT6jJads&t97iP za=IDcbp6L4V}DDt&h*KIn7`#K&HHKSG%*{XQj`218t=wQe9PI*%~qH-{9tuC-iZNi)PSDdc1=1&D;V^&o{zfCSub=UVK z%4=ugVU_V&9rADIWDk$U^&CAd8KpVM3f_kxX3al$%0(>0SvNP@oAyvF)s_LW^XVZK55YUy>+_j`t`H&s4du?9)!XrW$gm=84I; z%!oGCi#-%8pU1A=y!YhIVl(y_)n&#SDsoSVW5Hu1GjjbKjaOYV&%fibe|3fR{;&sI zy{oa>IJ^9reGuNf#R+F+MT_6>f_C-0IVRRBO~m36leOdfD8HrA^%rb-i#5;=Z(X?< zdu>-c?PgrrpP$O11*2O~F1Ak$p9n zHp-fLcprhci22rRk*F;#hLq^UBG!(5b*JaKb6OA8a~7>O*+w-n74)Emu zdKE|U@YYaFiQ33xv`DaThPbBi?_Byx=_1GH%^zpfpqTyc0c^+9Pe|t}&b1@oS|yq% zI@Ku3yvkLGI`S1a#2{|_{r=Uo*t&c4pnv}k&~NKnQGI%9gzTSg7(bEKA^glNPF*uz zpN!u8o84z5SP{3}*a=jLI?s>i%MN=)DD_52j_NXXQFGo}y-%a{{BQkFY|C^k5}``e zH`dtib6gge{J6dtwCPcL{Cm8Gx>%>Zr;RiX2l8N-GCOvnRC15L<0`W^19iPLplV}E z+qXj73v#@$-pm=(s-zgj_0)lJ*ID&;J$L1LM*Xa)kl;sq#h1=u>7pu2uJ25%7EgYd z(bmmrN~vz|`|)}TbKmmPp7(n<888>BE(WqggeuZ(^sb=1m3>~dFr1F$!t+Gc4n{I! zJwGjo7!>VfwJX6(#2n+JTIR^?uui|u7(<-CGZ$i4u|unUs@)w{=ldjwtIo~+GO71z z(u|h*DQ@wLEwyr8h)=Is#Of$U%txzQ52DU6xB24wL~I^pD*n{L-|J6Cqc`{}H?nk} z&C(l}u`?SlV7=Uks2qo~eO9rar`k7<;EZoJ(w_8yrX z-MPK9TU=dz%Fnm=6wl93iZ)eUu8FFc^cb(jYeaBTU%8B+x&6I*XHx#J-fx!m`mF2v z7ca1~3Sk^#)>rpWUB2^i^N_7Ksw^9NX0QGLpo-&@Wi(Bj$9SHDM`&>of)!YG#u&Ec)*??KQ$AF z&;5R~-OREY{?sbFdzavB73{is(a*eh{WrSj^-4c_Fz-D3SS?h&z9TWeMtk3*Xr()In!2kZmjUq(e?Q?OMVK^8F^uM zv8UwTgPH|#z4OAkUn?S;i_rek|Kq6qRs&&VpPXnx{p|M+Y6q*Lrz(wrT~pgK!`k9o ztXO~RqT{J{tK#_P)120JcPlTz+PEUb4F1F&sUUHl+RHa<(ev*=T&BRj@=`QJM8mg zt$6orjp!--sg3yddk?dTHGk6C6D>B3Uu&Qi@n3)7_S1F$A3|owo}g`TspikmdY^V< z*hpmene%zKcMWB#U_}{j{Or^+mU>}BF5P$6rtG?D`sQ`obbtBC8eV1*t12sBR*Eb3 zxrQFFy(?Cc-ah;0t=yz7V+3WL&@0!}Ax>GFPP7AmD(D%hR)77BKjJGFRuwbEKFvIH z>XEDLmG%tkt8SbY7FX-_In|0lc8B!l%NW07Fy3bjc^Kzf*fAr1;BERTw_Uwj9}nfC z997?GnwZvWtflXs)r<9AC!1HZThwqOR_mPPkIC0Cu8ompKd zv%LcCb^nwK#Uy@_4rdlpt^e1#=M}jgbQ#E-c@)W0mEdlb`uyDgtWOn%8fA?2u;Tys z!hbhYEiQw`0~gP%+dJyiB6F2ir<~L$|8SKhJ7^gh^NczNRxx46_rHGIh-m6dceT;s z$0%+!W=%{Ykrj8L=xV+Fvt*u`--rx=f}X={QFJe~Yg*Y!Z&uHG@RM`x5~+4!4_4d_ z27UP*>^`%M*(u0=Ob<1D!bl(68Gb&_h9pAreU=##XDUfwK$hv zSo*EK-nIU)kzD*F(*7!rs zKESk*?3tabz&dPm=FR+x_$qvS#e(Zwnf>vxNQk{?FZwW=8?Oy;TUT2a+-)SC{XN7V zFJLg@Mlj4LQ2k3g?0npB zcXqe8u3$6lj#%v~&TVYI-fYlY`tISiRYu4>V-RCshM4s#QwQZ7E7d_An3=T5=pDEH zaBEh&s5%VnmD$*Wff{Wz9~V&#lEY-Vv5wYl!9YUG|V^iD_LU&sG#Lc&mfZ@N9NUY?l03a!`%CE zaeGdDjK7WUtYb4~7b-LSud zg$3qiEZfbzeKzm<7;E?Pw~<)9M=j=Xx(bv$o@lXsJGJ?po?*|P)KyfaWlh`nS?tbS zec1uKD-=s;^!n}{;^*Aue)wZ?oVpH#o=b@_CpI&B`?&LP=uXgcb@FNk%HnC{vCr;n zjE2*kt5*s6)SvUrU7Xkv{YBkOIjs^})1K<93c_|=6GzA9Z&tFOoshFmbevI%MrWKx zZ63>J*;*|(V+|~?@Ev>l(HC=5gIL8bSH`qxx?eyk^)bGyQcl{n*Wy$E_YR$$gU}oi z6OqQ-Gu|C(*=XMAhTr^;Yipu!`pDnaf{Z`)&#KrdTVx$Bo%j9TvzKZcycf;7>6l3` zS$iJ8@5|C3jHwRqt%}iHd!%o6?9R_?Lkjy9pTsu(8CfjP6OlcmBOj>^?$a1^du&ay zaxS%-{nnI`;=0*TBU@R#Ll{T!UQJ9B`iwu>EQ@uf`NxkrT4(O=vACC!s)_w#H!hEu zRf{V9=`Dn;F;lM3dbwd&_vMV1wd|?a;$&ar>~1c6_}titwG85Xh(S)|<>nGU>@0Q| zz@r($K4-My%6dN*jhn6~I;{DK&1V$vqq{o1`0V`%T40ap+bx=Sx$ECi+x?2>tvW5w zzJ8v3nAg~OAElkCx6Yn2#(PopdZURmRicehV*yOzl=G16HCDdYMK-gxSQ*uK{CC`1 zV;xdM&wpKGJ+=`xtdic#c;D-1b7qzprq&z9?xKj50kV`esx~`4h9b~)QXaC!4dAzw>MXra7?irEjo0F0Ie6y48#$Xo9%U&1PuMo=) z*nOa(xNgoh+VY$_EIO42M=B5tS}YTsA-Zhviq%j#_I%gzcBmq-(4)we+S48$eHZ!O z=lA?P*r#DV=xbup*P?B||Sfs565c70zyEXJ_U?ESZ?uxPF6 zFD=5Ap7z@WEEzvm?9SrXxBNB+Y4L@v7=6{Vz11sD8B1&UE2G(BZ$4rrO~-`QHg;I= z8duyi>gj$)U`K!YCBytS?$)%ich}IaVH-F3xSguGK3AHC^y1x9zmJ!PuH|4U5?Gf9 zv*Tw(=rHcgu~jpaJ8Pu!I?YyJDdPPahhqAsvULbMx$oQPhhR}}VCMlE@s|nga!sj6 zTE{+5Td^ja^;oPm4rh#1*LZWz+73^=bbh)^uduIGachw|k5f0t;PMQk>GZ86*=;s< z%-Q|kf9jV3J$zRjv#}COAzH@8^yEGUK<;?)-aEKB@RbPOSMByMYs`q<4B2n}W}hl4 zZ{e@ncXeWgJjkv#jpQ@Zu~R11nSNk|hc?J$_%3@fVQ(fM{vHlX{ zc~_Q=y;iTPi1n!roO@@4Qu=p0{45q6#br$K-up5vjcw09pVeO#yF0was;b4Z9&ktH z#ofk}-zUDs))gsME^GLj^5Xku*$mv7P+r{c>#Q6NgFa&XvabCy0-F1X!MG!SGto$8 zG5fxoP*1Int1*}U>_B+HCOPaX>tV#_!E&2)atUO3gKw#j&lGxa_=p!&)3Nfyx`js<`xJoUCA9 zuX3l1&>7LN+w4(w@t>e*S&bk`7%UQtz|ceKU1d~HOYco?||Reb(b zhAW!uUf=aSS9KzRC;i|b%w03##u&71jII(3=N)DHiO{nlVy=hgoi9D?H3ZiuXNz@ch*a<8EtTs1eP5u?RsmJVn0V&mD&+cKoZm*p>Zc_BHL&=f z%9fGt0D0)vpf??%CdU6e)ku7GA^5}9v-H5HM!dJo?B zkOL6ZNkwt@E~a%kEW~YWXywW7-UZ-)=iV`CEflk@AL3;cUppU$7+bz#6LyT?aUR+9T=7&TU(_L233_dMEgzd_ z#60!x{Ru4ldC{Us^Txn=vsyKXO!E+%hR?0_DEj59-<=|Hjz z2Tx6JzQ~R_@zVwC(Ok%eQLwL3S#YN8!?`=kRq^PXdmrx&*kK%(jT@mB^p{rRX4h6c zR%x6iFZ9lEgAW^4r04LWrMD7`s@GxD%CfaHviat;TFKJ5H%5=^WA94Sk6Ft;`+f%V zvs3q17shEkj2l_iMd?1yfyZ;{gLBP!jb)cRpYuE0$UaG|JX&pS__?6?ID@}dy1p!U z$7+tvclC1bW#hyvqx`#i>Zk|1zv%O`J#uL#;`g0fFqS>9*!u>j44IFt^lC2UqKxU! zs5^5t**fRG)t`H_E#~11r}M-M19m9*GB=G{r8O)sKaJ?}eQ%cidA71{Je&i!@2Eb% zr(hp0yR2q?3~t2yysJ~I)>!WEUs`)r`sDET1*^Idq*RkleD(;-M<6p#__fcKW zLuZEA%J9C2vtr1$IkU^gqg)atJ)us+J~;o3A@+K$a~koJt@KlGle6+O^p0)?io<+h zrI^u+>=dJCt)bSf?=k`tk8dN- zJ9hsOdh;R6^S85a+hC6MTX)PAI}toJS=Kot1CGY>r-sr|5vK=^!AC@BO4QD*O43b{ zh%JO@Y&Cq(F<$ocaF4aiZYwmknBAu?%j4zi-dztfUgInsGRGtDU6WV+)#mPx^5{O8 zQf*TnEBFigfBOASt!#d(=BcstWA?NP7d&o;iiymS8~nM3_cQea`Ev!f#O>XNN9n|j zg1@Y15v!_>D2*@`W$_yW`IFgq#>|y56`6Bl98WQqPb_`=0n1fD9z*|=p1);TTYTOQ5vbLQrzUEk)k@vLJbEC1|9#rJy_@q=fs z-u$1W|MiDPLPTW5`-#z*QxuL4E`~2zXCxDL250vvU;4u0t_R-BJdf=h?5{82^>#v$ik-N$xzV(4UAnS7-`_wQ zlNIqq5z0mE+AJDxjC79z7FX~}%&Y*}eK6O11fxwgs^$4ClRWRIN?YVAB$n~!`sH@} z&6$eudAg`ai8*EEZ#K5hzSbR_wfpz{YYXJ|AsL8CdGbj9szR?kezGOYcvdA-N4b~v zi+@bSO?<{jxr0mRI&(F~_*=AbXaupp7@raDsz^=We?9ygW^894J7#AieSUk^&$2@7 z&uVH{x#gJgQ(JPy896ckH#+zDE|1lpnOK$UkO`?-1Z z>QXM3<@fq(-WXj68XsZ;IqTG3=&|j3vzIQHHCZ-46 z>&HB{&)4c!HG#xkMDiSqQjWMX`JS3lHMkyT--~U(MYMHeqpMyD7Vl*V48>3;SuMKG zw4X}!DqD19=U#VCVj21SgS4SyL9$-lYH43%Z)b7UV!PWQKGxopv*cHTn z_v`mS_y~#F@qPfs%kT0sREtTBPyfbp^K=oXZdPfLvp0_X4#BNCZHc>`VC=Ww+nxRK zXr!_EJ$Ct646PnZ-*Xt}@j2_o79*>(-v89+)O-q6g`zj=DI71UZlh*=XZB{8vu5g@ zUF*<3`pgN6Hxd!}+0uJ`E5xdWjT`k^7QU|=Lil3O69Gp>)`(TIrCP)1-lxT^wb!`x zbFUJi)0b!HuBpm=Kl_8{v}{&Ful;)mi(gk_^ZlcDv6*!vv`;&~E@l69o$4`4?0fAJ zNBUZK0+&DE@QPY1`xLa!Tz*y`o5hWhiBwMDg2SE}uO zbEZw6-}r_F_Sf5YMafUNu;34S)r^>)`7q1!r)Ou1N_**-9_PDW7{;piKwa6VCau84 zuOGy@d#EQb#be~psN|psm$!PE?^4^zUiIKzuHUzLddn8C?`yhQ=eo?_hb%6~>HBIt z?*87ZtcVd0y%%5BSfTaqQHaE9Z&mC+b7K9~CA0(Xbdcp%6vJoaDh9u*mkwZm+x76R zC-*ojYx01F>b1!3d(^3cUH*6{KvwFiyGJZ%=HKg$%D9>N?8A2^CceeQx?Km;%ajPV zF=H)w@9UcToD$-ySF@s~oyB7EO; zP^8o;kMNQQf4XlZ53+4WQYieP7}@<)DV+O%TJHbMY9~}VKb`*m1U_tGF&%8ge3{>U z3V!{O-|9TxuvIOJ5rk&$b{wqpSoZ^UmOIB@XK!Clp|d})WnJZI$C#SYV%#pqxDb_| z%Lsd!+0Qb=$g^cA+wTl+cf1y_D~BaDr9ZIgMb(+n+byS`%kqu&&92^JrL2`zJ?B-t zzgAq4n?u;pI=DqlU+`V`#nigg7x&x!M44KyrK^wCAMu2@d)r-0!0XX!0N?YwT-&9s zPV>O-KHvN7*Y2EN7@24B=x4Jjwl#Z&9UiHp(VIb6@qD9JFIZ!r5A(Toe0!@dVa7S< z&h}f`bsWy>MOJrB*MHIH&uHorPqwb7=X#hb%6EoBKb}&yuS0hJ~K9+&$I-9LYOJ=Vt)#}yrac<<(9c-Z-_KDJb$MX1R z4=aeVe_z2J*f2Msj+@PW&io$U-(FZ@b#sT|6@fXwH5W3Mrwp?rPG6d`twtX-CH)gEXL=xS&V26pY~o>@(7J1*S^Q;Ahr7XT`fk}?mOs6UsUFWSLPqAvR~Zy?jRK9MoL-rm$ zEw;ATs$@)T6brE5cRQ^)@n}O;5yL!RTsIQ2lyR$^^BcuZyW}z!%vuW9BXh<09$|&= zu=?8$uQFm6A$IMZ_Bm{IrWjEC{!b2A^k<(2xsqoew?p2@@bu;=jfg5VR8qg8$xn0q zRbyRQ-R*uEX2y|q=uCc!vx?&Q^C}VxDAazj-jIcO#6H+mQfv|@mhB)SdG6>4PKcmS z5{#TX=l;%b_)?;OKfsZ7prSx3TUA(mvd*y?5|z<;k3^j7RmFJORPD@!=gu^%ePW6^ zcBsJnZlLqN+ZsBwgTB0n@%lg)ACqR=Iy9^9UIs+1rS1Y`Ky=nm9N3l2FF5n0j@;pM zRmP5#>^^cCXO62l{_m6heUkaAT3DwF9XZC6D#qUKIygIJ%e=nLh?N@}vylh5UKKIx z5A!KAuhgs2VT+$^v4Z10o>^Y~v!WO-i`}=B^Y-`G%2xB#rGj<^$8Nkc=3$J!idGd5 zZPC_s&0c8RuQe?ivG)t>UmMMWTxEGIYt^$@6HWZ}iZ^+}_OiW<5?AvfW{2aAL@zfm z9<%HCt);uzX+qt;(U^D2tZ=VF+etxc?1?YpYjjb)A~V^Xc+ zZJ)||zojPSQD)3sEb=PKsn-m!M>eki)dZ`){oHI~gACQbIp-r@!XaOxiKDXOT76B+ zSZe+5+urA`;H^wksRUo?p=*r(-w4?ekLAYtjfESz88N3Yusa}gw%oN_+slH;%(6Wy zzMs0Nm$p(ye|6-046nVaH>=m*y`Ss6EZvC3vFm&1F22h%F;}nE3en4VIqdIJc^A~3 z!}al=i{m=m&4N`dt~m63t5Og+%J@d1e=0{Kcs~z$@(Ls!cb-gSY!{@uctDsbD#_y3_h(~_N(JFR%+9)K&3<8xi%jE-|v_3WLs?W^b`HZyc ze||%O{^GhtNL>4YX=B+ZW2(!r=?W(sOZN&N2+G1sn>@OdUWu{to{}UB{FIHJ|wmjVYh4B-W zT_!9lczQA_cP}fcsXF6~{ekPY`MW$Wu4}JM$ZDQL-z+eaiEdo>DbPRv{MPu@xwf@%vnu*BtOD+wc$U>?wCpo_>bVG( zL-!d+J+VGRtj~x-XKsjPe}fmls;`{hR~g3U);gEHw{maZE(5Vp>>V48)>Ij>h`LNJ zuVwmLx!DHkU86Sjl@)c}Cm?>}i^nng4S~$Q*QBO+*FRYam0qVoc60~)++D|PHykXR z8$FT65sXYV)v&fWleLXgl$K>w{3;;Kow=`z3cIn)j%WU@VZX=X6;>;*+TZkCUs+Uf zzT>(%#RB`Eer2V578qua>eRD7taE($Lk_-GnLL@HG7?i|U^&KioXjpg+J12}9q;|? zbbVf-jhG0#x%gIPt$d;?mf~DS{GD*`bY;}cxwg;v;3IauE}E@p{@4RXq8&zZwtox8 zx&r|`=%uwBc_v)m$qy0d_m~&?^^BaEE5>q5C83+{vCDOgnZM_qoK~NGfKG8US5C~T zIZ*?hDLQblD&@`kuoMR~W$BGrb^dyHds*?khnFAcwH{tkg>*5R(Ku><$}2)FqLiXr zj?1WM%}LK%3oUz8kH0}!1+ceFKasG$YY=CCwMtJtn?IH1+{F;@br)>rBer-ANgTvo zRpJ^E$!BNS>8@*ISAb@#rRzbJ-K&B#+U%JaQb|$AO*L+ua*;0Sl>2{4g4evMcYC62 z_)G?ix>rVg8S{+^-{tBukssn}pNPxSovjXvzHD7x zvfdg+i8t%2`06V*+4FvI&Yriw@4yeU7wd4ggE;M;MC$w3uvA;IG<#*TY>D$-(fP2h zwB~ilGt5|d_Q-O6uT?5QY^;(o5T~|sDzCRX@lxGv+$b*0&y67FcFh;>^O$|U=bQz3 z!K*g%E?PBj4DSrSkMVdjjfE6+quSNd)v}SbYJ`mPD3jv4K3b_BjKDqS7&OZ^Y{|FG ztWJCX%8F`A+niywY&q&^p04GwmLit{&c$}0o_nvA1zCzU*qOame{+7`0Gr?b3RJ#W ztJN~|8QD{RD#Z7za&M$yvBP_%U9al?A!CW{5>@MyxZ}Wm9cr zsoC~}#s}VDxXe|Bvfip*+3$MMI-eKWqyP8qFRY{**0Pf0{a9Jy5sldIdU%l&eUyRn z7b@0$UTL!~+KY*}QxdE6f`#%HFEKQ}?2d2S*zot4d^d17cLf4P)`lo`&t6>IOl(dY zSGUx_lMi>Y#%ogj>fXne?^v+^82u+YJs7#n#0R*EYPszm%xkvSZut1DOmZOe;0K+y z{W}zXYh9&?czTm@RqSbJtcE9Iqb z=#`tb?(;RzGPiSbefP5RxySdb`01T1=gqmRJ?CA99z=^JvtVSYo#?D+=k2fc*wy+) zGE76?`OjD52ZbK&80C}oJ&5F+3S8soYu;nzg`MqMCrN*GC6D#vP8In+PX@Z**_BNE zMA|*Jn(kig4?nu&KmEAt<=={k)nb;PDl2`cN%OYPctB~6IKM@yxT4 zKI^P?_OS0O#1OX^YrXsKd*%?k{@!#vTyC*FPK+aV#g-y^Wj%44y)+vmoUKn)i6Tzh z;8R6=)|PqAvahSVt$CjjS?4+0m{AWIyj(dh>ZfOInBl9XTe(Ci1M~{-Onf(lE&Srs zQT+QFi%&JApLSp&ON>MFu88LC<{@V`o%{PvJ6e6Ms|CmH@qG=~SI+8t3Sm{f(H)`N z{4=7leb=+=?ko>^@+#W<(NFfbcUGO7<=JvHTq?qycEI|FN{|h6=Pq|(XQM@C?UK8y zSUc9)-X+Xd6%ohZP?K#QZ}j*Dg=%*6d>$ueFza{Mtg5w+Q{b~7XXi#_@40z2s#Sfv zQWrSnTkEcz*2Pdy^IlV>gZw^1Yi-E{V@7d5>#klUZlIN7>O!z%Zq}ixq$S7y; z_fx#bigUB&UU7QmvP(E04qg|_X2IKib2gsez^Z<>AC2U-yuA%-dVC+4VZ!D&-`;;nd($$S(=}x9zwHy+wu9i&2q^q84_g-gx0g> zPm6Cos0CEvTB3nje*E15zsck80p!YOIhPlo)z$c{YU-YLr1^CUDnoBDo_t*${ zRu_vErm)#Mkf^XvPpO*h^gf}!AO?=W`n)5)_1NcSWlKzWH?Mnju1`vgC;TZY{eb^P? z$?)I{Og-P~DB75^?-eip`!RMAbzX(Qu2IN8@l<2u5ER*B|ILXOz(=gUx25jyr(w4v zzxR5a&taZFdFB{9WoRQ+W!>>!C1%BBT-p$!*LQQw-n(L6Wn6B}0vr_QQ)98C8tO+d zB_F@;;Ij7Yw8m+E#vE~rRu;k^rqp-eLlr|6I30P0xEfvScg%{8&2=cgy^3P2#@CH- z*DAUSYhjrOXVhb7%#G(%KpI;XTZ?b^i^Ajm{dv)kb2;peEbOf7k!~)^FoTE3?7A}z z%WOygZ(ge``gfXAfn~p=IJ`aEJjI}~$-!L}`fGvnyFx5htHERTVY{o#;_1q6*Y#em zYXaHq8l#2IJTVzp9=u!KzZK$Nni7!+JYTJv0nTZYHRZ5qH1THT15tJ_wnrH<-XUNL2yb6&0e1Qk1%J^NtXWZ~9dxbniM~xjVXS+uTXxr2yej6`pBIa?sEh_AX;oiX!-IW;7>Qs? zaer3E&8LF!@QjHg?Gyj=d|8egJ7N~(>+ZO9G1XSBFlrUto%;X8bXQ)@tbT~s3>f*4 zV1Hg&b03_6JMIe2%xW)WRqJO&TC3h#A18GgaitWzt1Gz{4QxAaw!^3Q}y$|AgUHw{C?<>PAU+66;iE*tD}epkE8$Iegd<3Z1@2c6Y-S75;i z%q8_fwKxAB#dY_2um;9rlr^-T-i_HAGTRyVJQ>lOG0Vdp=VeTZ{XQ3qS}HdoR6j9t zj~I7n;T>dq-hE`=-0{qe(HZS(W}dR9YV&bfh^hK2{Q5{!)<;jvEPYxV--+WC=X)5oB42bqq|wT^{!{EHf!AV zpVb=Hyk3GjMGUP&^(L+CR`X#AJY z?x%(rcFL|gwgTXKXnI#?c8m_|R|zt#J)TpCQO>Fw;<$U5?gEB1KOw_5bMaw>Wt#b7 zSBLJ;(;HbB-RDuiZ_`ss1JR-k6|c)$s&LM#w$qBZEI%=cZGB>xWTmTxc{Wn9^lhCR z)hE|!V0M=Qxbmp`FuEwJZ?zHbNDji{E)l8)RzCME|DWDaQ&sc4J8>7EBJdqk{B>CWAe+ac(;^$L*Nf5AycdTG57uz}addL?>OP&8Uh|DXLG~`DsUY z&_?vEwVQL#RNvyuZU5z_aJ3JmH4hfed>cf`M^RMXH9-KHs5j3-f{(7aOwJS>ALDdZ(z(b zV;{`O1G(#-EPNMFnUTYu>oC67Q>&=bu~R)>{LJbDn?&;D!d>-K znfcUNdyGF#VwcA^4Kk}oB6-I9bJO3RLM_`&8@qW`v$JOXOoj0-W))Z_>i|UE4&9QW z{E1V>Ri-+Xi_U^T9cbZO%&l1d(*&>f1Nj$^K0Q}$n2#socCVJ$83OqQIrns4H_CIh z9i!&4Y%+Vt?}#t!ayn*32*aSeXBpos@H(Ix_qdGcb9`WJ?`DUoCb;OD`)lXcwvd>7#0@*A^5pdLX4O=Pk zz80T**`Y4jcl%FTF<+{IXl@6=1N^3$Y{wnzjS0LWe&RAaoYU5Oq<3}d?-ud4%B+6a z7vgDDYoch6tKg}u<$2L~#`^OLS0pf*uQ7(rRagGn!+M!{kC^TH?-kGJ;u8x-!_Gw> z((>B+veB+(rWL>a2AB6ezVWF{voPz^h*{K%ou5_anNfM(=*H@7_iEh}dM~4b-}~di zUQwpBQ+AB980*c$C@w#@@GSfFw=6!h1nPOw!n*^&a(3Q~g?st}57(IPbe=h#Kl!|z zRz=omadG4i@!wsoxYz69D;9U7)@vVBw{w6?%|6%JjpV4#WFCtvL{HDn&gbqJUCFZ% zl-HEc-g2^$ok3lKwJT^^(O$MfH!msx+dQzJzn>F7X3X2|iLFydy>rXscFgN?6=&&m z$Hj!!Rc8uM{UB3WX51q`4M+p)%f=()c#-qxe%6$eMk>`$dER$LL}U&`*)@>(o;Ze( zk-YorxJ+`C;$~Z1W_2rz#b{!`d;J}g5fGITvx|3U$0OeTx$%gp3>gXQQtiduEIwKz z^C@Zb?@{ZW?}*(zzO!}x%$_?wpYICBF%9Ze#%MfDv#v0!{fo8Z1hhq{-?tU-c3J~> z?Epu9mWywraG?zA@Y`K*JFBv?Y`ptSp#0T4&0Tk#fA8O2ROhZITDgRcs%9#8CQhF7 zF(6mZb3`#~&k@SS7Z0mIODdaLIepIN#a(h4H`pPL?5%I8cP;Ddo@@Pfey)F?1Dm-S zgJ9|b&OD7-@cDQuS8MEWpKFYD_J>N#9oIXWHJ(K^9IfE>83FMz^Z9N=%*LN5)^^qB zOb@J@lOnzE8aQ`zwVkc97N2pclZ*D=qcFcaclmo_pvv~%LfhkpkqTq9UZRce1K{hf zT=eRwYf2e2Qr`Ql%2_kc@~Y+Gxf#0St%z%0%OZ6oL#QRjtmJZbtv$zPMKu>O5tRAH zbl8$anL~k5-<#08Un|$d+HsT>i`RB#YoJG>cbL9>o0j?R}nAMF#pVoy7{J= ztFX}41L||GA-1k+M$%r;T=t)5FYBu%xp8M*8(r0hyxRDQO7|12MsxAxcxw4c#?N|9 znXUlc`$05h(^$0_CZd#KbJY9tG~L}rHoH%;N3a^4+Lkk~{Kna?REekJ!mmFmZmVI( zqtzcXdh$N8Xyil`)ihaA7slbekde;I=V(2Sb7e42p6jvPzi`)z?Kc9^8_UL>iviBd z%2Q1$2CuTNrdpzY3#WH9i|^w`;JN@x6yTdM@j{+F{>y=uXzR z$H>z?@hta7F{OH@*=yl>#`sfU-qd@u&o$t}t~DSEIcvAC=rio!SoU0v)I~SWxPDGqcJgRS!KpI2(@>+-0i!p2tQcudDph*y`~svzwINZesYiPmedET)ay%@^55bvqeWSJUHcKiO1s%&EN8!_Iy>Bi5eD Sj#^Qp#u#I*axXE^2mT*$^iC20 diff --git a/src/components/htmlaudioplayer/plugin.js b/src/components/htmlaudioplayer/plugin.js index 2580481051..672bd06b8d 100644 --- a/src/components/htmlaudioplayer/plugin.js +++ b/src/components/htmlaudioplayer/plugin.js @@ -520,8 +520,8 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp var list = []; var audio = document.createElement('audio'); - if (typeof audio.playbackRate === "number") { - list.push("PlaybackRate"); + if (typeof audio.playbackRate === 'number') { + list.push('PlaybackRate'); } return list; diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index 064e4155ee..60f39c5ecc 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -1442,8 +1442,8 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa list.push('AirPlay'); } - if (typeof video.playbackRate === "number") { - list.push("PlaybackRate"); + if (typeof video.playbackRate === 'number') { + list.push('PlaybackRate'); } list.push('SetBrightness'); diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index e4ce40cf4e..ec0ee41402 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -54,6 +54,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (!serverId) { // Not a server item // We can expand on this later and possibly report them + events.trigger(playbackManagerInstance, 'reportplayback', [false]); return; } @@ -77,7 +78,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } var apiClient = connectionManager.getApiClient(serverId); - apiClient[method](info); + var reportPlaybackPromise = apiClient[method](info); + // Notify that report has been sent + reportPlaybackPromise.then(() => { + events.trigger(playbackManagerInstance, 'reportplayback', [true]); + }); } function getPlaylistSync(playbackManagerInstance, player) { @@ -3777,18 +3782,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } }; - PlaybackManager.prototype.setPlaybackRate = function (value, player) { - player = player || this._currentPlayer; - - if (player) { + PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) { + if (player && player.setPlaybackRate) { player.setPlaybackRate(value); } }; - PlaybackManager.prototype.getPlaybackRate = function (player) { - player = player || this._currentPlayer; - - if (player) { + PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) { + if (player && player.getPlaybackRate) { return player.getPlaybackRate(); } diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 404baab7eb..07fcd7070a 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -332,17 +332,17 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay var stats = syncplayManager.getStats(); syncStats.push({ - label: globalize.translate("LabelSyncplayTimeOffset"), - value: stats.TimeOffset + "ms" + label: globalize.translate('LabelSyncplayTimeOffset'), + value: stats.TimeOffset + globalize.translate('MillisecondsUnit') }); syncStats.push({ - label: globalize.translate("LabelSyncplayPlaybackDiff"), - value: stats.PlaybackDiff + "ms" + label: globalize.translate('LabelSyncplayPlaybackDiff'), + value: stats.PlaybackDiff + globalize.translate('MillisecondsUnit') }); syncStats.push({ - label: globalize.translate("LabelSyncplaySyncMethod"), + label: globalize.translate('LabelSyncplaySyncMethod'), value: stats.SyncMethod }); diff --git a/src/components/serverNotifications.js b/src/components/serverNotifications.js index 9776c88bd3..876c3f7e79 100644 --- a/src/components/serverNotifications.js +++ b/src/components/serverNotifications.js @@ -187,9 +187,9 @@ define(['connectionManager', 'playbackManager', 'syncplayManager', 'events', 'in events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]); } } - } else if (msg.MessageType === "SyncplayCommand") { + } else if (msg.MessageType === 'SyncplayCommand') { syncplayManager.processCommand(msg.Data, apiClient); - } else if (msg.MessageType === "SyncplayGroupUpdate") { + } else if (msg.MessageType === 'SyncplayGroupUpdate') { syncplayManager.processGroupUpdate(msg.Data, apiClient); } else { events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]); diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index be54d92214..af08f92776 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -3,7 +3,6 @@ import connectionManager from 'connectionManager'; import playbackManager from 'playbackManager'; import syncplayManager from 'syncplayManager'; import loading from 'loading'; -import datetime from 'datetime'; import toast from 'toast'; import actionsheet from 'actionsheet'; import globalize from 'globalize'; @@ -18,13 +17,6 @@ function getActivePlayerId () { return info ? info.id : null; } -/** - * Used to avoid console logs about uncaught promises - */ -function emptyCallback () { - // avoid console logs about uncaught promises -} - /** * Used when user needs to join a group. * @param {HTMLElement} button - Element where to place the menu. @@ -32,47 +24,43 @@ function emptyCallback () { * @param {Object} apiClient - ApiClient. */ function showNewJoinGroupSelection (button, user, apiClient) { - let sessionId = getActivePlayerId(); - sessionId = sessionId ? sessionId : "none"; - const inSession = sessionId !== "none"; + const sessionId = getActivePlayerId() || 'none'; + const inSession = sessionId !== 'none'; const policy = user.localUser ? user.localUser.Policy : {}; let playingItemId; try { const playState = playbackManager.getPlayerState(); playingItemId = playState.NowPlayingItem.Id; + console.debug('Item', playingItemId, 'is currently playing.'); } catch (error) { - playingItemId = ""; + playingItemId = ''; + console.debug('No item is currently playing.'); } - apiClient.sendSyncplayCommand(sessionId, "ListGroups").then(function (response) { - response.json().then(function (groups) { + apiClient.sendSyncplayCommand(sessionId, 'ListGroups').then(function (response) { + response.json().then(function (groups) { var menuItems = groups.map(function (group) { - // TODO: update running time if group is playing? - var name = datetime.getDisplayRunningTime(group.PositionTicks); - if (!inSession) { - name = group.PlayingItemName; - } return { - name: name, - icon: "group", + name: group.PlayingItemName, + icon: 'group', id: group.GroupId, selected: false, - secondaryText: group.Participants.join(", ") + secondaryText: group.Participants.join(', ') }; }); - if (inSession && policy.SyncplayAccess === "CreateAndJoinGroups") { + if (inSession && policy.SyncplayAccess === 'CreateAndJoinGroups') { menuItems.push({ name: globalize.translate('LabelSyncplayNewGroup'), - icon: "add", - id: "new-group", + icon: 'add', + id: 'new-group', selected: true, secondaryText: globalize.translate('LabelSyncplayNewGroupDescription') }); } if (menuItems.length === 0) { - if (inSession && policy.SyncplayAccess === "JoinGroups") { + if (inSession && policy.SyncplayAccess === 'JoinGroups') { toast({ text: globalize.translate('MessageSyncplayCreateGroupDenied') }); @@ -94,15 +82,17 @@ function showNewJoinGroupSelection (button, user, apiClient) { }; actionsheet.show(menuOptions).then(function (id) { - if (id == "new-group") { - apiClient.sendSyncplayCommand(sessionId, "NewGroup"); + if (id == 'new-group') { + apiClient.sendSyncplayCommand(sessionId, 'NewGroup'); } else { - apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', { GroupId: id, PlayingItemId: playingItemId }); } - }, emptyCallback); + }).catch((error) => { + console.error('Syncplay: unexpected error listing groups:', error); + }); loading.hide(); }); @@ -110,7 +100,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { console.error(error); loading.hide(); toast({ - text: globalize.translate('MessageSyncplayNoGroupsAvailable') + text: globalize.translate('MessageSyncplayErrorAccessingGroups') }); }); } @@ -126,17 +116,16 @@ function showLeaveGroupSelection (button, user, apiClient) { if (!sessionId) { syncplayManager.signalError(); toast({ - // TODO: translate - text: "Syncplay error occured." + text: globalize.translate('MessageSyncplayErrorNoActivePlayer') }); + showNewJoinGroupSelection(button, user, apiClient); return; } - const menuItems = [{ name: globalize.translate('LabelSyncplayLeaveGroup'), - icon: "meeting_room", - id: "leave-group", + icon: 'meeting_room', + id: 'leave-group', selected: true, secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription') }]; @@ -150,17 +139,19 @@ function showLeaveGroupSelection (button, user, apiClient) { }; actionsheet.show(menuOptions).then(function (id) { - if (id == "leave-group") { - apiClient.sendSyncplayCommand(sessionId, "LeaveGroup"); + if (id == 'leave-group') { + apiClient.sendSyncplayCommand(sessionId, 'LeaveGroup'); } - }, emptyCallback); + }).catch((error) => { + console.error('Syncplay: unexpected error showing group menu:', error); + }); loading.hide(); } // Register to Syncplay events let syncplayEnabled = false; -events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { +events.on(syncplayManager, 'enabled', function (e, enabled) { syncplayEnabled = enabled; }); @@ -173,11 +164,11 @@ export function show (button) { // TODO: should feature be disabled if playback permission is missing? playbackPermissionManager.check().then(() => { - console.debug("Playback is allowed."); + console.debug('Playback is allowed.'); }).catch((error) => { - console.error("Playback not allowed!", error); + console.error('Playback not allowed!', error); toast({ - text: globalize.translate("MessageSyncplayPlaybackPermissionRequired") + text: globalize.translate('MessageSyncplayPlaybackPermissionRequired') }); }); diff --git a/src/components/syncplay/playbackPermissionManager.js b/src/components/syncplay/playbackPermissionManager.js index df16545b39..3c258ad18d 100644 --- a/src/components/syncplay/playbackPermissionManager.js +++ b/src/components/syncplay/playbackPermissionManager.js @@ -11,7 +11,7 @@ function createTestMediaElement () { document.body.appendChild(elem); elem.volume = 1; // Volume should not be zero to trigger proper permissions - elem.src = "assets/audio/silence.wav"; // Silent sound + elem.src = 'assets/audio/silence.mp3'; // Silent sound return elem; } @@ -30,7 +30,7 @@ function destroyTestMediaElement (elem) { */ class PlaybackPermissionManager { /** - * Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction). + * Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction). * @returns {Promise} Promise that resolves succesfully if playback permission is allowed. */ check () { diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncplayManager.js index 0de232108b..b5694c88ff 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncplayManager.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module that manages the Syncplay feature. * @module components/syncplay/syncplayManager @@ -13,15 +11,25 @@ import toast from 'toast'; import globalize from 'globalize'; /** - * Waits for an event to be triggered on an object. + * Waits for an event to be triggered on an object. An optional timeout can specified after which the promise is rejected. * @param {Object} emitter Object on which to listen for events. * @param {string} eventType Event name to listen for. + * @param {number} timeout Time in milliseconds before rejecting promise if event does not trigger. * @returns {Promise} A promise that resolves when the event is triggered. */ -function waitForEvent(emitter, eventType) { - return new Promise((resolve) => { - var callback = () => { +function waitForEventOnce(emitter, eventType, timeout) { + return new Promise((resolve, reject) => { + let rejectTimeout; + if (timeout) { + rejectTimeout = setTimeout(() => { + reject('Timed out.'); + }, timeout); + } + const callback = () => { events.off(emitter, eventType, callback); + if (rejectTimeout) { + clearTimeout(rejectTimeout); + } resolve(arguments); }; events.on(emitter, eventType, callback); @@ -33,7 +41,7 @@ function waitForEvent(emitter, eventType) { * @returns {string} The player's id. */ function getActivePlayerId() { - var info = playbackManager.getPlayerInfo(); + var info = playbackManager.getPlayerInfo(); return info ? info.id : null; } @@ -48,11 +56,10 @@ const MaxAttemptsSpeedToSync = 3; // attempts before disabling SpeedToSync const MaxAttemptsSync = 5; // attempts before disabling syncing at all /** - * Time estimation + * Other constants */ -const PingIntervalTimeoutGreedy = 1000; // milliseconds -const PingIntervalTimeoutLowProfile = 60000; // milliseconds -const GreedyPingCount = 3; +const WaitForEventDefaultTimeout = 30000; // milliseconds +const WaitForPlayerEventTimeout = 500; // milliseconds /** * Class that manages the Syncplay feature. @@ -62,7 +69,7 @@ class SyncplayManager { this.playbackRateSupported = false; this.syncEnabled = false; this.playbackDiffMillis = 0; // used for stats - this.syncMethod = "None"; // used for stats + this.syncMethod = 'None'; // used for stats this.syncAttempts = 0; this.lastSyncTime = new Date(); this.syncWatcherTimeout = null; // interval that watches playback time and syncs it @@ -71,6 +78,7 @@ class SyncplayManager { this.minBufferingThresholdMillis = 1000; this.currentPlayer = null; + this.localPlayerPlaybackRate = 1.0; // used to restore user PlaybackRate this.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled this.syncplayReady = false; // Syncplay is ready after first ping to server @@ -84,24 +92,37 @@ class SyncplayManager { this.timeOffsetWithServer = 0; // server time minus local time this.roundTripDuration = 0; this.notifySyncplayReady = false; - - events.on(playbackManager, "playerchange", () => { + + events.on(playbackManager, 'playbackstart', (player, state) => { + this.onPlaybackStart(player, state); + }); + + events.on(playbackManager, 'playbackstop', (stopInfo) => { + this.onPlaybackStop(stopInfo); + }); + + events.on(playbackManager, 'playerchange', () => { this.onPlayerChange(); }); this.bindToPlayer(playbackManager.getCurrentPlayer()); - events.on(this, "TimeUpdate", (event) => { + events.on(this, 'timeupdate', (event) => { this.syncPlaybackTime(); }); - events.on(timeSyncManager, "Update", (event, timeOffset, ping) => { + events.on(timeSyncManager, 'update', (event, error, timeOffset, ping) => { + if (error) { + console.debug('Syncplay, time update issue', error); + return; + } + this.timeOffsetWithServer = timeOffset; this.roundTripDuration = ping * 2; if (this.notifySyncplayReady) { this.syncplayReady = true; - events.trigger(this, "SyncplayReady"); + events.trigger(this, 'ready'); this.notifySyncplayReady = false; } @@ -113,33 +134,55 @@ class SyncplayManager { if (!sessionId) { this.signalError(); toast({ - // TODO: translate - text: "Syncplay error occured." + text: globalize.translate('MessageSyncplayErrorMissingSession') }); return; } - apiClient.sendSyncplayCommand(sessionId, "UpdatePing", { + apiClient.sendSyncplayCommand(sessionId, 'UpdatePing', { Ping: ping }); } }); } + /** + * Called when playback starts. + */ + onPlaybackStart (player, state) { + events.trigger(this, 'playbackstart', [player, state]); + } + + /** + * Called when playback stops. + */ + onPlaybackStop (stopInfo) { + events.trigger(this, 'playbackstop', [stopInfo]); + if (this.isSyncplayEnabled()) { + this.disableSyncplay(false); + } + } + /** * Called when the player changes. */ onPlayerChange () { this.bindToPlayer(playbackManager.getCurrentPlayer()); - events.trigger(this, "PlayerChange", [this.currentPlayer]); + events.trigger(this, 'playerchange', [this.currentPlayer]); } /** - * Called on playback state changes. - * @param {Object} e The playback state change event. + * Called when playback unpauses. */ - onPlayPauseStateChanged (e) { - events.trigger(this, "PlayPauseStateChange", [this.currentPlayer]); + onPlayerUnpause () { + events.trigger(this, 'unpause', [this.currentPlayer]); + } + + /** + * Called when playback pauses. + */ + onPlayerPause() { + events.trigger(this, 'pause', [this.currentPlayer]); } /** @@ -149,7 +192,7 @@ class SyncplayManager { onTimeUpdate (e) { // NOTICE: this event is unreliable, at least in Safari // which just stops firing the event after a while. - events.trigger(this, "TimeUpdate", [e]); + events.trigger(this, 'timeupdate', [e]); } /** @@ -158,7 +201,7 @@ class SyncplayManager { onPlaying () { // TODO: implement group wait this.lastPlaybackWaiting = null; - events.trigger(this, "PlayerPlaying"); + events.trigger(this, 'playing'); } /** @@ -169,7 +212,7 @@ class SyncplayManager { if (!this.lastPlaybackWaiting) { this.lastPlaybackWaiting = new Date(); } - events.trigger(this, "PlayerWaiting"); + events.trigger(this, 'waiting'); } /** @@ -191,15 +234,18 @@ class SyncplayManager { this.currentPlayer = player; if (!player) return; } - - // TODO: remove this extra functions + + // FIXME: the following are needed because the 'events' module + // is changing the scope when executing the callbacks. + // For instance, calling 'onPlayerUnpause' from the wrong scope breaks things because 'this' + // points to 'player' (the event emitter) instead of pointing to the SyncplayManager singleton. const self = this; - this._onPlayPauseStateChanged = () => { - self.onPlayPauseStateChanged(); + this._onPlayerUnpause = () => { + self.onPlayerUnpause(); }; - this._onPlayPauseStateChanged = (e) => { - self.onPlayPauseStateChanged(e); + this._onPlayerPause = () => { + self.onPlayerPause(); }; this._onTimeUpdate = (e) => { @@ -214,12 +260,17 @@ class SyncplayManager { self.onWaiting(); }; - events.on(player, "pause", this._onPlayPauseStateChanged); - events.on(player, "unpause", this._onPlayPauseStateChanged); - events.on(player, "timeupdate", this._onTimeUpdate); - events.on(player, "playing", this._onPlaying); - events.on(player, "waiting", this._onWaiting); - this.playbackRateSupported = player.supports("PlaybackRate"); + events.on(player, 'unpause', this._onPlayerUnpause); + events.on(player, 'pause', this._onPlayerPause); + events.on(player, 'timeupdate', this._onTimeUpdate); + events.on(player, 'playing', this._onPlaying); + events.on(player, 'waiting', this._onWaiting); + this.playbackRateSupported = player.supports('PlaybackRate'); + + // Save player current PlaybackRate value + if (this.playbackRateSupported) { + this.localPlayerPlaybackRate = player.getPlaybackRate(); + } } /** @@ -228,13 +279,15 @@ class SyncplayManager { releaseCurrentPlayer () { var player = this.currentPlayer; if (player) { - events.off(player, "pause", this._onPlayPauseStateChanged); - events.off(player, "unpause", this._onPlayPauseStateChanged); - events.off(player, "timeupdate", this._onTimeUpdate); - events.off(player, "playing", this._onPlaying); - events.off(player, "waiting", this._onWaiting); + events.off(player, 'unpause', this._onPlayerUnpause); + events.off(player, 'pause', this._onPlayerPause); + events.off(player, 'timeupdate', this._onTimeUpdate); + events.off(player, 'playing', this._onPlaying); + events.off(player, 'waiting', this._onWaiting); + // Restore player original PlaybackRate value if (this.playbackRateSupported) { - player.setPlaybackRate(1); + player.setPlaybackRate(this.localPlayerPlaybackRate); + this.localPlayerPlaybackRate = 1.0; } this.currentPlayer = null; this.playbackRateSupported = false; @@ -262,8 +315,7 @@ class SyncplayManager { }); break; case 'GroupJoined': - const enabledAt = new Date(cmd.Data); - this.enableSyncplay(apiClient, enabledAt, true); + this.enableSyncplay(apiClient, new Date(cmd.Data), true); break; case 'NotInGroup': case 'GroupLeft': @@ -274,28 +326,28 @@ class SyncplayManager { text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) }); break; - case 'GroupNotJoined': + case 'GroupDoesNotExist': toast({ - text: globalize.translate('MessageSyncplayGroupNotJoined', cmd.Data) + text: globalize.translate('MessageSyncplayGroupDoesNotExist') }); break; case 'CreateGroupDenied': toast({ - text: globalize.translate('MessageSyncplayCreateGroupDenied', cmd.Data) + text: globalize.translate('MessageSyncplayCreateGroupDenied') }); break; case 'JoinGroupDenied': toast({ - text: globalize.translate('MessageSyncplayJoinGroupDenied', cmd.Data) + text: globalize.translate('MessageSyncplayJoinGroupDenied') }); break; case 'LibraryAccessDenied': toast({ - text: globalize.translate('MessageSyncplayLibraryAccessDenied', cmd.Data) + text: globalize.translate('MessageSyncplayLibraryAccessDenied') }); break; default: - console.error('processSyncplayGroupUpdate does not recognize: ' + cmd.Type); + console.error('processSyncplayGroupUpdate: command is not recognised: ' + cmd.Type); break; } } @@ -309,12 +361,12 @@ class SyncplayManager { if (cmd === null) return; if (!this.isSyncplayEnabled()) { - console.debug("Syncplay processCommand: ignoring command", cmd); + console.debug('Syncplay processCommand: SyncPlay not enabled, ignoring command', cmd); return; } if (!this.syncplayReady) { - console.debug("Syncplay processCommand: queued command", cmd); + console.debug('Syncplay processCommand: SyncPlay not ready, queued command', cmd); this.queuedCommand = cmd; return; } @@ -323,7 +375,7 @@ class SyncplayManager { cmd.EmittedAt = new Date(cmd.EmitttedAt); if (cmd.EmitttedAt < this.syncplayEnabledAt) { - console.debug("Syncplay processCommand: ignoring old command", cmd); + console.debug('Syncplay processCommand: ignoring old command', cmd); return; } @@ -333,12 +385,12 @@ class SyncplayManager { this.lastCommand.PositionTicks === cmd.PositionTicks && this.Command === cmd.Command ) { - console.debug("Syncplay processCommand: ignoring duplicate command", cmd); + console.debug('Syncplay processCommand: ignoring duplicate command', cmd); return; } this.lastCommand = cmd; - console.log("Syncplay will", cmd.Command, "at", cmd.When, "PositionTicks", cmd.PositionTicks); + console.log('Syncplay will', cmd.Command, 'at', cmd.When, 'PositionTicks', cmd.PositionTicks); switch (cmd.Command) { case 'Play': @@ -351,7 +403,7 @@ class SyncplayManager { this.scheduleSeek(cmd.When, cmd.PositionTicks); break; default: - console.error('processSyncplayCommand does not recognize: ' + cmd.Type); + console.error('processCommand: command is not recognised: ' + cmd.Type); break; } } @@ -363,7 +415,7 @@ class SyncplayManager { * @param {Object} sessionData Info about the content to load. */ prepareSession (apiClient, groupId, sessionData) { - var serverId = apiClient.serverInfo().Id; + const serverId = apiClient.serverInfo().Id; playbackManager.play({ ids: sessionData.ItemIds, startPositionTicks: sessionData.StartPositionTicks, @@ -373,14 +425,12 @@ class SyncplayManager { startIndex: sessionData.StartIndex, serverId: serverId }).then(() => { - waitForEvent(this, "PlayerChange").then(() => { - playbackManager.pause(); + waitForEventOnce(this, 'playbackstart', WaitForEventDefaultTimeout).then(() => { var sessionId = getActivePlayerId(); if (!sessionId) { - console.error("Missing sessionId!"); + console.error('Missing sessionId!'); toast({ - // TODO: translate - text: "Failed to enable Syncplay! Missing session id." + text: globalize.translate('MessageSyncplayErrorMissingSession') }); return; } @@ -390,21 +440,38 @@ class SyncplayManager { const playState = playbackManager.getPlayerState(); playingItemId = playState.NowPlayingItem.Id; } catch (error) { - playingItemId = ""; + playingItemId = ''; } - // Sometimes JoinGroup fails, maybe because server hasn't been updated yet - setTimeout(() => { - apiClient.sendSyncplayCommand(sessionId, "JoinGroup", { + // Make sure the server has received the player state + waitForEventOnce(playbackManager, 'reportplayback', WaitForEventDefaultTimeout).then((success) => { + this.localPause(); + if (!success) { + console.warning('Error reporting playback state to server. Joining group will fail.'); + } + apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', { GroupId: groupId, PlayingItemId: playingItemId }); - }, 500); + }).catch(() => { + console.error('Timed out while waiting for `reportplayback` event!'); + toast({ + text: globalize.translate('MessageSyncplayErrorMedia') + }); + return; + }); + }).catch(() => { + console.error('Timed out while waiting for `playbackstart` event!'); + if (!this.isSyncplayEnabled()) { + toast({ + text: globalize.translate('MessageSyncplayErrorMedia') + }); + } + return; }); }).catch((error) => { console.error(error); toast({ - // TODO: translate - text: "Failed to enable Syncplay! Media error." + text: globalize.translate('MessageSyncplayErrorMedia') }); }); } @@ -418,12 +485,13 @@ class SyncplayManager { enableSyncplay (apiClient, enabledAt, showMessage = false) { this.syncplayEnabledAt = enabledAt; this.injectPlaybackManager(); - events.trigger(this, "SyncplayEnabled", [true]); + events.trigger(this, 'enabled', [true]); - waitForEvent(this, "SyncplayReady").then(() => { + waitForEventOnce(this, 'ready').then(() => { this.processCommand(this.queuedCommand, apiClient); this.queuedCommand = null; }); + this.syncplayReady = false; this.notifySyncplayReady = true; @@ -446,7 +514,7 @@ class SyncplayManager { this.lastCommand = null; this.queuedCommand = null; this.syncEnabled = false; - events.trigger(this, "SyncplayEnabled", [false]); + events.trigger(this, 'enabled', [false]); this.restorePlaybackManager(); if (showMessage) { @@ -461,7 +529,7 @@ class SyncplayManager { * @returns {boolean} _true_ if user joined a group, _false_ otherwise. */ isSyncplayEnabled () { - return this.syncplayEnabledAt !== null ? true : false; + return this.syncplayEnabledAt !== null; } /** @@ -471,15 +539,15 @@ class SyncplayManager { */ schedulePlay (playAtTime, positionTicks) { this.clearScheduledCommand(); - var currentTime = new Date(); - var playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime); + const currentTime = new Date(); + const playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime); if (playAtTimeLocal > currentTime) { - var playTimeout = playAtTimeLocal - currentTime; - playbackManager.syncplay_seek(positionTicks); + const playTimeout = playAtTimeLocal - currentTime; + this.localSeek(positionTicks); this.scheduledCommand = setTimeout(() => { - playbackManager.syncplay_unpause(); + this.localUnpause(); this.syncTimeout = setTimeout(() => { this.syncEnabled = true; @@ -487,12 +555,14 @@ class SyncplayManager { }, playTimeout); - // console.debug("Syncplay schedulePlay:", playTimeout); + console.debug('Scheduled play in', playTimeout / 1000.0, 'seconds.'); } else { // Group playback already started - var serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; - playbackManager.syncplay_unpause(); - playbackManager.syncplay_seek(serverPositionTicks); + const serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000; + waitForEventOnce(this, 'unpause').then(() => { + this.localSeek(serverPositionTicks); + }); + this.localUnpause(); this.syncTimeout = setTimeout(() => { this.syncEnabled = true; @@ -507,24 +577,26 @@ class SyncplayManager { */ schedulePause (pauseAtTime, positionTicks) { this.clearScheduledCommand(); - var currentTime = new Date(); - var pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime); + const currentTime = new Date(); + const pauseAtTimeLocal = timeSyncManager.serverDateToLocal(pauseAtTime); + + const callback = () => { + waitForEventOnce(this, 'pause', WaitForPlayerEventTimeout).then(() => { + this.localSeek(positionTicks); + }).catch(() => { + // Player was already paused, seeking + this.localSeek(positionTicks); + }); + this.localPause(); + }; if (pauseAtTimeLocal > currentTime) { - var pauseTimeout = pauseAtTimeLocal - currentTime; + const pauseTimeout = pauseAtTimeLocal - currentTime; + this.scheduledCommand = setTimeout(callback, pauseTimeout); - this.scheduledCommand = setTimeout(() => { - playbackManager.syncplay_pause(); - setTimeout(() => { - playbackManager.syncplay_seek(positionTicks); - }, 800); - - }, pauseTimeout); + console.debug('Scheduled pause in', pauseTimeout / 1000.0, 'seconds.'); } else { - playbackManager.syncplay_pause(); - setTimeout(() => { - playbackManager.syncplay_seek(positionTicks); - }, 800); + callback(); } } @@ -558,10 +630,10 @@ class SyncplayManager { if (!this.isSyncplayEnabled()) return; if (playbackManager.syncplayEnabled) return; - // TODO: make this less hacky - playbackManager.syncplay_unpause = playbackManager.unpause; - playbackManager.syncplay_pause = playbackManager.pause; - playbackManager.syncplay_seek = playbackManager.seek; + // TODO: make this less hacky + playbackManager._localUnpause = playbackManager.unpause; + playbackManager._localPause = playbackManager.pause; + playbackManager._localSeek = playbackManager.seek; playbackManager.unpause = this.playRequest; playbackManager.pause = this.pauseRequest; @@ -576,9 +648,9 @@ class SyncplayManager { if (this.isSyncplayEnabled()) return; if (!playbackManager.syncplayEnabled) return; - playbackManager.unpause = playbackManager.syncplay_unpause; - playbackManager.pause = playbackManager.syncplay_pause; - playbackManager.seek = playbackManager.syncplay_seek; + playbackManager.unpause = playbackManager._localUnpause; + playbackManager.pause = playbackManager._localPause; + playbackManager.seek = playbackManager._localSeek; playbackManager.syncplayEnabled = false; } @@ -588,7 +660,7 @@ class SyncplayManager { playRequest (player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "PlayRequest"); + apiClient.sendSyncplayCommand(sessionId, 'PlayRequest'); } /** @@ -597,9 +669,9 @@ class SyncplayManager { pauseRequest (player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "PauseRequest"); + apiClient.sendSyncplayCommand(sessionId, 'PauseRequest'); // Pause locally as well, to give the user some little control - playbackManager.syncplay_pause(); + playbackManager._localUnpause(player); } /** @@ -608,14 +680,47 @@ class SyncplayManager { seekRequest (PositionTicks, player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, "SeekRequest", { + apiClient.sendSyncplayCommand(sessionId, 'SeekRequest', { PositionTicks: PositionTicks }); } + /** + * Calls original PlaybackManager's unpause method. + */ + localUnpause(player) { + if (playbackManager.syncplayEnabled) { + playbackManager._localUnpause(player); + } else { + playbackManager.unpause(player); + } + } + + /** + * Calls original PlaybackManager's pause method. + */ + localPause(player) { + if (playbackManager.syncplayEnabled) { + playbackManager._localPause(player); + } else { + playbackManager.pause(player); + } + } + + /** + * Calls original PlaybackManager's seek method. + */ + localSeek(PositionTicks, player) { + if (playbackManager.syncplayEnabled) { + playbackManager._localSeek(PositionTicks, player); + } else { + playbackManager.seek(PositionTicks, player); + } + } + /** * Attempts to sync playback time with estimated server time. - * + * * When sync is enabled, the following will be checked: * - check if local playback time is close enough to the server playback time * If it is not, then a playback time sync will be attempted. @@ -637,18 +742,15 @@ class SyncplayManager { const playAtTime = this.lastCommand.When; - const CurrentPositionTicks = playbackManager.currentTime(); + const currentPositionTicks = playbackManager.currentTime(); // Estimate PositionTicks on server - const ServerPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000; + const serverPositionTicks = this.lastCommand.PositionTicks + ((currentTime - playAtTime) + this.timeOffsetWithServer) * 10000; // Measure delay that needs to be recovered // diff might be caused by the player internally starting the playback - const diff = ServerPositionTicks - CurrentPositionTicks; - const diffMillis = diff / 10000; + const diffMillis = (serverPositionTicks - currentPositionTicks) / 10000.0; this.playbackDiffMillis = diffMillis; - // console.debug("Syncplay onTimeUpdate", diffMillis, CurrentPositionTicks, ServerPositionTicks); - if (this.syncEnabled) { const absDiffMillis = Math.abs(diffMillis); // TODO: SpeedToSync sounds bad on songs @@ -664,7 +766,7 @@ class SyncplayManager { this.currentPlayer.setPlaybackRate(speed); this.syncEnabled = false; this.syncAttempts++; - this.showSyncIcon("SpeedToSync (x" + speed + ")"); + this.showSyncIcon('SpeedToSync (x' + speed + ')'); this.syncTimeout = setTimeout(() => { this.currentPlayer.setPlaybackRate(1); @@ -675,13 +777,13 @@ class SyncplayManager { // Disable SkipToSync if it keeps failing if (this.syncAttempts > MaxAttemptsSync) { this.syncEnabled = false; - this.showSyncIcon("Sync disabled (too many attempts)"); + this.showSyncIcon('Sync disabled (too many attempts)'); } // SkipToSync method - playbackManager.syncplay_seek(ServerPositionTicks); + this.localSeek(serverPositionTicks); this.syncEnabled = false; this.syncAttempts++; - this.showSyncIcon("SkipToSync (" + this.syncAttempts + ")"); + this.showSyncIcon('SkipToSync (' + this.syncAttempts + ')'); this.syncTimeout = setTimeout(() => { this.syncEnabled = true; @@ -690,7 +792,7 @@ class SyncplayManager { } else { // Playback is synced if (this.syncAttempts > 0) { - // console.debug("Playback has been synced after", this.syncAttempts, "attempts."); + console.debug('Playback has been synced after', this.syncAttempts, 'attempts.'); } this.syncAttempts = 0; } @@ -706,7 +808,7 @@ class SyncplayManager { TimeOffset: this.timeOffsetWithServer, PlaybackDiff: this.playbackDiffMillis, SyncMethod: this.syncMethod - } + }; } /** @@ -714,15 +816,15 @@ class SyncplayManager { */ showSyncIcon (syncMethod) { this.syncMethod = syncMethod; - events.trigger(this, "SyncplayError", [true]); + events.trigger(this, 'syncing', [true, this.syncMethod]); } /** * Emits an event to clear the Syncplay status icon. */ clearSyncIcon () { - this.syncMethod = "None"; - events.trigger(this, "SyncplayError", [false]); + this.syncMethod = 'None'; + events.trigger(this, 'syncing', [false, this.syncMethod]); } /** diff --git a/src/components/syncplay/timeSyncManager.js b/src/components/syncplay/timeSyncManager.js index 74c98820c2..ca92939576 100644 --- a/src/components/syncplay/timeSyncManager.js +++ b/src/components/syncplay/timeSyncManager.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module that manages time syncing with server. * @module components/syncplay/timeSyncManager @@ -22,30 +20,30 @@ const GreedyPingCount = 3; class Measurement { /** * Creates a new measurement. - * @param {Date} t0 Client's timestamp of the request transmission - * @param {Date} t1 Server's timestamp of the request reception - * @param {Date} t2 Server's timestamp of the response transmission - * @param {Date} t3 Client's timestamp of the response reception + * @param {Date} requestSent Client's timestamp of the request transmission + * @param {Date} requestReceived Server's timestamp of the request reception + * @param {Date} responseSent Server's timestamp of the response transmission + * @param {Date} responseReceived Client's timestamp of the response reception */ - constructor(t0, t1, t2, t3) { - this.t0 = t0.getTime(); - this.t1 = t1.getTime(); - this.t2 = t2.getTime(); - this.t3 = t3.getTime(); + constructor(requestSent, requestReceived, responseSent, responseReceived) { + this.requestSent = requestSent.getTime(); + this.requestReceived = requestReceived.getTime(); + this.responseSent = responseSent.getTime(); + this.responseReceived = responseReceived.getTime(); } /** * Time offset from server. */ getOffset () { - return ((this.t1 - this.t0) + (this.t2 - this.t3)) / 2; + return ((this.requestReceived - this.requestSent) + (this.responseSent - this.responseReceived)) / 2; } /** * Get round-trip delay. */ getDelay () { - return (this.t3 - this.t0) - (this.t2 - this.t1); + return (this.responseReceived - this.requestSent) - (this.responseSent - this.requestReceived); } /** @@ -76,7 +74,7 @@ class TimeSyncManager { * @returns {boolean} _true_ if a measurement has been done, _false_ otherwise. */ isReady() { - return this.measurement ? true : false; + return !!this.measurement; } /** @@ -119,14 +117,14 @@ class TimeSyncManager { this.poller = setTimeout(() => { this.poller = null; const apiClient = connectionManager.currentApiClient(); - const t0 = new Date(); // pingStartTime + const requestSent = new Date(); apiClient.getServerTime().then((response) => { - const t3 = new Date(); // pingEndTime + const responseReceived = new Date(); response.json().then((data) => { - const t1 = new Date(data.RequestReceptionTime); // request received - const t2 = new Date(data.ResponseTransmissionTime); // response sent + const requestReceived = new Date(data.RequestReceptionTime); + const responseSent = new Date(data.ResponseTransmissionTime); - const measurement = new Measurement(t0, t1, t2, t3); + const measurement = new Measurement(requestSent, requestReceived, responseSent, responseReceived); this.updateTimeOffset(measurement); // Avoid overloading server @@ -136,11 +134,11 @@ class TimeSyncManager { this.pings++; } - events.trigger(this, "Update", [this.getTimeOffset(), this.getPing()]); + events.trigger(this, 'update', [null, this.getTimeOffset(), this.getPing()]); }); }).catch((error) => { console.error(error); - events.trigger(this, "Error", [error]); + events.trigger(this, 'update', [error, null, null]); }).finally(() => { this.requestPing(); }); diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js index 1daa200b8b..3113031ea8 100644 --- a/src/scripts/librarymenu.js +++ b/src/scripts/librarymenu.js @@ -1,4 +1,4 @@ -define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', 'viewManager', 'libraryBrowser', 'appRouter', 'apphost', 'playbackManager', 'syncplayManager', 'browser', 'globalize', 'scripts/imagehelper', 'paper-icon-button-light', 'material-icons', 'scrollStyles', 'flexStyles'], function (dom, layoutManager, inputManager, connectionManager, events, viewManager, libraryBrowser, appRouter, appHost, playbackManager, syncplayManager, browser, globalize, imageHelper) { +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'; function renderHeader() { @@ -89,12 +89,13 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' var policy = user.Policy ? user.Policy : user.localUser.Policy; - if (headerSyncButton && policy && policy.SyncplayAccess !== "None") { - headerSyncButton.classList.remove("hide"); + if (headerSyncButton && policy && policy.SyncplayAccess !== 'None') { + headerSyncButton.classList.remove('hide'); } } else { headerHomeButton.classList.add('hide'); headerCastButton.classList.add('hide'); + headerSyncButton.classList.add('hide'); if (headerSearchButton) { headerSearchButton.classList.add('hide'); @@ -188,27 +189,26 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' function onSyncButtonClicked() { var btn = this; - - require(["groupSelectionMenu"], function (groupSelectionMenu) { - groupSelectionMenu.show(btn); - }); + groupSelectionMenu.show(btn); } - function updateSyncplayIcon(event, enabled) { - var icon = headerSyncButton.querySelector("i"); + function onSyncplayEnabled(event, enabled) { + var icon = headerSyncButton.querySelector('span'); + icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (enabled) { - icon.innerHTML = "sync"; + icon.classList.add('sync'); } else { - icon.innerHTML = "sync_disabled"; + icon.classList.add('sync_disabled'); } } - function updateSyncplayErrorIcon(event, show_error) { - var icon = headerSyncButton.querySelector("i"); - if (show_error) { - icon.innerHTML = "sync_problem"; + function onSyncplaySyncing(event, is_syncing, syncMethod) { + var icon = headerSyncButton.querySelector('span'); + icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); + if (is_syncing) { + icon.classList.add('sync_problem'); } else { - icon.innerHTML = "sync"; + icon.classList.add('sync'); } } @@ -967,8 +967,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' updateUserInHeader(); }); events.on(playbackManager, 'playerchange', updateCastIcon); - events.on(syncplayManager, 'SyncplayEnabled', updateSyncplayIcon); - events.on(syncplayManager, 'SyncplayError', updateSyncplayErrorIcon); + events.on(syncplayManager, 'enabled', onSyncplayEnabled); + events.on(syncplayManager, 'syncing', onSyncplaySyncing); loadNavDrawer(); return LibraryMenu; }); diff --git a/src/scripts/site.js b/src/scripts/site.js index ecfeb27349..a07062ab3c 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -316,7 +316,7 @@ var AppInfo = {}; function returnDefault(obj) { if (obj.default === null) { - throw new Error("Object has no default!"); + throw new Error('Object has no default!'); } return obj.default; } @@ -825,7 +825,8 @@ var AppInfo = {}; define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); define('timeSyncManager', [componentsPath + '/syncplay/timeSyncManager'], returnDefault); - define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault); + define('groupSelectionMenu', [componentsPath + '/syncplay/groupSelectionMenu'], returnFirstDependency); + define('syncplayManager', [componentsPath + '/syncplay/syncplayManager'], returnDefault); define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index fb72e8881c..4759b670d8 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -855,7 +855,8 @@ "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelSubtitles": "Subtitles", "LabelSupportedMediaTypes": "Supported Media Types:", - "LabelSyncplayTimeOffset": "Time offset with server:", + "LabelSyncplayTimeOffset": "Time offset with the server:", + "MillisecondsUnit": "ms", "LabelSyncplayPlaybackDiff": "Playback time difference:", "LabelSyncplaySyncMethod": "Sync method:", "LabelSyncplayNewGroup": "New group", @@ -1031,15 +1032,19 @@ "MessageYouHaveVersionInstalled": "You currently have version {0} installed.", "MessageSyncplayEnabled": "Syncplay enabled.", "MessageSyncplayDisabled": "Syncplay disabled.", - "MessageSyncplayUserJoined": "{0} joined group.", - "MessageSyncplayUserLeft": "{0} left group.", + "MessageSyncplayUserJoined": "{0} has joined the group.", + "MessageSyncplayUserLeft": "{0} has left the group.", "MessageSyncplayGroupWait": "{0} is buffering...", "MessageSyncplayNoGroupsAvailable": "No groups available. Start playing something first.", "MessageSyncplayPlaybackPermissionRequired": "Playback permission required.", - "MessageSyncplayGroupNotJoined": "Failed to join requested group.", + "MessageSyncplayGroupDoesNotExist": "Failed to join group because it does not exist.", "MessageSyncplayCreateGroupDenied": "Permission required to create a group.", "MessageSyncplayJoinGroupDenied": "Permission required to use Syncplay.", "MessageSyncplayLibraryAccessDenied": "Access to this content is restricted.", + "MessageSyncplayErrorAccessingGroups": "An error occurred while accessing groups list.", + "MessageSyncplayErrorNoActivePlayer": "No active player found. Syncplay has been disabled.", + "MessageSyncplayErrorMissingSession": "Failed to enable Syncplay! Missing session.", + "MessageSyncplayErrorMedia": "Failed to enable Syncplay! Media error.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", diff --git a/webpack.dev.js b/webpack.dev.js index d8879fe808..b869147750 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -46,8 +46,8 @@ module.exports = merge(common, { ] }, { - test: /\.(wav)$/i, - use: ["file-loader"] + test: /\.(mp3)$/i, + use: ['file-loader'] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index cc4c57b9f4..2f5315ea75 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -39,8 +39,8 @@ module.exports = merge(common, { ] }, { - test: /\.(wav)$/i, - use: ["file-loader"] + test: /\.(mp3)$/i, + use: ['file-loader'] } ] } From 36d097291ee9ab1cfb841d2a5752e7f9026a7798 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 6 May 2020 23:41:54 +0200 Subject: [PATCH 013/181] Rename Syncplay to SyncPlay --- package.json | 2 +- src/components/playerstats/playerstats.js | 18 +-- src/components/serverNotifications.js | 10 +- src/components/syncplay/groupSelectionMenu.js | 56 +++---- ...{syncplayManager.js => syncPlayManager.js} | 150 +++++++++--------- src/controllers/useredit.js | 4 +- src/scripts/librarymenu.js | 12 +- src/scripts/site.js | 2 +- src/strings/en-us.json | 58 +++---- src/useredit.html | 12 +- 10 files changed, 162 insertions(+), 162 deletions(-) rename src/components/syncplay/{syncplayManager.js => syncPlayManager.js} (86%) diff --git a/package.json b/package.json index 33a7d2b004..1beb164990 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "src/components/syncplay/playbackPermissionManager.js", "src/components/syncplay/groupSelectionMenu.js", "src/components/syncplay/timeSyncManager.js", - "src/components/syncplay/syncplayManager.js", + "src/components/syncplay/syncPlayManager.js", "src/scripts/dfnshelper.js", "src/scripts/dom.js", "src/scripts/filesystem.js", diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 07fcd7070a..a65baf3553 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -1,4 +1,4 @@ -define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplayManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, syncplayManager, playMethodHelper, layoutManager, serverNotifications) { +define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncPlayManager', 'playMethodHelper', 'layoutManager', 'serverNotifications', 'paper-icon-button-light', 'css!./playerstats'], function (events, globalize, playbackManager, connectionManager, syncPlayManager, playMethodHelper, layoutManager, serverNotifications) { 'use strict'; function init(instance) { @@ -327,22 +327,22 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay return sessionStats; } - function getSyncplayStats() { + function getSyncPlayStats() { var syncStats = []; - var stats = syncplayManager.getStats(); + var stats = syncPlayManager.getStats(); syncStats.push({ - label: globalize.translate('LabelSyncplayTimeOffset'), + label: globalize.translate('LabelSyncPlayTimeOffset'), value: stats.TimeOffset + globalize.translate('MillisecondsUnit') }); syncStats.push({ - label: globalize.translate('LabelSyncplayPlaybackDiff'), + label: globalize.translate('LabelSyncPlayPlaybackDiff'), value: stats.PlaybackDiff + globalize.translate('MillisecondsUnit') }); syncStats.push({ - label: globalize.translate('LabelSyncplaySyncMethod'), + label: globalize.translate('LabelSyncPlaySyncMethod'), value: stats.SyncMethod }); @@ -405,10 +405,10 @@ define(['events', 'globalize', 'playbackManager', 'connectionManager', 'syncplay name: 'Original Media Info' }); - if (syncplayManager.isSyncplayEnabled()) { + if (syncPlayManager.isSyncPlayEnabled()) { categories.push({ - stats: getSyncplayStats(), - name: 'Syncplay Info' + stats: getSyncPlayStats(), + name: 'SyncPlay Info' }); } diff --git a/src/components/serverNotifications.js b/src/components/serverNotifications.js index 876c3f7e79..2553c284f0 100644 --- a/src/components/serverNotifications.js +++ b/src/components/serverNotifications.js @@ -1,4 +1,4 @@ -define(['connectionManager', 'playbackManager', 'syncplayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncplayManager, events, inputManager, focusManager, appRouter) { +define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'inputManager', 'focusManager', 'appRouter'], function (connectionManager, playbackManager, syncPlayManager, events, inputManager, focusManager, appRouter) { 'use strict'; var serverNotifications = {}; @@ -187,10 +187,10 @@ define(['connectionManager', 'playbackManager', 'syncplayManager', 'events', 'in events.trigger(serverNotifications, 'UserDataChanged', [apiClient, msg.Data.UserDataList[i]]); } } - } else if (msg.MessageType === 'SyncplayCommand') { - syncplayManager.processCommand(msg.Data, apiClient); - } else if (msg.MessageType === 'SyncplayGroupUpdate') { - syncplayManager.processGroupUpdate(msg.Data, apiClient); + } else if (msg.MessageType === 'SyncPlayCommand') { + syncPlayManager.processCommand(msg.Data, apiClient); + } else if (msg.MessageType === 'SyncPlayGroupUpdate') { + syncPlayManager.processGroupUpdate(msg.Data, apiClient); } else { events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]); } diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index af08f92776..067100ad73 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -1,7 +1,7 @@ import events from 'events'; import connectionManager from 'connectionManager'; import playbackManager from 'playbackManager'; -import syncplayManager from 'syncplayManager'; +import syncPlayManager from 'syncPlayManager'; import loading from 'loading'; import toast from 'toast'; import actionsheet from 'actionsheet'; @@ -37,7 +37,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { console.debug('No item is currently playing.'); } - apiClient.sendSyncplayCommand(sessionId, 'ListGroups').then(function (response) { + apiClient.sendSyncPlayCommand(sessionId, 'ListGroups').then(function (response) { response.json().then(function (groups) { var menuItems = groups.map(function (group) { return { @@ -49,24 +49,24 @@ function showNewJoinGroupSelection (button, user, apiClient) { }; }); - if (inSession && policy.SyncplayAccess === 'CreateAndJoinGroups') { + if (inSession && policy.SyncPlayAccess === 'CreateAndJoinGroups') { menuItems.push({ - name: globalize.translate('LabelSyncplayNewGroup'), + name: globalize.translate('LabelSyncPlayNewGroup'), icon: 'add', id: 'new-group', selected: true, - secondaryText: globalize.translate('LabelSyncplayNewGroupDescription') + secondaryText: globalize.translate('LabelSyncPlayNewGroupDescription') }); } if (menuItems.length === 0) { - if (inSession && policy.SyncplayAccess === 'JoinGroups') { + if (inSession && policy.SyncPlayAccess === 'JoinGroups') { toast({ - text: globalize.translate('MessageSyncplayCreateGroupDenied') + text: globalize.translate('MessageSyncPlayCreateGroupDenied') }); } else { toast({ - text: globalize.translate('MessageSyncplayNoGroupsAvailable') + text: globalize.translate('MessageSyncPlayNoGroupsAvailable') }); } loading.hide(); @@ -74,7 +74,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { } var menuOptions = { - title: globalize.translate('HeaderSyncplaySelectGroup'), + title: globalize.translate('HeaderSyncPlaySelectGroup'), items: menuItems, positionTo: button, resolveOnClick: true, @@ -83,15 +83,15 @@ function showNewJoinGroupSelection (button, user, apiClient) { actionsheet.show(menuOptions).then(function (id) { if (id == 'new-group') { - apiClient.sendSyncplayCommand(sessionId, 'NewGroup'); + apiClient.sendSyncPlayCommand(sessionId, 'NewGroup'); } else { - apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', { + apiClient.sendSyncPlayCommand(sessionId, 'JoinGroup', { GroupId: id, PlayingItemId: playingItemId }); } }).catch((error) => { - console.error('Syncplay: unexpected error listing groups:', error); + console.error('SyncPlay: unexpected error listing groups:', error); }); loading.hide(); @@ -100,7 +100,7 @@ function showNewJoinGroupSelection (button, user, apiClient) { console.error(error); loading.hide(); toast({ - text: globalize.translate('MessageSyncplayErrorAccessingGroups') + text: globalize.translate('MessageSyncPlayErrorAccessingGroups') }); }); } @@ -114,24 +114,24 @@ function showNewJoinGroupSelection (button, user, apiClient) { function showLeaveGroupSelection (button, user, apiClient) { const sessionId = getActivePlayerId(); if (!sessionId) { - syncplayManager.signalError(); + syncPlayManager.signalError(); toast({ - text: globalize.translate('MessageSyncplayErrorNoActivePlayer') + text: globalize.translate('MessageSyncPlayErrorNoActivePlayer') }); showNewJoinGroupSelection(button, user, apiClient); return; } const menuItems = [{ - name: globalize.translate('LabelSyncplayLeaveGroup'), + name: globalize.translate('LabelSyncPlayLeaveGroup'), icon: 'meeting_room', id: 'leave-group', selected: true, - secondaryText: globalize.translate('LabelSyncplayLeaveGroupDescription') + secondaryText: globalize.translate('LabelSyncPlayLeaveGroupDescription') }]; var menuOptions = { - title: globalize.translate('HeaderSyncplayEnabled'), + title: globalize.translate('HeaderSyncPlayEnabled'), items: menuItems, positionTo: button, resolveOnClick: true, @@ -140,23 +140,23 @@ function showLeaveGroupSelection (button, user, apiClient) { actionsheet.show(menuOptions).then(function (id) { if (id == 'leave-group') { - apiClient.sendSyncplayCommand(sessionId, 'LeaveGroup'); + apiClient.sendSyncPlayCommand(sessionId, 'LeaveGroup'); } }).catch((error) => { - console.error('Syncplay: unexpected error showing group menu:', error); + console.error('SyncPlay: unexpected error showing group menu:', error); }); loading.hide(); } -// Register to Syncplay events -let syncplayEnabled = false; -events.on(syncplayManager, 'enabled', function (e, enabled) { - syncplayEnabled = enabled; +// Register to SyncPlay events +let syncPlayEnabled = false; +events.on(syncPlayManager, 'enabled', function (e, enabled) { + syncPlayEnabled = enabled; }); /** - * Shows a menu to handle Syncplay groups. + * Shows a menu to handle SyncPlay groups. * @param {HTMLElement} button - Element where to place the menu. */ export function show (button) { @@ -168,13 +168,13 @@ export function show (button) { }).catch((error) => { console.error('Playback not allowed!', error); toast({ - text: globalize.translate('MessageSyncplayPlaybackPermissionRequired') + text: globalize.translate('MessageSyncPlayPlaybackPermissionRequired') }); }); const apiClient = connectionManager.currentApiClient(); connectionManager.user(apiClient).then((user) => { - if (syncplayEnabled) { + if (syncPlayEnabled) { showLeaveGroupSelection(button, user, apiClient); } else { showNewJoinGroupSelection(button, user, apiClient); @@ -183,7 +183,7 @@ export function show (button) { console.error(error); loading.hide(); toast({ - text: globalize.translate('MessageSyncplayNoGroupsAvailable') + text: globalize.translate('MessageSyncPlayNoGroupsAvailable') }); }); } diff --git a/src/components/syncplay/syncplayManager.js b/src/components/syncplay/syncPlayManager.js similarity index 86% rename from src/components/syncplay/syncplayManager.js rename to src/components/syncplay/syncPlayManager.js index b5694c88ff..f5c9ac446d 100644 --- a/src/components/syncplay/syncplayManager.js +++ b/src/components/syncplay/syncPlayManager.js @@ -1,6 +1,6 @@ /** - * Module that manages the Syncplay feature. - * @module components/syncplay/syncplayManager + * Module that manages the SyncPlay feature. + * @module components/syncplay/syncPlayManager */ import events from 'events'; @@ -62,9 +62,9 @@ const WaitForEventDefaultTimeout = 30000; // milliseconds const WaitForPlayerEventTimeout = 500; // milliseconds /** - * Class that manages the Syncplay feature. + * Class that manages the SyncPlay feature. */ -class SyncplayManager { +class SyncPlayManager { constructor() { this.playbackRateSupported = false; this.syncEnabled = false; @@ -80,8 +80,8 @@ class SyncplayManager { this.currentPlayer = null; this.localPlayerPlaybackRate = 1.0; // used to restore user PlaybackRate - this.syncplayEnabledAt = null; // Server time of when Syncplay has been enabled - this.syncplayReady = false; // Syncplay is ready after first ping to server + this.syncPlayEnabledAt = null; // Server time of when SyncPlay has been enabled + this.syncPlayReady = false; // SyncPlay is ready after first ping to server this.lastCommand = null; this.queuedCommand = null; @@ -91,7 +91,7 @@ class SyncplayManager { this.timeOffsetWithServer = 0; // server time minus local time this.roundTripDuration = 0; - this.notifySyncplayReady = false; + this.notifySyncPlayReady = false; events.on(playbackManager, 'playbackstart', (player, state) => { this.onPlaybackStart(player, state); @@ -113,17 +113,17 @@ class SyncplayManager { events.on(timeSyncManager, 'update', (event, error, timeOffset, ping) => { if (error) { - console.debug('Syncplay, time update issue', error); + console.debug('SyncPlay, time update issue', error); return; } this.timeOffsetWithServer = timeOffset; this.roundTripDuration = ping * 2; - if (this.notifySyncplayReady) { - this.syncplayReady = true; + if (this.notifySyncPlayReady) { + this.syncPlayReady = true; events.trigger(this, 'ready'); - this.notifySyncplayReady = false; + this.notifySyncPlayReady = false; } // Report ping @@ -134,12 +134,12 @@ class SyncplayManager { if (!sessionId) { this.signalError(); toast({ - text: globalize.translate('MessageSyncplayErrorMissingSession') + text: globalize.translate('MessageSyncPlayErrorMissingSession') }); return; } - apiClient.sendSyncplayCommand(sessionId, 'UpdatePing', { + apiClient.sendSyncPlayCommand(sessionId, 'UpdatePing', { Ping: ping }); } @@ -158,8 +158,8 @@ class SyncplayManager { */ onPlaybackStop (stopInfo) { events.trigger(this, 'playbackstop', [stopInfo]); - if (this.isSyncplayEnabled()) { - this.disableSyncplay(false); + if (this.isSyncPlayEnabled()) { + this.disableSyncPlay(false); } } @@ -238,7 +238,7 @@ class SyncplayManager { // FIXME: the following are needed because the 'events' module // is changing the scope when executing the callbacks. // For instance, calling 'onPlayerUnpause' from the wrong scope breaks things because 'this' - // points to 'player' (the event emitter) instead of pointing to the SyncplayManager singleton. + // points to 'player' (the event emitter) instead of pointing to the SyncPlayManager singleton. const self = this; this._onPlayerUnpause = () => { self.onPlayerUnpause(); @@ -306,48 +306,48 @@ class SyncplayManager { break; case 'UserJoined': toast({ - text: globalize.translate('MessageSyncplayUserJoined', cmd.Data) + text: globalize.translate('MessageSyncPlayUserJoined', cmd.Data) }); break; case 'UserLeft': toast({ - text: globalize.translate('MessageSyncplayUserLeft', cmd.Data) + text: globalize.translate('MessageSyncPlayUserLeft', cmd.Data) }); break; case 'GroupJoined': - this.enableSyncplay(apiClient, new Date(cmd.Data), true); + this.enableSyncPlay(apiClient, new Date(cmd.Data), true); break; case 'NotInGroup': case 'GroupLeft': - this.disableSyncplay(true); + this.disableSyncPlay(true); break; case 'GroupWait': toast({ - text: globalize.translate('MessageSyncplayGroupWait', cmd.Data) + text: globalize.translate('MessageSyncPlayGroupWait', cmd.Data) }); break; case 'GroupDoesNotExist': toast({ - text: globalize.translate('MessageSyncplayGroupDoesNotExist') + text: globalize.translate('MessageSyncPlayGroupDoesNotExist') }); break; case 'CreateGroupDenied': toast({ - text: globalize.translate('MessageSyncplayCreateGroupDenied') + text: globalize.translate('MessageSyncPlayCreateGroupDenied') }); break; case 'JoinGroupDenied': toast({ - text: globalize.translate('MessageSyncplayJoinGroupDenied') + text: globalize.translate('MessageSyncPlayJoinGroupDenied') }); break; case 'LibraryAccessDenied': toast({ - text: globalize.translate('MessageSyncplayLibraryAccessDenied') + text: globalize.translate('MessageSyncPlayLibraryAccessDenied') }); break; default: - console.error('processSyncplayGroupUpdate: command is not recognised: ' + cmd.Type); + console.error('processSyncPlayGroupUpdate: command is not recognised: ' + cmd.Type); break; } } @@ -360,13 +360,13 @@ class SyncplayManager { processCommand (cmd, apiClient) { if (cmd === null) return; - if (!this.isSyncplayEnabled()) { - console.debug('Syncplay processCommand: SyncPlay not enabled, ignoring command', cmd); + if (!this.isSyncPlayEnabled()) { + console.debug('SyncPlay processCommand: SyncPlay not enabled, ignoring command', cmd); return; } - if (!this.syncplayReady) { - console.debug('Syncplay processCommand: SyncPlay not ready, queued command', cmd); + if (!this.syncPlayReady) { + console.debug('SyncPlay processCommand: SyncPlay not ready, queued command', cmd); this.queuedCommand = cmd; return; } @@ -374,8 +374,8 @@ class SyncplayManager { cmd.When = new Date(cmd.When); cmd.EmittedAt = new Date(cmd.EmitttedAt); - if (cmd.EmitttedAt < this.syncplayEnabledAt) { - console.debug('Syncplay processCommand: ignoring old command', cmd); + if (cmd.EmitttedAt < this.syncPlayEnabledAt) { + console.debug('SyncPlay processCommand: ignoring old command', cmd); return; } @@ -385,12 +385,12 @@ class SyncplayManager { this.lastCommand.PositionTicks === cmd.PositionTicks && this.Command === cmd.Command ) { - console.debug('Syncplay processCommand: ignoring duplicate command', cmd); + console.debug('SyncPlay processCommand: ignoring duplicate command', cmd); return; } this.lastCommand = cmd; - console.log('Syncplay will', cmd.Command, 'at', cmd.When, 'PositionTicks', cmd.PositionTicks); + console.log('SyncPlay will', cmd.Command, 'at', cmd.When, 'PositionTicks', cmd.PositionTicks); switch (cmd.Command) { case 'Play': @@ -430,7 +430,7 @@ class SyncplayManager { if (!sessionId) { console.error('Missing sessionId!'); toast({ - text: globalize.translate('MessageSyncplayErrorMissingSession') + text: globalize.translate('MessageSyncPlayErrorMissingSession') }); return; } @@ -448,22 +448,22 @@ class SyncplayManager { if (!success) { console.warning('Error reporting playback state to server. Joining group will fail.'); } - apiClient.sendSyncplayCommand(sessionId, 'JoinGroup', { + apiClient.sendSyncPlayCommand(sessionId, 'JoinGroup', { GroupId: groupId, PlayingItemId: playingItemId }); }).catch(() => { console.error('Timed out while waiting for `reportplayback` event!'); toast({ - text: globalize.translate('MessageSyncplayErrorMedia') + text: globalize.translate('MessageSyncPlayErrorMedia') }); return; }); }).catch(() => { console.error('Timed out while waiting for `playbackstart` event!'); - if (!this.isSyncplayEnabled()) { + if (!this.isSyncPlayEnabled()) { toast({ - text: globalize.translate('MessageSyncplayErrorMedia') + text: globalize.translate('MessageSyncPlayErrorMedia') }); } return; @@ -471,19 +471,19 @@ class SyncplayManager { }).catch((error) => { console.error(error); toast({ - text: globalize.translate('MessageSyncplayErrorMedia') + text: globalize.translate('MessageSyncPlayErrorMedia') }); }); } /** - * Enables Syncplay. + * Enables SyncPlay. * @param {Object} apiClient The ApiClient. - * @param {Date} enabledAt When Syncplay has been enabled. Server side date. + * @param {Date} enabledAt When SyncPlay has been enabled. Server side date. * @param {boolean} showMessage Display message. */ - enableSyncplay (apiClient, enabledAt, showMessage = false) { - this.syncplayEnabledAt = enabledAt; + enableSyncPlay (apiClient, enabledAt, showMessage = false) { + this.syncPlayEnabledAt = enabledAt; this.injectPlaybackManager(); events.trigger(this, 'enabled', [true]); @@ -492,25 +492,25 @@ class SyncplayManager { this.queuedCommand = null; }); - this.syncplayReady = false; - this.notifySyncplayReady = true; + this.syncPlayReady = false; + this.notifySyncPlayReady = true; timeSyncManager.forceUpdate(); if (showMessage) { toast({ - text: globalize.translate('MessageSyncplayEnabled') + text: globalize.translate('MessageSyncPlayEnabled') }); } } /** - * Disables Syncplay. + * Disables SyncPlay. * @param {boolean} showMessage Display message. */ - disableSyncplay (showMessage = false) { - this.syncplayEnabledAt = null; - this.syncplayReady = false; + disableSyncPlay (showMessage = false) { + this.syncPlayEnabledAt = null; + this.syncPlayReady = false; this.lastCommand = null; this.queuedCommand = null; this.syncEnabled = false; @@ -519,17 +519,17 @@ class SyncplayManager { if (showMessage) { toast({ - text: globalize.translate('MessageSyncplayDisabled') + text: globalize.translate('MessageSyncPlayDisabled') }); } } /** - * Gets Syncplay status. + * Gets SyncPlay status. * @returns {boolean} _true_ if user joined a group, _false_ otherwise. */ - isSyncplayEnabled () { - return this.syncplayEnabledAt !== null; + isSyncPlayEnabled () { + return this.syncPlayEnabledAt !== null; } /** @@ -627,8 +627,8 @@ class SyncplayManager { * Overrides some PlaybackManager's methods to intercept playback commands. */ injectPlaybackManager () { - if (!this.isSyncplayEnabled()) return; - if (playbackManager.syncplayEnabled) return; + if (!this.isSyncPlayEnabled()) return; + if (playbackManager.syncPlayEnabled) return; // TODO: make this less hacky playbackManager._localUnpause = playbackManager.unpause; @@ -638,20 +638,20 @@ class SyncplayManager { playbackManager.unpause = this.playRequest; playbackManager.pause = this.pauseRequest; playbackManager.seek = this.seekRequest; - playbackManager.syncplayEnabled = true; + playbackManager.syncPlayEnabled = true; } /** * Restores original PlaybackManager's methods. */ restorePlaybackManager () { - if (this.isSyncplayEnabled()) return; - if (!playbackManager.syncplayEnabled) return; + if (this.isSyncPlayEnabled()) return; + if (!playbackManager.syncPlayEnabled) return; playbackManager.unpause = playbackManager._localUnpause; playbackManager.pause = playbackManager._localPause; playbackManager.seek = playbackManager._localSeek; - playbackManager.syncplayEnabled = false; + playbackManager.syncPlayEnabled = false; } /** @@ -660,7 +660,7 @@ class SyncplayManager { playRequest (player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, 'PlayRequest'); + apiClient.sendSyncPlayCommand(sessionId, 'PlayRequest'); } /** @@ -669,7 +669,7 @@ class SyncplayManager { pauseRequest (player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, 'PauseRequest'); + apiClient.sendSyncPlayCommand(sessionId, 'PauseRequest'); // Pause locally as well, to give the user some little control playbackManager._localUnpause(player); } @@ -680,7 +680,7 @@ class SyncplayManager { seekRequest (PositionTicks, player) { var apiClient = connectionManager.currentApiClient(); var sessionId = getActivePlayerId(); - apiClient.sendSyncplayCommand(sessionId, 'SeekRequest', { + apiClient.sendSyncPlayCommand(sessionId, 'SeekRequest', { PositionTicks: PositionTicks }); } @@ -689,7 +689,7 @@ class SyncplayManager { * Calls original PlaybackManager's unpause method. */ localUnpause(player) { - if (playbackManager.syncplayEnabled) { + if (playbackManager.syncPlayEnabled) { playbackManager._localUnpause(player); } else { playbackManager.unpause(player); @@ -700,7 +700,7 @@ class SyncplayManager { * Calls original PlaybackManager's pause method. */ localPause(player) { - if (playbackManager.syncplayEnabled) { + if (playbackManager.syncPlayEnabled) { playbackManager._localPause(player); } else { playbackManager.pause(player); @@ -711,7 +711,7 @@ class SyncplayManager { * Calls original PlaybackManager's seek method. */ localSeek(PositionTicks, player) { - if (playbackManager.syncplayEnabled) { + if (playbackManager.syncPlayEnabled) { playbackManager._localSeek(PositionTicks, player); } else { playbackManager.seek(PositionTicks, player); @@ -800,8 +800,8 @@ class SyncplayManager { } /** - * Gets Syncplay stats. - * @returns {Object} The Syncplay stats. + * Gets SyncPlay stats. + * @returns {Object} The SyncPlay stats. */ getStats () { return { @@ -812,7 +812,7 @@ class SyncplayManager { } /** - * Emits an event to update the Syncplay status icon. + * Emits an event to update the SyncPlay status icon. */ showSyncIcon (syncMethod) { this.syncMethod = syncMethod; @@ -820,7 +820,7 @@ class SyncplayManager { } /** - * Emits an event to clear the Syncplay status icon. + * Emits an event to clear the SyncPlay status icon. */ clearSyncIcon () { this.syncMethod = 'None'; @@ -828,12 +828,12 @@ class SyncplayManager { } /** - * Signals an error state, which disables and resets Syncplay for a new session. + * Signals an error state, which disables and resets SyncPlay for a new session. */ signalError () { - this.disableSyncplay(); + this.disableSyncPlay(); } } -/** SyncplayManager singleton. */ -export default new SyncplayManager(); +/** SyncPlayManager singleton. */ +export default new SyncPlayManager(); diff --git a/src/controllers/useredit.js b/src/controllers/useredit.js index cbe5acd36a..125c571362 100644 --- a/src/controllers/useredit.js +++ b/src/controllers/useredit.js @@ -104,7 +104,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'fnchecked'], function $('#chkEnableSharing', page).checked(user.Policy.EnablePublicSharing); $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); $('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); - $('#selectSyncplayAccess').val(user.Policy.SyncplayAccess); + $('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess); loading.hide(); } @@ -146,7 +146,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'fnchecked'], function }).map(function (c) { return c.getAttribute('data-id'); }); - user.Policy.SyncplayAccess = page.querySelector('#selectSyncplayAccess').value; + user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value; ApiClient.updateUser(user).then(function () { ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { onSaveComplete(page, user); diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js index 3113031ea8..a7aaeaa790 100644 --- a/src/scripts/librarymenu.js +++ b/src/scripts/librarymenu.js @@ -1,4 +1,4 @@ -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) { +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'; function renderHeader() { @@ -89,7 +89,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' var policy = user.Policy ? user.Policy : user.localUser.Policy; - if (headerSyncButton && policy && policy.SyncplayAccess !== 'None') { + if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None') { headerSyncButton.classList.remove('hide'); } } else { @@ -192,7 +192,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' groupSelectionMenu.show(btn); } - function onSyncplayEnabled(event, enabled) { + function onSyncPlayEnabled(event, enabled) { var icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (enabled) { @@ -202,7 +202,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' } } - function onSyncplaySyncing(event, is_syncing, syncMethod) { + function onSyncPlaySyncing(event, is_syncing, syncMethod) { var icon = headerSyncButton.querySelector('span'); icon.classList.remove('sync', 'sync_disabled', 'sync_problem'); if (is_syncing) { @@ -967,8 +967,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' updateUserInHeader(); }); events.on(playbackManager, 'playerchange', updateCastIcon); - events.on(syncplayManager, 'enabled', onSyncplayEnabled); - events.on(syncplayManager, 'syncing', onSyncplaySyncing); + events.on(syncPlayManager, 'enabled', onSyncPlayEnabled); + events.on(syncPlayManager, 'syncing', onSyncPlaySyncing); loadNavDrawer(); return LibraryMenu; }); diff --git a/src/scripts/site.js b/src/scripts/site.js index a07062ab3c..3cf6054603 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -826,7 +826,7 @@ var AppInfo = {}; define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); define('timeSyncManager', [componentsPath + '/syncplay/timeSyncManager'], returnDefault); define('groupSelectionMenu', [componentsPath + '/syncplay/groupSelectionMenu'], returnFirstDependency); - define('syncplayManager', [componentsPath + '/syncplay/syncplayManager'], returnDefault); + define('syncPlayManager', [componentsPath + '/syncplay/syncPlayManager'], returnDefault); define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 4759b670d8..495f90edb5 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -491,8 +491,8 @@ "HeaderSubtitleProfile": "Subtitle Profile", "HeaderSubtitleProfiles": "Subtitle Profiles", "HeaderSubtitleProfilesHelp": "Subtitle profiles describe the subtitle formats supported by the device.", - "HeaderSyncplaySelectGroup": "Join a group", - "HeaderSyncplayEnabled": "Syncplay enabled", + "HeaderSyncPlaySelectGroup": "Join a group", + "HeaderSyncPlayEnabled": "SyncPlay enabled", "HeaderSystemDlnaProfiles": "System Profiles", "HeaderTags": "Tags", "HeaderTaskTriggers": "Task Triggers", @@ -855,18 +855,18 @@ "LabelSubtitlePlaybackMode": "Subtitle mode:", "LabelSubtitles": "Subtitles", "LabelSupportedMediaTypes": "Supported Media Types:", - "LabelSyncplayTimeOffset": "Time offset with the server:", + "LabelSyncPlayTimeOffset": "Time offset with the server:", "MillisecondsUnit": "ms", - "LabelSyncplayPlaybackDiff": "Playback time difference:", - "LabelSyncplaySyncMethod": "Sync method:", - "LabelSyncplayNewGroup": "New group", - "LabelSyncplayNewGroupDescription": "Create a new group", - "LabelSyncplayLeaveGroup": "Leave group", - "LabelSyncplayLeaveGroupDescription": "Disable Syncplay", - "LabelSyncplayAccessCreateAndJoinGroups": "Allow user to create and join groups", - "LabelSyncplayAccessJoinGroups": "Allow user to join groups", - "LabelSyncplayAccessNone": "Disabled for this user", - "LabelSyncplayAccess": "Syncplay access", + "LabelSyncPlayPlaybackDiff": "Playback time difference:", + "LabelSyncPlaySyncMethod": "Sync method:", + "LabelSyncPlayNewGroup": "New group", + "LabelSyncPlayNewGroupDescription": "Create a new group", + "LabelSyncPlayLeaveGroup": "Leave group", + "LabelSyncPlayLeaveGroupDescription": "Disable SyncPlay", + "LabelSyncPlayAccessCreateAndJoinGroups": "Allow user to create and join groups", + "LabelSyncPlayAccessJoinGroups": "Allow user to join groups", + "LabelSyncPlayAccessNone": "Disabled for this user", + "LabelSyncPlayAccess": "SyncPlay access", "LabelTVHomeScreen": "TV mode home screen:", "LabelTag": "Tag:", "LabelTagline": "Tagline:", @@ -1030,21 +1030,21 @@ "MessageUnableToConnectToServer": "We're unable to connect to the selected server right now. Please ensure it is running and try again.", "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.", "MessageYouHaveVersionInstalled": "You currently have version {0} installed.", - "MessageSyncplayEnabled": "Syncplay enabled.", - "MessageSyncplayDisabled": "Syncplay disabled.", - "MessageSyncplayUserJoined": "{0} has joined the group.", - "MessageSyncplayUserLeft": "{0} has left the group.", - "MessageSyncplayGroupWait": "{0} is buffering...", - "MessageSyncplayNoGroupsAvailable": "No groups available. Start playing something first.", - "MessageSyncplayPlaybackPermissionRequired": "Playback permission required.", - "MessageSyncplayGroupDoesNotExist": "Failed to join group because it does not exist.", - "MessageSyncplayCreateGroupDenied": "Permission required to create a group.", - "MessageSyncplayJoinGroupDenied": "Permission required to use Syncplay.", - "MessageSyncplayLibraryAccessDenied": "Access to this content is restricted.", - "MessageSyncplayErrorAccessingGroups": "An error occurred while accessing groups list.", - "MessageSyncplayErrorNoActivePlayer": "No active player found. Syncplay has been disabled.", - "MessageSyncplayErrorMissingSession": "Failed to enable Syncplay! Missing session.", - "MessageSyncplayErrorMedia": "Failed to enable Syncplay! Media error.", + "MessageSyncPlayEnabled": "SyncPlay enabled.", + "MessageSyncPlayDisabled": "SyncPlay disabled.", + "MessageSyncPlayUserJoined": "{0} has joined the group.", + "MessageSyncPlayUserLeft": "{0} has left the group.", + "MessageSyncPlayGroupWait": "{0} is buffering...", + "MessageSyncPlayNoGroupsAvailable": "No groups available. Start playing something first.", + "MessageSyncPlayPlaybackPermissionRequired": "Playback permission required.", + "MessageSyncPlayGroupDoesNotExist": "Failed to join group because it does not exist.", + "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", + "MessageSyncPlayJoinGroupDenied": "Permission required to use SyncPlay.", + "MessageSyncPlayLibraryAccessDenied": "Access to this content is restricted.", + "MessageSyncPlayErrorAccessingGroups": "An error occurred while accessing groups list.", + "MessageSyncPlayErrorNoActivePlayer": "No active player found. SyncPlay has been disabled.", + "MessageSyncPlayErrorMissingSession": "Failed to enable SyncPlay! Missing session.", + "MessageSyncPlayErrorMedia": "Failed to enable SyncPlay! Media error.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", @@ -1393,7 +1393,7 @@ "Suggestions": "Suggestions", "Sunday": "Sunday", "Sync": "Sync", - "SyncplayAccessHelp": "Select the level of access this user has to the Syncplay feature. Syncplay enables to sync playback with other users.", + "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other users.", "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", "TV": "TV", "TabAccess": "Access", diff --git a/src/useredit.html b/src/useredit.html index 5f759c973c..2a8f17af47 100644 --- a/src/useredit.html +++ b/src/useredit.html @@ -105,13 +105,13 @@
-
- + + + -
${SyncplayAccessHelp}
+
${SyncPlayAccessHelp}
From d2bd631cbe93768d36f9bb797be1e1d4a2e9b5b8 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 23:25:22 +0200 Subject: [PATCH 014/181] Fix more sonarqube bugs --- src/components/apphost.js | 40 ++++++++++++++----- src/components/htmlvideoplayer/plugin.js | 15 ------- .../libraryoptionseditor.js | 7 +++- src/components/skinManager.js | 1 - .../dashboard/metadataimagespage.js | 12 ++++-- .../dashboard/notifications/notification.js | 3 -- src/controllers/playback/videoosd.js | 2 +- src/dlnaprofiles.html | 2 +- src/elements/emby-slider/emby-slider.js | 2 +- src/scripts/browser.js | 22 +++------- src/scripts/browserdeviceprofile.js | 17 -------- src/scripts/editorsidebar.js | 4 +- src/scripts/librarymenu.js | 21 +++++----- src/scripts/playlists.js | 3 +- src/scripts/site.js | 17 +++----- 15 files changed, 72 insertions(+), 96 deletions(-) diff --git a/src/components/apphost.js b/src/components/apphost.js index 75e8ba17f1..2fe0cbd33a 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -5,7 +5,7 @@ define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'g var disableHlsVideoAudioCodecs = []; if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { - if (browser.edge || browser.msie) { + if (browser.edge) { disableHlsVideoAudioCodecs.push('mp3'); } @@ -93,18 +93,36 @@ define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'g function getDeviceName() { var deviceName; - deviceName = browser.tizen ? 'Samsung Smart TV' : browser.web0s ? 'LG Smart TV' : browser.operaTv ? 'Opera TV' : browser.xboxOne ? 'Xbox One' : browser.ps4 ? 'Sony PS4' : browser.chrome ? 'Chrome' : browser.edge ? 'Edge' : browser.firefox ? 'Firefox' : browser.msie ? 'Internet Explorer' : browser.opera ? 'Opera' : browser.safari ? 'Safari' : 'Web Browser'; + if (browser.tizen) { + deviceName = 'Samsung Smart TV'; + } else if (browser.web0s) { + deviceName = 'LG Smart TV'; + } else if (browser.operaTv) { + deviceName = 'Opera TV'; + } else if (browser.xboxOne) { + deviceName = 'Xbox One'; + } else if (browser.ps4) { + deviceName = 'Sony PS4'; + } else if (browser.chrome) { + deviceName = 'Chrome'; + } else if (browser.edge) { + deviceName = 'Edge'; + } else if (browser.firefox) { + deviceName = 'Firefox'; + } else if (browser.opera) { + deviceName = 'Opera'; + } else if (browser.safari) { + deviceName = 'Safari'; + } else { + deviceName = 'Web Browser'; + } if (browser.ipad) { deviceName += ' iPad'; - } else { - if (browser.iphone) { - deviceName += ' iPhone'; - } else { - if (browser.android) { - deviceName += ' Android'; - } - } + } else if (browser.iphone) { + deviceName += ' iPhone'; + } else if (browser.android) { + deviceName += ' Android'; } return deviceName; @@ -267,7 +285,7 @@ define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'g if (enabled) features.push('multiserver'); }); - if (!browser.orsay && !browser.msie && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { + if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { features.push('subtitleappearancesettings'); } diff --git a/src/components/htmlvideoplayer/plugin.js b/src/components/htmlvideoplayer/plugin.js index a1e51b44f1..bab41eb369 100644 --- a/src/components/htmlvideoplayer/plugin.js +++ b/src/components/htmlvideoplayer/plugin.js @@ -279,14 +279,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } self.play = function (options) { - - if (browser.msie) { - if (options.playMethod === 'Transcode' && !window.MediaSource) { - alert('Playback of this content is not supported in Internet Explorer. For a better experience, try a modern browser such as Microsoft Edge, Google Chrome, Firefox or Opera.'); - return Promise.reject(); - } - } - self._started = false; self._timeUpdated = false; @@ -1411,13 +1403,6 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa var video = document.createElement('video'); if (video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function' || document.pictureInPictureEnabled) { list.push('PictureInPicture'); - } else if (browser.ipad) { - // Unfortunately this creates a false positive on devices where its' not actually supported - if (navigator.userAgent.toLowerCase().indexOf('os 9') === -1) { - if (video.webkitSupportsPresentationMode && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { - list.push('PictureInPicture'); - } - } } else if (window.Windows) { if (Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) { list.push('PictureInPicture'); diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index fec4656406..832a6ffc5e 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -120,7 +120,12 @@ define(['globalize', 'dom', 'emby-checkbox', 'emby-select', 'emby-input'], funct html += plugin.Name; html += ''; html += '
'; - index > 0 ? html += '' : plugins.length > 1 && (html += ''), html += ''; + if (index > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; + } + html += ''; }); html += ''; diff --git a/src/components/skinManager.js b/src/components/skinManager.js index 871b6d999f..5c9fe71a58 100644 --- a/src/components/skinManager.js +++ b/src/components/skinManager.js @@ -57,7 +57,6 @@ define(['apphost', 'userSettings', 'browser', 'events', 'pluginManager', 'backdr var selectedTheme; for (var i = 0, length = themes.length; i < length; i++) { - var theme = themes[i]; if (theme[isDefaultProperty]) { defaultTheme = theme; diff --git a/src/controllers/dashboard/metadataimagespage.js b/src/controllers/dashboard/metadataimagespage.js index 277eecb42a..3047736a68 100644 --- a/src/controllers/dashboard/metadataimagespage.js +++ b/src/controllers/dashboard/metadataimagespage.js @@ -29,14 +29,18 @@ define(['jQuery', 'dom', 'loading', 'libraryMenu', 'globalize', 'listViewStyle'] var promises = [ApiClient.getServerConfiguration(), populateLanguages(page.querySelector('#selectLanguage')), populateCountries(page.querySelector('#selectCountry'))]; Promise.all(promises).then(function(responses) { var config = responses[0]; - page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || '', page.querySelector('#selectCountry').value = config.MetadataCountryCode || '', loading.hide(); + page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; + page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; + loading.hide(); }); } function onSubmit() { var form = this; return loading.show(), ApiClient.getServerConfiguration().then(function(config) { - config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value, config.MetadataCountryCode = form.querySelector('#selectCountry').value, ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + config.MetadataCountryCode = form.querySelector('#selectCountry').value; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }), !1; } @@ -59,6 +63,8 @@ define(['jQuery', 'dom', 'loading', 'libraryMenu', 'globalize', 'listViewStyle'] $(document).on('pageinit', '#metadataImagesConfigurationPage', function() { $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#metadataImagesConfigurationPage', function() { - libraryMenu.setTabs('metadata', 2, getTabs), loading.show(), loadPage(this); + libraryMenu.setTabs('metadata', 2, getTabs); + loading.show(); + loadPage(this); }); }); diff --git a/src/controllers/dashboard/notifications/notification.js b/src/controllers/dashboard/notifications/notification.js index 90d453cdae..2b6f7a148a 100644 --- a/src/controllers/dashboard/notifications/notification.js +++ b/src/controllers/dashboard/notifications/notification.js @@ -73,9 +73,6 @@ define(['jQuery', 'emby-checkbox', 'fnchecked'], function ($) { notificationOptions.Options.push(notificationConfig); } - types.filter(function (n) { - return n.Type == type; - })[0]; notificationConfig.Enabled = $('#chkEnabled', page).checked(); notificationConfig.SendToUserMode = $('#selectUsers', page).val(); notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) { diff --git a/src/controllers/playback/videoosd.js b/src/controllers/playback/videoosd.js index e9923d779c..ac42554b39 100644 --- a/src/controllers/playback/videoosd.js +++ b/src/controllers/playback/videoosd.js @@ -157,7 +157,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med }); if (!displayName) { - displayItem.Type; + displayName = displayItem.Type; } titleElement.innerHTML = displayName; diff --git a/src/dlnaprofiles.html b/src/dlnaprofiles.html index 9f2a5e129e..b47b2fd6bf 100644 --- a/src/dlnaprofiles.html +++ b/src/dlnaprofiles.html @@ -11,7 +11,7 @@
- ${Help} + ${Help}

${CustomDlnaProfilesHelp}

diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index e37455dfe1..1b78fca0ae 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -148,7 +148,7 @@ define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-sli this.classList.add('mdl-slider'); this.classList.add('mdl-js-slider'); - if (browser.edge || browser.msie) { + if (browser.edge) { this.classList.add('slider-browser-edge'); } if (!layoutManager.mobile) { diff --git a/src/scripts/browser.js b/src/scripts/browser.js index b1912862b3..a377f08fdb 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -14,10 +14,6 @@ define([], function () { return true; } - if (userAgent.indexOf('nintendo') !== -1) { - return true; - } - if (userAgent.indexOf('viera') !== -1) { return true; } @@ -93,7 +89,7 @@ define([], function () { var _supportsCssAnimation; var _supportsCssAnimationWithPrefix; function supportsCssAnimation(allowPrefix) { - + // TODO: Assess if this is still needed, as all of our targets should natively support CSS animations. if (allowPrefix) { if (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false) { return _supportsCssAnimationWithPrefix; @@ -145,7 +141,6 @@ define([], function () { /(chrome)[ \/]([\w.]+)/.exec(ua) || /(safari)[ \/]([\w.]+)/.exec(ua) || /(firefox)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; @@ -161,14 +156,6 @@ define([], function () { if (browser === 'edge') { platform_match = ['']; - } else { - if (ua.indexOf('windows phone') !== -1 || ua.indexOf('iemobile') !== -1) { - - // http://www.neowin.net/news/ie11-fakes-user-agent-to-fool-gmail-in-windows-phone-81-gdr1-update - browser = 'msie'; - } else if (ua.indexOf('like gecko') !== -1 && ua.indexOf('webkit') === -1 && ua.indexOf('opera') === -1 && ua.indexOf('chrome') === -1 && ua.indexOf('safari') === -1) { - browser = 'msie'; - } } if (browser === 'opr') { @@ -211,7 +198,7 @@ define([], function () { browser[matched.platform] = true; } - if (!browser.chrome && !browser.msie && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf('webkit') !== -1) { + if (!browser.chrome && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf('webkit') !== -1) { browser.safari = true; } @@ -224,7 +211,10 @@ define([], function () { browser.mobile = true; } - browser.xboxOne = userAgent.toLowerCase().indexOf('xbox') !== -1; + if (userAgent.toLowerCase().indexOf('xbox') !== -1) { + browser.xboxOne = true; + browser.tv = true; + } browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null; browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || self.tizen != null; browser.web0s = userAgent.toLowerCase().indexOf('Web0S'.toLowerCase()) !== -1; diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserdeviceprofile.js index 2668f20913..a550b69198 100644 --- a/src/scripts/browserdeviceprofile.js +++ b/src/scripts/browserdeviceprofile.js @@ -188,20 +188,6 @@ define(['browser'], function (browser) { return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; } - function getFlvMseDirectPlayProfile() { - var videoAudioCodecs = ['aac']; - if (!browser.edge && !browser.msie) { - videoAudioCodecs.push('mp3'); - } - - return { - Container: 'flv', - Type: 'Video', - VideoCodec: 'h264', - AudioCodec: videoAudioCodecs.join(',') - }; - } - function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) { var supported = false; var profileContainer = container; @@ -230,9 +216,6 @@ define(['browser'], function (browser) { break; case 'flv': supported = browser.tizen || browser.orsay; - //if (!supported && window.MediaSource != null && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) { - // return getFlvMseDirectPlayProfile(); - //} break; case '3gp': case 'mts': diff --git a/src/scripts/editorsidebar.js b/src/scripts/editorsidebar.js index ee7788407c..8168389566 100644 --- a/src/scripts/editorsidebar.js +++ b/src/scripts/editorsidebar.js @@ -209,7 +209,7 @@ define(['datetime', 'jQuery', 'globalize', 'material-icons'], function (datetime function onNodeOpen(event, data) { var page = $(this).parents('.page')[0]; var node = data.node; - if (node.children && node.children) { + if (node.children) { loadNodesToLoad(page, node); } if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) { @@ -221,7 +221,7 @@ define(['datetime', 'jQuery', 'globalize', 'material-icons'], function (datetime function onNodeLoad(event, data) { var page = $(this).parents('.page')[0]; var node = data.node; - if (node.children && node.children) { + if (node.children) { loadNodesToLoad(page, node); } if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) { diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js index 81a381bff2..669284ec47 100644 --- a/src/scripts/librarymenu.js +++ b/src/scripts/librarymenu.js @@ -567,27 +567,25 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' if (libraryMenuOptions) { getUserViews(apiClient, userId).then(function (result) { var items = result; - var html = ''; - html += '

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

'; + var html = `

${globalize.translate('HeaderMedia')}

`; html += items.map(function (i) { var icon = i.icon || imageHelper.getLibraryIcon(i.CollectionType); var itemId = i.Id; - if (i.onclick) { - i.onclick; - } + const linkHtml = ` + + ${i.Name} + `; - return '' + i.Name + ''; + return linkHtml; }).join(''); libraryMenuOptions.innerHTML = html; var elem = libraryMenuOptions; var sidebarLinks = elem.querySelectorAll('.navMenuOption'); - for (var i = 0, length = sidebarLinks.length; i < length; i++) { - sidebarLinks[i].removeEventListener('click', onSidebarLinkClick); - sidebarLinks[i].addEventListener('click', onSidebarLinkClick); + for (const sidebarLink of sidebarLinks) { + sidebarLink.removeEventListener('click', onSidebarLinkClick); + sidebarLink.addEventListener('click', onSidebarLinkClick); } }); } @@ -900,6 +898,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' updateMenuForPageType(isDashboardPage, isLibraryPage); + // TODO: Seems to do nothing? Check if needed (also in other views). if (!e.detail.isRestored) { window.scrollTo(0, 0); } diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index 52e7ccb3bd..974d00f8e6 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -69,10 +69,11 @@ define(['loading', 'listView', 'cardBuilder', 'libraryMenu', 'libraryBrowser', ' showLoadingMessage(); var query = getQuery(view); var promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query); + // TODO: promise2 is unused, check if necessary. var promise2 = Dashboard.getCurrentUser(); Promise.all([promise1, promise2]).then(function (responses) { var result = responses[0]; - responses[1]; + // TODO: Is the scroll necessary? window.scrollTo(0, 0); var html = ''; var viewStyle = getPageData(view).view; diff --git a/src/scripts/site.js b/src/scripts/site.js index c00169d224..939d362da5 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -368,8 +368,7 @@ var AppInfo = {}; } } - function initRequireWithBrowser(browser) { - var bowerPath = getBowerPath(); + function initRequireWithBrowser() { var componentsPath = getComponentsPath(); var scriptsPath = getScriptsPath(); @@ -378,13 +377,7 @@ var AppInfo = {}; define('lazyLoader', [componentsPath + '/lazyloader/lazyloader-intersectionobserver'], returnFirstDependency); define('shell', [componentsPath + '/shell'], returnFirstDependency); - if ('registerElement' in document) { - define('registerElement', []); - } else if (browser.msie) { - define('registerElement', ['webcomponents'], returnFirstDependency); - } else { - define('registerElement', ['document-register-element'], returnFirstDependency); - } + define('registerElement', ['document-register-element'], returnFirstDependency); define('alert', [componentsPath + '/alert'], returnFirstDependency); @@ -609,8 +602,8 @@ var AppInfo = {}; /* eslint-enable compat/compat */ } - function onWebComponentsReady(browser) { - initRequireWithBrowser(browser); + function onWebComponentsReady() { + initRequireWithBrowser(); if (self.appMode === 'cordova' || self.appMode === 'android' || self.appMode === 'standalone') { AppInfo.isNativeApp = true; @@ -1127,7 +1120,7 @@ var AppInfo = {}; }); })(); - return require(['browser'], onWebComponentsReady); + return onWebComponentsReady(); }(); pageClassOn('viewshow', 'standalonePage', function () { From d4fbbea9915125142b7c41c999cbf9ce9476b487 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 23:34:23 +0200 Subject: [PATCH 015/181] Remove code smells --- src/controllers/dashboard/notifications/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dashboard/notifications/notification.js b/src/controllers/dashboard/notifications/notification.js index 2b6f7a148a..8654f3e991 100644 --- a/src/controllers/dashboard/notifications/notification.js +++ b/src/controllers/dashboard/notifications/notification.js @@ -58,10 +58,10 @@ define(['jQuery', 'emby-checkbox', 'fnchecked'], function ($) { function save(page) { var type = getParameterByName('type'); var promise1 = ApiClient.getNamedConfiguration(notificationsConfigurationKey); + // TODO: Check if this promise is really needed, as it's unused. var promise2 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')); Promise.all([promise1, promise2]).then(function (responses) { var notificationOptions = responses[0]; - var types = responses[1]; var notificationConfig = notificationOptions.Options.filter(function (n) { return n.Type == type; })[0]; From ac060e1270a33c19e55ec8ae7c6ae2271f007b8f Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 23:55:23 +0200 Subject: [PATCH 016/181] Fix some code smells --- .../accessschedule/accessschedule.js | 6 ++-- src/components/actionsheet/actionsheet.js | 33 ++++--------------- src/components/activitylog.js | 8 +++-- src/components/appRouter.js | 21 +++++------- 4 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/components/accessschedule/accessschedule.js b/src/components/accessschedule/accessschedule.js index 870231cf03..caf3e82045 100644 --- a/src/components/accessschedule/accessschedule.js +++ b/src/components/accessschedule/accessschedule.js @@ -72,12 +72,12 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt reject(); } }); - dlg.querySelector('.btnCancel').addEventListener('click', function (e) { + dlg.querySelector('.btnCancel').addEventListener('click', function () { dialogHelper.close(dlg); }); - dlg.querySelector('form').addEventListener('submit', function (e) { + dlg.querySelector('form').addEventListener('submit', function (event) { submitSchedule(dlg, options); - e.preventDefault(); + event.preventDefault(); return false; }); }; diff --git a/src/components/actionsheet/actionsheet.js b/src/components/actionsheet/actionsheet.js index e08fbf4a25..ab8998f1bc 100644 --- a/src/components/actionsheet/actionsheet.js +++ b/src/components/actionsheet/actionsheet.js @@ -11,20 +11,10 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu } var box; - var elem; + for (let [index, elem] of elems) { + box = elem.getBoundingClientRect(); - for (var i = 0, length = elems.length; i < length; i++) { - - elem = elems[i]; - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - box = elem.getBoundingClientRect(); - } else { - box = { top: 0, left: 0 }; - } - - results[i] = { + results[index] = { top: box.top, left: box.left, width: box.width, @@ -96,13 +86,11 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu scrollY: false }; - var backButton = false; var isFullscreen; if (layoutManager.tv) { dialogOptions.size = 'fullscreen'; isFullscreen = true; - backButton = true; dialogOptions.autoFocus = true; } else { @@ -139,16 +127,10 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu style += 'min-width:' + minWidth + 'px;'; } - var i; - var length; - var option; var renderIcon = false; var icons = []; var itemIcon; - for (i = 0, length = options.items.length; i < length; i++) { - - option = options.items[i]; - + for (let option of options.items) { itemIcon = option.icon || (option.selected ? 'check' : null); if (itemIcon) { @@ -206,10 +188,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu menuItemClass += ' actionsheet-xlargeFont'; } - for (i = 0, length = options.items.length; i < length; i++) { - - option = options.items[i]; - + for (let [index, option] of options) { if (option.divider) { html += '
'; @@ -222,7 +201,7 @@ define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-bu var optionId = option.id == null || option.id === '' ? option.value : option.id; html += ''; - itemIcon = icons[i]; + itemIcon = icons[index]; if (itemIcon) { diff --git a/src/components/activitylog.js b/src/components/activitylog.js index a7b3f48bc2..bbb0995063 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -34,10 +34,14 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', html += ''; if (entry.Overview) { - html += ''; + html += ``; } - return html += ''; + html += ''; + + return html; } function renderList(elem, apiClient, result, startIndex, limit) { diff --git a/src/components/appRouter.js b/src/components/appRouter.js index 2e11ef88d9..77b64f8e88 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -26,11 +26,11 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM connectionManager.connect({ enableAutoLogin: appSettings.enableAutoLogin() }).then(function (result) { - handleConnectionResult(result, loading); + handleConnectionResult(result); }); } - function handleConnectionResult(result, loading) { + function handleConnectionResult(result) { switch (result.State) { case 'SignedIn': loading.hide(); @@ -246,13 +246,11 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } if (setQuality) { - - var quality = 100; - + var quality; var type = options.type || 'Primary'; if (browser.tv || browser.slow) { - + // TODO: wtf if (browser.chrome) { // webp support quality = type === 'Primary' ? 40 : 50; @@ -384,7 +382,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM if (firstResult.State !== 'SignedIn' && !route.anonymous) { - handleConnectionResult(firstResult, loading); + handleConnectionResult(firstResult); return; } } @@ -463,7 +461,6 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM return Promise.resolve(); } - var isHandlingBackToDefault; var isDummyBackToHome; function loadContent(ctx, route, html, request) { @@ -589,8 +586,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM path = '/' + path; } - var baseRoute = baseUrl(); - path = path.replace(baseRoute, ''); + path = path.replace(baseUrl(), ''); if (currentRouteInfo && currentRouteInfo.path === path) { // can't use this with home right now due to the back menu @@ -621,10 +617,11 @@ define(['loading', 'globalize', 'events', 'viewManager', 'layoutManager', 'skinM } function showItem(item, serverId, options) { + // TODO: Refactor this so it only gets items, not strings. if (typeof (item) === 'string') { var apiClient = serverId ? connectionManager.getApiClient(serverId) : connectionManager.currentApiClient(); - apiClient.getItem(apiClient.getCurrentUserId(), item).then(function (item) { - appRouter.showItem(item, options); + apiClient.getItem(apiClient.getCurrentUserId(), item).then(function (itemObject) { + appRouter.showItem(itemObject, options); }); } else { if (arguments.length === 2) { From 8478b0ed569a3df0c538ecd18256e90a93da1a7c Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 5 May 2020 23:02:05 +0900 Subject: [PATCH 017/181] basic skeleton for epub reader --- package.json | 1 + src/assets/css/site.css | 8 +++++ src/bundle.js | 5 ++++ src/components/bookplayer/plugin.js | 35 ++++++++++++++++++++++ src/components/playback/playbackmanager.js | 6 ++-- src/scripts/site.js | 2 ++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/components/bookplayer/plugin.js diff --git a/package.json b/package.json index 02d0c804d2..8a6ddcf2aa 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "core-js": "^3.6.5", "date-fns": "^2.13.0", "document-register-element": "^1.14.3", + "epubjs": "^0.3.85", "fast-text-encoding": "^1.0.1", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 627145abc1..467f48d4fd 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -96,6 +96,14 @@ div[data-role=page] { } } +#bookPlayer { + position: relative; + height: 100%; + overflow: auto; + z-index: 100; + background: #fff; +} + .headerHelpButton { margin-left: 1.25em !important; padding-bottom: 0.4em !important; diff --git a/src/bundle.js b/src/bundle.js index d7ba6c6a51..d4a97247f8 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -102,6 +102,11 @@ _define('jellyfin-noto', function () { return noto; }); +var epubjs = require('epubjs'); +_define('epubjs', function () { + return epubjs; +}); + // page.js var page = require('page'); _define('page', function() { diff --git a/src/components/bookplayer/plugin.js b/src/components/bookplayer/plugin.js new file mode 100644 index 0000000000..c00b4b7c40 --- /dev/null +++ b/src/components/bookplayer/plugin.js @@ -0,0 +1,35 @@ +define(['connectionManager', 'dom'], function (connectionManager, dom) { + 'use strict'; + + function BookPlayer() { + var self = this; + + self.name = 'Book Player'; + self.type = 'mediaplayer'; + self.id = 'bookplayer'; + self.priority = 1; + } + + BookPlayer.prototype.play = function (values) { + var serverId = values.items[0].ServerId + var apiClient = connectionManager.getApiClient(serverId); + var options = values; + + require(['epubjs', 'appFooter-shared'], function (epubjs, appFooter) { + appFooter.element.insertAdjacentHTML('beforebegin', '
'); + var element = document.getElementById('bookPlayer'); + + var downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); + + var book = epubjs.default(downloadHref, { openAs: 'epub' }); + var rendition = book.renderTo(element, { method: "continuous", width: "100%", height: "100%" }); + var displayed = rendition.display(); + }); + }; + + BookPlayer.prototype.canPlayMediaType = function (mediaType) { + return (mediaType || '').toLowerCase() === 'book'; + }; + + return BookPlayer; +}); diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index dea419c8cc..381ab78693 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2182,7 +2182,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla // Only used internally self.getCurrentTicks = getCurrentTicks; - function playPhotos(items, options, user) { + function playOther(items, options, user) { var playStartIndex = options.startIndex || 0; var player = getPlayer(items[playStartIndex], options); @@ -2211,9 +2211,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return Promise.reject(); } - if (firstItem.MediaType === 'Photo') { + if (firstItem.MediaType === 'Photo' || firstItem.MediaType === 'Book') { - return playPhotos(items, options, user); + return playOther(items, options, user); } var apiClient = connectionManager.getApiClient(firstItem.ServerId); diff --git a/src/scripts/site.js b/src/scripts/site.js index ddb152eb4c..0672ae0db5 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -484,6 +484,7 @@ var AppInfo = {}; 'components/htmlAudioPlayer/plugin', 'components/htmlVideoPlayer/plugin', 'components/photoPlayer/plugin', + 'components/bookplayer/plugin', 'components/youtubeplayer/plugin', 'components/backdropScreensaver/plugin', 'components/logoScreensaver/plugin' @@ -670,6 +671,7 @@ var AppInfo = {}; 'fetch', 'flvjs', 'jstree', + 'epubjs', 'jQuery', 'hlsjs', 'howler', From 40e24bcaf0f5b4894c1f3087b9590da21dd2b0f3 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Sat, 16 May 2020 22:02:52 +1000 Subject: [PATCH 018/181] Implement minimalistic EPUB reader --- src/assets/css/site.css | 8 -- src/components/bookPlayer/plugin.js | 133 ++++++++++++++++++++++++++++ src/components/bookPlayer/style.css | 20 +++++ src/components/bookplayer/plugin.js | 35 -------- src/scripts/site.js | 2 +- yarn.lock | 94 +++++++++++++++++++- 6 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 src/components/bookPlayer/plugin.js create mode 100644 src/components/bookPlayer/style.css delete mode 100644 src/components/bookplayer/plugin.js diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 467f48d4fd..627145abc1 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -96,14 +96,6 @@ div[data-role=page] { } } -#bookPlayer { - position: relative; - height: 100%; - overflow: auto; - z-index: 100; - background: #fff; -} - .headerHelpButton { margin-left: 1.25em !important; padding-bottom: 0.4em !important; diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js new file mode 100644 index 0000000000..d0f040c4fd --- /dev/null +++ b/src/components/bookPlayer/plugin.js @@ -0,0 +1,133 @@ +define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavigation', 'dialogHelper', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (connectionManager, dom, loading, playbackManager, keyboardnavigation, dialogHelper, appHost) { + 'use strict'; + + function BookPlayer() { + let self = this; + + self.name = 'Book Player'; + self.type = 'mediaplayer'; + self.id = 'bookplayer'; + self.priority = 1; + + self.play = function (options) { + loading.show(); + let elem = createMediaElement(); + return setCurrentSrc(elem, options); + }; + + self.stop = function () { + let elem = self._mediaElement; + let rendition = self._rendition; + + if (elem && rendition) { + rendition.destroy(); + + elem.remove(); + self._mediaElement = null; + } + }; + + function onWindowKeyUp(e) { + let key = keyboardnavigation.getKeyName(e); + let rendition = self._rendition; + let book = rendition.book; + + switch (key) { + case 'l': + case 'ArrowRight': + case 'Right': + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + break; + case 'j': + case 'ArrowLeft': + case 'Left': + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + break; + case 'Escape': + dialogHelper.close(self._mediaElement); + break; + } + } + + function onDialogClosed() { + self.stop(); + } + + function createMediaElement() { + let elem = self._mediaElement; + + if (elem) { + return elem; + } + + elem = document.getElementById('bookPlayer'); + + if (!elem) { + elem = dialogHelper.createDialog({ + exitAnimationDuration: 400, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + elem.id = 'bookPlayer'; + + let html = ''; + html += '
'; + html += ''; + html += '
'; + + elem.innerHTML = html; + + elem.querySelector('.btnBookplayerExit').addEventListener('click', function () { + dialogHelper.close(elem); + }); + + dialogHelper.open(elem); + + elem.addEventListener('close', onDialogClosed); + } + + self._mediaElement = elem; + + return elem; + } + + function setCurrentSrc(elem, options) { + let serverId = options.items[0].ServerId; + let apiClient = connectionManager.getApiClient(serverId); + + return new Promise(function (resolve, reject) { + require(['epubjs'], function (epubjs) { + let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); + self._currentSrc = downloadHref; + + let book = epubjs.default(downloadHref, {openAs: 'epub'}); + + let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + self._rendition = rendition; + + return rendition.display().then(function () { + document.addEventListener('keyup', onWindowKeyUp); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus + self._rendition.on('keyup', onWindowKeyUp); + + loading.hide(); + + return resolve(); + }, function () { + console.error('Failed to display epub'); + return reject(); + }); + }); + }); + } + } + + BookPlayer.prototype.canPlayMediaType = function (mediaType) { + return (mediaType || '').toLowerCase() === 'book'; + }; + + return BookPlayer; +}); diff --git a/src/components/bookPlayer/style.css b/src/components/bookPlayer/style.css new file mode 100644 index 0000000000..295fae5c5b --- /dev/null +++ b/src/components/bookPlayer/style.css @@ -0,0 +1,20 @@ +#bookPlayer { + position: relative; + height: 100%; + width: 100%; + overflow: auto; + z-index: 100; + background: #fff; +} + +.topActionButtons { + right: 0.5vh; + top: 0.5vh; + z-index: 1002; + position: absolute; +} + +.bookplayerButtonIcon { + color: black; + opacity: 0.7; +} diff --git a/src/components/bookplayer/plugin.js b/src/components/bookplayer/plugin.js deleted file mode 100644 index c00b4b7c40..0000000000 --- a/src/components/bookplayer/plugin.js +++ /dev/null @@ -1,35 +0,0 @@ -define(['connectionManager', 'dom'], function (connectionManager, dom) { - 'use strict'; - - function BookPlayer() { - var self = this; - - self.name = 'Book Player'; - self.type = 'mediaplayer'; - self.id = 'bookplayer'; - self.priority = 1; - } - - BookPlayer.prototype.play = function (values) { - var serverId = values.items[0].ServerId - var apiClient = connectionManager.getApiClient(serverId); - var options = values; - - require(['epubjs', 'appFooter-shared'], function (epubjs, appFooter) { - appFooter.element.insertAdjacentHTML('beforebegin', '
'); - var element = document.getElementById('bookPlayer'); - - var downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); - - var book = epubjs.default(downloadHref, { openAs: 'epub' }); - var rendition = book.renderTo(element, { method: "continuous", width: "100%", height: "100%" }); - var displayed = rendition.display(); - }); - }; - - BookPlayer.prototype.canPlayMediaType = function (mediaType) { - return (mediaType || '').toLowerCase() === 'book'; - }; - - return BookPlayer; -}); diff --git a/src/scripts/site.js b/src/scripts/site.js index 0672ae0db5..dd86701724 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -484,7 +484,7 @@ var AppInfo = {}; 'components/htmlAudioPlayer/plugin', 'components/htmlVideoPlayer/plugin', 'components/photoPlayer/plugin', - 'components/bookplayer/plugin', + 'components/bookPlayer/plugin', 'components/youtubeplayer/plugin', 'components/backdropScreensaver/plugin', 'components/logoScreensaver/plugin' diff --git a/yarn.lock b/yarn.lock index 2be9c6baf9..3dac9c2dfb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -858,6 +858,20 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA== +"@types/jszip@^3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@types/jszip/-/jszip-3.4.1.tgz#e7a4059486e494c949ef750933d009684227846f" + integrity sha512-TezXjmf3lj+zQ651r6hPqvSScqBLvyPI9FxdXBqpEwBijNGQ2NXpaFW/7joGzveYkKQUil7iiDHLo6LV71Pc0A== + dependencies: + jszip "*" + +"@types/localforage@0.0.34": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@types/localforage/-/localforage-0.0.34.tgz#5e31c32dd8791ec4b9ff3ef47c9cb55b2d0d9438" + integrity sha1-XjHDLdh5HsS5/z70fJy1Wy0NlDg= + dependencies: + localforage "*" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -3822,6 +3836,23 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== +epubjs@^0.3.85: + version "0.3.87" + resolved "https://registry.yarnpkg.com/epubjs/-/epubjs-0.3.87.tgz#0a2a94e59777e04548deff49a1c713ccbf3378fc" + integrity sha512-UlzXj04JQaUJ4p6ux/glQcVC4ayBtnpHT7niw4ozGy8EOQTAr8+/z7UZEHUmqQj4yHIoPYC4qGXtmzNqImWx1A== + dependencies: + "@types/jszip" "^3.4.1" + "@types/localforage" "0.0.34" + event-emitter "^0.3.5" + jszip "^3.4.0" + localforage "^1.7.3" + lodash "^4.17.15" + marks-pane "^1.0.9" + path-webpack "0.0.3" + stream-browserify "^2.0.1" + url-polyfill "^1.1.9" + xmldom "^0.1.27" + errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -5856,6 +5887,11 @@ imagemin@^7.0.0: p-pipe "^3.0.0" replace-ext "^1.0.0" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + immutable@^3: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" @@ -6654,6 +6690,16 @@ jstree@^3.3.7: dependencies: jquery ">=1.9.1" +jszip@*, jszip@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + junk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" @@ -6782,6 +6828,20 @@ levn@^0.3.0, levn@~0.3.0: version "4.0.0" resolved "https://github.com/jellyfin/JavascriptSubtitlesOctopus#58e9a3f1a7f7883556ee002545f445a430120639" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + liftoff@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" @@ -6874,6 +6934,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +localforage@*, localforage@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204" + integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ== + dependencies: + lie "3.1.1" + localtunnel@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.9.2.tgz#0012fcabc29cf964c130a01858768aa2bb65b5af" @@ -7244,6 +7311,11 @@ markdown-table@^2.0.0: dependencies: repeat-string "^1.0.0" +marks-pane@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/marks-pane/-/marks-pane-1.0.9.tgz#c0b5ab813384d8cd81faaeb3bbf3397dc809c1b3" + integrity sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg== + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" @@ -8312,7 +8384,7 @@ page@^1.11.6: dependencies: path-to-regexp "~1.2.1" -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -8559,6 +8631,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path-webpack@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/path-webpack/-/path-webpack-0.0.3.tgz#ff6dec749eec5a94605c04d5f63fc55607a03a16" + integrity sha1-/23sdJ7sWpRgXATV9j/FVgegOhY= + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -10510,6 +10587,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -12188,6 +12270,11 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" +url-polyfill@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.9.tgz#2c8d4224889a5c942800f708f5585368085603d9" + integrity sha512-q/R5sowGuRfKHm497swkV+s9cPYtZRkHxzpDjRhqLO58FwdWTIkt6Y/fJlznUD/exaKx/XnDzCYXz0V16ND7ow== + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -12745,6 +12832,11 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= +xmldom@^0.1.27: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" + integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== + xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" From 05b9f4df3ebc0fadfd8d2309463515f46635052c Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 19 May 2020 03:59:37 +0900 Subject: [PATCH 019/181] convert photo player to class --- package.json | 1 + src/components/photoPlayer/plugin.js | 39 +++++++++++++++------------- src/components/pluginManager.js | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 02d0c804d2..7eab7662b0 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "src/components/actionSheet/actionSheet.js", "src/components/playmenu.js", "src/components/indicators/indicators.js", + "src/components/photoPlayer/plugin.js", "src/scripts/keyboardNavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", diff --git a/src/components/photoPlayer/plugin.js b/src/components/photoPlayer/plugin.js index 4dc00809dc..aa5464d77c 100644 --- a/src/components/photoPlayer/plugin.js +++ b/src/components/photoPlayer/plugin.js @@ -1,19 +1,22 @@ -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager) { - 'use strict'; +import browser from 'browser'; +import require from 'require'; +import events from 'events'; +import appHost from 'apphost'; +import loading from 'loading'; +import dom from 'dom'; +import playbackManager from 'playbackManager'; +import appRouter from 'appRouter'; +import connectionManager from 'connectionManager'; - function PhotoPlayer() { - - var self = this; - - self.name = 'Photo Player'; - self.type = 'mediaplayer'; - self.id = 'photoplayer'; - - // Let any players created by plugins take priority - self.priority = 1; +export class PhotoPlayer { + constructor() { + this.name = 'Photo Player'; + this.type = 'mediaplayer'; + this.id = 'photoplayer'; + this.priority = 1; } - PhotoPlayer.prototype.play = function (options) { + play(options) { return new Promise(function (resolve, reject) { @@ -41,12 +44,12 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa }); }); }); - }; + } - PhotoPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'photo'; - }; + } +} - return PhotoPlayer; -}); +export default PhotoPlayer; diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index 2126d73b3c..35754a1094 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -34,7 +34,7 @@ define(['events'], function (events) { require([url, 'globalize', 'appRouter'], function (pluginFactory, globalize, appRouter) { - var plugin = new pluginFactory(); + var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory(); // See if it's already installed var existing = instance.pluginsList.filter(function (p) { From c94add0a623217339f82eb505f1cd85ace6720ff Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Tue, 19 May 2020 16:05:44 +1000 Subject: [PATCH 020/181] Add table of contents to EPUB reader --- src/components/bookPlayer/plugin.js | 61 ++++++++++++++++++++++++++++- src/components/bookPlayer/style.css | 17 +++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index d0f040c4fd..1abbf778c8 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -45,6 +45,9 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig break; case 'Escape': dialogHelper.close(self._mediaElement); + if (self._tocElement) { + dialogHelper.close(self._tocElement); + } break; } } @@ -53,6 +56,19 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig self.stop(); } + function replaceLinks(contents, f) { + let links = contents.querySelectorAll('a[href]'); + + links.forEach((link) => { + let href = link.getAttribute('href'); + + link.onclick = function () { + f(href); + return false; + }; + }); + } + function createMediaElement() { let elem = self._mediaElement; @@ -74,9 +90,12 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig elem.id = 'bookPlayer'; let html = ''; - html += '
'; + html += '
'; html += ''; html += '
'; + html += '
'; + html += ''; + html += '
'; elem.innerHTML = html; @@ -84,6 +103,46 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig dialogHelper.close(elem); }); + elem.querySelector('.btnBookplayerToc').addEventListener('click', function () { + let rendition = self._rendition; + if (rendition) { + let tocElement = dialogHelper.createDialog({ + size: 'small', + autoFocus: false, + removeOnClose: true + }); + tocElement.id = 'dialogToc'; + + let tocHtml = '
'; + tocHtml += ''; + tocHtml += '
'; + tocHtml += '
    '; + rendition.book.navigation.forEach((chapter) => { + tocHtml += '
  • '; + // Remove '../' from href + let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + tocHtml += `${chapter.label}`; + tocHtml += '
  • '; + }); + tocHtml += '
'; + tocElement.innerHTML = tocHtml; + + tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', function () { + dialogHelper.close(tocElement); + }); + + replaceLinks(tocElement, (href) => { + let relative = rendition.book.path.relative(href); + rendition.display(relative); + dialogHelper.close(tocElement); + }); + + self._tocElement = tocElement; + + dialogHelper.open(tocElement); + } + }); + dialogHelper.open(elem); elem.addEventListener('close', onDialogClosed); diff --git a/src/components/bookPlayer/style.css b/src/components/bookPlayer/style.css index 295fae5c5b..581438a003 100644 --- a/src/components/bookPlayer/style.css +++ b/src/components/bookPlayer/style.css @@ -7,14 +7,29 @@ background: #fff; } -.topActionButtons { +.topRightActionButtons { right: 0.5vh; top: 0.5vh; z-index: 1002; position: absolute; } +.topLeftActionButtons { + left: 0.5vh; + top: 0.5vh; + z-index: 1002; + position: absolute; +} + .bookplayerButtonIcon { color: black; opacity: 0.7; } + +#dialogToc { + background-color: white; +} + +.toc li { + margin-bottom: 5px; +} From 4c467730d2a5835c4243d484c3d8d80822f85362 Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 20 May 2020 10:18:01 -0400 Subject: [PATCH 021/181] Move Network paths to be next to Optional Path This was present from when network paths were valid for libraries. No longer valid but could still be useful for Optional Network Path, --- src/components/directorybrowser/directorybrowser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index 7f13f89ef5..ac4a4d7943 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -89,7 +89,6 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in var instruction = options.instruction ? options.instruction + '

' : ''; html += '
'; html += instruction; - html += globalize.translate('MessageDirectoryPickerInstruction', '\\\\server', '\\\\192.168.1.101'); if ('bsd' === systemInfo.OperatingSystem.toLowerCase()) { html += '
'; html += '
'; @@ -127,6 +126,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in html += ''; html += '
'; html += globalize.translate('LabelOptionalNetworkPathHelp'); + html += globalize.translate('MessageDirectoryPickerInstruction', '\\\\server', '\\\\192.168.1.101'); html += '
'; html += '
'; } From 2497282134fec6321302b179454d47c2b27b0bed Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 20 May 2020 10:43:39 -0400 Subject: [PATCH 022/181] Update en-us.json --- src/strings/en-us.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 7df1001667..2e44628f65 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -766,7 +766,7 @@ "LabelNumberOfGuideDays": "Number of days of guide data to download:", "LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will choose based on the number of channels.", "LabelOptionalNetworkPath": "(Optional) Shared network folder:", - "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly.", + "LabelOptionalNetworkPathHelp": "If this folder is shared on your network, supplying the network share path can allow Jellyfin apps on other devices to access media files directly. For example, {0} or {1}.", "LabelOriginalAspectRatio": "Original aspect ratio:", "LabelOriginalTitle": "Original title:", "LabelOverview": "Overview:", @@ -991,7 +991,6 @@ "MessageCreateAccountAt": "Create an account at {0}", "MessageDeleteTaskTrigger": "Are you sure you wish to delete this task trigger?", "MessageDirectoryPickerBSDInstruction": "For BSD, you may need to configure storage within your FreeNAS Jail in order to allow Jellyfin to access it.", - "MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.", "MessageDirectoryPickerLinuxInstruction": "For Linux on Arch Linux, CentOS, Debian, Fedora, openSUSE, or Ubuntu, you must grant the service user at least read access to your storage locations.", "MessageDownloadQueued": "Download queued.", "MessageEnablingOptionLongerScans": "Enabling this option may result in significantly longer library scans.", From 28f5bdb854a75315c4269e0461de82a95635bc3f Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 20 May 2020 10:44:27 -0400 Subject: [PATCH 023/181] Update directorybrowser.js --- src/components/directorybrowser/directorybrowser.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/directorybrowser/directorybrowser.js b/src/components/directorybrowser/directorybrowser.js index ac4a4d7943..fc30affbc8 100644 --- a/src/components/directorybrowser/directorybrowser.js +++ b/src/components/directorybrowser/directorybrowser.js @@ -125,8 +125,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in html += '
'; html += ''; html += '
'; - html += globalize.translate('LabelOptionalNetworkPathHelp'); - html += globalize.translate('MessageDirectoryPickerInstruction', '\\\\server', '\\\\192.168.1.101'); + html += globalize.translate('LabelOptionalNetworkPathHelp', '\\\\server', '\\\\192.168.1.101'); html += '
'; html += '
'; } From b5a6c276b6caffe35957f096a817f766c027c3c3 Mon Sep 17 00:00:00 2001 From: abdulaziz Date: Thu, 21 May 2020 01:18:31 +0000 Subject: [PATCH 024/181] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index f6f64c5d41..2c8a90f2de 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -234,7 +234,7 @@ "HeaderPlayback": "تشغيل الوسائط", "HeaderPleaseSignIn": "الرجاء تسجيل الدخول", "HeaderPluginInstallation": "تثبيت الملحفات", - "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات:", + "HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات", "HeaderProfile": "الحساب", "HeaderProfileInformation": "معلومات العريضة", "HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل خادم أمبي في الجهاز", @@ -1045,5 +1045,35 @@ "Artist": "الفنان", "AllowFfmpegThrottling": "إبطاء الترميزات", "AlbumArtist": "المؤدي", - "Album": "الألبوم" + "Album": "الألبوم", + "Disconnect": "قطع الاتصال", + "Disc": "القرص", + "Disabled": "تعطيل", + "Directors": "المخرجون", + "Director": "المخرج", + "DirectPlaying": "بث بدون تحويل الصيغة", + "DirectStreaming": "البث المباشر", + "DirectStreamHelp2": "البث المباشر للملف يستخدم طاقة معالجة قليلة جدًا دون أي خسارة في جودة الفيديو.", + "DirectStreamHelp1": "الوسائط متوافقة مع الجهاز فيما يتعلق بالدقة ونوع الوسائط (H.264 ، AC3 ، إلخ) ، ولكنها في حاوية ملفات غير متوافقة (mkv ، avi ، wmv ، إلخ). سيتم إعادة حزم الفيديو في الوقت الحقيقي قبل بثه إلى الجهاز.", + "DetectingDevices": "يتم الكشف عن الأجهزة", + "Desktop": "سطح المكتب", + "Descending": "تنازلي", + "Depressed": "منخفض", + "DeinterlaceMethodHelp": "حدد طريقة فك التشابك لاستخدامها عند تحويل محتوى متشابك.", + "DefaultSubtitlesHelp": "يتم تحميل الترجمات استنادًا إلى العلامات الافتراضية والقسرية في البيانات الوصفية المضمنة. سيتم اعتبار تفضيلات اللغة عند توفر خيارات متعددة.", + "DefaultMetadataLangaugeDescription": "هذه هي إعداداتك الافتراضية ويمكن تخصيصها على أساس كل مكتبة.", + "Default": "افتراضي", + "CopyStreamURLError": "توجد مشكله في نسخ الرابط", + "CopyStreamURL": "نسخ عنوان الرابط", + "Continuing": "مستمر", + "CopyStreamURLSuccess": "URL copied successfully.", + "Connect": "اتصال", + "ConfirmEndPlayerSession": "هل تريد اطفاء النظام؟", + "ColorPrimaries": "الألوان", + "ClientSettings": "إعدادات التطبيق", + "ButtonTogglePlaylist": "قائمة التشغيل", + "BoxSet": "طقم", + "ButtonToggleContextMenu": "المزيد", + "ButtonSplit": "تقسيم", + "AllowFfmpegThrottlingHelp": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل." } From 2363b017146ba8a83cf775fc4b04d9c986474a6e Mon Sep 17 00:00:00 2001 From: Oneness Date: Thu, 21 May 2020 00:47:42 +0000 Subject: [PATCH 025/181] Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en_GB/ --- src/strings/en-gb.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index b111dfa24a..723f4b1658 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -660,7 +660,7 @@ "OptionOnInterval": "On an interval", "OptionOnAppStartup": "On application startup", "OptionNone": "None", - "OptionNew": "New...", + "OptionNew": "New…", "OptionMissingEpisode": "Missing Episodes", "OptionMax": "Max", "OptionLoginAttemptsBeforeLockoutHelp": "A value of zero means inheriting the default of three attempts for normal users and five for administrators. Setting this to -1 will disable the feature.", @@ -1032,7 +1032,7 @@ "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play content.", "LabelEnableDlnaDebugLoggingHelp": "Create large log files and should only be used as needed for troubleshooting purposes.", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Jellyfin.", - "LabelEnableAutomaticPortMapHelp": "Attempt to automatically map the public port to the local port via UPnP. This may not work with some router models. Changes will not apply until after a server restart.", + "LabelEnableAutomaticPortMapHelp": "Automatically forward public ports on your router to local ports on your server via UPnP. This may not work with some router models or network configurations. Changes will not apply until after a server restart.", "InstallingPackage": "Installing {0} (version {1})", "ImportMissingEpisodesHelp": "If enabled, information about missing episodes will be imported into your Jellyfin database and displayed within seasons and series. This may cause significantly longer library scans.", "HeaderSubtitleAppearance": "Subtitle Appearance", @@ -1118,7 +1118,7 @@ "LabelForgotPasswordUsernameHelp": "Enter your username, if you remember it.", "LabelFont": "Font:", "LabelExtractChaptersDuringLibraryScanHelp": "Generate chapter images when videos are imported during the library scan. Otherwise, they will be extracted during the chapter images scheduled task, allowing the regular library scan to complete faster.", - "LabelBaseUrlHelp": "You can add a custom subdirectory here to access the server from a more unique URL.", + "LabelBaseUrlHelp": "Adds a custom subdirectory to the server URL. For example: http://example.com/<baseurl>", "LabelEveryXMinutes": "Every:", "LabelEvent": "Event:", "LabelEpisodeNumber": "Episode number:", @@ -1513,5 +1513,7 @@ "UnsupportedPlayback": "Jellyfin cannot decrypt content protected by DRM but all content will be attempted regardless, including protected titles. Some files may appear completely black due to encryption or other unsupported features, such as interactive titles.", "MessageUnauthorizedUser": "You are not authorized to access the server at this time. Please contact your server administrator for more information.", "ButtonTogglePlaylist": "Playlist", - "ButtonToggleContextMenu": "More" + "ButtonToggleContextMenu": "More", + "HeaderDVR": "DVR", + "ApiKeysCaption": "List of the currently enabled API keys" } From 8dfb113440acb53fe37abc99a9e6297920588f9d Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Thu, 21 May 2020 02:11:43 +0000 Subject: [PATCH 026/181] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index d7418749e8..e220ba1d5d 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -566,7 +566,7 @@ "LabelEmbedAlbumArtDidl": "Arte do álbum incorporada no Didl", "LabelEmbedAlbumArtDidlHelp": "Alguns dispositivos preferem este método para obter a arte do álbum. Outros podem falhar ao reproduzir com esta opção ativada.", "LabelEnableAutomaticPortMap": "Ativar mapeamento automático de portas", - "LabelEnableAutomaticPortMapHelp": "Tentar mapear automaticamente a porta pública para a porta local através de UPnP. Pode não funcionar em alguns modelos de roteadores. As mudanças não serão aplicadas até a reinicialização do servidor.", + "LabelEnableAutomaticPortMapHelp": "Tentar mapear automaticamente a porta pública para a porta local do seu servidor através de UPnP. Pode não funcionar em alguns modelos de roteadores. As mudanças não serão aplicadas até a reinicialização do servidor.", "LabelEnableBlastAliveMessages": "Mensagens ao vivo", "LabelEnableBlastAliveMessagesHelp": "Ative esta função se o servidor não for detectado por outros dispositivos UPnP em sua rede.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo para descoberta do cliente (segundos)", @@ -1343,11 +1343,11 @@ "OptionAuto": "Automático", "AuthProviderHelp": "Seleciona um provedor de autenticação que será usado para autenticar a senha do usuário.", "HeaderFavoriteMovies": "Filmes Favoritos", - "HeaderFavoriteShows": "Séries Favoritas", - "HeaderFavoriteEpisodes": "Episódios Favoritos", + "HeaderFavoriteShows": "Séries favoritas", + "HeaderFavoriteEpisodes": "Episódios favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos", - "HeaderFavoriteArtists": "Artistas Favoritos", - "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteSongs": "Músicas favoritas", "HeaderFavoriteVideos": "Videos favoritos", "HeaderHome": "Inicio", "HeaderRestartingServer": "Reiniciando servidor", @@ -1434,7 +1434,7 @@ "LabelPlayMethod": "Método de Reprodução:", "LabelPlayer": "Reprodutor:", "LabelFolder": "Pasta:", - "LabelBaseUrlHelp": "Você pode adicionar um subdiretório aqui para acessar o servidor de uma única URL.", + "LabelBaseUrlHelp": "Você pode adicionar um subdiretório aqui para acessar o servidor de uma única URL. Por exemplo:http://exemplo.com/<baseurl>", "LabelBaseUrl": "URL Base:", "LabelBitrate": "Bitrate:", "LabelAudioSampleRate": "Taxa de amostragem do áudio:", @@ -1507,5 +1507,18 @@ "Filter": "Filtro", "New": "Novo", "HeaderFavoritePlaylists": "Playlists Favoritas", - "ApiKeysCaption": "Lista de chaves API ativadas no momento" + "ApiKeysCaption": "Lista de chaves API ativadas no momento", + "TabDVR": "DVR", + "SaveChanges": "Salvar mudanças", + "LabelRequireHttpsHelp": "Se selecionado, o servidor vai automaticamente redirecionar todas as solicitações HTTP para HTTPS. Isso não terá efeito se o servidor não estiver escutando HTTPS.", + "LabelRequireHttps": "Necessita HTTPS", + "LabelNightly": "Nightly", + "LabelStable": "Estável", + "LabelChromecastVersion": "Versão do Chromecast", + "LabelEnableHttpsHelp": "Habilita que o servidor escute na localização HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttps": "Habilitar HTTPS", + "HeaderServerAddressSettings": "Configurações da localização do servidor", + "HeaderRemoteAccessSettings": "Configurações de acesso remoto", + "HeaderHttpsSettings": "Configurações HTTPS", + "HeaderDVR": "DVR" } From a219998d6120bf76a161c6c1740a68b0b1a8c476 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 20 May 2020 22:53:01 +0000 Subject: [PATCH 027/181] Translated using Weblate (Pirate) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pr/ --- src/strings/pr.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/pr.json b/src/strings/pr.json index 0967ef424b..2f5c120d40 100644 --- a/src/strings/pr.json +++ b/src/strings/pr.json @@ -1 +1,6 @@ -{} +{ + "TabLogs": "Crow's Nest", + "HeaderAdmin": "Cap'n", + "WelcomeToProject": "Ahoy, matey! This be Jellyfin!", + "ButtonOk": "Aye" +} From e32df64c9f5c5c8ff67180039c0d04451f441a04 Mon Sep 17 00:00:00 2001 From: abdulaziz Date: Thu, 21 May 2020 04:09:53 +0000 Subject: [PATCH 028/181] Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ar/ --- src/strings/ar.json | 81 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/strings/ar.json b/src/strings/ar.json index 2c8a90f2de..f93146dc01 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -17,7 +17,7 @@ "ButtonAddScheduledTaskTrigger": "إضافة زناد", "ButtonAddServer": "إضافة خادم", "ButtonAddUser": "اضافة مستخدم", - "ButtonArrowDown": "أدنى", + "ButtonArrowDown": "أسفل", "ButtonArrowLeft": "يسار", "ButtonArrowRight": "يمين", "ButtonArrowUp": "أعلى", @@ -1075,5 +1075,82 @@ "BoxSet": "طقم", "ButtonToggleContextMenu": "المزيد", "ButtonSplit": "تقسيم", - "AllowFfmpegThrottlingHelp": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل." + "AllowFfmpegThrottlingHelp": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل.", + "InstallingPackage": "تثبيت {0} (الإصدار {1})", + "Images": "الصور", + "Identify": "التعرف على الوسائط", + "HttpsRequiresCert": "لتمكين الاتصالات الآمنة ، ستحتاج إلى توفير شهادة SSL موثوقة ، مثل Letsencrypt. يرجى إما تقديم شهادة أو تعطيل الاتصالات الآمنة.", + "HeaderServerAddressSettings": "إعدادات عنوان السيرفر", + "HeaderRemoteAccessSettings": "إعدادات الوصول عن بعد", + "HeaderKeepSeries": "حافظ على السلسلة", + "HeaderKeepRecording": "استمر في التسجيل", + "HeaderIdentifyItemHelp": "أدخل معيار بحث واحد أو أكثر. إزالة المعايير لزيادة نتائج البحث.", + "HeaderHttpsSettings": "إعدادات HTTPS", + "HeaderHome": "الصفحة الرئيسية", + "HeaderFetcherSettings": "إعدادات الجلب", + "HeaderFavoritePlaylists": "قوائم التشغيل المفضلة", + "HeaderFavoriteVideos": "مقاطع الفيديو المفضلة", + "HeaderFavoritePeople": "أناس مفضلين", + "HeaderFavoriteMovies": "الأفلام المفضلة", + "HeaderFavoriteBooks": "الكتب المفضلة", + "HeaderExternalIds": "المعرفات الخارجية:", + "HeaderEnabledFieldsHelp": "قم بإلغاء تحديد حقل لقفله ومنع تغيير بياناته.", + "HeaderEnabledFields": "الحقول الممكّنة", + "HeaderEditImages": "تحرير الصور", + "HeaderDVR": "DVR", + "HeaderDownloadSync": "تنزيل ومزامنة", + "HeaderDetectMyDevices": "كشف أجهزتي", + "HeaderDeleteItems": "حذف العناصر", + "HeaderContinueListening": "استمر في الاستماع", + "HeaderConfigureRemoteAccess": "إعدادات الوصول عن بعد", + "HeaderChapterImages": "صور الفصل", + "HeaderCancelSeries": "إلغاء السلسلة", + "HeaderCancelRecording": "إلغاء التسجيل", + "HeaderBlockItemsWithNoRating": "حظر العناصر التي لا تحتوي على معلومات تصنيف أو لم يتم التعرف عليها:", + "HeaderAudioBooks": "الكتب الصوتية", + "HeaderAppearsOn": "يظهر على", + "ApiKeysCaption": "قائمة مفاتيح API الممكّنة حاليًا", + "HeaderAddToPlaylist": "أضف إلى قائمة التشغيل", + "HeaderAddToCollection": "أضف إلى المجموعة", + "HDPrograms": "برامج HD", + "Guide": "الدليل", + "GuestStar": "النجم الضيف", + "GroupVersions": "إصدارات المجموعة", + "GroupBySeries": "تجميع حسب السلسلة", + "Genre": "نوع أدبي", + "General": "الاعدادات العامة", + "FormatValue": "صيغة الملف: {0}", + "Filters": "مرشحات", + "File": "ملف", + "FetchingData": "يتم تنزيل بيانات إضافية", + "Features": "الميزات", + "Favorite": "المفضلة", + "Extras": "مواسم إضافية", + "ErrorDeletingItem": "حدث خطأ في حذف العنصر من سيرفر Jellyfin. يرجى التحقق من أن سيرفر Jellyfin لديه حق الوصول للكتابة إلى مجلد الوسائط وحاول مرة أخرى.", + "Episode": "حلقة", + "EnableThemeVideosHelp": "قم بتشغيل الفيديوهات الرئيسية في الخلفية أثناء تصفح المكتبة.", + "EnableThemeVideos": "الفيديوهات الرئيسية", + "EnableThemeSongsHelp": "قم بتشغيل اللحن الرئيسي في الخلفية أثناء تصفح المكتبة.", + "EnableThemeSongs": "اللحن الرئيسي", + "EnableStreamLoopingHelp": "قم بتمكين هذا إذا كانت عمليات البث المباشر تحتوي فقط على بضع ثوان من البيانات وتحتاج إلى إعادة طلب مستمر. قد يؤدي تمكين هذا عندما لا تكون هناك حاجة إلى مشاكل.", + "EnableStreamLooping": "تكرار البث المباشر", + "EnableHardwareEncoding": "تمكين تشفير الأجهزة", + "EnableExternalVideoPlayersHelp": "سيتم عرض قائمة مشغل خارجي عند بدء تشغيل الفيديو.", + "EnableExternalVideoPlayers": "مشغلات الفيديو الخارجية", + "EnableDisplayMirroring": "اعرض شاشتك على شاشة أخرى", + "EnableColorCodedBackgrounds": "تصنيف الخلفيات حسب اللون", + "EnableCinemaMode": "وضع السينما", + "EnableBackdropsHelp": "اعرض الخلفيات في خلفية بعض الصفحات أثناء تصفح المكتبة.", + "EnableBackdrops": "الخلفيات", + "DownloadsValue": "عدد التنزيلات {0}", + "Download": "تحميل", + "Down": "أسفل", + "DoNotRecord": "لا تسجل", + "DisplayModeHelp": "حدد نمط العرض الذي تريده للواجهة.", + "DisplayMissingEpisodesWithinSeasonsHelp": "يجب تمكين هذا أيضًا لمكتبات التلفزيون في إعدادات السيرفر.", + "DisplayMissingEpisodesWithinSeasons": "عرض الحلقات المفقودة خلال المواسم", + "DisplayInOtherHomeScreenSections": "عرض في أقسام الشاشة الرئيسية مثل أحدث الوسائط واستمر في المشاهدة", + "DisplayInMyMedia": "عرض على الشاشة الرئيسية", + "Display": "عرض", + "Dislike": "لم يعجبنى" } From 1f8e753592f2d6549426268478f95078912258c3 Mon Sep 17 00:00:00 2001 From: danieladov Date: Thu, 21 May 2020 11:36:12 +0000 Subject: [PATCH 029/181] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/es.json b/src/strings/es.json index f2921c32bc..87582a3bb6 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1521,5 +1521,7 @@ "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes de HTTP hacia HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", "LabelRequireHttps": "Necesita HTTPS", "LabelEnableHttpsHelp": "Permite que el servidor escuche en el puesto HTTPS configurado. También se debe configurar un certificado válido para que esto surta efecto.", - "LabelEnableHttps": "Activar HTTPS" + "LabelEnableHttps": "Activar HTTPS", + "TabDVR": "DVR", + "SaveChanges": "Guardar cambios" } From 2d03e8714c4f813880e98a4098153921b24357c7 Mon Sep 17 00:00:00 2001 From: millallo Date: Thu, 21 May 2020 09:12:23 +0000 Subject: [PATCH 030/181] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/it.json b/src/strings/it.json index e698a629b5..cd9ed65536 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1516,5 +1516,8 @@ "LabelEnableHttps": "Abilita HTTPS", "HeaderServerAddressSettings": "Configurazione Indirizzo Server", "HeaderRemoteAccessSettings": "Configurazione Access Remoto", - "HeaderHttpsSettings": "Configurazione HTTPS" + "HeaderHttpsSettings": "Configurazione HTTPS", + "TabDVR": "DVR", + "SaveChanges": "Salva modifiche", + "HeaderDVR": "DVR" } From de1a79d0fec0af3c92f862e8b0f922a5b6010cf5 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Thu, 21 May 2020 11:09:52 +0000 Subject: [PATCH 031/181] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index 545500d0c6..7cb9f980e6 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -1516,5 +1516,8 @@ "LabelEnableHttps": "Activați HTTPS", "HeaderServerAddressSettings": "Setările adresei serverului", "HeaderRemoteAccessSettings": "Setări pentru aces remote", - "HeaderHttpsSettings": "Setări https" + "HeaderHttpsSettings": "Setări https", + "TabDVR": "DVR", + "SaveChanges": "Salvează modificările", + "HeaderDVR": "DVR" } From af4e3c6c6995d5f818323d767c8ed7e39ec053d4 Mon Sep 17 00:00:00 2001 From: fatbill27 Date: Thu, 21 May 2020 07:31:08 +0000 Subject: [PATCH 032/181] Translated using Weblate (Chinese (Hong Kong)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hant_HK/ --- src/strings/zh-hk.json | 43 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/strings/zh-hk.json b/src/strings/zh-hk.json index 2d9634d333..97b2d2d1f7 100644 --- a/src/strings/zh-hk.json +++ b/src/strings/zh-hk.json @@ -286,7 +286,7 @@ "TabAdvanced": "進階", "TabAlbumArtists": "唱片歌手", "TabAlbums": "專輯", - "TabArtists": "歌手", + "TabArtists": "藝人", "TabCatalog": "目錄", "TabChannels": "頻道", "TabCollections": "藏品", @@ -339,7 +339,7 @@ "Anytime": "任何時間", "AnyLanguage": "任何語言", "Artists": "藝人", - "AsManyAsPossible": "盡可能地越多越好", + "AsManyAsPossible": "越多越好", "Audio": "音頻", "Auto": "自動", "AutoBasedOnLanguageSetting": "自動 (基於語言設定)", @@ -359,15 +359,48 @@ "HeaderFavoriteSongs": "最愛的歌曲", "HeaderFavoriteShows": "最愛的節目", "HeaderFavoriteEpisodes": "最愛的劇集", - "HeaderFavoriteArtists": "最愛藝術家", + "HeaderFavoriteArtists": "最愛的藝人", "HeaderFavoriteAlbums": "最愛專輯", "HeaderContinueWatching": "繼續觀看", - "HeaderAlbumArtists": "專輯藝術家", + "HeaderAlbumArtists": "專輯藝人", "Genres": "風格", "Folders": "檔案夾", "Favorites": "我的最愛", "Collections": "合輯", "Channels": "頻道", "Books": "圖書", - "Albums": "專輯" + "Albums": "專輯", + "Absolute": "絕對", + "AuthProviderHelp": "選擇用於驗證該用戶密碼的身份驗證提供程序。", + "AttributeNew": "新", + "AspectRatio": "長寬比", + "AskAdminToCreateLibrary": "要求管理員創建一個庫。", + "Ascending": "上升", + "Artist": "藝人", + "Art": "藝術", + "AroundTime": "大約{0}", + "AlwaysPlaySubtitlesHelp": "無論語言是哪種音頻,都將加載與語言首選項匹配的字幕。", + "AllowedRemoteAddressesHelp": "IP地址或IP /網絡掩碼條目的逗號分隔列表,用於允許遠程連接的網絡。 如果保留為空白,將允許所有遠程地址。", + "AllowRemoteAccessHelp": "如果未選中,則將阻止所有遠程連接。", + "AllowRemoteAccess": "允許與此Jellyfin服務器的遠程連接。", + "AllowFfmpegThrottlingHelp": "當轉碼或remux距離當前播放位置足夠遠時,請暫停該過程,以減少資源消耗。 在不經常觀看的情況下,此功能最為有用。 如果遇到播放問題,請關閉此功能。", + "AllowOnTheFlySubtitleExtractionHelp": "可以從視頻中提取嵌入式字幕,然後以純文本格式將其交付給客戶端,以幫助防止視頻轉碼。 在某些系統上,這可能需要很長時間,並且會導致提取過程中視頻播放停止。 如果客戶端設備本身不支持嵌入的字幕,則可以禁用此選項以通過視頻轉碼刻錄字幕。", + "AllowOnTheFlySubtitleExtraction": "允許即時提取字幕", + "AllowMediaConversionHelp": "授予或拒絕訪問轉換媒體功能的權限。", + "AllowMediaConversion": "允許媒體轉換", + "AllowHWTranscodingHelp": "允許調諧器即時轉碼流。 這可以幫助減少服務器所需的代碼轉換。", + "AllLibraries": "所有媒體庫", + "AllEpisodes": "所有劇集", + "AllComplexFormats": "所有格式(ASS,SSA,VOBSUB,PGS,SUB,IDX等)", + "AllChannels": "所有頻道", + "Alerts": "警報", + "AlbumArtist": "專輯歌手", + "Album": "專輯", + "Aired": "已播出", + "AirDate": "播出日期", + "AdditionalNotificationServices": "瀏覽插件目錄以安裝其他通知服務。", + "AddToPlayQueue": "添加到播放列", + "AddToCollection": "添加到收藏", + "AddItemToCollectionHelp": "通過搜索項目並使用右鍵單擊或點擊菜單將其添加到集合中,從而將它們添加到集合中。", + "AccessRestrictedTryAgainLater": "目前限制訪問。 請稍後再試。" } From 1631ac7721a8b7e523764acce7310d3b8f4f94aa Mon Sep 17 00:00:00 2001 From: Franco Castillo Date: Thu, 21 May 2020 19:56:12 +0000 Subject: [PATCH 033/181] Translated using Weblate (Spanish (Argentina)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_AR/ --- src/strings/es-ar.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index b45a7e8ca8..2fc290c66c 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -649,5 +649,14 @@ "HeaderHttpsSettings": "Configuraciones HTTPS", "HeaderEnabledFieldsHelp": "Desmarque un campo para bloquearlo y evitar que se modifiquen sus datos.", "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", - "ApiKeysCaption": "Lista de las claves de API habilitadas actualmente" + "ApiKeysCaption": "Lista de las claves de API habilitadas actualmente", + "LabelArtists": "Artistas:", + "LabelAppName": "Nombre de la aplicación", + "LabelAllowedRemoteAddressesMode": "Modo de filtro de dirección IP remota:", + "LabelAlbum": "Álbum:", + "LabelAirTime": "Tiempo al aire:", + "LabelAirDays": "Días al aire:", + "LabelAccessStart": "Hora de inicio:", + "LabelAccessEnd": "Hora de finalización:", + "HeaderDVR": "DVR" } From eeab297da8f21174909ab06f055c0fa39272ffe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93skar=20Freyr?= Date: Thu, 21 May 2020 17:11:05 +0000 Subject: [PATCH 034/181] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/is/ --- src/strings/is-is.json | 127 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/src/strings/is-is.json b/src/strings/is-is.json index ee0498679c..6c8a559ff9 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -93,7 +93,7 @@ "AllowOnTheFlySubtitleExtraction": "Leyfa að taka út texta á meðan það er í keyrslu", "AllowRemoteAccess": "Leyfa fjartengingar í þennan Jellyfin þjón.", "AllowRemoteAccessHelp": "Ef þetta er afhakað, allar fjartengingar, þ.e. í gegnum internetið, verða bannaðar.", - "AlwaysPlaySubtitles": "Alltaf spila texta", + "AlwaysPlaySubtitles": "Spila alltaf", "AnyLanguage": "Öll tungumál", "AroundTime": "Um {0}", "Art": "List", @@ -165,8 +165,8 @@ "HeaderProfile": "Prófíll", "HeaderPeople": "Fólk", "HeaderPassword": "Lykilorð", - "HeaderLatestMovies": "Nýjustu Kvikmyndir", - "HeaderLatestEpisodes": "Nýjustu Þættirnir", + "HeaderLatestMovies": "Kvikmyndir, nýlega bætt við", + "HeaderLatestEpisodes": "Þættir, nýlega bætt við", "HeaderHome": "Heim", "HeaderFavoriteVideos": "Uppáhalds Myndbönd", "HeaderFavoriteMovies": "Uppáhalds Kvikmyndir", @@ -282,8 +282,125 @@ "AllowedRemoteAddressesHelp": "Kommu aðskilinn listi yfir ip tölur eða ip-númeramát fyrir net sem mega fjartengjas. Ef þetta er autt eru allar fjartengingar leyfðar.", "AllowHWTranscodingHelp": "Leyfa viðtæki að umbreyta straumi í rauntíma.Þetta getur minnkað álag á þjón.", "ValueSpecialEpisodeName": "Sérstakt - {0}", - "Shows": "Þættir", + "Shows": "Sýningar", "Playlists": "Spilunarlisti", "ButtonScanAllLibraries": "Skanna Öll Gagnasöfn", - "AllLibraries": "Öll gagnasöfn" + "AllLibraries": "Öll gagnasöfn", + "RefreshMetadata": "Endurhlaða lýsigögn", + "Refresh": "Endurhlaða", + "ReleaseDate": "Útgáfudagur", + "RememberMe": "Muna eftir mér", + "RepeatAll": "Endurtaka allt", + "Repeat": "Endurtaka", + "RemoveFromPlaylist": "Fjarlægja úr spilunarlista", + "RemoveFromCollection": "Fjarlægja úr safni", + "HeaderHttpsSettings": "HTTPS Stillingar", + "HeaderFavoriteBooks": "Uppáhalds Bækur", + "HeaderEditImages": "Breyta ljósmyndum", + "HeaderContinueListening": "Halda áfram að hlusta", + "ChannelNumber": "Númer rásar", + "ChannelNameOnly": "Aðeins rás {0}", + "ButtonSubmit": "Senda", + "ButtonShutdown": "Slökkva á netþjón", + "EnableThemeVideosHelp": "Spila þema myndbönd í bakgrunni þegar gagnasafn er skoðað.", + "EnableThemeSongsHelp": "Spila þema lög í bakgrunni þegar gagnasafn er skoðað.", + "FormatValue": "Snið: {0}", + "Genre": "Tegund", + "HeaderActiveDevices": "Virk Tæki", + "HeaderAddToCollection": "Bæta í Safn", + "HeaderAddUpdateImage": "Bæta við/uppfæra ljósmynd", + "HeaderAddToPlaylist": "Bæta við á Spilunarlista", + "HeaderAlert": "Viðvörun", + "HeaderAppearsOn": "Birtist á", + "HeaderChannels": "Rásir", + "HeaderDetectMyDevices": "Finna tækin mín", + "HeaderFavoritePeople": "Uppáhalds Fólk", + "HeaderFavoritePlaylists": "Uppáhalds spilunarlistar", + "HeaderFilters": "Síur", + "HeaderForgotPassword": "Gleymt lykilorð", + "HeaderForKids": "Fyrir Krakka", + "HeaderFrequentlyPlayed": "Oft Spilað", + "HeaderGenres": "Flokkar", + "HeaderLatestMusic": "Tónlist, nýlega bætt við", + "HeaderMetadataSettings": "Stillingar lýsigagna", + "HeaderMedia": "Margmiðlunarsafn", + "HeaderLiveTv": "Sjónvarp í beinni útsendingu", + "HeaderLoginFailure": "Innskráning Mistókst", + "HeaderMyDevice": "Tækið mitt", + "HeaderMusicVideos": "Tónlistarmyndbönd", + "HeaderMusicQuality": "Tónlistargæði", + "HeaderMovies": "Kvikmyndir", + "HeaderNewDevices": "Ný tæki", + "HeaderPasswordReset": "Endurstilla Lykilorð", + "HeaderPhotoAlbums": "Myndaalbúm", + "OnApplicationStartup": "Við ræsingu forrits", + "EveryXHours": "Hverjum {0} klukkustundum", + "EveryHour": "Hverja klukkustund", + "Episode": "Þáttur", + "EnableThemeVideos": "Þema myndbönd", + "EnableThemeSongs": "Þema lög", + "EnablePhotos": "Birta myndir", + "EnableHardwareEncoding": "Virkja vélbúnaðarkóðun", + "LabelScreensaver": "Skjáhvíla:", + "LabelRequireHttps": "Krefjast HTTPS", + "LabelReleaseDate": "Útgáfudagur:", + "LabelReasonForTranscoding": "Ástæða fyrir umkóðun:", + "LabelPlayMethod": "Spilunaraðferð:", + "LabelPlaylist": "Spilunarlisti:", + "LabelPlayer": "Spilari:", + "LabelPlayDefaultAudioTrack": "Spila sjálfgefna hljóðrás óháð tungumáli", + "LabelPlaceOfBirth": "Fæðingarstaður:", + "LabelPersonRoleHelp": "Dæmi: Ísbílstjóri", + "LabelPersonRole": "Hlutverk:", + "LabelPath": "Slóð:", + "LabelPasswordRecoveryPinCode": "PIN númer:", + "LabelPasswordConfirm": "Lykilorð (staðfesta):", + "LabelPassword": "Lykilorð:", + "LabelOverview": "Yfirlit:", + "LabelOriginalTitle": "Upphaflegur Titill:", + "LabelOriginalAspectRatio": "Upprunalegt skjáhlutfall:", + "LabelNumber": "Númer:", + "LabelNotificationEnabled": "Virkja þessa tilkynningu", + "LabelNewsCategories": "Frétta flokkar:", + "LabelNewPasswordConfirm": "Staðfesta nýtt lykilorð:", + "LabelNewPassword": "Nýtt lykilorð:", + "LabelNewName": "Nýtt nafn:", + "LabelName": "Nafn:", + "LabelLanNetworks": "LAN net:", + "MessageImageFileTypeAllowed": "Aðeins JPEG og PNG skrár eru studdar.", + "DrmChannelsNotImported": "Rásir með DRM verða ekki fluttar inn.", + "DoNotRecord": "Ekki taka upp", + "DisplayModeHelp": "Veldu útlit fyrir viðmótið.", + "DisplayMissingEpisodesWithinSeasons": "Birta þætti sem vantar inn í þáttaraðir", + "DisplayInMyMedia": "Birta á heimaskjá", + "Display": "Birta", + "Dislike": "Mislíka", + "Disabled": "Óvirkt", + "Directors": "Leikstjórar", + "DirectStreamHelp2": "Beint streymi á skrá notar mjög litið vinnsluafl án þess að tapa myndgæðum.", + "Descending": "Niður", + "DeleteImageConfirmation": "Ertu viss um að þú viljir eyða þessari mynd?", + "DefaultErrorMessage": "Villa varð við vinnslu beiðninnar. Reyndu aftur síðar.", + "DeathDateValue": "Dó: {0}", + "DatePlayed": "Dagsetning spilað", + "DateAdded": "Dagsetning bætt við", + "CriticRating": "Einkunn gagnrýnanda", + "CopyStreamURLError": "Villa varð við afritun vefslóðar.", + "CopyStreamURLSuccess": "Afrit af vefslóð tókst.", + "CopyStreamURL": "Afrita vefslóð streymis", + "Continuing": "Áframhaldandi", + "ConfirmEndPlayerSession": "Langar þig að slökkva á Jellyfin á {0}?", + "ConfirmDeletion": "Staðfesta eyðingu", + "ConfirmDeleteItem": "Ef þessari skrá er eytt verður hún fjarlægð úr bæði skráarkerfinu og miðlasafninu. Ertu viss um að þú viljir halda áfram?", + "Composer": "Tónskáld", + "ClientSettings": "Stillingar biðlara", + "ButtonTogglePlaylist": "Spilunarlisti", + "ButtonToggleContextMenu": "Meira", + "ButtonSplit": "Skipta", + "ButtonStop": "Stöðva", + "ButtonResetEasyPassword": "Endurstilla Easy PIN númer", + "ButtonRefreshGuideData": "Uppfæra sjónvarpsþáttagögn", + "Artist": "Listamaður", + "AllowFfmpegThrottling": "Takmarka Umkóðun", + "Album": "Albúm" } From 903278b380abbf15655c13055d6961d9f2d2151a Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Thu, 21 May 2020 23:34:03 +0000 Subject: [PATCH 035/181] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 7116c2df19..e1017a20b8 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -1520,5 +1520,6 @@ "HeaderServerAddressSettings": "Nastavenie adresy servera", "HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu", "HeaderHttpsSettings": "Nastavenia HTTPS", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "SaveChanges": "Uložiť zmeny" } From eac572ef7ad531315b4919190de9b585a5b56aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93skar=20Freyr?= Date: Thu, 21 May 2020 23:42:54 +0000 Subject: [PATCH 036/181] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/is/ --- src/strings/is-is.json | 70 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/strings/is-is.json b/src/strings/is-is.json index 6c8a559ff9..a69f9576ed 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -46,7 +46,7 @@ "OptionContinuing": "Heldur áfram", "OptionBlockTvShows": "Sjónvarpsþættir", "OptionBlockMusic": "Tónlist", - "OptionBlockTrailers": "Stiklur", + "OptionBlockTrailers": "Sýnishorn", "AllowOnTheFlySubtitleExtractionHelp": "Hægt er að sækja texta sem eru innbyggðir í myndaskrá og senda þá beint til notanda á textaformi til þess að sleppa við að umbreyta (transcode) myndaskránni. Í sumum tölvum getur þetta tekið langan tíma og valdið hikstum á meðan verið er að sækja textan. Afvirkjaðu þetta til þess að láta alla texta vera brennda inn í myndaskránna ef tæki notenda styður ekki að spila skránna beint.", "AccessRestrictedTryAgainLater": "Aðgangur bannaður í augnablikinu. Vinsamlegast reynið síðar.", "Actor": "Leikari", @@ -217,7 +217,7 @@ "ButtonViewWebsite": "Skoða vefsíðu", "ButtonUp": "Upp", "ButtonUninstall": "Fjarlægja", - "ButtonTrailer": "Stikla", + "ButtonTrailer": "Sýnishorn", "ButtonSubtitles": "Texti", "ButtonSort": "Flokka", "ButtonSignIn": "Innskráning", @@ -402,5 +402,69 @@ "ButtonRefreshGuideData": "Uppfæra sjónvarpsþáttagögn", "Artist": "Listamaður", "AllowFfmpegThrottling": "Takmarka Umkóðun", - "Album": "Albúm" + "Album": "Plata", + "SettingsSaved": "Stillingar vistaðar.", + "Settings": "Stillingar", + "Series": "Seríur", + "SendMessage": "Senda skilaboð", + "SelectAdminUsername": "Veldu notandanafn fyrir stjórnanda aðganginn þinn.", + "Season": "Sería", + "SearchResults": "Leitarniðurstöður", + "SearchForSubtitles": "Leita að skjátexta", + "Search": "Leita", + "Screenshots": "Skjámyndir", + "Screenshot": "Skjámynd", + "ScanLibrary": "Skanna gagnasafn", + "SaveSubtitlesIntoMediaFolders": "Vista skjátexta í miðlamöppur", + "SaveChanges": "Vista breytingar", + "Save": "Vista", + "Saturday": "Laugardagur", + "RunAtStartup": "Keyra við ræsingu", + "Rewind": "Spóla til baka", + "AlbumArtist": "Höfundur plötu", + "OptionHasTrailer": "Sýnishorn", + "ViewArtist": "Skoða listamann", + "ValueSongCount": "{0} lög", + "ValueSeriesCount": "{0} Þáttaraðir", + "ValueSeconds": "{0} sekúndur", + "ValueOneSong": "1 lag", + "ValueOneSeries": "1 Þáttaröð", + "ValueOneMusicVideo": "1 tónlistarmyndband", + "ValueOneMovie": "1 kvikmynd", + "ValueOneEpisode": "1 þáttur", + "Up": "Upp", + "Unplayed": "Óspilað", + "UninstallPluginHeader": "Fjarlægja Viðbót", + "Tuesday": "Þriðjudagur", + "Transcoding": "Umkóðun", + "Trailers": "Sýnishorn", + "TitlePlayback": "Spilun", + "Thursday": "Fimmtudagur", + "ThemeVideos": "Þemu myndbönd", + "ThemeSongs": "Þemu lög", + "TellUsAboutYourself": "Segðu okkur frá sjálfum þér", + "TabUsers": "Notendur", + "TabUpcoming": "Væntanlegt", + "TabTranscoding": "Umkóðun", + "TabTrailers": "Sýnishorn", + "TabSuggestions": "Tillögur", + "TabSongs": "Lög", + "TabResumeSettings": "Halda áfram", + "TabProfile": "Prófíll", + "TabPlugins": "Viðbætur", + "TabOther": "Annað", + "TabNetworks": "Netkerfi", + "TabMyPlugins": "Mínar viðbætur", + "TabMusicVideos": "Tónlistarmyndbönd", + "TabMusic": "Tónlist", + "TabMovies": "Kvikmyndir", + "PleaseRestartServerName": "Vinsamlegast endurræstu Jellyfin netþjóninn - {0}.", + "Previous": "Fyrri", + "Premiere": "Frumsýning", + "Producer": "Framleiðandi", + "Quality": "Gæði", + "RecentlyWatched": "Nýlega horft á", + "RecommendationBecauseYouLike": "Af því að þér líkar {0}", + "RecommendationBecauseYouWatched": "Af því að þú horfðir á {0}", + "RecommendationDirectedBy": "Leikstýrt af {0}" } From 494c201aaaebd2dc20c48fd908aacdb42ffed8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93skar=20Freyr?= Date: Fri, 22 May 2020 00:19:18 +0000 Subject: [PATCH 037/181] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/is/ --- src/strings/is-is.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/strings/is-is.json b/src/strings/is-is.json index a69f9576ed..fa3a826764 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -466,5 +466,14 @@ "RecentlyWatched": "Nýlega horft á", "RecommendationBecauseYouLike": "Af því að þér líkar {0}", "RecommendationBecauseYouWatched": "Af því að þú horfðir á {0}", - "RecommendationDirectedBy": "Leikstýrt af {0}" + "RecommendationDirectedBy": "Leikstýrt af {0}", + "SortChannelsBy": "Raða rásum eftir:", + "SortByValue": "Raða eftir {0}", + "Sort": "Raða", + "Filter": "Sía", + "New": "Nýtt", + "Shuffle": "Stokka", + "ShowYear": "Sýna ár", + "ShowTitle": "Sýna titil", + "Share": "Deila" } From 5b2837f1377a5a0a53b1ee1c9f6f8b4f5f494c1e Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 22 May 2020 10:39:21 +0900 Subject: [PATCH 038/181] use a class and imports for the book player --- package.json | 1 + src/components/bookPlayer/plugin.js | 92 ++++++++++++++++------------- src/components/pluginManager.js | 2 +- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index a24030de0d..de95105298 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "src/components/playback/mediasession.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/components/bookPlayer/plugin.js", "src/scripts/dfnshelper.js", "src/scripts/dom.js", "src/scripts/filesystem.js", diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 1abbf778c8..62fbd1a593 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -1,35 +1,44 @@ -define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavigation', 'dialogHelper', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (connectionManager, dom, loading, playbackManager, keyboardnavigation, dialogHelper, appHost) { - 'use strict'; +import connectionManager from 'connectionManager'; +import dom from 'dom'; +import loading from 'loading'; +import playbackManager from 'playbackManager'; +import keyboardnavigation from 'keyboardnavigation'; +import dialogHelper from 'dialogHelper'; +import appHost from 'apphost'; +import 'css!./style'; +import 'material-icons'; +import 'paper-icon-button-light'; - function BookPlayer() { - let self = this; +/* eslint-disable indent */ +export class BookPlayer { + constructor() { + this.name = 'Book Player'; + this.type = 'mediaplayer'; + this.id = 'bookplayer'; + this.priority = 1; + } - self.name = 'Book Player'; - self.type = 'mediaplayer'; - self.id = 'bookplayer'; - self.priority = 1; - - self.play = function (options) { + play(options) { loading.show(); - let elem = createMediaElement(); - return setCurrentSrc(elem, options); - }; + let elem = this.createMediaElement(); + return this.setCurrentSrc(elem, options); + } - self.stop = function () { - let elem = self._mediaElement; - let rendition = self._rendition; + stop() { + let elem = this._mediaElement; + let rendition = this._rendition; if (elem && rendition) { rendition.destroy(); elem.remove(); - self._mediaElement = null; + this._mediaElement = null; } - }; + } - function onWindowKeyUp(e) { + onWindowKeyUp(e) { let key = keyboardnavigation.getKeyName(e); - let rendition = self._rendition; + let rendition = this._rendition; let book = rendition.book; switch (key) { @@ -44,19 +53,19 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); break; case 'Escape': - dialogHelper.close(self._mediaElement); - if (self._tocElement) { - dialogHelper.close(self._tocElement); + dialogHelper.close(this._mediaElement); + if (this._tocElement) { + dialogHelper.close(this._tocElement); } break; } } - function onDialogClosed() { - self.stop(); + onDialogClosed() { + this.stop(); } - function replaceLinks(contents, f) { + replaceLinks(contents, f) { let links = contents.querySelectorAll('a[href]'); links.forEach((link) => { @@ -69,8 +78,8 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig }); } - function createMediaElement() { - let elem = self._mediaElement; + createMediaElement() { + let elem = this._mediaElement; if (elem) { return elem; @@ -104,7 +113,7 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig }); elem.querySelector('.btnBookplayerToc').addEventListener('click', function () { - let rendition = self._rendition; + let rendition = this._rendition; if (rendition) { let tocElement = dialogHelper.createDialog({ size: 'small', @@ -131,13 +140,13 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig dialogHelper.close(tocElement); }); - replaceLinks(tocElement, (href) => { + this.replaceLinks(tocElement, (href) => { let relative = rendition.book.path.relative(href); rendition.display(relative); dialogHelper.close(tocElement); }); - self._tocElement = tocElement; + this._tocElement = tocElement; dialogHelper.open(tocElement); } @@ -145,18 +154,19 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig dialogHelper.open(elem); - elem.addEventListener('close', onDialogClosed); + elem.addEventListener('close', this.onDialogClosed.bind(this)); } - self._mediaElement = elem; + this._mediaElement = elem; return elem; } - function setCurrentSrc(elem, options) { + setCurrentSrc(elem, options) { let serverId = options.items[0].ServerId; let apiClient = connectionManager.getApiClient(serverId); + const self = this; return new Promise(function (resolve, reject) { require(['epubjs'], function (epubjs) { let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); @@ -168,9 +178,9 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig self._rendition = rendition; return rendition.display().then(function () { - document.addEventListener('keyup', onWindowKeyUp); + document.addEventListener('keyup', self.onWindowKeyUp.bind(self)); // FIXME: I don't really get why document keyup event is not triggered when epub is in focus - self._rendition.on('keyup', onWindowKeyUp); + self._rendition.on('keyup', self.onWindowKeyUp.bind(self)); loading.hide(); @@ -182,11 +192,11 @@ define(['connectionManager', 'dom', 'loading', 'playbackManager', 'keyboardnavig }); }); } - } - BookPlayer.prototype.canPlayMediaType = function (mediaType) { + canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'book'; - }; + } +} - return BookPlayer; -}); +/* eslint-enable indent */ +export default BookPlayer; diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index 6cb56d767b..fd35d344bf 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -58,7 +58,7 @@ define(['events', 'globalize'], function (events, globalize) { return new Promise(function (resolve, reject) { require([pluginSpec], (pluginFactory) => { - var plugin = new pluginFactory(); + var plugin = pluginFactory.default ? new pluginFactory.default() : new pluginFactory(); // See if it's already installed var existing = instance.pluginsList.filter(function (p) { From 512f33a7fb30b2f36eb214cc78d2724788d9405f Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 22 May 2020 10:42:58 +0900 Subject: [PATCH 039/181] fix all indentation issues in book player --- src/components/bookPlayer/plugin.js | 324 ++++++++++++++-------------- 1 file changed, 161 insertions(+), 163 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 62fbd1a593..557e3aa427 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -9,194 +9,192 @@ import 'css!./style'; import 'material-icons'; import 'paper-icon-button-light'; -/* eslint-disable indent */ export class BookPlayer { - constructor() { - this.name = 'Book Player'; - this.type = 'mediaplayer'; - this.id = 'bookplayer'; - this.priority = 1; + constructor() { + this.name = 'Book Player'; + this.type = 'mediaplayer'; + this.id = 'bookplayer'; + this.priority = 1; + } + + play(options) { + loading.show(); + let elem = this.createMediaElement(); + return this.setCurrentSrc(elem, options); + } + + stop() { + let elem = this._mediaElement; + let rendition = this._rendition; + + if (elem && rendition) { + rendition.destroy(); + + elem.remove(); + this._mediaElement = null; } + } - play(options) { - loading.show(); - let elem = this.createMediaElement(); - return this.setCurrentSrc(elem, options); + onWindowKeyUp(e) { + let key = keyboardnavigation.getKeyName(e); + let rendition = this._rendition; + let book = rendition.book; + + switch (key) { + case 'l': + case 'ArrowRight': + case 'Right': + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + break; + case 'j': + case 'ArrowLeft': + case 'Left': + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + break; + case 'Escape': + dialogHelper.close(this._mediaElement); + if (this._tocElement) { + dialogHelper.close(this._tocElement); + } + break; } + } - stop() { - let elem = this._mediaElement; - let rendition = this._rendition; + onDialogClosed() { + this.stop(); + } - if (elem && rendition) { - rendition.destroy(); + replaceLinks(contents, f) { + let links = contents.querySelectorAll('a[href]'); - elem.remove(); - this._mediaElement = null; - } - } + links.forEach((link) => { + let href = link.getAttribute('href'); - onWindowKeyUp(e) { - let key = keyboardnavigation.getKeyName(e); - let rendition = this._rendition; - let book = rendition.book; + link.onclick = function () { + f(href); + return false; + }; + }); + } - switch (key) { - case 'l': - case 'ArrowRight': - case 'Right': - book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); - break; - case 'j': - case 'ArrowLeft': - case 'Left': - book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); - break; - case 'Escape': - dialogHelper.close(this._mediaElement); - if (this._tocElement) { - dialogHelper.close(this._tocElement); - } - break; - } - } - - onDialogClosed() { - this.stop(); - } - - replaceLinks(contents, f) { - let links = contents.querySelectorAll('a[href]'); - - links.forEach((link) => { - let href = link.getAttribute('href'); - - link.onclick = function () { - f(href); - return false; - }; - }); - } - - createMediaElement() { - let elem = this._mediaElement; - - if (elem) { - return elem; - } - - elem = document.getElementById('bookPlayer'); - - if (!elem) { - elem = dialogHelper.createDialog({ - exitAnimationDuration: 400, - size: 'fullscreen', - autoFocus: false, - scrollY: false, - exitAnimation: 'fadeout', - removeOnClose: true - }); - elem.id = 'bookPlayer'; - - let html = ''; - html += '
'; - html += ''; - html += '
'; - html += '
'; - html += ''; - html += '
'; - - elem.innerHTML = html; - - elem.querySelector('.btnBookplayerExit').addEventListener('click', function () { - dialogHelper.close(elem); - }); - - elem.querySelector('.btnBookplayerToc').addEventListener('click', function () { - let rendition = this._rendition; - if (rendition) { - let tocElement = dialogHelper.createDialog({ - size: 'small', - autoFocus: false, - removeOnClose: true - }); - tocElement.id = 'dialogToc'; - - let tocHtml = '
'; - tocHtml += ''; - tocHtml += '
'; - tocHtml += '
    '; - rendition.book.navigation.forEach((chapter) => { - tocHtml += '
  • '; - // Remove '../' from href - let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; - tocHtml += `${chapter.label}`; - tocHtml += '
  • '; - }); - tocHtml += '
'; - tocElement.innerHTML = tocHtml; - - tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', function () { - dialogHelper.close(tocElement); - }); - - this.replaceLinks(tocElement, (href) => { - let relative = rendition.book.path.relative(href); - rendition.display(relative); - dialogHelper.close(tocElement); - }); - - this._tocElement = tocElement; - - dialogHelper.open(tocElement); - } - }); - - dialogHelper.open(elem); - - elem.addEventListener('close', this.onDialogClosed.bind(this)); - } - - this._mediaElement = elem; + createMediaElement() { + let elem = this._mediaElement; + if (elem) { return elem; } - setCurrentSrc(elem, options) { - let serverId = options.items[0].ServerId; - let apiClient = connectionManager.getApiClient(serverId); + elem = document.getElementById('bookPlayer'); - const self = this; - return new Promise(function (resolve, reject) { - require(['epubjs'], function (epubjs) { - let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); - self._currentSrc = downloadHref; + if (!elem) { + elem = dialogHelper.createDialog({ + exitAnimationDuration: 400, + size: 'fullscreen', + autoFocus: false, + scrollY: false, + exitAnimation: 'fadeout', + removeOnClose: true + }); + elem.id = 'bookPlayer'; - let book = epubjs.default(downloadHref, {openAs: 'epub'}); + let html = ''; + html += '
'; + html += ''; + html += '
'; + html += '
'; + html += ''; + html += '
'; - let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); - self._rendition = rendition; + elem.innerHTML = html; - return rendition.display().then(function () { - document.addEventListener('keyup', self.onWindowKeyUp.bind(self)); - // FIXME: I don't really get why document keyup event is not triggered when epub is in focus - self._rendition.on('keyup', self.onWindowKeyUp.bind(self)); + elem.querySelector('.btnBookplayerExit').addEventListener('click', function () { + dialogHelper.close(elem); + }); - loading.hide(); - - return resolve(); - }, function () { - console.error('Failed to display epub'); - return reject(); + elem.querySelector('.btnBookplayerToc').addEventListener('click', function () { + let rendition = this._rendition; + if (rendition) { + let tocElement = dialogHelper.createDialog({ + size: 'small', + autoFocus: false, + removeOnClose: true }); + tocElement.id = 'dialogToc'; + + let tocHtml = '
'; + tocHtml += ''; + tocHtml += '
'; + tocHtml += '
    '; + rendition.book.navigation.forEach((chapter) => { + tocHtml += '
  • '; + // Remove '../' from href + let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + tocHtml += `${chapter.label}`; + tocHtml += '
  • '; + }); + tocHtml += '
'; + tocElement.innerHTML = tocHtml; + + tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', function () { + dialogHelper.close(tocElement); + }); + + this.replaceLinks(tocElement, (href) => { + let relative = rendition.book.path.relative(href); + rendition.display(relative); + dialogHelper.close(tocElement); + }); + + this._tocElement = tocElement; + + dialogHelper.open(tocElement); + } + }); + + dialogHelper.open(elem); + + elem.addEventListener('close', this.onDialogClosed.bind(this)); + } + + this._mediaElement = elem; + + return elem; + } + + setCurrentSrc(elem, options) { + let serverId = options.items[0].ServerId; + let apiClient = connectionManager.getApiClient(serverId); + + const self = this; + return new Promise(function (resolve, reject) { + require(['epubjs'], function (epubjs) { + let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); + self._currentSrc = downloadHref; + + let book = epubjs.default(downloadHref, {openAs: 'epub'}); + + let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + self._rendition = rendition; + + return rendition.display().then(function () { + document.addEventListener('keyup', self.onWindowKeyUp.bind(self)); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus + self._rendition.on('keyup', self.onWindowKeyUp.bind(self)); + + loading.hide(); + + return resolve(); + }, function () { + console.error('Failed to display epub'); + return reject(); }); }); - } + }); + } canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'book'; } } -/* eslint-enable indent */ export default BookPlayer; From f7f778b3532bd288e68f8f2e17bb8241a98030c8 Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 22 May 2020 10:55:34 +0900 Subject: [PATCH 040/181] fix toc button for new book player class --- src/components/bookPlayer/plugin.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 557e3aa427..9b33ee5ab6 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -149,7 +149,7 @@ export class BookPlayer { dialogHelper.open(tocElement); } - }); + }.bind(this)); dialogHelper.open(elem); @@ -169,20 +169,18 @@ export class BookPlayer { return new Promise(function (resolve, reject) { require(['epubjs'], function (epubjs) { let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); - self._currentSrc = downloadHref; - let book = epubjs.default(downloadHref, {openAs: 'epub'}); - let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); - self._rendition = rendition; + self._currentSrc = downloadHref; + self._rendition = rendition; return rendition.display().then(function () { document.addEventListener('keyup', self.onWindowKeyUp.bind(self)); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus self._rendition.on('keyup', self.onWindowKeyUp.bind(self)); loading.hide(); - return resolve(); }, function () { console.error('Failed to display epub'); From 9981430f30b5413b816006c2170155d58489e872 Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 22 May 2020 00:42:50 +0000 Subject: [PATCH 041/181] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 48 +++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 39ac284514..c24d2db275 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -15,18 +15,18 @@ "Alerts": "Alertas", "All": "Todo", "AllChannels": "Todos los canales", - "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX...)", "AllEpisodes": "Todos los episodios", "AllLanguages": "Todos los idiomas", "AllLibraries": "Todas las bibliotecas", - "AllowHWTranscodingHelp": "Permite que el sintonizador transcodifique las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllowHWTranscodingHelp": "Permite al sintonizador transcodificar las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", "AllowMediaConversion": "Permitir conversión de medios", "AllowMediaConversionHelp": "Permitir o denegar acceso a la función de convertir medios.", "AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha", "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", - "AllowRemoteAccess": "Permitir conexiones remotas a este Servidor Jellyfin.", - "AllowRemoteAccessHelp": "Si se deshabilita, todas las conexiones remotas serán bloqueadas.", - "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/mascaras de subred para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, todas las IP remotas serán permitidas.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", + "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", + "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", "AlwaysPlaySubtitles": "Siempre mostrar subtítulos", "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el lenguaje preferido serán cargados independientemente del lenguaje del audio.", "AnamorphicVideoNotSupported": "Video anamorfico no soportado", @@ -48,13 +48,13 @@ "BirthLocation": "Lugar de nacimiento", "BirthPlaceValue": "Lugar de nacimiento: {0}", "Blacklist": "Bloqueados", - "BookLibraryHelp": "Los libros de texto y audio libros son soportados. Revise la {0}Guía para Nomenclatura de Libros{1}.", + "BookLibraryHelp": "Los libros de texto y audiolibros están soportados. Revisa la {0} guía de nombrado de libros {1}.", "Books": "Libros", "Box": "Caja", "BoxRear": "Reverso de caja", "Browse": "Navegar", "BrowsePluginCatalogMessage": "Explorar el catalogo de complementos para ver los complementos disponibles.", - "BurnSubtitlesHelp": "Determina si el servidor debe grabar subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB/IDX) y ciertos subtítulos ASS o SSA.", + "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", "ButtonAdd": "Agregar", "ButtonAddMediaLibrary": "Agregar Biblioteca de Medios", "ButtonAddScheduledTaskTrigger": "Agregar Disparador", @@ -310,7 +310,7 @@ "HeaderContainerProfile": "Perfil del Contenedor", "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si aplica una limitación el medio será transcodificado, aún si el formato ha sifo configurado para reproducción directa.", "HeaderContinueListening": "Continuar Escuchando", - "HeaderContinueWatching": "Continuar Viendo", + "HeaderContinueWatching": "Continuar viendo", "HeaderCustomDlnaProfiles": "Perfiles Personalizados", "HeaderDateIssued": "Fecha de Emisión", "HeaderDefaultRecordingSettings": "Configuración Predeterminada de Grabaciones", @@ -366,7 +366,7 @@ "HeaderLibraryFolders": "Carpetas de Biblioteca", "HeaderLibraryOrder": "Orden de Bibliotecas", "HeaderLibrarySettings": "Configuraciones de Biblioteca", - "HeaderLiveTV": "TV en Vivo", + "HeaderLiveTV": "TV en vivo", "HeaderLiveTv": "TV en Vivo", "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV", "HeaderLoginFailure": "Falló el Inicio de Sesión", @@ -384,7 +384,7 @@ "HeaderNewApiKey": "Nueva clave API", "HeaderNewDevices": "Nuevos Dispositivos", "HeaderNextEpisodePlayingInValue": "El Siguiente Episodio se Reproducirá en {0}", - "HeaderNextUp": "A Continuación", + "HeaderNextUp": "A continuación", "HeaderNextVideoPlayingInValue": "El Siguiente Video se Reproducirá en {0}", "HeaderOnNow": "Transmitiéndo Ahora", "HeaderOtherItems": "Otros Ítems", @@ -574,7 +574,7 @@ "LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL", "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte del álbum. Otros podrían fallar al reproducir con esta opción habilitada.", "LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos", - "LabelEnableAutomaticPortMapHelp": "Intentar mapear automáticamente el puerto público con el puerto local vía UPnP. Esto podría no funcionar con algunos modelos de ruteadores.", + "LabelEnableAutomaticPortMapHelp": "Redirecciona automáticamente los puertos públicos de tu router a los puertos locales de tu servidor a través de UPnP. Esto puede no funcionar con algunos modelos de routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", "LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida", "LabelEnableBlastAliveMessagesHelp": "Habilite esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en su red.", "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de Detección de Clientes (segundos)", @@ -1046,7 +1046,7 @@ "OptionMissingEpisode": "Episodios Faltantes", "OptionMonday": "Lunes", "OptionNameSort": "Nombre", - "OptionNew": "Nuevo...", + "OptionNew": "Nuevo…", "OptionNone": "Ninguno", "OptionOnAppStartup": "Al iniciar la aplicación", "OptionOnInterval": "En un intervalo", @@ -1432,7 +1432,7 @@ "LabelPlayMethod": "Método de reproducción:", "LabelPlayer": "Reproductor:", "LabelFolder": "Carpeta:", - "LabelBaseUrlHelp": "Puede añadir un subdirectorio personalizado aquí para acceder al servidor desde una URL más única.", + "LabelBaseUrlHelp": "Añade un subdirectorio personalizado a la URL del servidor. Por ejemplo: http://ejemplo.com/<urlbase>", "LabelBaseUrl": "URL base:", "LabelBitrate": "Velocidad de bits:", "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", @@ -1494,9 +1494,9 @@ "BoxSet": "Box Set", "AskAdminToCreateLibrary": "Preguntar al administrador para crear una biblioteca.", "Artist": "Artista", - "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remux se encuentra muy por delante de la posición de reproducción, pausar el proceso para consumir menos recursos. Esto es mas practico cuando se esta viendo sin buscar constantemente. Deshabilitar esta opción si experimentas problemas de reproducción.", - "AllowFfmpegThrottling": "Aceleración de Transcoders", - "AlbumArtist": "Álbum de artista", + "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", + "AllowFfmpegThrottling": "Regular transcodificaciones", + "AlbumArtist": "Artista del álbum", "Album": "Álbum", "YadifBob": "YADIF Bob", "Yadif": "YADIF", @@ -1510,5 +1510,19 @@ "HeaderFavoritePlaylists": "Listas de reproducción favoritas", "ButtonTogglePlaylist": "Lista de reproducción", "ButtonToggleContextMenu": "Más", - "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM aún así será intentado, incluyendo títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos alternativos." + "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM aún así será intentado, incluyendo títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos alternativos.", + "TabDVR": "DVR", + "SaveChanges": "Guardar cambios", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRequireHttps": "Requerir HTTPS", + "LabelNightly": "Nocturno", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "LabelEnableHttpsHelp": "Permite al servidor escuchar en el puerto HTTPS configurado. Un certificado válido también debe ser configurado para que esto tenga efecto.", + "LabelEnableHttps": "Habilitar HTTPS", + "HeaderServerAddressSettings": "Opciones de la dirección del servidor", + "HeaderRemoteAccessSettings": "Opciones de acceso remoto", + "HeaderHttpsSettings": "Opciones HTTPS", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lista de claves API actualmente habilitadas" } From 1a65a3a0540124b76cede06b6af209121f9bd138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93skar=20Freyr?= Date: Fri, 22 May 2020 00:21:36 +0000 Subject: [PATCH 042/181] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/is/ --- src/strings/is-is.json | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/strings/is-is.json b/src/strings/is-is.json index fa3a826764..b9a8429ce9 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -475,5 +475,41 @@ "Shuffle": "Stokka", "ShowYear": "Sýna ár", "ShowTitle": "Sýna titil", - "Share": "Deila" + "Share": "Deila", + "LabelDefaultUser": "Sjálfgefinn notandi:", + "LabelDefaultScreen": "Sjálfgefinn skjár:", + "LabelDeathDate": "Dánardagur:", + "LabelDay": "Dagur:", + "LabelCurrentPassword": "Núverandi lykilorð:", + "LabelCollection": "Safn:", + "LabelChannels": "Rásir:", + "LabelCachePath": "Slóð skyndiminnis:", + "LabelCache": "Skyndiminni:", + "LabelBurnSubtitles": "Brenna skjátexta:", + "LabelBitrate": "Bitahraði:", + "LabelBirthYear": "Fæðingarár:", + "LabelBirthDate": "Fæðingardagur:", + "LabelAudio": "Hljóð", + "LabelArtists": "Listamenn:", + "LabelAppNameExample": "Dæmi: Sickbeard, Sonarr", + "LabelAll": "Allt", + "LabelAccessDay": "Vikudagur:", + "Kids": "Krakkar", + "Hide": "Fela", + "Help": "Hjálp", + "Home": "Heim", + "HeaderYears": "Ár", + "HeaderVideos": "Myndbönd", + "HeaderVideoQuality": "Myndgæði", + "HeaderUsers": "Notendur", + "HeaderUser": "Notandi", + "TabMetadata": "Lýsigögn", + "TabGenres": "Flokkar", + "TabFavorites": "Eftirlæti", + "TabEpisodes": "Þættir", + "TabDirectPlay": "Bein Spilun", + "TabAdvanced": "Ítarlegt", + "Sunday": "Sunnudagur", + "Suggestions": "Tillögur", + "Subtitles": "Skjátexti" } From 8761ad6f96a5ce3fad181c97bce991f7adfe4f60 Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 22 May 2020 03:17:42 +0000 Subject: [PATCH 043/181] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index c24d2db275..63fb57f63d 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -27,8 +27,8 @@ "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", - "AlwaysPlaySubtitles": "Siempre mostrar subtítulos", - "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el lenguaje preferido serán cargados independientemente del lenguaje del audio.", + "AlwaysPlaySubtitles": "Siempre reproducir", + "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados independientemente del idioma del audio.", "AnamorphicVideoNotSupported": "Video anamorfico no soportado", "AnyLanguage": "Cualquier idioma", "Anytime": "En cualquier momento", @@ -39,43 +39,43 @@ "Ascending": "Ascendente", "AspectRatio": "Relación de aspecto", "AttributeNew": "Nuevo", - "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del lenguaje)", - "Backdrop": "Imagen de Fondo", + "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del idioma)", + "Backdrop": "Imagen de fondo", "Backdrops": "Imágenes de fondo", - "Banner": "Cartel", + "Banner": "Banner", "BestFit": "Mejor ajuste", "BirthDateValue": "Nacimiento: {0}", "BirthLocation": "Lugar de nacimiento", "BirthPlaceValue": "Lugar de nacimiento: {0}", - "Blacklist": "Bloqueados", + "Blacklist": "Lista negra", "BookLibraryHelp": "Los libros de texto y audiolibros están soportados. Revisa la {0} guía de nombrado de libros {1}.", "Books": "Libros", "Box": "Caja", - "BoxRear": "Reverso de caja", - "Browse": "Navegar", - "BrowsePluginCatalogMessage": "Explorar el catalogo de complementos para ver los complementos disponibles.", + "BoxRear": "Caja (parte trasera)", + "Browse": "Explorar", + "BrowsePluginCatalogMessage": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", "ButtonAdd": "Agregar", - "ButtonAddMediaLibrary": "Agregar Biblioteca de Medios", - "ButtonAddScheduledTaskTrigger": "Agregar Disparador", - "ButtonAddServer": "Agregar Servidor", - "ButtonAddUser": "Agregar Usuario", + "ButtonAddMediaLibrary": "Agregar biblioteca de medios", + "ButtonAddScheduledTaskTrigger": "Agregar disparador", + "ButtonAddServer": "Agregar servidor", + "ButtonAddUser": "Agregar usuario", "ButtonArrowDown": "Abajo", "ButtonArrowLeft": "Izquierda", "ButtonArrowRight": "Derecha", "ButtonArrowUp": "Arriba", - "ButtonAudioTracks": "Pistas de Audio", + "ButtonAudioTracks": "Pistas de audio", "ButtonBack": "Atrás", "ButtonCancel": "Cancelar", - "ButtonChangeServer": "Cambiar Servidor", + "ButtonChangeServer": "Cambiar servidor", "ButtonConnect": "Conectar", "ButtonDelete": "Eliminar", - "ButtonDeleteImage": "Eliminar Imagen", + "ButtonDeleteImage": "Eliminar imagen", "ButtonDown": "Abajo", "ButtonDownload": "Descargar", "ButtonEdit": "Editar", "ButtonEditImages": "Editar imágenes", - "ButtonEditOtherUserPreferences": "Editar el perfíl de este usuario. imágen y preferencias personales.", + "ButtonEditOtherUserPreferences": "Editar el perfil, la imagen y las preferencias personales de este usuario.", "ButtonFilter": "Filtro", "ButtonForgotPassword": "Olvidé mi contraseña", "ButtonFullscreen": "Pantalla completa", @@ -83,7 +83,7 @@ "ButtonGuide": "Guía", "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", - "ButtonLearnMore": "Aprenda más", + "ButtonLearnMore": "Aprender más", "ButtonLibraryAccess": "Acceso a biblioteca", "ButtonManualLogin": "Inicio de Sesión Manual", "ButtonMore": "Más", @@ -1346,7 +1346,7 @@ "ButtonNo": "No", "ButtonOk": "Ok", "ButtonTrailer": "Trailer", - "AuthProviderHelp": "Seleccione un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "Director": "Director", "Extras": "Extras", "General": "General", @@ -1492,7 +1492,7 @@ "Episode": "Episodio", "ClientSettings": "Configuración de cliente", "BoxSet": "Box Set", - "AskAdminToCreateLibrary": "Preguntar al administrador para crear una biblioteca.", + "AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.", "Artist": "Artista", "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", "AllowFfmpegThrottling": "Regular transcodificaciones", From 9824477f3199cafe6bf067b7da047961f6cbeba9 Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 22 May 2020 04:01:12 +0000 Subject: [PATCH 044/181] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 192 ++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 63fb57f63d..404af6de98 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -84,8 +84,8 @@ "ButtonHelp": "Ayuda", "ButtonHome": "Inicio", "ButtonLearnMore": "Aprender más", - "ButtonLibraryAccess": "Acceso a biblioteca", - "ButtonManualLogin": "Inicio de Sesión Manual", + "ButtonLibraryAccess": "Acceso a biblioteca(s)", + "ButtonManualLogin": "Inicio de sesión manual", "ButtonMore": "Más", "ButtonNetwork": "Red", "ButtonNew": "Nuevo", @@ -96,29 +96,29 @@ "ButtonPause": "Pausar", "ButtonPlay": "Reproducir", "ButtonPreviousTrack": "Pista anterior", - "ButtonProfile": "Perfíl", - "ButtonQuickStartGuide": "Guía de Inicio Rápido", + "ButtonProfile": "Perfil", + "ButtonQuickStartGuide": "Guía de inicio rápido", "ButtonRefresh": "Actualizar", - "ButtonRefreshGuideData": "Actualizar Datos de la Guía", - "ButtonRemove": "Eliminar", + "ButtonRefreshGuideData": "Actualizar datos de la guía", + "ButtonRemove": "Remover", "ButtonRename": "Renombrar", "ButtonRepeat": "Repetir", - "ButtonResetEasyPassword": "Restaurar código pin sencillo", - "ButtonResetPassword": "Restablecer Contraseña", + "ButtonResetEasyPassword": "Reiniciar el código pin sencillo", + "ButtonResetPassword": "Restablecer contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Continuar", "ButtonRevoke": "Revocar", "ButtonSave": "Guardar", - "ButtonScanAllLibraries": "Escanear Todas las Bibliotecas", + "ButtonScanAllLibraries": "Escanear todas las bibliotecas", "ButtonSearch": "Búsqueda", - "ButtonSelectDirectory": "Seleccionar Carpeta", - "ButtonSelectServer": "Seleccionar Servidor", + "ButtonSelectDirectory": "Seleccionar directorio", + "ButtonSelectServer": "Seleccionar servidor", "ButtonSelectView": "Seleccionar vista", "ButtonSend": "Enviar", "ButtonSettings": "Configuración", "ButtonShuffle": "Aleatorio", "ButtonShutdown": "Apagar", - "ButtonSignIn": "Iniciar Sesión", + "ButtonSignIn": "Iniciar sesión", "ButtonSignOut": "Cerrar sesión", "ButtonSort": "Ordenar", "ButtonStart": "Iniciar", @@ -132,70 +132,70 @@ "CancelRecording": "Cancelar grabación", "CancelSeries": "Cancelar serie", "Categories": "Categorías", - "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicaran a contenido nuevo agregado a su biblioteca. Para aplicar los cambios a los títulos existentes, necesita actualizar los metadatos manualmente.", - "ChannelAccessHelp": "Seleccione los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", + "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicarán a contenido nuevo agregado a tu biblioteca. Para aplicar los cambios a los títulos existentes, necesitarás actualizar sus metadatos manualmente.", + "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", "ChannelNameOnly": "Canal {0} solamente", - "ChannelNumber": "Numero de canal", + "ChannelNumber": "Número de canal", "Channels": "Canales", - "CinemaModeConfigurationHelp": "El modo cine trae la experiencia del cine directo al la sala de TV con la habilidad de reproducir tráilers e intros personalizados antes de la presentación estelar.", + "CinemaModeConfigurationHelp": "El modo cine lleva la experiencia del cine directamente a tu sala de estar con la capacidad de reproducir trailers e introducciones personalizadas antes de la función principal.", "Collections": "Colecciones", "ColorPrimaries": "Colores primarios", "ColorSpace": "Espacio de color", "ColorTransfer": "Transferencia de color", "CommunityRating": "Calificación de la comunidad", "Composer": "Compositor", - "ConfigureDateAdded": "Configure como la fecha de adición es determinada en el Panel de Control del Servidor Jellyfin bajo la configuración de Bibliotecas", + "ConfigureDateAdded": "Configura cómo se determina la fecha de adición en el panel de control del servidor Jellyfin en la configuración de la biblioteca", "ConfirmDeleteImage": "¿Eliminar imagen?", - "ConfirmDeleteItem": "Al eliminar este ítem se eliminará tanto del sistema de archivos como de su biblioteca de medios. ¿Esta seguro de querer continuar?", - "ConfirmDeleteItems": "Al borrar estos items serán eliminados tanto del sistema de archivos como de la biblioteca de medios. ¿Esta seguro que desea continuar?", + "ConfirmDeleteItem": "Eliminar este objeto lo eliminará tanto del sistema como de su biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItems": "Eliminar estos objetos los eliminará tanto del sistema como de su biblioteca de medios. ¿Estás seguro de querer continuar?", "ConfirmDeletion": "Confirmar eliminación", - "ConfirmEndPlayerSession": "¿Desea cerrar Jellyfin en {0}?", + "ConfirmEndPlayerSession": "¿Deseas apagar Jellyfin en {0}?", "Connect": "Conectar", "ContinueWatching": "Continuar viendo", "Continuing": "Continuando", - "CriticRating": "Calificación de la crítica", + "CriticRating": "Calificación de los críticos", "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", "DateAdded": "Fecha de adición", "DatePlayed": "Fecha de reproducción", - "DeathDateValue": "Terminación: {0}", + "DeathDateValue": "Falleció: {0}", "Default": "Por defecto", - "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor inténtelo de nuevo mas tarde.", - "DefaultMetadataLangaugeDescription": "Estas son sus configuraciones por defecto y puedes ser personalizadas independientemente en cada biblioteca.", - "DefaultSubtitlesHelp": "Los subtitulos son cargados basados en los indicadores \"por defecto\" y \"forzado\" incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", + "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor, inténtalo de nuevo más tarde.", + "DefaultMetadataLangaugeDescription": "Estos son sus valores por defecto y pueden ser personalizados en cada biblioteca.", + "DefaultSubtitlesHelp": "Los subtítulos se cargan basados en los indicadores «por defecto» y «forzado» incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", "Delete": "Eliminar", - "DeleteDeviceConfirmation": "¿Está seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión en él.", + "DeleteDeviceConfirmation": "¿Estás seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión con él.", "DeleteImage": "Eliminar imagen", - "DeleteImageConfirmation": "¿Está seguro de querer eliminar esta imagen?", + "DeleteImageConfirmation": "¿Estás seguro de querer eliminar esta imagen?", "DeleteMedia": "Eliminar medios", - "DeleteUser": "Eliminar Usuario", - "DeleteUserConfirmation": "¿Está seguro de querer eliminar este usuario?", + "DeleteUser": "Eliminar usuario", + "DeleteUserConfirmation": "¿Estás seguro de querer eliminar este usuario?", "Depressed": "Depresión", "Descending": "Descendente", "Desktop": "Escritorio", "DetectingDevices": "Detectando dispositivos", - "DeviceAccessHelp": "Esto solo aplica a dispositivos que pueden ser identificados de manera individual y no evitará acceso al navegador. Al filtrar el acceso de usuarios a dispositivos se impedirá que utilicen nuevos dispositivos hasta que hayan sido aprobados aquí.", + "DeviceAccessHelp": "Esto solo se aplica a los dispositivos que pueden ser identificados de manera única y no impedirá el acceso desde navegadores. Filtrar el acceso a los dispositivos de los usuarios les impedirá usar nuevos dispositivos hasta que hayan sido aprobados aquí.", "DirectPlaying": "Reproducción directa", - "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video sera reempaquetado en el acto antes de transmitirlo al dispositivo.", - "DirectStreamHelp2": "La Transmisión Directa de un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", - "DirectStreaming": "Transmisión Directa", + "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video será reempaquetado sobre la marcha antes de transmitirlo al dispositivo.", + "DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", + "DirectStreaming": "Transmisión directa", "Directors": "Directores", "Disabled": "Desactivado", "Disc": "DIsco", "Disconnect": "Desconectar", "Dislike": "No me gusta", "Display": "Pantalla", - "DisplayInMyMedia": "Mostrar en pantalla de inicio", - "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como Recientes o Continua Viendo", + "DisplayInMyMedia": "Mostrar en la pantalla de inicio", + "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como recientes o continuar viendo", "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", - "DisplayModeHelp": "Seleccione el estilo de diseño que desea en la Interfaz.", + "DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.", "DoNotRecord": "No grabar", "Down": "Abajo", "Download": "Descargar", "DownloadsValue": "{0} descargas", "DrmChannelsNotImported": "Los canales con DRM no serán importados.", "DropShadow": "Sombra paralela", - "EasyPasswordHelp": "El código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", + "EasyPasswordHelp": "Tu código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", "Edit": "Editar", "EditImages": "Editar imágenes", "EditMetadata": "Editar metadatos", @@ -203,17 +203,17 @@ "EnableBackdrops": "Imágenes de fondo", "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", "EnableCinemaMode": "Modo cine", - "EnableColorCodedBackgrounds": "Fondos de color codificado", + "EnableColorCodedBackgrounds": "Fondos de color codificados", "EnableDisplayMirroring": "Duplicado de pantalla", "EnableExternalVideoPlayers": "Reproductores de video externos", "EnableExternalVideoPlayersHelp": "Un menú de reproductor externo se mostrara cuando inicie la reproducción de un video.", "EnableHardwareEncoding": "Habilitar codificación por hardware", "EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción", - "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción.", + "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción actual.", "EnablePhotos": "Mostrar fotografías", "EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.", - "EnableStreamLooping": "Repetir automáticamente transmisiones en vivo", - "EnableStreamLoopingHelp": "Habilite esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitarlo cuando no es requerido puede causar problemas.", + "EnableStreamLooping": "Repetir automáticamente las transmisiones en vivo", + "EnableStreamLoopingHelp": "Habilita esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitar esto cuando no es requerido puede causar problemas.", "EnableThemeSongs": "Canciones temáticas", "EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.", "EnableThemeVideos": "Videos temáticos", @@ -221,21 +221,21 @@ "Ended": "Finalizado", "EndsAtValue": "Termina a las {0}", "Episodes": "Episodios", - "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de su cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesite acceder al sitio web de Schedules Direct y eliminar otras programaciones de su cuenta antes de continuar.", - "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor asegúrese de que la ruta es valida y que el proceso del Servidor Jellyfin tenga acceso a ese destino.", - "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor asegúrese de que este disponible e intente de nuevo.", - "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor asegúrese de que el archivo existe e intente de nuevo.", - "ErrorDeletingItem": "Hubo un error eliminando el ítem del Servidor Jellyfin. Por favor verifique tenga permisos de escritura en la carpeta de medios e intente de nuevo.", - "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrese de que su información este correcta e inténtelo de nuevo.", - "ErrorMessageStartHourGreaterThanEnd": "El horario de fin debe ser mayor al de comienzo.", - "ErrorPleaseSelectLineup": "Por favor seleccione una programación e intente de nuevo. Si no hay disponible ninguna, entonces por favor verifique que su nombre de usuario, contraseña, y código postal sean correctos.", - "ErrorSavingTvProvider": "Hubo un error al salvar el proveedor de TV. Por favor asegúrese de que este disponible e intente de nuevo.", + "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de tu cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesites acceder al sitio web de Schedules Direct y eliminar otras programaciones de tu cuenta antes de continuar.", + "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a ese destino.", + "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor, asegúrate de que esté disponible e inténtalo de nuevo.", + "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor, asegúrate de que el archivo existe e inténtalo de nuevo.", + "ErrorDeletingItem": "Hubo un error eliminando el objeto del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", + "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrate de que tu información sea correcta e inténtalo de nuevo.", + "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización debe ser mayor que la hora de inicio.", + "ErrorPleaseSelectLineup": "Por favor, selecciona una programación e inténtalo de nuevo. Si no hay disponible ninguna, entonces, por favor, verifica que tu nombre de usuario, contraseña, y código postal sean correctos.", + "ErrorSavingTvProvider": "Hubo un error al guardar el proveedor de TV. Por favor, asegúrate de que sea accesible e inténtalo de nuevo.", "EveryNDays": "Cada {0} días", - "ExitFullscreen": "Salir de Pantalla Completa", + "ExitFullscreen": "Salir de la pantalla completa", "ExtraLarge": "Extra grande", "ExtractChapterImagesHelp": "La extracción de imágenes de capítulos permitirá a los clientes mostrar menús gráficos de selección de escenas. El proceso puede ser lento, intensivo en recursos y puede requerir varios gigabytes de espacio. Se ejecuta cuando se descubren los videos y también como una tarea programada cada noche. La programación es configurable en el área de tareas programadas. No se recomienda ejecutar esta tarea durante las horas de mayor uso.", - "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que introdujo. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor verifique la ruta e intente de nuevo.", - "FastForward": "Avance Rápido", + "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que has introducido. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor, verifica la ruta e inténtalo de nuevo.", + "FastForward": "Avance rápido", "Favorite": "Favorito", "Favorites": "Favoritos", "Features": "Características", @@ -253,61 +253,61 @@ "Folders": "Carpetas", "FormatValue": "Formato: {0}", "Friday": "Viernes", - "Fullscreen": "Pantalla Completa", - "Genre": "Genero", + "Fullscreen": "Pantalla completa", + "Genre": "Género", "Genres": "Géneros", "GroupBySeries": "Agrupar por series", "GroupVersions": "Agrupar versiones", "GuestStar": "Estrella invitada", "Guide": "Guía", - "GuideProviderLogin": "Iniciar Sesión", - "GuideProviderSelectListings": "Elegir Listados", - "H264CrfHelp": "El \"Factor de Transferencia Constante\" o \"Constant Rate Factor\" (CFR) es la configuración por defecto para el codificador x264. Puede poner valores entre 0 y 51, donde los valores mas bajos dan como resultado mejor calidad (a expensas de archivos mas grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, puede usar este valor como punto de referencia.", - "EncoderPresetHelp": "Elija un valor mas rápido para mejorar el rendimiento, o uno mas lento para mejorar la calidad.", + "GuideProviderLogin": "Iniciar sesión", + "GuideProviderSelectListings": "Elegir listados", + "H264CrfHelp": "El «Factor de transferencia constante» (CRF) es la configuración de calidad por defecto para el codificador x264. Puedes establecer valores entre 0 y 51, donde los valores más bajos dan como resultado mejor calidad (a expensas de archivos más grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, así que puedes usar este valor como punto de referencia.", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar el rendimiento, o uno más lento para mejorar la calidad.", "HDPrograms": "Programas en HD", "HandledByProxy": "Manejado por un proxy inverso", - "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrese de que su sistema operativo y controladores de video están actualizados. Si tiene dificultades reproduciendo vides después de habilitar esto, necesita volver a cambiar la configuración a NO.", - "HeaderAccessSchedule": "Acceder Programación", - "HeaderAccessScheduleHelp": "Crear programación de acceso para limitar el acceso a ciertos horarios.", - "HeaderActiveDevices": "Dispositivos Activos", - "HeaderActiveRecordings": "Grabaciones Activas", + "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrate de que tu sistema operativo y controladores de video están actualizados. Si tienes dificultades reproduciendo videos después de habilitar esto, necesitarás volver a cambiar la configuración a Ninguno.", + "HeaderAccessSchedule": "Programación de acceso", + "HeaderAccessScheduleHelp": "Crea una programación de acceso para limitar el acceso a ciertos horarios.", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderActiveRecordings": "Grabaciones activas", "HeaderActivity": "Actividad", - "HeaderAddScheduledTaskTrigger": "Agregar Disparador", - "HeaderAddToCollection": "Agregar a Colección", - "HeaderAddToPlaylist": "Agregar a Lista de Reproducción", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderAddToCollection": "Agregar a colección", + "HeaderAddToPlaylist": "Agregar a lista de reproducción", "HeaderAddUpdateImage": "Agregar/Actualizar Imagen", - "HeaderAddUser": "Agregar Usuario", - "HeaderAdditionalParts": "Partes Adicionales", + "HeaderAddUser": "Agregar usuario", + "HeaderAdditionalParts": "Partes adicionales", "HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbums": "Álbumes", "HeaderAlert": "Alerta", - "HeaderAllowMediaDeletionFrom": "Permitir Eliminacion de Medios De", + "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", "HeaderApiKey": "Clave API", - "HeaderApiKeys": "Claves de API", - "HeaderApiKeysHelp": "Son necesarias aplicaciones externas para obtener una clave API para comunicarse con el Servidor Jellyfin. Las clave son emitidas accediendo con una cuenta Jellyfin, o bien concediéndole manualmente una clave a la aplicación.", - "HeaderAppearsOn": "Aparece En", - "HeaderAudioBooks": "Audio Libros", - "HeaderAudioSettings": "Configuración de Audio", - "HeaderAutomaticUpdates": "Actualizaciones Automáticas", - "HeaderBlockItemsWithNoRating": "Bloquear ítems sin clasificación o con información de clasificación desconocida:", + "HeaderApiKeys": "Claves API", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderAppearsOn": "Aparece en", + "HeaderAudioBooks": "Audiolibros", + "HeaderAudioSettings": "Configuración de audio", + "HeaderAutomaticUpdates": "Actualizaciones automáticas", + "HeaderBlockItemsWithNoRating": "Bloquear objetos sin clasificación o con información de clasificación desconocida:", "HeaderBooks": "Libros", - "HeaderBranding": "Establecer Marca", - "HeaderCancelRecording": "Cancelar Grabación", - "HeaderCancelSeries": "Cancelar Serie", - "HeaderCastAndCrew": "Reparto & Personal", - "HeaderCastCrew": "Reparto y Personal", - "HeaderChannelAccess": "Acceso a los Canales", + "HeaderBranding": "Establecer marca", + "HeaderCancelRecording": "Cancelar grabación", + "HeaderCancelSeries": "Cancelar serie", + "HeaderCastAndCrew": "Reparto y equipo", + "HeaderCastCrew": "Reparto y equipo", + "HeaderChannelAccess": "Acceso a los canales", "HeaderChannels": "Canales", - "HeaderChapterImages": "Imagenes de Capitulo", - "HeaderCodecProfile": "Perfil de Codecs", - "HeaderCodecProfileHelp": "Los perfiles de codificación indican las limitaciones de un dispositivo al reproducir con codecs específicos. Si aplica una limitación el medio será transcodificado, aún si el codec ha sido configurado para reprodución directa.", - "HeaderConfigureRemoteAccess": "Configurar Acceso Remoto", - "HeaderConfirmPluginInstallation": "Confirmar Instalación de Complemento", - "HeaderConfirmProfileDeletion": "Confirmar Eliminación del Perfil", - "HeaderConfirmRevokeApiKey": "Revocar llave de API", + "HeaderChapterImages": "Imágenes de los capítulos", + "HeaderCodecProfile": "Perfil de códec", + "HeaderCodecProfileHelp": "Los perfiles de códecs indican las limitaciones de un dispositivo al reproducir códecs específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el códec ha sido configurado para reproducción directa.", + "HeaderConfigureRemoteAccess": "Configurar acceso remoto", + "HeaderConfirmPluginInstallation": "Confirmar instalación de complemento", + "HeaderConfirmProfileDeletion": "Confirmar eliminación de perfil", + "HeaderConfirmRevokeApiKey": "Revocar clave API", "HeaderConnectToServer": "Conectarse al servidor", - "HeaderConnectionFailure": "Falla de Conexión", - "HeaderContainerProfile": "Perfil del Contenedor", + "HeaderConnectionFailure": "Falla de conexión", + "HeaderContainerProfile": "Perfil del contenedor", "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si aplica una limitación el medio será transcodificado, aún si el formato ha sifo configurado para reproducción directa.", "HeaderContinueListening": "Continuar Escuchando", "HeaderContinueWatching": "Continuar viendo", @@ -1344,7 +1344,7 @@ "Auto": "Auto", "ButtonInfo": "Info", "ButtonNo": "No", - "ButtonOk": "Ok", + "ButtonOk": "OK", "ButtonTrailer": "Trailer", "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", "Director": "Director", @@ -1455,7 +1455,7 @@ "SelectAdminUsername": "Por favor seleccione un nombre de usuario para la cuenta de administrador.", "EnableFastImageFadeInHelp": "Habilita la animación de desvanecido rápido para las imágenes cargadas", "LabelDroppedFrames": "Cuadros saltados:", - "CopyStreamURLError": "Hubo un error copiando la URL.", + "CopyStreamURLError": "Hubo un error al copiar la URL.", "ButtonSplit": "Dividir", "WeeklyAt": "{0}s a las {1}", "OnApplicationStartup": "Cuando se inicia la aplicación", @@ -1490,7 +1490,7 @@ "HeaderNavigation": "Navegación", "HeaderFavoritePeople": "Personas favoritas", "Episode": "Episodio", - "ClientSettings": "Configuración de cliente", + "ClientSettings": "Configuración del cliente", "BoxSet": "Box Set", "AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.", "Artist": "Artista", @@ -1501,7 +1501,7 @@ "YadifBob": "YADIF Bob", "Yadif": "YADIF", "LabelDeinterlaceMethod": "Metodo de Desentrelazado:", - "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al codificar el contenido entrelazado.", + "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", "Filter": "Filtro", "New": "Nuevo", "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor contacta con el administrador del servidor para mas información.", From ba7e99c9e2a1d79823b3662018c012c95924123a Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 22 May 2020 05:35:57 +0000 Subject: [PATCH 045/181] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 1092 ++++++++++++++++++++-------------------- 1 file changed, 546 insertions(+), 546 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 404af6de98..140addc8f9 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -1,14 +1,14 @@ { "Absolute": "Absoluto", "Accept": "Aceptar", - "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtelo más tarde.", + "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", "Add": "Agregar", - "AddItemToCollectionHelp": "Agregue elementos a las colecciones buscándolos y utilizando sus menús al hacer clic derecho o al tocarlos para agregarlos a una colección.", + "AddItemToCollectionHelp": "Agrega elementos a las colecciones buscándolos y utilizando sus menúes al hacer clic derecho o al tocarlos para agregarlos a una colección.", "AddToCollection": "Agregar a colección", "AddToPlayQueue": "Agregar a la cola de reproducción", "AddToPlaylist": "Agregar a lista de reproducción", "AddedOnValue": "Agregado {0}", - "AdditionalNotificationServices": "Explore el catálogo de complementos para instalar servicios de notificaciones adicionales.", + "AdditionalNotificationServices": "Explora el catálogo de complementos para instalar servicios de notificaciones adicionales.", "AirDate": "Fecha de emisión", "Aired": "Transmitido", "Albums": "Álbumes", @@ -146,8 +146,8 @@ "Composer": "Compositor", "ConfigureDateAdded": "Configura cómo se determina la fecha de adición en el panel de control del servidor Jellyfin en la configuración de la biblioteca", "ConfirmDeleteImage": "¿Eliminar imagen?", - "ConfirmDeleteItem": "Eliminar este objeto lo eliminará tanto del sistema como de su biblioteca de medios. ¿Estás seguro de querer continuar?", - "ConfirmDeleteItems": "Eliminar estos objetos los eliminará tanto del sistema como de su biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", "ConfirmDeletion": "Confirmar eliminación", "ConfirmEndPlayerSession": "¿Deseas apagar Jellyfin en {0}?", "Connect": "Conectar", @@ -169,7 +169,7 @@ "DeleteMedia": "Eliminar medios", "DeleteUser": "Eliminar usuario", "DeleteUserConfirmation": "¿Estás seguro de querer eliminar este usuario?", - "Depressed": "Depresión", + "Depressed": "Presionado", "Descending": "Descendente", "Desktop": "Escritorio", "DetectingDevices": "Detectando dispositivos", @@ -225,7 +225,7 @@ "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a ese destino.", "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor, asegúrate de que esté disponible e inténtalo de nuevo.", "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor, asegúrate de que el archivo existe e inténtalo de nuevo.", - "ErrorDeletingItem": "Hubo un error eliminando el objeto del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", + "ErrorDeletingItem": "Hubo un error eliminando el elemento del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrate de que tu información sea correcta e inténtalo de nuevo.", "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización debe ser mayor que la hora de inicio.", "ErrorPleaseSelectLineup": "Por favor, selecciona una programación e inténtalo de nuevo. Si no hay disponible ninguna, entonces, por favor, verifica que tu nombre de usuario, contraseña, y código postal sean correctos.", @@ -289,7 +289,7 @@ "HeaderAudioBooks": "Audiolibros", "HeaderAudioSettings": "Configuración de audio", "HeaderAutomaticUpdates": "Actualizaciones automáticas", - "HeaderBlockItemsWithNoRating": "Bloquear objetos sin clasificación o con información de clasificación desconocida:", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin clasificación o con información de clasificación desconocida:", "HeaderBooks": "Libros", "HeaderBranding": "Establecer marca", "HeaderCancelRecording": "Cancelar grabación", @@ -308,294 +308,294 @@ "HeaderConnectToServer": "Conectarse al servidor", "HeaderConnectionFailure": "Falla de conexión", "HeaderContainerProfile": "Perfil del contenedor", - "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si aplica una limitación el medio será transcodificado, aún si el formato ha sifo configurado para reproducción directa.", - "HeaderContinueListening": "Continuar Escuchando", + "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el formato ha sido configurado para reproducción directa.", + "HeaderContinueListening": "Continuar escuchando", "HeaderContinueWatching": "Continuar viendo", - "HeaderCustomDlnaProfiles": "Perfiles Personalizados", - "HeaderDateIssued": "Fecha de Emisión", - "HeaderDefaultRecordingSettings": "Configuración Predeterminada de Grabaciones", - "HeaderDeleteDevice": "Eliminar Dispositivo", - "HeaderDeleteItem": "Eliminar Ítem", - "HeaderDeleteItems": "Borrar items", - "HeaderDeleteProvider": "Eliminar Proveedor", - "HeaderDeleteTaskTrigger": "Borrar Disparador de Tarea", - "HeaderDetectMyDevices": "Detectar Mis Dispositivos", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderDateIssued": "Fecha de emisión", + "HeaderDefaultRecordingSettings": "Configuración predeterminada de grabaciones", + "HeaderDeleteDevice": "Eliminar dispositivo", + "HeaderDeleteItem": "Eliminar elemento", + "HeaderDeleteItems": "Eliminar elementos", + "HeaderDeleteProvider": "Eliminar proveedor", + "HeaderDeleteTaskTrigger": "Borrar disparador de tarea", + "HeaderDetectMyDevices": "Detectar mis dispositivos", "HeaderDeveloperInfo": "Info del desarrollador", - "HeaderDeviceAccess": "Acceso a Dispositivos", + "HeaderDeviceAccess": "Acceso a dispositivos", "HeaderDevices": "Dispositivos", - "HeaderDirectPlayProfile": "Perfil de Reproducción Directa", - "HeaderDirectPlayProfileHelp": "Agregue perfiles de reproducción directa para indicar que formatos puede manejar el dispositivo de manera nativa.", + "HeaderDirectPlayProfile": "Perfil de reproducción directa", + "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", "HeaderDisplay": "Pantalla", - "HeaderDownloadSync": "Descargar y Sincronizar", - "HeaderEasyPinCode": "Código Pin Sencillo", + "HeaderDownloadSync": "Descargar y sincronizar", + "HeaderEasyPinCode": "Código PIN sencillo", "HeaderEditImages": "Editar imágenes", - "HeaderEnabledFields": "Campos Habilitados", - "HeaderEnabledFieldsHelp": "Desmarcar un campo para bloquearlo y prevenir que sus datos cambien.", + "HeaderEnabledFields": "Campos habilitados", + "HeaderEnabledFieldsHelp": "Desmarca un campo para bloquearlo y prevenir que sus datos sean cambiados.", "HeaderEpisodes": "Episodios", - "HeaderExternalIds": "IDs Externos:", - "HeaderFeatureAccess": "Permisos de acceso", + "HeaderExternalIds": "IDs externos:", + "HeaderFeatureAccess": "Acceso a características", "HeaderFeatures": "Características", - "HeaderFetchImages": "Buscar imágenes:", - "HeaderFetcherSettings": "Configuración de Recolectores", + "HeaderFetchImages": "Obtener imágenes:", + "HeaderFetcherSettings": "Configuración del recolector", "HeaderFilters": "Filtros", - "HeaderForKids": "Para Niños", - "HeaderForgotPassword": "Contraseña Olvidada", - "HeaderFrequentlyPlayed": "Reproducido Frecuentemente", + "HeaderForKids": "Para niños", + "HeaderForgotPassword": "Olvidé mi contraseña", + "HeaderFrequentlyPlayed": "Reproducido frecuentemente", "HeaderGenres": "Géneros", "HeaderGuideProviders": "Proveedores de Guías de TV", "HeaderHttpHeaders": "Encabezados HTTP", "HeaderIdentification": "Identificación", "HeaderIdentificationCriteriaHelp": "Introduzca, al menos, un criterio de identificación.", - "HeaderIdentificationHeader": "Encabezado de Identificación", - "HeaderIdentifyItemHelp": "Introduzca uno o más criterios de búsqueda. Elimine criterios para expandir los resultados.", - "HeaderImageOptions": "Opciones de Imagen", - "HeaderImageSettings": "Configuración de Imágenes", + "HeaderIdentificationHeader": "Encabezado de identificación", + "HeaderIdentifyItemHelp": "Introduce uno o más criterios de búsqueda. Elimina criterios para expandir los resultados.", + "HeaderImageOptions": "Opciones de imagen", + "HeaderImageSettings": "Configuración de imagen", "HeaderInstall": "Instalar", - "HeaderInstantMix": "Mezcla Instantánea", - "HeaderItems": "Ítems", - "HeaderKeepRecording": "Conservar Grabaciones", - "HeaderKeepSeries": "Conservar Serie", - "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos Nfo, edite una biblioteca en la configuracion de bibliotecas de Jellyfin y busque la sección de grabadores de metadatos.", - "HeaderLatestEpisodes": "Episodios Recientes", - "HeaderLatestMedia": "Agregadas Recientemente", - "HeaderLatestMovies": "Películas Recientes", - "HeaderLatestMusic": "Música Reciente", - "HeaderLatestRecordings": "Grabaciones Recientes", + "HeaderInstantMix": "Mix instantáneo", + "HeaderItems": "Elementos", + "HeaderKeepRecording": "Conservar grabación", + "HeaderKeepSeries": "Conservar serie", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderLatestEpisodes": "Últimos episodios", + "HeaderLatestMedia": "Últimos medios", + "HeaderLatestMovies": "Últimas películas", + "HeaderLatestMusic": "Última música", + "HeaderLatestRecordings": "Últimas grabaciones", "HeaderLibraries": "Bibliotecas", - "HeaderLibraryAccess": "Acceso a la Biblioteca", - "HeaderLibraryFolders": "Carpetas de Biblioteca", - "HeaderLibraryOrder": "Orden de Bibliotecas", - "HeaderLibrarySettings": "Configuraciones de Biblioteca", + "HeaderLibraryAccess": "Acceso a bibliotecas", + "HeaderLibraryFolders": "Carpetas de bibliotecas", + "HeaderLibraryOrder": "Orden de las bibliotecas", + "HeaderLibrarySettings": "Configuraciones de biblioteca", "HeaderLiveTV": "TV en vivo", - "HeaderLiveTv": "TV en Vivo", + "HeaderLiveTv": "TV en vivo", "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV", - "HeaderLoginFailure": "Falló el Inicio de Sesión", + "HeaderLoginFailure": "Falló el inicio de sesión", "HeaderMedia": "Medios", - "HeaderMediaFolders": "Carpetas de Medios", - "HeaderMediaInfo": "Info del Medio", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderMediaInfo": "Info del medio", "HeaderMetadataSettings": "Configuración de metadatos", - "HeaderMoreLikeThis": "Mas Como Esto", + "HeaderMoreLikeThis": "Más como esto", "HeaderMovies": "Películas", - "HeaderMusicQuality": "Calidad de Musica", - "HeaderMusicVideos": "Videos Musicales", - "HeaderMyDevice": "Mi Dispositivo", - "HeaderMyMedia": "Mis Medios", + "HeaderMusicQuality": "Calidad de la música", + "HeaderMusicVideos": "Videos musicales", + "HeaderMyDevice": "Mi dispositivo", + "HeaderMyMedia": "Mis medios", "HeaderMyMediaSmall": "Mis medios (pequeño)", "HeaderNewApiKey": "Nueva clave API", - "HeaderNewDevices": "Nuevos Dispositivos", - "HeaderNextEpisodePlayingInValue": "El Siguiente Episodio se Reproducirá en {0}", + "HeaderNewDevices": "Nuevos dispositivos", + "HeaderNextEpisodePlayingInValue": "El siguiente episodio se reproducirá en {0}", "HeaderNextUp": "A continuación", - "HeaderNextVideoPlayingInValue": "El Siguiente Video se Reproducirá en {0}", - "HeaderOnNow": "Transmitiéndo Ahora", - "HeaderOtherItems": "Otros Ítems", - "HeaderParentalRatings": "Clasificación Parental", + "HeaderNextVideoPlayingInValue": "El siguiente video se reproducirá en {0}", + "HeaderOnNow": "Transmitiendo ahora", + "HeaderOtherItems": "Otros elementos", + "HeaderParentalRatings": "Clasificación parental", "HeaderPassword": "Contraseña", - "HeaderPasswordReset": "Restablecer Contraseña", + "HeaderPasswordReset": "Restablecer contraseña", "HeaderPaths": "Rutas", - "HeaderPendingInvitations": "Invitaciones Pendientes", + "HeaderPendingInvitations": "Invitaciones pendientes", "HeaderPeople": "Personas", - "HeaderPhotoAlbums": "Álbumes de Fotos", - "HeaderPinCodeReset": "Restablecer Código Pin", - "HeaderPlayAll": "Reproducir Todo", - "HeaderPlayOn": "Reproducir En", - "HeaderPlayback": "Reproducción de Medios", - "HeaderPlaybackError": "Error de Reproducción", - "HeaderPleaseSignIn": "Por favor inicie sesión", + "HeaderPhotoAlbums": "Álbumes de fotos", + "HeaderPinCodeReset": "Restablecer código PIN", + "HeaderPlayAll": "Reproducir todo", + "HeaderPlayOn": "Reproducir en", + "HeaderPlayback": "Reproducción de medios", + "HeaderPlaybackError": "Error de reproducción", + "HeaderPleaseSignIn": "Por favor, inicia sesión", "HeaderPluginInstallation": "Instalación de complemento", - "HeaderPreferredMetadataLanguage": "Idioma Preferido para Metadatos", - "HeaderProfile": "Perfíl", - "HeaderProfileInformation": "Información de Perfil", - "HeaderProfileServerSettingsHelp": "Estos valores controlan como el Servidor Jellyfin se presentara al dispositivo.", - "HeaderRecentlyPlayed": "Reproducido Recientemente", - "HeaderRecordingOptions": "Opciones de Grabación", - "HeaderRecordingPostProcessing": "Post Procesado de las Grabaciones", - "HeaderRemoteControl": "Control Remoto", - "HeaderRemoveMediaFolder": "Eliminar Carpteta de Medios", - "HeaderRemoveMediaLocation": "Eliminar Ubicación de Medios", - "HeaderResponseProfile": "Perfil de Respuesta", - "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada a un dispositivo cuando se reproducen ciertos tipos de medios.", + "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", + "HeaderProfile": "Perfil", + "HeaderProfileInformation": "Información del perfil", + "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderRecentlyPlayed": "Reproducido recientemente", + "HeaderRecordingOptions": "Opciones de grabación", + "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", + "HeaderRemoteControl": "Control remoto", + "HeaderRemoveMediaFolder": "Eliminar carpeta de medios", + "HeaderRemoveMediaLocation": "Eliminar ubicación de medios", + "HeaderResponseProfile": "Perfil de respuesta", + "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", "HeaderRestart": "Reiniciar", - "HeaderRevisionHistory": "Historial de Versiones", - "HeaderRunningTasks": "Tareas en Ejecución", + "HeaderRevisionHistory": "Historial de versiones", + "HeaderRunningTasks": "Tareas en ejecución", "HeaderScenes": "Escenas", "HeaderSchedule": "Programación", "HeaderSeasons": "Temporadas", - "HeaderSecondsValue": "{0} Segundos", - "HeaderSelectCertificatePath": "Seleccione la Ruta del Certificado", - "HeaderSelectMetadataPath": "Seleccionar Ruta para Metadatos", - "HeaderSelectMetadataPathHelp": "Explore o introduzca la ruta donde desea almacenar los metadatos. La carpeta debe tener permisos de escritura.", - "HeaderSelectPath": "Seleccionar Ruta", - "HeaderSelectServer": "Seleccionar Servidor", - "HeaderSelectServerCachePath": "Seleccionar ruta para Caché del Servidor", - "HeaderSelectServerCachePathHelp": "Explore o introduzca la ruta a utilizar para los archivos del caché del servidor. La carpeta debe tener permisos de escritura.", - "HeaderSelectTranscodingPath": "Seleccionar Ruta para Transcodificación Temporal", - "HeaderSelectTranscodingPathHelp": "Explore o introduzca la ruta a utilizar para los archivos temporales de transcodificación. La carpeta debe tener permisos de escritura.", - "HeaderSendMessage": "Enviar Mensaje", + "HeaderSecondsValue": "{0} segundos", + "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", + "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", + "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectPath": "Seleccionar ruta", + "HeaderSelectServer": "Seleccionar servidor", + "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", + "HeaderSelectServerCachePathHelp": "Explora o introduce la ruta a utilizar para los archivos caché del servidor. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", + "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSendMessage": "Enviar mensaje", "HeaderSeries": "Series", - "HeaderSeriesOptions": "Opciones de Serie", - "HeaderSeriesStatus": "Estado de la Serie", - "HeaderServerSettings": "Configuración del Servidor", + "HeaderSeriesOptions": "Opciones de serie", + "HeaderSeriesStatus": "Estado de la serie", + "HeaderServerSettings": "Configuración del servidor", "HeaderSettings": "Configuración", - "HeaderSetupLibrary": "Configurar sus bibliotecas de medios", + "HeaderSetupLibrary": "Configura tus bibliotecas de medios", "HeaderShutdown": "Apagar", - "HeaderSortBy": "Ordenar Por", - "HeaderSortOrder": "Ordenado Por", - "HeaderSpecialEpisodeInfo": "Información del Episodio Especial", - "HeaderSpecialFeatures": "Características Especiales", - "HeaderStartNow": "Iniciar Ahora", + "HeaderSortBy": "Ordenar por", + "HeaderSortOrder": "Clasificar ordenado", + "HeaderSpecialEpisodeInfo": "Información del episodio especial", + "HeaderSpecialFeatures": "Características especiales", + "HeaderStartNow": "Iniciar ahora", "HeaderStatus": "Estado", - "HeaderStopRecording": "Detener Grabación", - "HeaderSubtitleAppearance": "Apariencia de Subtitulos", - "HeaderSubtitleDownloads": "Descarga de Subtitulos", - "HeaderSubtitleProfile": "Perfíl de Subtítulo", - "HeaderSubtitleProfiles": "Perfiles de Subtítulos", - "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulos describen el formato del subtítulo soportado por el dispositivo.", - "HeaderSystemDlnaProfiles": "Perfiles del Sistema", + "HeaderStopRecording": "Detener grabación", + "HeaderSubtitleAppearance": "Apariencia de subtítulos", + "HeaderSubtitleDownloads": "Descarga de subtítulos", + "HeaderSubtitleProfile": "Perfil de subtítulo", + "HeaderSubtitleProfiles": "Perfiles de subtítulo", + "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulo describen los formatos de subtítulo soportados por el dispositivo.", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", "HeaderTags": "Etiquetas", - "HeaderTaskTriggers": "Disparadores de Tarea", + "HeaderTaskTriggers": "Disparadores de tarea", "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", "HeaderTracks": "Pistas", - "HeaderTranscodingProfile": "Perfil de Transcodificación", - "HeaderTranscodingProfileHelp": "Agruegue perfiles de transcodificación para indicar que formatos deben ser usados cuando se requiera transcodificar.", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTranscodingProfileHelp": "Agrega perfiles de transcodificación para indicar qué formatos deben ser usados cuando se requiere transcodificar.", "HeaderTunerDevices": "Sintonizadores", "HeaderTuners": "Sintonizador", - "HeaderTypeImageFetchers": "{0} Recolectores de Imágenes", - "HeaderTypeText": "Introduzca Texto", + "HeaderTypeImageFetchers": "Recolectores de imágenes para {0}", + "HeaderTypeText": "Introducir texto", "HeaderUpcomingOnTV": "Próximamente en TV", - "HeaderUploadImage": "Subir Imagen", + "HeaderUploadImage": "Subir imagen", "HeaderUser": "Usuario", "HeaderUsers": "Usuarios", - "HeaderVideoQuality": "Calidad de Video", - "HeaderVideoType": "Tipo de Video", - "HeaderVideoTypes": "Tipos de Video", - "HeaderXmlDocumentAttribute": "Atributo del Documento XML", - "HeaderXmlDocumentAttributes": "Atributos del Documento XML", + "HeaderVideoQuality": "Calidad de video", + "HeaderVideoType": "Tipo de video", + "HeaderVideoTypes": "Tipos de video", + "HeaderXmlDocumentAttribute": "Atributo del documento XML", + "HeaderXmlDocumentAttributes": "Atributos del documento XML", "HeaderXmlSettings": "Configuración XML", "HeaderYears": "Años", "HeadersFolders": "Carpetas", "Help": "Ayuda", "Hide": "Ocultar", - "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de Agregadas Recientemente", + "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de últimos medios", "Home": "Inicio", - "HttpsRequiresCert": "Para habilitar las conexiones seguras, es necesario proporcionar un certificado SSL de confianza, como el de \"Let's Encrypt\". Por favor proporcione un certificado, o desactive las conexiones seguras.", + "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", "Identify": "Identificar", "Images": "Imágenes", "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", - "ImportMissingEpisodesHelp": "Si se habilita, se importara a su base de datos de Jellyfin información sobre episodios faltantes y se mostrara dentro de las temporadas y series. Esto podría ocasionar escaneos de biblioteca significativamente mas largos.", + "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", "InstallingPackage": "Instalando {0} (versión {1})", "InstantMix": "Mix instantáneo", - "ItemCount": "{0} ítems", - "Items": "Ítems", + "ItemCount": "{0} elementos", + "Items": "Elementos", "Kids": "Niños", "Label3DFormat": "Formato 3D:", - "LabelAbortedByServerShutdown": "(Abortada por apagado del servidor)", + "LabelAbortedByServerShutdown": "(Abortado por apagado del servidor)", "LabelAccessDay": "Día de la semana:", - "LabelAccessEnd": "Horario de fin:", - "LabelAccessStart": "Horario de comienzo:", + "LabelAccessEnd": "Hora de finalización:", + "LabelAccessStart": "Hora de inicio:", "LabelAirDays": "Se emite los días:", "LabelAirTime": "Duración:", "LabelAirsAfterSeason": "Transmisión después de la temporada:", "LabelAirsBeforeEpisode": "Transmisión antes del episodio:", "LabelAirsBeforeSeason": "Transmisión antes de la temporada:", "LabelAlbum": "Álbum:", - "LabelAlbumArtHelp": "PN usado para arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", - "LabelAlbumArtMaxHeight": "Altura máxima para arte del álbum:", - "LabelAlbumArtMaxHeightHelp": "Máxima resolución para arte del album expuesta via upnp:albumArtURI.", - "LabelAlbumArtMaxWidth": "Ancho máximo para arte del álbum:", - "LabelAlbumArtMaxWidthHelp": "Máxima resolución para arte del album expuesta via upnp:albumArtURI.", - "LabelAlbumArtPN": "PN para arte del álbum:", + "LabelAlbumArtHelp": "PN usado para el arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", + "LabelAlbumArtMaxHeight": "Altura máxima del arte del álbum:", + "LabelAlbumArtMaxHeightHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Ancho máximo del arte del álbum:", + "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtPN": "PN del arte del álbum:", "LabelAlbumArtists": "Artistas del álbum:", "LabelAll": "Todos", - "LabelAllowHWTranscoding": "Permitir transcodificacion de hardware", - "LabelAllowServerAutoRestart": "Permite al servidor reiniciar automáticamente para aplicar actualizaciones", + "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "LabelAllowServerAutoRestart": "Permite al servidor reiniciarse automáticamente para aplicar actualizaciones", "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante los períodos de inactividad cuando no haya usuarios activos.", - "LabelAllowedRemoteAddresses": "Filtrar IP remota:", - "LabelAllowedRemoteAddressesMode": "Modo de filtrado de IP remota:", - "LabelAppName": "Nombre del App", + "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", + "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", + "LabelAppName": "Nombre de la aplicación", "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", "LabelArtists": "Artistas:", "LabelArtistsHelp": "Separar múltiples empleando ;", "LabelAudioLanguagePreference": "Idioma preferido de audio:", - "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente metadatos de internet:", - "LabelBindToLocalNetworkAddress": "Atar a la dirección de red local:", - "LabelBindToLocalNetworkAddressHelp": "Opcional. Forzar la dirección IP local a la que se vinculara el servidor http. Si se deja en blanco, el servidor vinculara todas las direcciones disponibles. Cambiando este valor se requerirá reiniciar el Servidor Jellyfin.", - "LabelBirthDate": "Fecha de Nacimiento:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", + "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", + "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBirthDate": "Fecha de nacimiento:", "LabelBirthYear": "Año de nacimiento:", "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", - "LabelBlockContentWithTags": "Bloquear ítems con etiquetas:", - "LabelBurnSubtitles": "Subtitulos quemados:", + "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", + "LabelBurnSubtitles": "Quemar subtítulos:", "LabelCache": "Caché:", - "LabelCachePath": "Ruta para el Caché:", - "LabelCachePathHelp": "Especifique una ubicación personalizada para los archivos de caché del servidor tales como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", + "LabelCachePath": "Ruta de la caché:", + "LabelCachePathHelp": "Especifica una ubicación personalizada para los archivos caché del servidor como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", "LabelCancelled": "Cancelado", "LabelCertificatePassword": "Contraseña del certificado:", - "LabelCertificatePasswordHelp": "Si su certificado requiere de una contraseña, por favor introdúzcala aquí.", + "LabelCertificatePasswordHelp": "Si tu certificado requiere una contraseña, por favor, introdúcela aquí.", "LabelChannels": "Canales:", "LabelCollection": "Colección:", "LabelCommunityRating": "Calificación de la comunidad:", - "LabelContentType": "Tipo de Contenido:", + "LabelContentType": "Tipo de contenido:", "LabelCountry": "País:", - "LabelCriticRating": "Calificación de la crítica:", + "LabelCriticRating": "Calificación de los críticos:", "LabelCurrentPassword": "Contraseña actual:", - "LabelCustomCertificatePath": "Ruta personalizada del certificado SSL:", - "LabelCustomCertificatePathHelp": "Trayectoria a un archivo PKCS #12 que contiene el certificado y la llave privada para habilitar soporte a TLS en un dominio personalizado.", + "LabelCustomCertificatePath": "Ruta del certificado SSL personalizado:", + "LabelCustomCertificatePathHelp": "Ruta a un archivo PKCS #12 que contiene un certificado y una clave privada para habilitar el soporte TLS en un dominio personalizado.", "LabelCustomCss": "CSS personalizado:", - "LabelCustomCssHelp": "Aplicar tu propio estilo personalizado a la interfaz web.", + "LabelCustomCssHelp": "Aplica tu propio estilo personalizado a la interfaz web.", "LabelCustomDeviceDisplayName": "Nombre a mostrar:", - "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjelo vacío para usar el nombre reportado por el dispositivo.", + "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjalo vacío para usar el nombre reportado por el dispositivo.", "LabelCustomRating": "Calificación personalizada:", - "LabelDashboardTheme": "Tema del panel de control del Servidor:", + "LabelDashboardTheme": "Tema del panel de control del servidor:", "LabelDateAdded": "Fecha de adición:", - "LabelDateAddedBehavior": "Comportamiento de fecha de adición para nuevo contenido:", - "LabelDateAddedBehaviorHelp": "Si se encuentra un valor en los metadados siempre será empleado antes que cualquiera de estas opciones.", - "LabelDateTimeLocale": "Configuración regional de Fecha y Hora:", + "LabelDateAddedBehavior": "Comportamiento de la fecha de adición para nuevo contenido:", + "LabelDateAddedBehaviorHelp": "Si un valor de metadatos está presente, siempre se utilizará antes de cualquiera de estas opciones.", + "LabelDateTimeLocale": "Configuración regional de fecha y hora:", "LabelDay": "Día:", "LabelDeathDate": "Fecha de defunción:", "LabelDefaultScreen": "Pantalla por defecto:", "LabelDefaultUser": "Usuario por defecto:", - "LabelDefaultUserHelp": "Determina que usuario de la biblioteca será mostrado en los dispositivos conectados. Este puede ser reemplazado para cada dispositivo empleando perfiles.", + "LabelDefaultUserHelp": "Determina qué biblioteca de usuario será mostrada en los dispositivos conectados. Esto puede ser reemplazado para cada dispositivo empleando perfiles.", "LabelDeviceDescription": "Descripción del dispositivo", "LabelDidlMode": "Modo DIDL:", "LabelDiscNumber": "Número de disco:", - "LabelDisplayLanguage": "Lenguaje de Despliege:", + "LabelDisplayLanguage": "Idioma de pantalla:", "LabelDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", - "LabelDisplayMissingEpisodesWithinSeasons": "Mostar episodios no disponibles en las temporadas", - "LabelDisplayMode": "Modo de Pantalla:", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "LabelDisplayMode": "Modo de pantalla:", "LabelDisplayName": "Nombre a mostrar:", "LabelDisplayOrder": "Orden para mostrar:", - "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en que fueron transmitidos", - "LabelDownMixAudioScale": "Fortalecimiento de audio durante el downmix:", - "LabelDownMixAudioScaleHelp": "Realzar el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", - "LabelDownloadLanguages": "Descargar lenguajes:", - "LabelDropImageHere": "Arrastre la imagen aquí, o de clic para navegar.", + "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en las que fueron transmitidas", + "LabelDownMixAudioScale": "Incremento del audio cuando se hace downmix:", + "LabelDownMixAudioScaleHelp": "Incrementa el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", + "LabelDownloadLanguages": "Descargar idiomas:", + "LabelDropImageHere": "Arrastre la imagen aquí o haz para explorar.", "LabelDropShadow": "Mostrar sombra:", - "LabelEasyPinCode": "Código pin sencillo:", + "LabelEasyPinCode": "Código PIN sencillo:", "LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL", - "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte del álbum. Otros podrían fallar al reproducir con esta opción habilitada.", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte de álbumes. Otros podrían fallar al reproducir con esta opción habilitada.", "LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos", "LabelEnableAutomaticPortMapHelp": "Redirecciona automáticamente los puertos públicos de tu router a los puertos locales de tu servidor a través de UPnP. Esto puede no funcionar con algunos modelos de routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", "LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida", - "LabelEnableBlastAliveMessagesHelp": "Habilite esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en su red.", - "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de Detección de Clientes (segundos)", + "LabelEnableBlastAliveMessagesHelp": "Habilita esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en tu red.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de descubrimiento de clientes (segundos)", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duración en segundos entre búsquedas SSDP realizadas por Jellyfin.", - "LabelEnableDlnaDebugLogging": "Habilitar el registro de DLNA en la bitácora", + "LabelEnableDlnaDebugLogging": "Habilitar el registro de depuración de DLNA", "LabelEnableDlnaDebugLoggingHelp": "Crea grandes archivos de registro y solo se debe usar cuando se requiera para solucionar problemas.", "LabelEnableDlnaPlayTo": "Habilitar Reproducir En mediante DLNA", - "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de su red y ofrece la capacidad de controlarlos remotamente.", + "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de tu red y ofrece la capacidad de controlarlos remotamente.", "LabelEnableDlnaServer": "Habilitar servidor DLNA", - "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en su red navegar y reproducir contenido.", - "LabelEnableHardwareDecodingFor": "Habilitar decodificacion por hardware para:", + "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en tu red explorar y reproducir contenido.", + "LabelEnableHardwareDecodingFor": "Habilitar decodificación por hardware para:", "LabelEnableRealtimeMonitor": "Activar monitoreo en tiempo real", - "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo que lo soporten.", + "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo soportados.", "LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada", - "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no renderisaran apropiadamente si hay múltiples imágenes incrustadas en el Didl.", - "LabelEndDate": "Fecha de Fin:", - "LabelEpisodeNumber": "Episodio numero:", + "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no se renderizarán correctamente si se incrustan varias imágenes en DIDL.", + "LabelEndDate": "Fecha de fin:", + "LabelEpisodeNumber": "Episodio número:", "LabelEvent": "Evento:", "LabelEveryXMinutes": "Cada:", - "LabelExtractChaptersDuringLibraryScan": "Extraer imágenes de capítulos durante la exploración de la biblioteca", - "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de capítulos cuando se importan videos durante el análisis de la biblioteca. De lo contrario, se extraerán durante la tarea programada de las imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", + "LabelExtractChaptersDuringLibraryScan": "Extraer las imágenes de los capítulos durante el escaneo de la biblioteca", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de los capítulos cuando se importan videos durante el escaneo de la biblioteca. De lo contrario, se extraerán durante la tarea programada imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", "LabelFailed": "Fallido", "LabelFileOrUrl": "Archivo o URL:", "LabelFinish": "Terminar", @@ -605,73 +605,73 @@ "LabelFriendlyName": "Nombre amistoso:", "LabelServerNameHelp": "Este nombre se usará para identificar el servidor y se predeterminará al nombre de la computadora del servidor.", "LabelGroupMoviesIntoCollections": "Agrupar películas en colecciones", - "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo ítem.", + "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo artículo.", "LabelH264Crf": "CRF de codificación H264:", - "LabelEncoderPreset": "Codificación H264 predefinido:", - "LabelHardwareAccelerationType": "Aceleración por Hardware:", - "LabelHardwareAccelerationTypeHelp": "Aceleración por Hardware requiere configuración adicional.", - "LabelHomeNetworkQuality": "Calidad en Red Local:", - "LabelHomeScreenSectionValue": "Pagina de inicio sección {0}:", + "LabelEncoderPreset": "Codificación H264 y H265 preestablecida:", + "LabelHardwareAccelerationType": "Aceleración por hardware:", + "LabelHardwareAccelerationTypeHelp": "La aceleración por hardware requiere configuración adicional.", + "LabelHomeNetworkQuality": "Calidad en red local:", + "LabelHomeScreenSectionValue": "Sección {0} de la pantalla de inicio:", "LabelHttpsPort": "Número de puerto local HTTPS:", "LabelHttpsPortHelp": "El número de puerto TCP al que el servidor HTTPS de Jellyfin debería enlazar.", "LabelIconMaxHeight": "Altura máxima del ícono:", - "LabelIconMaxHeightHelp": "Máxima resolución de íconos expuestos vía upnp:icon.", - "LabelIconMaxWidth": "Ancho máximo de ícono:", - "LabelIconMaxWidthHelp": "Máxima resolución de íconos expuestos vía upnp:icon.", - "LabelIdentificationFieldHelp": "Una subcadena insensible a la diferencia entre minúsculas y mayúsculas o expresión regex.", - "LabelImageFetchersHelp": "Habilite y priorice sus recolectores de imágenes preferidos.", + "LabelIconMaxHeightHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxWidth": "Ancho máximo del ícono:", + "LabelIconMaxWidthHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIdentificationFieldHelp": "Una subcadena indiferente a las mayúsculas y minúsculas o una expresión regular (regex).", + "LabelImageFetchersHelp": "Habilita y prioriza tus recolectores de imágenes preferidos.", "LabelImageType": "Tipo de imagen:", "LabelImportOnlyFavoriteChannels": "Restringir a canales marcados como favoritos", - "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código pin sencillo para conexiones dentro de la red", - "LabelInNetworkSignInWithEasyPasswordHelp": "Utilice el código PIN fácil para acceder a los clientes en su red local. Su contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitará una contraseña dentro de su red local.", - "LabelInternetQuality": "Calidad en internet:", + "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código PIN sencillo para conexiones dentro de la red", + "LabelInNetworkSignInWithEasyPasswordHelp": "Utiliza el código PIN sencillo para acceder a los clientes en tu red local. Tu contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitarás una contraseña dentro de tu red local.", + "LabelInternetQuality": "Calidad en Internet:", "LabelKeepUpTo": "Mantener hasta:", "LabelKidsCategories": "Categorías infantiles:", "LabelKodiMetadataDateFormat": "Formato de fecha de estreno:", "LabelKodiMetadataDateFormatHelp": "Todas las fechas dentro de los archivos NFO serán analizadas usando este formato.", "LabelKodiMetadataEnableExtraThumbs": "Copiar extrafanart al campo extrathumbs", - "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con skins de Kodi.", + "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes estas pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con las pieles de Kodi.", "LabelKodiMetadataEnablePathSubstitution": "Habilitar sustitución de ruta", "LabelKodiMetadataEnablePathSubstitutionHelp": "Habilita la sustitución de rutas de imágenes usando la configuración de sustitución de rutas del servidor.", - "LabelKodiMetadataSaveImagePaths": "Guardar rutas de imágenes en los archivos nfo", - "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tiene nombres de imagenes que no se ajustan a los lineamientos de Kodi.", + "LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO", + "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.", "LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:", - "LabelKodiMetadataUserHelp": "Guarda los datos de visto en los archivos NFO para que otras aplicaciones los utilicen.", + "LabelKodiMetadataUserHelp": "Guarda los datos de visto en archivos NFO para que otras aplicaciones los utilicen.", "LabelLanNetworks": "Redes LAN:", "LabelLanguage": "Idioma:", "LabelLineup": "Programación:", "LabelLocalHttpServerPortNumber": "Número de puerto local HTTP:", "LabelLocalHttpServerPortNumberHelp": "El número de puerto TCP al que el servidor HTTP de Jellyfin debería enlazar.", - "LabelLockItemToPreventChanges": "Bloquear este ítem para evitar cambios futuros", + "LabelLockItemToPreventChanges": "Bloquear este elemento para evitar cambios futuros", "LabelLoginDisclaimer": "Aviso legal:", "LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.", - "LabelLogs": "Bitácoras:", + "LabelLogs": "Registros:", "LabelManufacturer": "Fabricante:", "LabelManufacturerUrl": "URL del fabricante", - "LabelMatchType": "Tipo de Coincidencia:", - "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por ítem:", - "LabelMaxChromecastBitrate": "Tasa maxima de bits para El Chromecast:", + "LabelMatchType": "Tipo de coincidencia:", + "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:", + "LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:", "LabelMaxParentalRating": "Máxima clasificación parental permitida:", "LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:", "LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.", - "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por ítem:", + "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por elemento:", "LabelMaxStreamingBitrate": "Calidad máxima de transmisión:", - "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima al hacer streaming.", - "LabelMessageText": "Texto del Mensaje:", - "LabelMessageTitle": "Título del Mensaje:", + "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima cuando se transmite.", + "LabelMessageText": "Texto del mensaje:", + "LabelMessageTitle": "Título del mensaje:", "LabelMetadata": "Metadatos:", - "LabelMetadataDownloadLanguage": "Lenguaje preferido para descargas:", - "LabelMetadataDownloadersHelp": "Habilite y priorice sus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", - "LabelMetadataPath": "Ruta para metadatos:", - "LabelMetadataPathHelp": "Especifique una ubicación personalizada para ilustraciones y metadatos descargados.", + "LabelMetadataDownloadLanguage": "Idioma preferido para las descargas:", + "LabelMetadataDownloadersHelp": "Habilita y prioriza tus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", + "LabelMetadataPath": "Ruta para los metadatos:", + "LabelMetadataPathHelp": "Especifique una ubicación personalizada para las ilustraciones y los metadatos descargados.", "LabelMetadataReaders": "Lectores de metadatos:", - "LabelMetadataReadersHelp": "Ordene sus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", + "LabelMetadataReadersHelp": "Ordena tus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", "LabelMetadataSavers": "Grabadores de metadatos:", - "LabelMetadataSaversHelp": "Seleccione los formatos de archivo con los que se guardaran sus metadatos.", + "LabelMetadataSaversHelp": "Selecciona los formatos de archivo con los que se guardarán tus metadatos.", "LabelMethod": "Método:", "LabelMinBackdropDownloadWidth": "Anchura mínima de descarga de imágenes de fondo:", "LabelMinResumeDuration": "Duración mínima para la reanudación:", - "LabelMinResumeDurationHelp": "La duración de vídeo más corta en segundos que guardará la ubicación de reproducción y le permitirá reanudar la reproducción.", + "LabelMinResumeDurationHelp": "La duración de video más corta en segundos que guardará la ubicación de reproducción y te permitirá reanudarla.", "LabelMinResumePercentage": "Porcentaje mínimo para reanudar:", "LabelMinResumePercentageHelp": "Los medios se asumen como no reproducidos si se detienen antes de este tiempo.", "LabelMinScreenshotDownloadWidth": "Anchura mínima de descarga de capturas de pantalla:", @@ -682,15 +682,15 @@ "LabelMonitorUsers": "Monitorear actividad desde:", "LabelMovieCategories": "Categorías de películas:", "LabelMoviePrefix": "Prefijo de la película:", - "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúzcalo aquí para que el servidor pueda manejarlo correctamente.", - "LabelMovieRecordingPath": "Ruta para grabaciones de Películas (opcional):", - "LabelMusicStreamingTranscodingBitrate": "Tasa de transcodificación de música:", - "LabelMusicStreamingTranscodingBitrateHelp": "Especifique la tasa de bits máxima al transmitir música.", + "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", "LabelName": "Nombre:", "LabelNewName": "Nuevo nombre:", - "LabelNewPassword": "Nueva contraseña:", + "LabelNewPassword": "Contraseña nueva:", "LabelNewPasswordConfirm": "Confirmación de contraseña nueva:", - "LabelNewsCategories": "Categorías de Noticias:", + "LabelNewsCategories": "Categorías de noticias:", "LabelNext": "Siguiente", "LabelNotificationEnabled": "Habilitar esta notificación", "LabelNumber": "Número:", @@ -699,279 +699,279 @@ "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente.", "LabelOriginalAspectRatio": "Relación de aspecto original:", - "LabelOriginalTitle": "Titulo original:", - "LabelOverview": "Sinopsis:", + "LabelOriginalTitle": "Título original:", + "LabelOverview": "Resumen:", "LabelParentNumber": "Número antecesor:", "LabelParentalRating": "Clasificación parental:", "LabelPassword": "Contraseña:", "LabelPasswordConfirm": "Contraseña (confirmar):", - "LabelPasswordRecoveryPinCode": "Código pin:", + "LabelPasswordRecoveryPinCode": "Código PIN:", "LabelPath": "Ruta:", "LabelPersonRole": "Rol:", "LabelPersonRoleHelp": "Ejemplo: Conductor de camión de helados", "LabelPlaceOfBirth": "Lugar de nacimiento:", - "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del lenguaje", - "LabelPlaylist": "Lista de Reproducción:", - "LabelPostProcessor": "Aplicación de Post Procesado:", - "LabelPostProcessorArguments": "Argumentos de linea de comando para el post-procesador:", - "LabelPostProcessorArgumentsHelp": "Use {path} como la ruta a el archivo de grabado.", + "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del idioma", + "LabelPlaylist": "Lista de reproducción:", + "LabelPostProcessor": "Aplicación de postprocesamiento:", + "LabelPostProcessorArguments": "Argumentos de la línea de comandos del post-procesador:", + "LabelPostProcessorArgumentsHelp": "Usar {path} como la ruta del archivo grabado.", "LabelPreferredDisplayLanguage": "Idioma de pantalla preferido:", "LabelPreferredDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", - "LabelPreferredSubtitleLanguage": "Idioma preferido para subtitulos:", + "LabelPreferredSubtitleLanguage": "Idioma preferido para los subtítulos:", "LabelPrevious": "Anterior", - "LabelProfileAudioCodecs": "Codecs de Audio:", - "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vació para aplicarlo a todos los codecs.", + "LabelProfileAudioCodecs": "Códecs de audio:", + "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los códecs.", "LabelProfileContainer": "Contenedor:", - "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vació para aplicarlo a todos los contenedores.", - "LabelProfileVideoCodecs": "Codecs de Video:", + "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los contenedores.", + "LabelProfileVideoCodecs": "Códecs de video:", "LabelProtocol": "Protocolo:", "LabelProtocolInfo": "Información del protocolo:", - "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responde a solicitudes GetProtocolInfo desde el dispositivo.", + "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responda a solicitudes GetProtocolInfo desde el dispositivo.", "LabelPublicHttpPort": "Número de puerto HTTP público:", "LabelPublicHttpPortHelp": "El número de puerto público que debe asignarse al puerto HTTP local.", "LabelPublicHttpsPort": "Número de puerto HTTPS público:", "LabelPublicHttpsPortHelp": "El número de puerto público que debe asignarse al puerto HTTPS local.", - "LabelReadHowYouCanContribute": "Infórmese de cómo puede contribuir.", + "LabelReadHowYouCanContribute": "Aprende cómo puedes contribuir.", "LabelReasonForTranscoding": "Motivo para transcodificar:", "LabelRecord": "Grabar:", - "LabelRecordingPath": "Ruta por defecto para grabaciones:", - "LabelRecordingPathHelp": "Especifique la locación para guardar grabaciones. Si se deja en blanco, se usara la carpeta de datos de programa del servidor.", + "LabelRecordingPath": "Ruta por defecto para las grabaciones:", + "LabelRecordingPathHelp": "Especifica la ubicación por defecto para guardar las grabaciones. Si se deja en blanco, se usará la carpeta de datos de programa del servidor.", "LabelRefreshMode": "Modo de actualización:", "LabelReleaseDate": "Fecha de estreno:", - "LabelRemoteClientBitrateLimit": "Limite de tasa de bits para transmisión por Internet (Mbps):", - "LabelRemoteClientBitrateLimitHelp": "Un límite opcional en la tasa de bits para cada transmisión para todos los dispositivos fuera de la red local. Esto es útil para evitar que los clientes soliciten una tasa de bits mayor a la que su conexión de internet puede soportar. Puede resultar en un incremente en la carga del CPU de su servidor para poder transmitir videos al vuelo a una resolución baja.", + "LabelRemoteClientBitrateLimit": "Limite de velocidad de bits para la transmisión por Internet (Mbps):", + "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", "LabelRuntimeMinutes": "Duración (minutos):", - "LabelSaveLocalMetadata": "Guardar ilustraciones en las carpetas de medios", - "LabelSaveLocalMetadataHelp": "Guardar ilustraciones directamente en las carpetas de medios los colocará en un lugar donde se pueden editar fácilmente.", - "LabelScheduledTaskLastRan": "Ejecutado hace {0}, tomando {1}.", - "LabelScreensaver": "Protector de Pantalla:", - "LabelSeasonNumber": "Temporada numero:", + "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", + "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", + "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", + "LabelScreensaver": "Protector de pantalla:", + "LabelSeasonNumber": "Temporada número:", "LabelSecureConnectionsMode": "Modo de conexión segura:", - "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas en vistas tales como Películas, Música y TV:", + "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:", "LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.", - "LabelSelectUsers": "Seleccionar Usuarios:", + "LabelSelectUsers": "Seleccionar usuarios:", "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", "LabelSendNotificationToUsers": "Enviar la notificación a:", "LabelSerialNumber": "Número de serie", - "LabelSeriesRecordingPath": "Ruta para grabaciones de Series (Opcional):", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", "LabelServerHost": "Servidor:", "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", - "LabelSimultaneousConnectionLimit": "Limite de transmisiones simultaneas:", + "LabelSimultaneousConnectionLimit": "Límite de transmisiones simultáneas:", "LabelSkin": "Apariencia:", "LabelSkipBackLength": "Longitud de salto hacia atrás:", "LabelSkipForwardLength": "Longitud de salto hacia adelante:", - "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el lenguaje de descarga", - "LabelSkipIfAudioTrackPresentHelp": "Desactive esto para asegurar que todos los videos tengan subtítulos, independientemente del lenguaje del audio.", - "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos embebidos", - "LabelSkipIfGraphicalSubsPresentHelp": "Manteniendo las versiones de texto de los subtitulos resultara una entrega mas eficiente de los mismos y disminuirá las posibilidades de que un video sea transcodificado.", - "LabelSonyAggregationFlags": "Banderas de agregación Sony:", + "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", + "LabelSkipIfAudioTrackPresentHelp": "Desactiva esto para asegurarte de que todos los videos tengan subtítulos, independientemente del idioma del audio.", + "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos incrustados", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos resultará en una entrega más eficiente y disminuirá las posibilidades de que un video sea transcodificado.", + "LabelSonyAggregationFlags": "Marcas de agregación Sony:", "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el namespace urn:schemas-sonycom:av.", "LabelSortBy": "Ordenar por:", - "LabelSortOrder": "Modo de ordenar:", - "LabelSortTitle": "Titulo para ordenar:", - "LabelSoundEffects": "Efectos de Sonido:", + "LabelSortOrder": "Clasificar ordenado:", + "LabelSortTitle": "Título para ordenar:", + "LabelSoundEffects": "Efectos de sonido:", "LabelSource": "Fuente:", - "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de Especiales:", - "LabelSportsCategories": "Categorías de Deportes:", + "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de especiales:", + "LabelSportsCategories": "Categorías de deportes:", "LabelStartWhenPossible": "Iniciar cuando sea posible:", "LabelStatus": "Estado:", "LabelStopWhenPossible": "Detener cuando sea posible:", "LabelStopping": "Deteniendo", - "LabelSubtitleDownloaders": "Recolectores de Subtitulos:", + "LabelSubtitleDownloaders": "Recolectores de subtítulos:", "LabelSubtitleFormatHelp": "Ejemplo: srt", "LabelSubtitlePlaybackMode": "Modo de subtítulo:", "LabelSubtitles": "Subtítulos", - "LabelSupportedMediaTypes": "Tipos de Medios Soportados:", + "LabelSupportedMediaTypes": "Tipos de medios soportados:", "LabelTVHomeScreen": "Modo de pantalla de TV:", "LabelTag": "Etiqueta:", "LabelTagline": "Eslogan:", "LabelTextBackgroundColor": "Color de fondo para el texto:", - "LabelTextColor": "Color de texto:", - "LabelTextSize": "Tamaño de texto:", + "LabelTextColor": "Color del texto:", + "LabelTextSize": "Tamaño del texto:", "LabelTheme": "Tema:", "LabelTime": "Hora:", - "LabelTimeLimitHours": "Límite de Tiempo (horas):", - "LabelTitle": "Titulo:", - "LabelTrackNumber": "Número de Pista:", - "LabelTranscodingAudioCodec": "Codec de audio:", + "LabelTimeLimitHours": "Límite de tiempo (horas):", + "LabelTitle": "Título:", + "LabelTrackNumber": "Número de pista:", + "LabelTranscodingAudioCodec": "Códec de audio:", "LabelTranscodingContainer": "Contenedor:", - "LabelTranscodingTempPathHelp": "Especifique una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deje en blanco para utilizar el predeterminado del servidor.", - "LabelTranscodingThreadCount": "Conteo de hilos de transcodificación:", - "LabelTranscodingThreadCountHelp": "Seleccione el número máximo de hilos a utilizar al transcodificar. La reducción del número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", - "LabelTranscodingVideoCodec": "Codec de video:", - "LabelTriggerType": "Tipo de Evento:", - "LabelTunerIpAddress": "Dirección IP del Sintonizador:", + "LabelTranscodingTempPathHelp": "Especifica una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deja en blanco para utilizar el predeterminado del servidor.", + "LabelTranscodingThreadCount": "Conteo de hilos de la transcodificación:", + "LabelTranscodingThreadCountHelp": "Selecciona el número máximo de hilos a utilizar al transcodificar. Reducir el número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", + "LabelTranscodingVideoCodec": "Códec de video:", + "LabelTriggerType": "Tipo de disparador:", + "LabelTunerIpAddress": "Dirección IP del sintonizador:", "LabelTunerType": "Tipo de sintonizador:", "LabelType": "Tipo:", - "LabelTypeMetadataDownloaders": "{0} recolectores de metadatos:", + "LabelTypeMetadataDownloaders": "Recolectores de metadatos para {0}:", "LabelTypeText": "Texto", - "LabelUseNotificationServices": "Emplear los siguientes servicios:", + "LabelUseNotificationServices": "Usar los siguientes servicios:", "LabelUser": "Usuario:", "LabelUserAgent": "Agente de usuario:", - "LabelUserLibrary": "Biblioteca del Usuario:", - "LabelUserLibraryHelp": "Seleccione la biblioteca de usuario que se mostrara en el dispositivo. Deje vacío para heredar la configuración por defecto.", + "LabelUserLibrary": "Biblioteca de usuario:", + "LabelUserLibraryHelp": "Selecciona la biblioteca de usuario que se mostrará en el dispositivo. Déjalo vacío para heredar la configuración por defecto.", "LabelUserRemoteClientBitrateLimitHelp": "Sobrescribe el valor global predeterminado establecido en la configuración de reproducción del servidor.", - "LabelUsername": "Nombre Usuario:", + "LabelUsername": "Nombre de usuario:", "LabelVaapiDevice": "Dispositivo VA API:", - "LabelVaapiDeviceHelp": "Este es un nodo de renderizado que es usado para aceleración por hardware.", + "LabelVaapiDeviceHelp": "Este es el nodo de renderizado que es usado para la aceleración por hardware.", "LabelValue": "Valor:", "LabelVersion": "Versión:", "LabelVersionInstalled": "{0} instalado", "LabelVersionNumber": "Versión {0}", - "LabelXDlnaCap": "X-DLNA cap:", + "LabelXDlnaCap": "X-DLNA límite:", "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", "LabelXDlnaDoc": "Documento X-DLNA:", "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.", "LabelYear": "Año:", - "LabelYourFirstName": "Su nombre:", - "LabelYoureDone": "¡Ha terminado!", - "LabelZipCode": "Código Postal:", - "LabelffmpegPath": "Ruta FFmpeg:", - "LabelffmpegPathHelp": "La ruta hacia el archivo de aplicación de ffmpeg, o la carpeta que contenga ffmpeg.", - "LanNetworksHelp": "Lista separada por comas de direcciones IP/mascaras de subred para las redes que serán consideradas como locales al enforzar restricciones de ancho de banda. Si se establece, todas las demás direcciones IP serán consideradas como redes externas y estarán sujetas a restricciones de ancho de banda. Si se deja en blanco, sólo la subred del servidor será considerada como red local.", + "LabelYourFirstName": "Tu nombre:", + "LabelYoureDone": "¡Has terminado!", + "LabelZipCode": "Código postal:", + "LabelffmpegPath": "Ruta del FFmpeg:", + "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", "Large": "Grande", - "LatestFromLibrary": "Más recientes - {0}", - "LearnHowYouCanContribute": "Aprenda como puede contribuír.", - "LibraryAccessHelp": "Seleccione las bibliotecas que desea compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", + "LatestFromLibrary": "Últimas - {0}", + "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", "Like": "Me gusta", "LinksValue": "Enlaces: {0}", "List": "Lista", - "Live": "En Vivo", - "LiveBroadcasts": "Transmisiones en vivo", - "LiveTV": "TV en Vivo", - "Logo": "Logotipo", + "Live": "En vivo", + "LiveBroadcasts": "Emisiones en vivo", + "LiveTV": "TV en vivo", + "Logo": "Logo", "ManageLibrary": "Administrar biblioteca", - "ManageRecording": "Administrar grabaciones", - "MapChannels": "Mapear Canales", - "MarkPlayed": "Marcar como Reproducido", - "MarkUnplayed": "Marcar como No Reproducido", - "MaxParentalRatingHelp": "El contenido con clasificación parental superior se ocultará para este usuario.", + "ManageRecording": "Administrar grabación", + "MapChannels": "Mapear canales", + "MarkPlayed": "Marcar como reproducido", + "MarkUnplayed": "Marcar como no reproducido", + "MaxParentalRatingHelp": "El contenido con una calificación más alta será ocultado a este usuario.", "MediaInfoAnamorphic": "Anamórfico", "MediaInfoAspectRatio": "Relación de aspecto", "MediaInfoBitDepth": "Profundidad de bit", - "MediaInfoBitrate": "Tasa de bits", + "MediaInfoBitrate": "Velocidad de bits", "MediaInfoChannels": "Canales", - "MediaInfoCodecTag": "Etiqueta de Codec", + "MediaInfoCodecTag": "Etiqueta de códec", "MediaInfoContainer": "Contenedor", "MediaInfoDefault": "Por defecto", "MediaInfoExternal": "Externo", "MediaInfoForced": "Forzado", - "MediaInfoFramerate": "Velocidad de cuadro", + "MediaInfoFramerate": "Velocidad de cuadros", "MediaInfoInterlaced": "Entrelazado", - "MediaInfoLanguage": "Lenguaje", + "MediaInfoLanguage": "Idioma", "MediaInfoLayout": "Esquema", "MediaInfoLevel": "Nivel", "MediaInfoPath": "Ruta", "MediaInfoPixelFormat": "Formato de pixel", - "MediaInfoProfile": "Perfíl", + "MediaInfoProfile": "Perfil", "MediaInfoRefFrames": "Tramas de referencia", "MediaInfoResolution": "Resolución", "MediaInfoSampleRate": "Tasa de muestreo", "MediaInfoSize": "Tamaño", "MediaInfoTimestamp": "Fecha y hora", - "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que esta reproduciendo el medio.", + "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que está reproduciendo el medio.", "Menu": "Menú", "MessageAlreadyInstalled": "Esta versión ya se encuentra instalada.", - "MessageAreYouSureDeleteSubtitles": "¿Está seguro de querer eliminar este archivo de subtitulos?", - "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Está seguro de querer eliminar esta carpeta de medios?", - "MessageConfirmDeleteGuideProvider": "¿Está seguro de querer eliminar este proveedor de guía?", - "MessageConfirmDeleteTunerDevice": "¿Está seguro de querer eliminar este dispositivo?", - "MessageConfirmProfileDeletion": "¿Está seguro de querer eliminar este perfil?", - "MessageConfirmRecordingCancellation": "¿cancelar esta grabación?", - "MessageConfirmRemoveMediaLocation": "¿Está seguro de querer eliminar esta ubicación?", - "MessageConfirmRestart": "¿Esta seguro de que desea reiniciar el Servidor Jellyfin?", - "MessageConfirmRevokeApiKey": "¿Esta seguro de querer revocar esta clave api? La conexión de la aplicación con el Servidor Jellyfin sera terminada abruptamente.", - "MessageConfirmShutdown": "¿Está seguro de que desea apagar el servidor?", - "MessageContactAdminToResetPassword": "Por favor contacte a su administrador para restablecer su contraseña.", + "MessageAreYouSureDeleteSubtitles": "¿Estás seguro de querer eliminar este subtítulo?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Estás seguro de querer remover esta carpeta de medios?", + "MessageConfirmDeleteGuideProvider": "¿Estás seguro de querer eliminar este proveedor de guía?", + "MessageConfirmDeleteTunerDevice": "¿Estás seguro de querer eliminar este dispositivo?", + "MessageConfirmProfileDeletion": "¿Estás seguro de querer eliminar este perfil?", + "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer eliminar esta ubicación?", + "MessageConfirmRestart": "¿Estás seguro de que deseas reiniciar el servidor Jellyfin?", + "MessageConfirmRevokeApiKey": "¿Estás seguro de querer revocar esta clave API? La conexión de la aplicación con el servidor Jellyfin será terminada abruptamente.", + "MessageConfirmShutdown": "¿Estás seguro de que deseas apagar el servidor?", + "MessageContactAdminToResetPassword": "Por favor, contacta a tu administrador para restablecer tu contraseña.", "MessageCreateAccountAt": "Crear una cuenta en {0}", - "MessageDeleteTaskTrigger": "¿Está seguro de querer eliminar este disparador de tarea?", - "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesite configurar el almacenamiento dentro de su \"FreeNAS Jail\" de manera que permita a Jellyfin accesarlo.", - "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el botón de Red no pueda localizar sus dispositivos. Por ejemplo, {0} or {1}.", - "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE, o Ubuntu. Debe conceder al usuario del servicio al menos permisos de lectura a sus ubicaciones de almacenamiento.", - "MessageDownloadQueued": "Descargar cola.", - "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente mas largos.", - "MessageFileReadError": "Hubo un error al leer el archivo. Por favor intente de nuevo.", + "MessageDeleteTaskTrigger": "¿Estás seguro de querer eliminar este disparador de tarea?", + "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesites configurar el almacenamiento dentro de tu «FreeNAS Jail» de manera que permita a Jellyfin accederlo.", + "MessageDirectoryPickerInstruction": "Las rutas de red pueden ser introducidas manualmente en caso de que el botón de Red no pueda localizar sus dispositivos. Por ejemplo, {0} o {1}.", + "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE o Ubuntu, debes conceder al usuario del servicio al menos permisos de lectura a tus ubicaciones de almacenamiento.", + "MessageDownloadQueued": "Descarga puesta en la cola.", + "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente más largos.", + "MessageFileReadError": "Hubo un error al leer el archivo. Por favor, intenta de nuevo.", "MessageForgotPasswordFileCreated": "El siguiente archivo fue creado en tu servidor y contiene instrucciones de como proceder:", - "MessageForgotPasswordInNetworkRequired": "Por favor intente de nuevo dentro de su red de hogar para iniciar el proceso de restablecimiento de contraseña.", - "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que desea usarlo.", - "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtelo de nuevo.", - "MessageInvalidUser": "Nombre del usuario o contraseña inválidos. Por favor intenta de nuevo.", - "MessageItemSaved": "Ítem guardado.", - "MessageItemsAdded": "Ítems agregados.", + "MessageForgotPasswordInNetworkRequired": "Por favor, intenta de nuevo dentro de tu red local para iniciar el proceso de restablecimiento de contraseña.", + "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que deseas usarlo.", + "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtalo de nuevo.", + "MessageInvalidUser": "Nombre de usuario o contraseña inválidos. Por favor, intenta de nuevo.", + "MessageItemSaved": "Elemento guardado.", + "MessageItemsAdded": "Elementos agregados.", "MessageLeaveEmptyToInherit": "Dejar vacío para heredar la configuración de un elemento superior o del valor predeterminado global.", "MessageNoAvailablePlugins": "No hay complementos disponibles.", - "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y regresa para ver tus recomendaciones.", - "MessageNoPluginsInstalled": "No tienes extensiones instaladas.", - "MessageNoTrailersFound": "No se encontraron tráilers. Instale el canal de tráilers para mejorar su experiencia con películas al agregar una biblioteca de tráilers desde el Internet.", + "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y luego regresa para ver tus recomendaciones.", + "MessageNoPluginsInstalled": "No tienes complementos instalados.", + "MessageNoTrailersFound": "No se encontraron trailers. Instala el canal de trailers para mejorar tu experiencia con películas al agregar una biblioteca de trailers desde Internet.", "MessageNothingHere": "Nada aquí.", "MessagePasswordResetForUsers": "Los siguientes usuarios han restablecido sus contraseñas. Ahora pueden iniciar sesión con los códigos PIN que se usaron para realizar el restablecimiento.", - "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Póngase en contacto con el administrador del servidor para obtener más información.", - "MessagePleaseEnsureInternetMetadata": "Por favor asegúrese que la descarga de metadatos de internet esta habilitada.", - "MessagePleaseWait": "Espere por favor. Esto podría tomar un minuto.", - "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor inicie sesión en su servidor local directamente.", - "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar su experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, conozca el impacto que pueden ocasionar en su Servidor Jellyfin, tales como exploración de la biblioteca que puede tomar más tiempo, procesamiento en segundo plano adicional y estabilidad del sistema reducida.", - "MessageReenableUser": "Vea abajo para volverlo a habilitar", - "MessageSettingsSaved": "Configuración guardada.", - "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se eliminarán de su biblioteca:", - "MessageUnableToConnectToServer": "No es posible conectarse al servidor seleccionado en este momento. Por favor asegúrese de que se encuentra en ejecución e inténtelo nuevamente.", - "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utilice el administrador de metadatos para establecer los tipos de contenido para las sub-carpetas.", - "MessageYouHaveVersionInstalled": "Actualmente cuenta con la vesión {0} instalada.", + "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Por favor, contacta al administrador del servidor para obtener más información.", + "MessagePleaseEnsureInternetMetadata": "Por favor, asegúrate de que la descarga de metadatos de Internet está habilitada.", + "MessagePleaseWait": "Por favor, espera. Esto podría tomar un minuto.", + "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor, inicia sesión en tu servidor local directamente.", + "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar tu experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor Jellyfin, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", + "MessageReenableUser": "Ver abajo para volver a habilitar", + "MessageSettingsSaved": "Configuraciones guardadas.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se eliminarán de tu biblioteca:", + "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", + "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utiliza el administrador de metadatos para establecer los tipos de contenido para las subcarpetas.", + "MessageYouHaveVersionInstalled": "Actualmente cuentas con la versión {0} instalada.", "Metadata": "Metadatos", - "MetadataManager": "Administrador de Metadatos", - "MetadataSettingChangeHelp": "Cambiar los ajustes de metadata afectará al contenido nuevo añadido a partir de ahora. Para actualizar el contenido existente, abra la pantalla de detalles y haga clic en actualizar, o realice actualizaciones masivas usando el administrador de metadata.", - "MinutesAfter": "minutos despues", - "MinutesBefore": "Minutos antes", + "MetadataManager": "Administrador de metadatos", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MinutesAfter": "minutos después", + "MinutesBefore": "minutos antes", "Mobile": "Móvil", "Monday": "Lunes", - "MoreFromValue": "Mas de {0}", + "MoreFromValue": "Más de {0}", "MoreUsersCanBeAddedLater": "Más usuarios pueden ser añadidos más tarde desde el panel de control.", "MoveLeft": "Mover a la izquierda", "MoveRight": "Mover a la derecha", - "MovieLibraryHelp": "Revisar la {0}Guía para nombrar películas{1}.", + "MovieLibraryHelp": "Revisar la {0}guía de nombrado de películas{1}.", "Movies": "Películas", "Mute": "Silenciar", - "MySubtitles": "Mis Subtitulos", + "MySubtitles": "Mis subtítulos", "Name": "Nombre", "Never": "Nunca", - "NewCollection": "Nueva Colección", - "NewCollectionHelp": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", + "NewCollection": "Nueva colección", + "NewCollectionHelp": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", "NewCollectionNameExample": "Ejemplo: Colección Guerra de las Galaxias", "NewEpisodes": "Episodios nuevos", "NewEpisodesOnly": "Solo episodios nuevos", "News": "Noticias", "Next": "Siguiente", - "NextUp": "A Continuación", - "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierre este cuadro de dialogo e introduzca a la información del dispositivo manualmente.", + "NextUp": "A continuación", + "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierra este diálogo e introduce la información del dispositivo manualmente.", "NoNextUpItemsMessage": "No se encontró nada. ¡Comienza a ver tus programas!", - "NoPluginConfigurationMessage": "El complemento no tiene configuraciones disponibles.", + "NoPluginConfigurationMessage": "Este complemento no tiene configuraciones disponibles.", "NoSubtitleSearchResultsFound": "No se encontraron resultados.", - "NoSubtitles": "Nada", - "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pero pueden ser activados manualmente durante la reproducción.", + "NoSubtitles": "Ninguno", + "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pueden ser activados manualmente durante la reproducción.", "None": "Ninguno", "NumLocationsValue": "{0} carpetas", "Off": "Apagar", "OneChannel": "Un canal", - "OnlyForcedSubtitles": "Únicamente forzados", - "OnlyForcedSubtitlesHelp": "Se cargarán únicamente subtítulos marcados como forzados.", + "OnlyForcedSubtitles": "Solo forzados", + "OnlyForcedSubtitlesHelp": "Solo se cargarán subtítulos marcados como forzados.", "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", "OptionAdminUsers": "Administradores", "OptionAlbum": "Álbum", - "OptionAlbumArtist": "Artista del Álbum", + "OptionAlbumArtist": "Artista del álbum", "OptionAllUsers": "Todos los usuarios", - "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera de transcodificación", - "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en Vivo", + "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera transcodificación", + "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en vivo", "OptionAllowContentDownloading": "Permitir descarga y sincronización de medios", "OptionAllowLinkSharing": "Permitir compartir medios en redes sociales", - "OptionAllowLinkSharingHelp": "Solo son compartidas paginas web que contengan información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Son compartidos por un tiempo limitado y expiraran después de {0} días.", - "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en Vivo", + "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", + "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", "OptionAllowMediaPlayback": "Permitir reproducción de medios", - "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a transcodificacion podría causar fallas en la reproducción in las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", - "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se consideran compartidos hasta que un usuario comienza a controlarlos.", + "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", "OptionAllowSyncTranscoding": "Permitir descarga y sincronización de medios que requieran transcodificación", "OptionAllowUserToManageServer": "Permitir a este usuario administrar el servidor", - "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin re-codificar", + "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin recodificar", "OptionAllowVideoPlaybackTranscoding": "Permitir la reproducción de video que requiera de transcodificación", "OptionArtist": "Artista", "OptionAscending": "Ascendente", @@ -980,129 +980,129 @@ "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", "OptionBlockBooks": "Libros", - "OptionBlockChannelContent": "Contenido de Canales de Internet", - "OptionBlockLiveTvChannels": "Canales de TV en Vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet", + "OptionBlockLiveTvChannels": "Canales de TV en vivo", "OptionBlockMovies": "Películas", "OptionBlockMusic": "Música", - "OptionBlockTrailers": "Tráilers", + "OptionBlockTrailers": "Trailers", "OptionBlockTvShows": "Programas de TV", - "OptionCommunityRating": "Calificación de la Comunidad", + "OptionCommunityRating": "Calificación de la comunidad", "OptionContinuing": "Continuando", - "OptionCriticRating": "Calificación de la Crítica", + "OptionCriticRating": "Calificación de los críticos", "OptionCustomUsers": "Personalizado", "OptionDaily": "Diario", - "OptionDateAdded": "Fecha de Adición", - "OptionDateAddedFileTime": "Emplear fecha de creación del archivo", - "OptionDateAddedImportTime": "Emplear la fecha de escaneo en la biblioteca", - "OptionDatePlayed": "Fecha de Reproducción", + "OptionDateAdded": "Fecha de adición", + "OptionDateAddedFileTime": "Usar la fecha de creación del archivo", + "OptionDateAddedImportTime": "Usar la fecha de escaneo en la biblioteca", + "OptionDatePlayed": "Fecha de reproducción", "OptionDescending": "Descendente", "OptionDisableUser": "Desactivar este usuario", - "OptionDisableUserHelp": "Si está desactivado, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", "OptionDislikes": "No me gusta", - "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar carpetas de medios simples", - "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas multimedia. Esto puede ser útil si desea tener una vista de carpeta sencilla.", + "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", + "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", "OptionDownloadArtImage": "Arte", - "OptionDownloadBackImage": "Atras", + "OptionDownloadBackImage": "Parte trasera", "OptionDownloadBannerImage": "Banner", "OptionDownloadBoxImage": "Caja", - "OptionDownloadDiscImage": "DIsco", - "OptionDownloadImagesInAdvance": "Descargar las imágenes desde el inicio", - "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes son descargadas solo cuando son solicitadas por una aplicacion Jellyfin. Habilite esta opción para descargar todas las imágenes por adelantado, a medida que se agregen mas medios. Esto podría causar escaneos de bibliotecas significativamente largos.", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", "OptionDownloadMenuImage": "Menú", "OptionDownloadPrimaryImage": "Principal", "OptionDownloadThumbImage": "Miniatura", "OptionDvd": "DVD", - "OptionEmbedSubtitles": "Embeber dentro del contenedor", + "OptionEmbedSubtitles": "Incrustar dentro del contenedor", "OptionEnableAccessFromAllDevices": "Habilitar acceso desde todos los dispositivos", "OptionEnableAccessToAllChannels": "Habilitar acceso a todos los canales", - "OptionEnableAccessToAllLibraries": "Habilitar el acceso a todas las bibliotecas", + "OptionEnableAccessToAllLibraries": "Habilitar acceso a todas las bibliotecas", "OptionEnableExternalContentInSuggestions": "Habilitar contenido externo en las sugerencias", "OptionEnableExternalContentInSuggestionsHelp": "Permitir que los trailers de Internet y los programas de televisión en vivo se incluyan en el contenido sugerido.", "OptionEnableForAllTuners": "Habilitar para todos los dispositivos sintonizadores", - "OptionEnableM2tsMode": "Habilitar modo M2ts", - "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegs.", + "OptionEnableM2tsMode": "Habilitar modo M2TS", + "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegts.", "OptionEnded": "Finalizado", "OptionEquals": "Igual a", "OptionEstimateContentLength": "Estimar la duración del contenido cuando se transcodifica", "OptionEveryday": "Todos los días", "OptionExternallyDownloaded": "Descarga externa", - "OptionExtractChapterImage": "Habilitar extracción de imágenes de capitulo", + "OptionExtractChapterImage": "Habilitar la extracción de imágenes de los capítulos", "OptionFavorite": "Favoritos", "OptionFriday": "Viernes", - "OptionHasSpecialFeatures": "Características Especiales", + "OptionHasSpecialFeatures": "Características especiales", "OptionHasSubtitles": "Subtítulos", - "OptionHasThemeSong": "Canción del Tema", - "OptionHasThemeVideo": "Video del Tema", - "OptionHasTrailer": "Tráiler", - "OptionHideUser": "Ocultar este usuario en las pantallas de inicio de sesión", + "OptionHasThemeSong": "Canción temática", + "OptionHasThemeVideo": "Video temático", + "OptionHasTrailer": "Trailer", + "OptionHideUser": "Ocultar este usuario de las pantallas de inicio de sesión", "OptionHideUserFromLoginHelp": "Útil para cuentas privadas o de administrador ocultas. El usuario tendrá que iniciar sesión manualmente introduciendo su nombre de usuario y contraseña.", "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", "OptionHomeVideos": "Fotos", - "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de byte", - "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes seran honradas pero se ignorará el encabezado de rango de byte.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", "OptionImdbRating": "Calificación de IMDb", "OptionLikes": "Me gusta", "OptionMax": "Máximo", - "OptionMissingEpisode": "Episodios Faltantes", + "OptionMissingEpisode": "Episodios faltantes", "OptionMonday": "Lunes", "OptionNameSort": "Nombre", "OptionNew": "Nuevo…", "OptionNone": "Ninguno", "OptionOnAppStartup": "Al iniciar la aplicación", "OptionOnInterval": "En un intervalo", - "OptionParentalRating": "Clasificación Parental", + "OptionParentalRating": "Clasificación parental", "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", - "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como \"object.container.storageFolder\" en lugar de un tipo más específico, como \"object.container.person.musicArtist\".", + "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", - "OptionPlainVideoItemsHelp": "Se se habilita, todos los videos serán representados en DIDL como \"object.item.videoItem\" en lugar de un tipo más específico, como \"object.item.videoItem.movie\".", - "OptionPlayCount": "Contador", + "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlayCount": "Contador de reproducciones", "OptionPlayed": "Reproducido", - "OptionPremiereDate": "Fecha de Estreno", + "OptionPremiereDate": "Fecha de estreno", "OptionProfilePhoto": "Foto", - "OptionProfileVideoAudio": "Audio del Video", - "OptionProtocolHls": "Transmisión en vivo por Http", - "OptionReleaseDate": "Fecha de Estreno", - "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta busqueda de bytes al transcodificar", - "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas por tiempo muy bien.", - "OptionRequirePerfectSubtitleMatch": "Solo descargar subtitulos que corresponden perfectamente para mis archivos de video", - "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrara los subtitulos para incluir solo aquellos que han sido verificados exactamente con su archivo de video. Desmarcar esta opción incrementara las probabilidades de que los subtitulos sean descargados, a la vez que incrementara las posibilidades de obtener subtitulos mal sincronizados o con texto incorrecto.", - "OptionResElement": "Elemento Res", + "OptionProfileVideoAudio": "Audio del video", + "OptionProtocolHls": "Transmisión en vivo por HTTP", + "OptionReleaseDate": "Fecha de estreno", + "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta la búsqueda de bytes cuando se transcodifica", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas de tiempo muy bien.", + "OptionRequirePerfectSubtitleMatch": "Solo descargar subtítulos que coincidan perfectamente con mis archivos de video", + "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrará los subtítulos para incluir solo aquellos que han sido probados y verificados exactamente con tu archivo de video. Desmarcar esta opción incrementará las probabilidades de que se descarguen subtítulos, pero incrementará las posibilidades de obtener subtítulos mal sincronizados o con texto incorrecto.", + "OptionResElement": "elemento res", "OptionResumable": "Reanudable", "OptionRuntime": "Duración", "OptionSaturday": "Sábado", "OptionSaveMetadataAsHidden": "Guardar metadatos e imágenes como archivos ocultos", - "OptionSaveMetadataAsHiddenHelp": "Cambiar esto aplicará a nuevos metadatos alamacenados en lo sucesivo. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el Servidor Jellyfin.", + "OptionSaveMetadataAsHiddenHelp": "Cambiar esto se aplicará a los nuevos metadatos guardados en el futuro. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el servidor Jellyfin.", "OptionSpecialEpisode": "Especiales", "OptionSubstring": "Subcadena", "OptionSunday": "Domingo", "OptionThursday": "Jueves", - "OptionTrackName": "Nombre de la Pista", + "OptionTrackName": "Nombre de la pista", "OptionTuesday": "Martes", "OptionTvdbRating": "Calificación de TVDB", - "OptionUnairedEpisode": "Episodios no Emitidos", + "OptionUnairedEpisode": "Episodios no emitidos", "OptionUnplayed": "No reproducido", - "OptionWakeFromSleep": "Al Despertar", + "OptionWakeFromSleep": "Despertar de la suspensión", "OptionWednesday": "Miércoles", - "OptionWeekdays": "Entre semana", + "OptionWeekdays": "Días de semana", "OptionWeekends": "Fines de semana", "OptionWeekly": "Semanal", "OriginalAirDateValue": "Fecha de emisión original: {0}", - "Overview": "Sinopsis", - "PackageInstallCancelled": "{0} (versión {1}) instalación cancelada.", - "PackageInstallCompleted": "{0} (version {1}) instalación completada.", - "PackageInstallFailed": "{0} (versión {1}) instalación fallida.", - "ParentalRating": "Clasificación Parental", - "PasswordMatchError": "La Contraseña y la confirmación de la contraseña deben coincidir.", + "Overview": "Resumen", + "PackageInstallCancelled": "Instalación cancelada de {0} (versión {1}).", + "PackageInstallCompleted": "Instalación completada de {0} (versión {1}).", + "PackageInstallFailed": "Instalación fallida de {0} (versión {1}).", + "ParentalRating": "Clasificación parental", + "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben coincidir.", "PasswordResetComplete": "La contraseña ha sido restablecida.", - "PasswordResetConfirmation": "¿Está seguro de querer restablecer la contraseña?", - "PasswordResetHeader": "Restablecer Contraseña", + "PasswordResetConfirmation": "¿Estás seguro de querer restablecer la contraseña?", + "PasswordResetHeader": "Restablecer contraseña", "PasswordSaved": "Contraseña guardada.", "People": "Personas", - "PerfectMatch": "Coincidencia exacta", + "PerfectMatch": "Coincidencia perfecta", "Photos": "Fotos", "PictureInPicture": "Pantalla en pantalla", - "PinCodeResetComplete": "El código pin ha sido restablecido.", - "PinCodeResetConfirmation": "¿Esta seguro de querer restablecer el código pin?", + "PinCodeResetComplete": "El código PIN ha sido restablecido.", + "PinCodeResetConfirmation": "¿Estás seguro de que quieres restablecer el código PIN?", "PlaceFavoriteChannelsAtBeginning": "Colocar canales favoritos al inicio", "Play": "Reproducir", "PlayAllFromHere": "Reproducir todos desde aquí", @@ -1112,85 +1112,85 @@ "PlayNextEpisodeAutomatically": "Reproducir el siguiente episodio automáticamente", "Played": "Reproducido", "Playlists": "Listas de reproducción", - "PleaseAddAtLeastOneFolder": "Por favor agregue al menos una carpeta a esta biblioteca dando clic al botón de Agregar.", - "PleaseConfirmPluginInstallation": "Por favor haga clic en OK para confirmar que ha leido lo que se encuentra arriba y que desea proceder con la instalación del complemento.", - "PleaseEnterNameOrId": "Por favor introduzca un nombre o ID externo.", - "PleaseRestartServerName": "Por favor reinicie el Servidor Jellyfin - {0}.", - "PleaseSelectTwoItems": "Por favor selecciona al menos dos ítems.", - "PluginInstalledMessage": "El complemento ha sido instalado exitosamente. El Servidor Jellyfin necesitará reiniciarse para que los cambios surtan efecto.", - "PreferEmbeddedTitlesOverFileNames": "Prefererir titulos embebidos por encima de los nombres de archivo", - "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el titulo mostrado por defecto cuando no hay disponibles metadatos en internet o localmente.", + "PleaseAddAtLeastOneFolder": "Por favor, agrega al menos una carpeta a esta biblioteca dando clic al botón Agregar.", + "PleaseConfirmPluginInstallation": "Por favor, haz clic en OK para confirmar que has leído lo que se encuentra arriba y que deseas proceder con la instalación del complemento.", + "PleaseEnterNameOrId": "Por favor, introduce un nombre o ID externo.", + "PleaseRestartServerName": "Por favor, reinicia el servidor Jellyfin - {0}.", + "PleaseSelectTwoItems": "Por favor, selecciona al menos dos elementos.", + "PluginInstalledMessage": "El complemento ha sido instalado con éxito. El servidor Jellyfin necesitará ser reiniciado para que los cambios surtan efecto.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incrustados a los nombres de archivo", + "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el título mostrado por defecto cuando no hay disponibles metadatos en Internet o localmente.", "PreferredNotRequired": "Preferido, más no es requerido", - "Premiere": "Premier", + "Premiere": "Estreno", "Premieres": "Estrenos", "Previous": "Anterior", "Primary": "Principal", "Producer": "Productor", - "ProductionLocations": "Lugares de produccion", + "ProductionLocations": "Lugares de producción", "Programs": "Programas", "Quality": "Calidad", "QueueAllFromHere": "Encolar todos desde aquí", - "Raised": "Elevación", + "Raised": "Elevado", "Rate": "Calificación", "RecentlyWatched": "Visto recientemente", - "RecommendationBecauseYouLike": "Porque te gustó {0}", + "RecommendationBecauseYouLike": "Porque te gusta {0}", "RecommendationBecauseYouWatched": "Porque viste {0}", "RecommendationDirectedBy": "Dirigido por {0}", "RecommendationStarring": "Protagonizado por {0}", "Record": "Grabar", - "RecordSeries": "Grabar Series", + "RecordSeries": "Grabar series", "RecordingCancelled": "Grabación cancelada.", - "RecordingPathChangeMessage": "Cambiar su carpeta de grabaciones no moverá sus grabaciones existentes del la antigua locación a la nueva. Necesita moverlas manualmente si lo desea.", + "RecordingPathChangeMessage": "Cambiar la carpeta de grabaciones no moverá las grabaciones existentes de la antigua ubicación a la nueva. Necesitan moverse manualmente si se desea.", "RecordingScheduled": "Grabación programada.", "Recordings": "Grabaciones", "Refresh": "Actualizar", - "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de internet que que estén activados en el panel de control de su Servidor de Jellyfin.", + "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", "RefreshMetadata": "Actualizar metadatos", - "RefreshQueued": "Actualización programada.", + "RefreshQueued": "Actualización puesta en la cola.", "ReleaseDate": "Fecha de estreno", - "RememberMe": "Recuerdame", + "RememberMe": "Recuérdame", "RemoveFromCollection": "Remover de la colección", - "RemoveFromPlaylist": "Eliminar de la lista de reproducción", + "RemoveFromPlaylist": "Remover de la lista de reproducción", "Repeat": "Repetir", - "RepeatAll": "Repetir todas", + "RepeatAll": "Repetir todos", "RepeatEpisodes": "Repetir episodios", "RepeatMode": "Modo de repetición", "RepeatOne": "Repetir uno", - "ReplaceAllMetadata": "Remplazar todos los metadatos", + "ReplaceAllMetadata": "Reemplazar todos los metadatos", "ReplaceExistingImages": "Reemplazar imágenes existentes", "RequiredForAllRemoteConnections": "Requerido para todas las conexiones remotas", - "RestartPleaseWaitMessage": "Por favor espere mientras el Servidor Jellyfin cierra y reinicia. Este puede tomar un minuto o dos.", + "RestartPleaseWaitMessage": "Por favor, espera mientras el servidor Jellyfin se apaga y reinicia. Esto puede tomar un minuto o dos.", "ResumeAt": "Reanudar desde {0}", - "Rewind": "Regresar", + "Rewind": "Rebobinar", "RunAtStartup": "Ejecutar al iniciar", "Runtime": "Duración", "Saturday": "Sábado", "Save": "Guardar", - "SaveSubtitlesIntoMediaFolders": "Guardar subtitulos en las carpetas de medios", - "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar subtitulos junto al archivo de video permitirá administrarlos con mas facilidad.", - "ScanForNewAndUpdatedFiles": "Buscar archivos nuevos y actualizados", + "SaveSubtitlesIntoMediaFolders": "Guardar subtítulos en las carpetas de los medios", + "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar los subtítulos junto a los archivos de video permitirá administrarlos con más facilidad.", + "ScanForNewAndUpdatedFiles": "Escanear por archivos nuevos y actualizados", "ScanLibrary": "Escanear biblioteca", - "Schedule": "Programacion", + "Schedule": "Programación", "Screenshot": "Captura de pantalla", "Screenshots": "Capturas de pantalla", "Search": "Buscar", - "SearchForCollectionInternetMetadata": "Buscar en internet ilustraciones y metadatos", + "SearchForCollectionInternetMetadata": "Buscar en Internet por ilustraciones y metadatos", "SearchForMissingMetadata": "Buscar metadatos faltantes", - "SearchForSubtitles": "Buscar Subtitulos", + "SearchForSubtitles": "Buscar subtítulos", "SearchResults": "Resultados de la búsqueda", "SendMessage": "Enviar mensaje", "SeriesCancelled": "Serie cancelada.", - "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha transmisión, orden del DVD o por su numeración absoluta.", + "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha de emisión, orden del DVD o numeración absoluta.", "SeriesRecordingScheduled": "Grabación de series programadas.", - "SeriesSettings": "Configuración de la Serie", + "SeriesSettings": "Configuración de la serie", "SeriesYearToPresent": "{0} - Actualidad", - "ServerNameIsRestarting": "El Servidor Jellyfin - {0} se esta reiniciando.", - "ServerNameIsShuttingDown": "El Servidor Jellyfin - {0} se esta apagando.", - "ServerRestartNeededAfterPluginInstall": "El Servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", - "ServerUpdateNeeded": "Este Servidor Jellyfin necesita ser actualizado. Para descargar la ultima versión, por favor visite {0}", + "ServerNameIsRestarting": "El servidor Jellyfin - {0} se está reiniciando.", + "ServerNameIsShuttingDown": "El servidor Jellyfin - {0} se está apagando.", + "ServerRestartNeededAfterPluginInstall": "El servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", + "ServerUpdateNeeded": "Este servidor Jellyfin necesita ser actualizado. Para descargar la última versión, por favor, visita {0}", "Settings": "Configuración", "SettingsSaved": "Configuración guardada.", - "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión, si experimenta cualquier problema, recomendamos volver a los valores por defecto.", + "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión. Si experimentas cualquier problema, recomendamos volver a los valores por defecto.", "Share": "Compartir", "ShowAdvancedSettings": "Mostrar configuraciones avanzadas", "ShowIndicatorsFor": "Mostrar indicadores para:", @@ -1198,14 +1198,14 @@ "ShowYear": "Mostrar año", "Shows": "Programas", "Shuffle": "Aleatorio", - "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultaneas permitidas. Ingrese 0 para ilimitados.", + "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultáneas permitidas. Ingresa 0 para sin límite.", "SkipEpisodesAlreadyInMyLibrary": "No grabar episodios que ya se encuentran en mi biblioteca", "SkipEpisodesAlreadyInMyLibraryHelp": "Los episodios serán comparados usando el numero de temporada y de episodio, cuando estén disponibles.", "Small": "Pequeño", "SmallCaps": "Mayúsculas pequeñas", "Smaller": "Más pequeño", "Smart": "Inteligente", - "SmartSubtitlesHelp": "Los subtítulos que coincidan con el lenguaje preferido serán cargados cuando el audio se encuentre en un lenguaje extranjero.", + "SmartSubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados cuando el audio se encuentre en un idioma extranjero.", "Songs": "Canciones", "Sort": "Ordenar", "SortByValue": "Ordenar por {0}", @@ -1214,94 +1214,94 @@ "Sports": "Deportes", "StopRecording": "Detener grabación", "Studios": "Estudios", - "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción de Chromecast iniciada por este dispositivo.", - "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incrustan sus propios estilos.", - "SubtitleDownloadersHelp": "Habilite y priorice sus recolectores de subtitulos en orden de preferencia.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción en Chromecast iniciada por este dispositivo.", + "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incorporen sus propios estilos.", + "SubtitleDownloadersHelp": "Habilita y prioriza tus recolectores de subtítulos en orden de prioridad.", "Subtitles": "Subtítulos", "Suggestions": "Sugerencias", "Sunday": "Domingo", "Sync": "Sincronizar", - "SystemDlnaProfilesHelp": "Los perfiles del sistema son de sólo lectura. Los cambios a un perfíl de sistema serán guardados en un perfíl personalizado nuevo.", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de solo lectura. Los cambios a un perfil del sistema serán guardados en un nuevo perfil personalizado.", "TabAccess": "Acceso", "TabAdvanced": "Avanzado", - "TabAlbumArtists": "Artistas del Álbum", + "TabAlbumArtists": "Artistas del álbum", "TabAlbums": "Álbumes", "TabArtists": "Artistas", "TabCatalog": "Catálogo", "TabChannels": "Canales", "TabCollections": "Colecciones", "TabContainers": "Contenedores", - "TabDashboard": "Panel de Control", + "TabDashboard": "Panel de control", "TabDevices": "Dispositivos", - "TabDirectPlay": "Reproducción Directa", - "TabDisplay": "Pantalla", + "TabDirectPlay": "Reproducción directa", + "TabDisplay": "Mostrar", "TabEpisodes": "Episodios", "TabFavorites": "Favoritos", "TabGenres": "Géneros", "TabGuide": "Guía", "TabLatest": "Recientes", - "TabLiveTV": "TV en Vivo", - "TabLogs": "Bitácoras", + "TabLiveTV": "TV en vivo", + "TabLogs": "Registros", "TabMetadata": "Metadatos", "TabMovies": "Películas", "TabMusic": "Música", - "TabMusicVideos": "Videos Musicales", - "TabMyPlugins": "Mis Complementos", + "TabMusicVideos": "Videos musicales", + "TabMyPlugins": "Mis complementos", "TabNetworks": "Cadenas", "TabNfoSettings": "Configuración de NFO", "TabNotifications": "Notificaciones", "TabOther": "Otros", - "TabParentalControl": "Control Parental", + "TabParentalControl": "Control parental", "TabPassword": "Contraseña", "TabPlayback": "Reproducción", - "TabPlaylist": "Lista de Reproducción", + "TabPlaylist": "Lista de reproducción", "TabPlaylists": "Listas de reproducción", "TabPlugins": "Complementos", - "TabProfile": "Perfíl", + "TabProfile": "Perfil", "TabProfiles": "Perfiles", "TabRecordings": "Grabaciones", "TabResponses": "Respuestas", "TabResumeSettings": "Reanudar", - "TabScheduledTasks": "Tareas Programadas", + "TabScheduledTasks": "Tareas programadas", "TabServer": "Servidor", "TabSettings": "Configuración", "TabShows": "Programas", "TabSongs": "Canciones", "TabStreaming": "Transmisión", "TabSuggestions": "Sugerencias", - "TabTrailers": "Tráilers", + "TabTrailers": "Trailers", "TabTranscoding": "Transcodificación", - "TabUpcoming": "Proximamente", + "TabUpcoming": "Próximamente", "TabUsers": "Usuarios", "Tags": "Etiquetas", "TagsValue": "Etiquetas: {0}", - "TellUsAboutYourself": "Díganos sobre usted", - "ThemeSongs": "Canciones de Tema", - "ThemeVideos": "Videos de Tema", - "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones solo afectan subtitulo de este dispositivo", - "ThisWizardWillGuideYou": "Este asistente le guiará a través del proceso de instalación. Para comenzar, por favor seleccione su lenguaje preferido.", + "TellUsAboutYourself": "Háblanos de ti", + "ThemeSongs": "Canciones temáticas", + "ThemeVideos": "Videos temáticos", + "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones afectan a los subtítulos en este dispositivo", + "ThisWizardWillGuideYou": "Este asistente te guiará a través del proceso de instalación. Para comenzar, por favor, selecciona tu idioma preferido.", "Thumb": "Miniatura", "Thursday": "Jueves", - "TitleHardwareAcceleration": "Aceleración por Hardware", - "TitleHostingSettings": "Configuraciones de Alojamiento", + "TitleHardwareAcceleration": "Aceleración por hardware", + "TitleHostingSettings": "Configuraciones de alojamiento", "TitlePlayback": "Reproducción", - "TrackCount": "{0} Pistas", - "Trailers": "Tráilers", + "TrackCount": "{0} pistas", + "Trailers": "Trailers", "Transcoding": "Transcodificando", "Tuesday": "Martes", - "TvLibraryHelp": "Vea la {0}Guía para nombrar series de TV{1}.", + "TvLibraryHelp": "Revisa la {0}guía de nombrado de series de TV{1}.", "Uniform": "Uniforme", - "UninstallPluginConfirmation": "¿Está seguro de querer desinstalar {0}?", - "UninstallPluginHeader": "Desinstalar Complemento", - "Unmute": "Activar Sonido", + "UninstallPluginConfirmation": "¿Estás seguro de querer desinstalar {0}?", + "UninstallPluginHeader": "Desinstalar complemento", + "Unmute": "Activar sonido", "Unplayed": "No reproducido", "Unrated": "Sin clasificar", "Up": "Arriba", "Upload": "Subir", - "UserAgentHelp": "Proporcionar un encabezado HTTP personalizado de agente de usuario.", - "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con configuraciones de pantalla detalladas, estado de reproducción y controles parentales.", + "UserAgentHelp": "Proporciona un encabezado HTTP de agente de usuario personalizado.", + "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con detalladas configuraciones de pantalla, estado de reproducción y controles parentales.", "ValueAlbumCount": "{0} álbumes", - "ValueAudioCodec": "Códec de Audio: {0}", + "ValueAudioCodec": "Códec de audio: {0}", "ValueCodec": "Códec: {0}", "ValueConditions": "Condiciones: {0}", "ValueContainer": "Contenedor: {0}", @@ -1320,23 +1320,23 @@ "ValueSpecialEpisodeName": "Especial - {0}", "ValueTimeLimitMultiHour": "Límite de tiempo: {0} horas", "ValueTimeLimitSingleHour": "Límite de tiempo: 1 hora", - "ValueVideoCodec": "Códec de Video: {0}", + "ValueVideoCodec": "Códec de video: {0}", "VideoRange": "Rango de video", - "ViewAlbum": "Ver album", + "ViewAlbum": "Ver álbum", "ViewArtist": "Ver artista", "ViewPlaybackInfo": "Ver información de reproducción", "Watched": "Visto", "Wednesday": "Miércoles", "WelcomeToProject": "¡Bienvenido a Jellyfin!", - "Whitelist": "Permitidos", - "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre su biblioteca multimedia. Revise algunas de nuestras aplicaciones, y luego haga clic en Finalizar para ver el Panel de Control.", + "Whitelist": "Lista blanca", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre tu biblioteca de medios. Revisa alguna de nuestras aplicaciones, y luego haz clic en Finalizar para ver el Panel de control.", "Writer": "Escritor", "XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.", - "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separe varios con un \"|\".", - "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separe varios con un \"|\".", - "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas noticiosos. Separe varios con un \"|\".", - "XmlTvPathHelp": "Ruta al archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Usted es responsable de crear y actualizar el archivo.", - "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separe varios con un \"|\".", + "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separa varios con un «|».", + "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separa varios con un «|».", + "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas de noticias. Separa varios con un «|».", + "XmlTvPathHelp": "Una ruta a un archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Tú eres responsable de crear y actualizar el archivo.", + "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separa varios con un «|».", "Yes": "Sí", "Yesterday": "Ayer", "Actor": "Actor", @@ -1353,7 +1353,7 @@ "HeaderAdmin": "Administrador", "HeaderApp": "Aplicación", "HeaderError": "Error", - "HeaderFavoriteMovies": "Peliculas favoritas", + "HeaderFavoriteMovies": "Películas favoritas", "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos", @@ -1372,7 +1372,7 @@ "LabelServerName": "Nombre del servidor:", "LabelTranscodePath": "Ruta de transcodificación:", "LabelTranscodes": "Transcodificaciones:", - "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario se bloquee:", + "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario sea bloqueado:", "DashboardVersionNumber": "Versión: {0}", "DashboardServerName": "Servidor: {0}", "DashboardOperatingSystem": "Sistema operativo: {0}", @@ -1381,40 +1381,40 @@ "LabelWeb": "Web:", "LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor", "LaunchWebAppOnStartupHelp": "Abre el cliente web en su navegador web predeterminado cuando se inicia el servidor. Esto no ocurrirá cuando se utilice la función de reinicio del servidor.", - "LeaveBlankToNotSetAPassword": "Puede dejar este campo en blanco para no establecer ninguna contraseña.", + "LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.", "MediaInfoCodec": "Códec", "MediaInfoSoftware": "Software", "MediaInfoStreamTypeAudio": "Audio", "MediaInfoStreamTypeData": "Dato", "MediaInfoStreamTypeEmbeddedImage": "Imagen incrustada", - "MediaInfoStreamTypeSubtitle": "Subtitulo", + "MediaInfoStreamTypeSubtitle": "Subtítulo", "MediaInfoStreamTypeVideo": "Video", - "MessageImageFileTypeAllowed": "Solo se admite archivos JPEG y PNG.", - "MessageImageTypeNotSelected": "Seleccione un tipo de imagen en el menú desplegable.", - "MessageNoCollectionsAvailable": "Las colecciones le permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haga clic en el botón + para comenzar a crear colecciones.", - "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático del servidor.", + "MessageImageFileTypeAllowed": "Solo son soportados archivos JPEG y PNG.", + "MessageImageTypeNotSelected": "Por favor, selecciona un tipo de imagen del menú desplegable.", + "MessageNoCollectionsAvailable": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haz clic en el botón + para comenzar a crear colecciones.", + "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático de servidores.", "MusicAlbum": "Álbum de música", "MusicArtist": "Artista musical", "MusicVideo": "Video musical", "No": "No", "Normal": "Normal", "Option3D": "3D", - "OptionBanner": "Baner", + "OptionBanner": "Banner", "OptionBluray": "Blu-ray", "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", "OptionDownloadLogoImage": "Logo", "OptionIsHD": "HD", "OptionIsSD": "SD", "OptionList": "Lista", - "OptionLoginAttemptsBeforeLockout": "Determina cuántos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", - "OptionLoginAttemptsBeforeLockoutHelp": "Un valor cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", + "OptionLoginAttemptsBeforeLockout": "Determina cuantos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", + "OptionLoginAttemptsBeforeLockoutHelp": "Un valor de cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", "OptionPoster": "Póster", "OptionPosterCard": "Ficha de póster", "OptionProfileAudio": "Audio", "OptionProfileVideo": "Video", "OptionProtocolHttp": "HTTP", "OptionRegex": "Expresión regular", - "PasswordResetProviderHelp": "Elija un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", + "PasswordResetProviderHelp": "Elige un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", "PlaybackData": "Datos de reproducción", "Series": "Series", "SubtitleOffset": "Desplazamiento de subtítulos", @@ -1426,8 +1426,8 @@ "ValueSeriesCount": "{0} series", "Vertical": "Vertical", "OptionThumb": "Miniatura", - "OptionThumbCard": "Pequeña miniatura", - "HeaderFavoriteBooks": "Libros Favoritos", + "OptionThumbCard": "Tarjeta miniatura", + "HeaderFavoriteBooks": "Libros favoritos", "LabelPleaseRestart": "Los cambios tendrán efecto después de recargar manualmente el cliente web.", "LabelPlayMethod": "Método de reproducción:", "LabelPlayer": "Reproductor:", @@ -1439,21 +1439,21 @@ "LabelAudioCodec": "Códec de audio:", "LabelAudioChannels": "Canales de audio:", "LabelAudioBitrate": "Velocidad de bits de audio:", - "LabelAudioBitDepth": "Profundidad de bit de audio:", + "LabelAudioBitDepth": "Profundidad de bits de audio:", "FetchingData": "Obteniendo datos adicionales", "CopyStreamURLSuccess": "URL copiada con éxito.", "CopyStreamURL": "Copiar la URL de la transmisión", "ButtonAddImage": "Agregar imagen", "TabNetworking": "Redes", - "MusicLibraryHelp": "Revisar la {0}Guía para nombrar música{1}.", + "MusicLibraryHelp": "Revisar la {0}guía de nombrado de música{1}.", "MoreMediaInfo": "Información multimedia", "LabelVideoCodec": "Códec de video:", - "LabelVideoBitrate": "Velocidad de bits:", + "LabelVideoBitrate": "Velocidad de bits de video:", "LabelTranscodingProgress": "Progreso de la transcodificación:", "LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:", "LabelSize": "Tamaño:", - "SelectAdminUsername": "Por favor seleccione un nombre de usuario para la cuenta de administrador.", - "EnableFastImageFadeInHelp": "Habilita la animación de desvanecido rápido para las imágenes cargadas", + "SelectAdminUsername": "Por favor, selecciona un nombre de usuario para la cuenta de administrador.", + "EnableFastImageFadeInHelp": "Habilita una animación más rápida de desvanecimiento para las imágenes cargadas", "LabelDroppedFrames": "Cuadros saltados:", "CopyStreamURLError": "Hubo un error al copiar la URL.", "ButtonSplit": "Dividir", @@ -1462,29 +1462,29 @@ "EveryXHours": "Cada {0} horas", "EveryHour": "Cada hora", "EveryXMinutes": "Cada {0} minutos", - "OnWakeFromSleep": "Activarse del Modo Suspensión", + "OnWakeFromSleep": "Al despertar de la suspensión", "DailyAt": "Diariamente a las {0}", "LastSeen": "Ultima vez visto {0}", "PersonRole": "como {0}", "ListPaging": "{0}-{1} de {2}", - "WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor brinde acceso de escritura e intente de nuevo.", - "PathNotFound": "No se pudo encontrar la ruta. Por favor asegúrese de que la ruta es valida e intente de nuevo.", + "WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor, asegúrate de tener acceso de escritura e inténtalo de nuevo.", + "PathNotFound": "No se pudo encontrar la ruta. Por favor, asegúrate de que la ruta es válida e inténtalo de nuevo.", "Track": "Pista", "Season": "Temporada", - "ReleaseGroup": "Grupo de salida", - "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información embebida en el episodio sobre los nombres de archivo", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información del episodio desde metadatos embebidos si esta disponible.", + "ReleaseGroup": "Grupo que lo estrenó", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información del episodio incrustada a los nombres de archivo", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información del episodio desde los metadatos incrustados si están disponibles.", "PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no está enviando un formato de medios compatible.", "Person": "Persona", "OtherArtist": "Otro artista", "OptionRandom": "Aleatorio", - "OptionForceRemoteSourceTranscoding": "Forzar transcodificación para fuentes remotas (como TV en Vivo)", - "NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}Quisieras crear una ahora?{1}", + "OptionForceRemoteSourceTranscoding": "Forzar transcodificación de fuentes remotas (como TV en vivo)", + "NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}¿Quisieras crear una ahora?{1}", "Movie": "Película", - "MessageConfirmAppExit": "Deseas salir?", + "MessageConfirmAppExit": "¿Deseas salir?", "LabelVideoResolution": "Resolución de video:", "LabelStreamType": "Tipo de transmisión:", - "EnableFastImageFadeIn": "Desvanecido de imagen rápido", + "EnableFastImageFadeIn": "Desvanecimiento rápido de las imágenes", "LabelPlayerDimensions": "Dimensiones del reproductor:", "LabelCorruptedFrames": "Cuadros corruptos:", "HeaderNavigation": "Navegación", @@ -1500,17 +1500,17 @@ "Album": "Álbum", "YadifBob": "YADIF Bob", "Yadif": "YADIF", - "LabelDeinterlaceMethod": "Metodo de Desentrelazado:", + "LabelDeinterlaceMethod": "Método de desentrelazado:", "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", "Filter": "Filtro", "New": "Nuevo", - "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor contacta con el administrador del servidor para mas información.", - "LabelLibraryPageSizeHelp": "Establecer el número de elementos a mostrar en la página biblioteca. Establezca 0 para deshabilitar paginado.", - "LabelLibraryPageSize": "Tamaño de la página Biblioteca:", + "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor, contacta al administrador del servidor para más información.", + "LabelLibraryPageSizeHelp": "Establece el número de elementos a mostrar en una página de biblioteca. Establece en 0 para deshabilitar el paginado.", + "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", "HeaderFavoritePlaylists": "Listas de reproducción favoritas", "ButtonTogglePlaylist": "Lista de reproducción", "ButtonToggleContextMenu": "Más", - "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM aún así será intentado, incluyendo títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos alternativos.", + "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM de todas formas todo el contenido será intentado, incluyendo los títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos interactivos.", "TabDVR": "DVR", "SaveChanges": "Guardar cambios", "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", @@ -1520,7 +1520,7 @@ "LabelChromecastVersion": "Versión de Chromecast", "LabelEnableHttpsHelp": "Permite al servidor escuchar en el puerto HTTPS configurado. Un certificado válido también debe ser configurado para que esto tenga efecto.", "LabelEnableHttps": "Habilitar HTTPS", - "HeaderServerAddressSettings": "Opciones de la dirección del servidor", + "HeaderServerAddressSettings": "Configuración de la dirección del servidor", "HeaderRemoteAccessSettings": "Opciones de acceso remoto", "HeaderHttpsSettings": "Opciones HTTPS", "HeaderDVR": "DVR", From 18d8abc48af1852c1edbf4eb2447e2bdd0765d57 Mon Sep 17 00:00:00 2001 From: Antonio Date: Fri, 22 May 2020 07:32:21 +0000 Subject: [PATCH 046/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 3f1d22c75a..add033a3e6 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -14,7 +14,7 @@ "Alerts": "Alertes", "All": "Tout", "AllChannels": "Toutes les chaînes", - "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc…)", + "AllComplexFormats": "Tous les formats complexes (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", "AllEpisodes": "Tous les épisodes", "AllLanguages": "Toutes les langues", "AllLibraries": "Toutes les médiathèques", @@ -23,7 +23,7 @@ "AllowMediaConversion": "Autoriser la conversion des médias", "AllowMediaConversionHelp": "Autoriser ou refuser l'accès à la fonctionnalité de conversion des médias.", "AllowOnTheFlySubtitleExtraction": "Autoriser l'extraction des sous-titres à la volée", - "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et distribués aux clients au format texte pour éviter le transcodage. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour graver les sous-titres avec un transcodage quand l'appareil client ne les prend pas en charge nativement.", + "AllowOnTheFlySubtitleExtractionHelp": "Les sous-titres intégrés peuvent être extraits des vidéos et envoyés vers les clients au format texte afin d'éviter le transcodage vidéo. Sur certains systèmes, cela peut prendre du temps et arrêter la lecture de la vidéo pendant le processus d'extraction. Désactivez cette option pour conserver les sous-titres pendant le transcodage si l'appareil client ne les prend pas en charge nativement.", "AllowRemoteAccess": "Autoriser les connexions distantes à ce serveur Jellyfin.", "AllowRemoteAccessHelp": "Si l'option est désactivée, toutes les connexions distantes seront bloquées.", "AllowedRemoteAddressesHelp": "Liste d'adresses IP ou d'IP/masque de sous-réseau séparées par des virgules qui seront autorisées à se connecter à distance. Si la liste est vide, toutes les adresses distantes seront autorisées.", @@ -45,13 +45,13 @@ "BirthLocation": "Lieu de naissance", "BirthPlaceValue": "Lieu de naissance : {0}", "Blacklist": "Liste noire", - "BookLibraryHelp": "Les livres audios et numériques sont supportés. Consultez le {0}Guide de nommage pour livre{1}.", + "BookLibraryHelp": "Les livres audios et numériques sont supportés. Consultez le {0} guide de nommage pour livre {1}.", "Books": "Livres", "Box": "Boîtier", "BoxRear": "Dos de boîtier", "Browse": "Parcourir", "BrowsePluginCatalogMessage": "Explorer notre catalogue des plugins pour voir les plugins disponibles.", - "BurnSubtitlesHelp": "Détermine si le serveur doit incruster les sous-titres lors du transcodage de la vidéo. Éviter cela améliorera nettement la performance. Sélectionnez Auto pour incruster les formats basés sur l'image (VOBSUB, PGS, SUB, IDX etc) et certains sous-titres ASS ou SSA.", + "BurnSubtitlesHelp": "Détermine si le serveur doit incruster les sous-titres lors du transcodage de la vidéo. Les performances seront grandement améliorées sans incrustation. Sélectionnez Auto pour incruster par image les formats (VOBSUB, PGS, SUB, IDX etc) et certains sous-titres ASS ou SSA.", "ButtonAdd": "Ajouter", "ButtonAddMediaLibrary": "Ajouter une médiathèque", "ButtonAddScheduledTaskTrigger": "Ajouter un déclencheur", @@ -569,7 +569,7 @@ "LabelEmbedAlbumArtDidl": "Intégrer les images d'album dans le DIDL", "LabelEmbedAlbumArtDidlHelp": "Certains appareils préfèrent cette méthode pour obtenir les images d'album. D'autres peuvent échouer à lire avec cette option activée.", "LabelEnableAutomaticPortMap": "Autoriser le mapping automatique de port", - "LabelEnableAutomaticPortMapHelp": "Essayer de mapper automatiquement le port public au port local via UPnP. Cela peut ne pas fonctionner avec certains modèles de routeurs. La modification de ce paramètre ne sera effective qu’après redémarrage du serveur.", + "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)", @@ -1038,7 +1038,7 @@ "OptionMissingEpisode": "Épisodes manquantes", "OptionMonday": "Lundi", "OptionNameSort": "Nom", - "OptionNew": "Nouveau...", + "OptionNew": "Nouveau…", "OptionNone": "Aucun", "OptionOnAppStartup": "Au démarrage de l'application", "OptionOnInterval": "Par intervalle", @@ -1448,7 +1448,7 @@ "FetchingData": "Récuperer des données suplémentaires", "CopyStreamURLSuccess": "URL copiée avec succès.", "CopyStreamURL": "Copier l'URL du flux", - "LabelBaseUrlHelp": "Vous pouvez ajouter un sous-répertoire personnalisé ici pour accéder au serveur via un lien unique.", + "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple: http://example.com/<baseurl>", "HeaderFavoritePeople": "Personnes préférées", "OptionRandom": "Aléatoire", "ButtonSplit": "Séparer", @@ -1507,5 +1507,19 @@ "ButtonToggleContextMenu": "Plus", "Filter": "Filtre", "New": "Nouveau", - "HeaderFavoritePlaylists": "Listes de lecture favorites" + "HeaderFavoritePlaylists": "Listes de lecture favorites", + "TabDVR": "DVR", + "LabelChromecastVersion": "Version de Chromecast", + "LabelEnableHttpsHelp": "Autorise le serveur à écouter les requêtes HTTPS configurées. Un certificat valide doit être configuré pour permettre ce mode de fonctionnement.", + "LabelEnableHttps": "Activer HTTPS", + "HeaderServerAddressSettings": "Paramètres adresses serveur", + "HeaderRemoteAccessSettings": "Paramètres d'accès distant", + "HeaderHttpsSettings": "Paramètres HTTPS", + "HeaderDVR": "Enregistreur vidéo numérique", + "ApiKeysCaption": "Liste actuelle des clés API actives", + "SaveChanges": "Enregistrer les modifications", + "LabelRequireHttpsHelp": "Si activé, le serveur va automatiquement rediriger toutes les requêtes en HTTP vers HTTPS. Cette option n'a aucun effet si le serveur n'écoute pas HTTPS.", + "LabelRequireHttps": "Nécessite HTTPS", + "LabelNightly": "De nuit", + "LabelStable": "Stable" } From e6ba4a4dc89e0569bd9a83e702da66ef13c07d61 Mon Sep 17 00:00:00 2001 From: millallo Date: Fri, 22 May 2020 06:01:51 +0000 Subject: [PATCH 047/181] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/it.json b/src/strings/it.json index cd9ed65536..b862fc1175 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1519,5 +1519,6 @@ "HeaderHttpsSettings": "Configurazione HTTPS", "TabDVR": "DVR", "SaveChanges": "Salva modifiche", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "LabelNightly": "Nightly" } From 40d3153d2cbb7ecc0bd1f7cc31c18fcc96e019d3 Mon Sep 17 00:00:00 2001 From: Antonio Date: Fri, 22 May 2020 08:13:58 +0000 Subject: [PATCH 048/181] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index 4bf5591347..e05e5bf668 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1079,7 +1079,7 @@ "BoxRear": "Caixa (verso)", "Box": "Caixa", "Books": "Livros", - "BookLibraryHelp": "Livros de texto e áudio são suportados. Consulte o guia de nomenclatura de livros{1}.", + "BookLibraryHelp": "Livros de texto e áudio são suportados. Consulte o guia {0} de nomenclatura de livros {1}.", "Blacklist": "Lista Negra", "BirthPlaceValue": "Local de nascimento: {0}", "BirthLocation": "Local de nascimento", @@ -1394,5 +1394,31 @@ "OptionAutomaticallyGroupSeries": "Mesclar automaticamente séries que estão espalhadas por várias pastas", "OptionAllowSyncTranscoding": "Permitir download e sincronização de mídia que requeiram transcodificação", "OptionForceRemoteSourceTranscoding": "Forçar a transcodificação de fontes de mídia remota (como LiveTV)", - "MessageUnauthorizedUser": "Você não está autorizado a acessar o servidor no momento. Entre em contato com o administrador do servidor para obter mais informações." + "MessageUnauthorizedUser": "Você não está autorizado a acessar o servidor no momento. Entre em contato com o administrador do servidor para obter mais informações.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incorporados sobre nomes de arquivos", + "OptionSaveMetadataAsHiddenHelp": "Alterar isso será aplicado aos novos metadados salvos daqui para frente. Os arquivos de metadados existentes serão atualizados na próxima vez em que forem salvos pelo Jellyfin Server.", + "OptionRegex": "Regex", + "OptionLoginAttemptsBeforeLockoutHelp": "Um valor zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Definir como -1 desativará o recurso.", + "OptionExtractChapterImage": "Ativar extração de imagem de capítulo", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações de episódios incorporados sobre nomes de arquivos", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso usa as informações do episódio dos metadados incorporados, se disponíveis.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Isso determina o título quando nenhum metadado da Internet ou local está disponível.", + "PlaybackErrorNoCompatibleStream": "Este cliente não é compatível com a mídia e o servidor não está enviando um formato de mídia compatível.", + "Person": "Pessoa", + "OtherArtist": "Outro artista", + "OptionThumbCard": "Cartão de polegar", + "OptionPosterCard": "Cartão de pôster", + "LabelRequireHttpsHelp": "Se marcado, o servidor redirecionará automaticamente todas as solicitações por HTTP para HTTPS. Isso não terá efeito se o servidor não estiver escutando HTTPS.", + "LabelRequireHttps": "Requer HTTPS", + "LabelNightly": "À noite", + "LabelChromecastVersion": "Versão do Chromecast", + "LabelEnableHttpsHelp": "Permite que o servidor escute na postagem HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttps": "Ativar HTTPS", + "HeaderServerAddressSettings": "Configurações de endereço do servidor", + "HeaderRemoteAccessSettings": "Configurações de acesso remoto", + "HeaderHttpsSettings": "Configurações HTTPS", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lista das chaves de API ativadas no momento", + "ButtonTogglePlaylist": "Lista de leitura", + "ButtonToggleContextMenu": "Mais" } From 25ed919c334542b2f7322e36fb17aaed353ec1b2 Mon Sep 17 00:00:00 2001 From: artiume Date: Fri, 22 May 2020 13:01:07 +0000 Subject: [PATCH 049/181] Translated using Weblate (Pirate) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pr/ --- src/strings/pr.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/strings/pr.json b/src/strings/pr.json index 2f5c120d40..91962aaa46 100644 --- a/src/strings/pr.json +++ b/src/strings/pr.json @@ -2,5 +2,22 @@ "TabLogs": "Crow's Nest", "HeaderAdmin": "Cap'n", "WelcomeToProject": "Ahoy, matey! This be Jellyfin!", - "ButtonOk": "Aye" + "ButtonOk": "Aye", + "DisplayInMyMedia": "Show on the Poop Deck", + "HeaderCastAndCrew": "Mateys", + "HeaderMusicQuality": "Sea Shanty Strength", + "HeaderLatestMusic": "Latest Sea Shanties", + "FolderTypeMusic": "Sea Shanties", + "OptionBlockBooks": "Tall Tales", + "HeaderFavoriteBooks": "Fav'rit Tales", + "HeaderBooks": "Tall Tales", + "HeaderAudioBooks": "Spoken Tales", + "FolderTypeBooks": "Tall Tales", + "Books": "Tall Tales", + "LabelHomeNetworkQuality": "Sail strength:", + "Home": "Poop Deck", + "HeaderHome": "Poop Deck", + "DisplayInOtherHomeScreenSections": "Show on Poop Deck such as latest booty and continue plundering", + "ButtonHome": "Poop deck", + "HeaderCastCrew": "Mateys" } From 3d38553079f8ffc6e13dbfb90b7cf08175c06ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93skar=20Freyr?= Date: Fri, 22 May 2020 20:52:05 +0000 Subject: [PATCH 050/181] Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/is/ --- src/strings/is-is.json | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/strings/is-is.json b/src/strings/is-is.json index b9a8429ce9..1042737ae2 100644 --- a/src/strings/is-is.json +++ b/src/strings/is-is.json @@ -511,5 +511,37 @@ "TabAdvanced": "Ítarlegt", "Sunday": "Sunnudagur", "Suggestions": "Tillögur", - "Subtitles": "Skjátexti" + "Subtitles": "Skjátexti", + "LabelMetadataPath": "Slóð lýsigagna:", + "LabelMetadata": "Lýsigögn:", + "LabelMessageTitle": "Titill skilaboðs:", + "LabelMessageText": "Texti skilaboðs:", + "LabelMaxStreamingBitrate": "Hámarks gæði streymis:", + "LabelLineup": "Uppröðun:", + "LabelKodiMetadataDateFormat": "Snið útgáfudags:", + "LabelInternetQuality": "Gæði Internets:", + "LabelIconMaxWidth": "Hámarksbreidd tákns:", + "LabelHomeNetworkQuality": "Gæði heimanets:", + "LabelHardwareAccelerationType": "Hröðun vélbúnaðar:", + "LabelFriendlyName": "Vinalegt nafn:", + "LabelFormat": "Snið:", + "LabelForgotPasswordUsernameHelp": "Sláðu inn notandanafnið þitt, ef þú manst eftir því.", + "LabelFont": "Leturgerð:", + "LabelFolder": "Mappa:", + "LabelEpisodeNumber": "Þáttur númer:", + "LabelEnableRealtimeMonitor": "Virkja vöktun í rauntíma", + "LabelEnableHardwareDecodingFor": "Gera vélbúnaðarafkóðun virka fyrir:", + "LabelDroppedFrames": "Felldir rammar:", + "LabelDiscNumber": "Númer disks:", + "LabelDeviceDescription": "Lýsing tækis", + "LabelDashboardTheme": "Þema mælaborðs:", + "LabelCustomCss": "Sérsniðin CSS:", + "LabelCriticRating": "Einkunn gagnrýnanda:", + "LabelCorruptedFrames": "Skemmdir rammar:", + "LabelCancelled": "Hætt við", + "LabelAppName": "Heiti forrits", + "LabelAllowServerAutoRestart": "Leyfa netþjóni að endurræsa sig sjálfkrafa til þess að uppfæra sig", + "LabelAllowHWTranscoding": "Leyfa vélbúnaðarumkóðun", + "Label3DFormat": "3D snið:", + "HeaderIdentification": "Auðkenning" } From 1cb35c23be40a7836cfcbf0ce8987db668bec28a Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Sat, 23 May 2020 04:52:14 +0000 Subject: [PATCH 051/181] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index e220ba1d5d..ff89d00307 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -688,7 +688,7 @@ "LabelNumberOfGuideDays": "Número de dias de dados do guia para baixar:", "LabelNumberOfGuideDaysHelp": "Baixar mais dias do guia da TV permite agendar com maior antecedência e visualizar mais listas, mas também levará mais tempo para baixar. Se selecionar Automático, será escolhido o período baseado no número de canais.", "LabelOptionalNetworkPath": "(Opcional) Pasta compartilhada em rede:", - "LabelOptionalNetworkPathHelp": "Se esta pasta estiver compartilhada em sua rede, informar o caminho do compartilhamento permitirá que os apps Jellyfin em outros dispositivos acessem arquivos de mídia diretamente.", + "LabelOptionalNetworkPathHelp": "Se esta pasta estiver compartilhada em sua rede, informar o caminho do compartilhamento permitirá que os apps Jellyfin em outros dispositivos acessem arquivos de mídia diretamente. Por exemplo, {0} ou {1}.", "LabelOriginalAspectRatio": "Proporção original da tela:", "LabelOriginalTitle": "Título original:", "LabelOverview": "Sinopse:", From 0e24cbabd67867cb1beb7ab48faa5ca31470545e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 23 May 2020 09:01:40 +0000 Subject: [PATCH 052/181] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index 9ff05dcb6b..736c3b5b4f 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -706,5 +706,30 @@ "LabelDashboardTheme": "Chủ đề bảng điều khiển máy chủ:", "LabelCustomRating": "Đánh giá tuỳ chọn:", "HeaderFavoritePlaylists": "Danh Sách Phát Yêu Thích", - "ApiKeysCaption": "Danh sách các mã API đang hoạt động" + "ApiKeysCaption": "Danh sách các mã API đang hoạt động", + "LabelBaseUrl": "URL cơ bản:", + "LabelEveryXMinutes": "Mỗi:", + "LabelEpisodeNumber": "Tập số:", + "LabelEndDate": "Ngày kết thúc:", + "LabelEnableSingleImageInDidlLimitHelp": "Một số thiết bị không hiển thị rõ ràng nếu có nhiều hình ảnh được nhúng trong Didl.", + "LabelEnableSingleImageInDidlLimit": "Giới hạn chỉ một hình ảnh nhúng", + "LabelEnableRealtimeMonitorHelp": "Thay đổi để nội dung sẽ được xử lý ngay lập tức trên các hệ thống được hỗ trợ.", + "LabelEnableRealtimeMonitor": "Bật tính năng theo dõi thời gian thực", + "LabelEnableHttpsHelp": "Cho phép máy chủ theo dõi thiết lập HTTPS. Cần phải có chứng chỉ hợp lệ để tính năng này có hiệu quả.", + "LabelEnableHttps": "Bật HTTPS", + "LabelEnableHardwareDecodingFor": "Bật tính năng giãi mã phần cứng cho:", + "LabelEnableDlnaServerHelp": "Cho phép các thiết bị UPnP trong mạng của bản để duyệt và phát nội dung.", + "LabelEnableDlnaServer": "Bật tính năng máy chủ DLNA", + "LabelEnableDlnaPlayToHelp": "Tìm kiếm thiết bị trong mạng của bạn và đưa ra khả năng điều khiển từ xa những thiết bị đó.", + "LabelEnableDlnaDebugLoggingHelp": "Tạo những tập tin gỡ lỗi lớn và chỉ nên được sử dụng khi cần thiết để xử lý sự cố.", + "LabelEnableDlnaDebugLogging": "Bật tính năng gỡ lỗi DLNA", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Xác định thời gian tính bằng giây giữa các tìm kiếm SSDP thực hiện bởi Jellyfin.", + "LabelEnableDlnaClientDiscoveryInterval": "Thời gian tìm kiếm thiết bị phát (giây)", + "LabelEnableBlastAliveMessagesHelp": "Bật tính năng này nếu máy chủ không thể kết nối chắc chắn với những thiết bị UPnP trong mạng của bạn.", + "LabelEnableBlastAliveMessages": "Phát tin nhắn trực tiếp", + "LabelEnableAutomaticPortMapHelp": "Tự động chuyển tiếp những port công khai trên bộ định tuyến đến port trên máy chủ thông qua UPnP. Cài đặt này có thể không hoạt động trên một số loại bộ định tuyến hoặc thiết lập mạng. Thay đổi sẽ được áp dụng sau khi khởi động lại máy chủ.", + "HeaderServerAddressSettings": "Cài Đặt Địa Chỉ Máy Chủ", + "HeaderRemoteAccessSettings": "Cài Đặt Truy Cập Từ Xa", + "HeaderHttpsSettings": "Cài Đặt HTTPS", + "HeaderDVR": "DVR" } From 648da07aaa0c6cac4eae0d903d1377da1db67b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=9E=E5=94=AF?= Date: Sat, 23 May 2020 09:30:32 +0000 Subject: [PATCH 053/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 28a52b1d61..45191405f7 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1510,5 +1510,9 @@ "New": "新的", "HeaderFavoritePlaylists": "收藏的播放列表", "ButtonTogglePlaylist": "播放列表", - "ButtonToggleContextMenu": "更多" + "ButtonToggleContextMenu": "更多", + "HeaderServerAddressSettings": "服务器地址设置", + "HeaderRemoteAccessSettings": "远程访问设置", + "HeaderHttpsSettings": "HTTPS 设置", + "ApiKeysCaption": "当前启用的 API 密钥" } From eb85fd1bf4e75793f18adc2ac46b0b09b013c33b Mon Sep 17 00:00:00 2001 From: DINESH REDDY Date: Sat, 23 May 2020 10:34:18 +0000 Subject: [PATCH 054/181] Translated using Weblate (Hindi) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hi/ --- src/strings/hi-in.json | 84 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index 122f4aa98b..7a51a4f695 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -21,11 +21,11 @@ "AddToCollection": "संग्रह में जोड़ें", "Add": "जोड़ें", "Actor": "अभिनेता", - "AccessRestrictedTryAgainLater": "अभी प्रवेश प्रतिबंधित है। थोड़ी देर बाद कोशिश करें।", + "AccessRestrictedTryAgainLater": "वर्तमान में पहुंच प्रतिबंधित है। कृपया बाद में पुनः प्रयास करें.", "AllowHWTranscodingHelp": "ट्यूनर को निरंतर रूप से धाराओं को ट्रांसकोड करने दें। यह सर्वर द्वारा ट्रांसकोडिंग को कम करने में मदद कर सकता है।", "AllLanguages": "सभी भाषाएं", "AllEpisodes": "सभी प्रकरण", - "AllComplexFormats": "सभी जटिल प्रारूप (ASS, SSA, VOBSUB, PGS, SUB / IDX, आदि)", + "AllComplexFormats": "सभी जटिल प्रारूप (ASS, SSA, VOBSUB, PGS, SUB, IDX,…)", "AllChannels": "सभी चैनल्स", "Alerts": "चेतावनियां", "Albums": "संग्रहिकाएँ", @@ -34,5 +34,83 @@ "AddedOnValue": "जोड़ दिया", "AddToPlaylist": "प्लेलिस्ट में जोड़ें", "AllowMediaConversionHelp": "मीडिया परिवर्तन के लिये अनुमति दें", - "AllowMediaConversion": "मीडिया रूपांतरण की अनुमति दें" + "AllowMediaConversion": "मीडिया रूपांतरण की अनुमति दें", + "ButtonOk": "ठीक", + "ButtonOff": "बंद", + "ButtonNextTrack": "आगे धावन पथ", + "ButtonNew": "नया", + "ButtonNetwork": "संजाल", + "ButtonMore": "अधिक", + "ButtonManualLogin": "मैनुअल लॉगिन", + "ButtonLibraryAccess": "पुस्तकालय का उपयोग", + "ButtonLearnMore": "और अधिक जानें", + "ButtonInfo": "जानकारी", + "ButtonHome": "घर", + "ButtonHelp": "मदद", + "ButtonGuide": "मार्गदर्शक", + "ButtonGotIt": "समझ गया", + "ButtonFullscreen": "पूर्ण स्क्रीन", + "ButtonForgotPassword": "पासवर्ड भूल गए", + "ButtonFilter": "निस्पंदन", + "ButtonEditOtherUserPreferences": "इस उपयोगकर्ता की प्रोफ़ाइल, छवि और व्यक्तिगत प्राथमिकताएँ संपादित करें।", + "ButtonEditImages": "छवियों को संपादित करें", + "ButtonEdit": "संपादित करें", + "ButtonDownload": "डाउनलोड", + "ButtonDown": "नीचे", + "ButtonDeleteImage": "छवि हटाएं", + "ButtonDelete": "हटाएं", + "ButtonConnect": "जुडिये", + "ButtonChangeServer": "सर्वर बदलें", + "ButtonCancel": "रद्द करना", + "ButtonBack": "वापस", + "ButtonAudioTracks": "ऑडियो ट्रैक्स", + "ButtonArrowUp": "ऊपर", + "ButtonArrowRight": "दाएँ", + "ButtonArrowLeft": "बाएं", + "ButtonArrowDown": "नीचे", + "ButtonAddUser": "उपयोगकर्ता जोड़ें", + "ButtonAddServer": "सर्वर जोड़े", + "ButtonAddScheduledTaskTrigger": "ट्रिगर जोड़ें", + "ButtonAddMediaLibrary": "मीडिया लाइब्रेरी जोड़ें", + "ButtonAddImage": "छवि जोड़ें", + "ButtonAdd": "जोड़ना", + "UnsupportedPlayback": "Jellyfin DRM द्वारा संरक्षित सामग्री को डिक्रिप्ट नहीं कर सकता है, लेकिन सभी सामग्री की परवाह किए बिना, संरक्षित शीर्षकों सहित प्रयास किया जाएगा। एन्क्रिप्शन या अन्य असमर्थित सुविधाओं जैसे इंटरेक्टिव शीर्षक के कारण कुछ फाइलें पूरी तरह से काली दिखाई दे सकती हैं।", + "BoxRear": "बॉक्स (पीछे)", + "Box": "डिब्बा", + "Books": "पुस्तकें", + "BookLibraryHelp": "ऑडियो और पाठ्य पुस्तकें समर्थित हैं। {0} पुस्तक नामकरण गाइड {1} की समीक्षा करें।", + "Blacklist": "काला सूची में डालना", + "BirthPlaceValue": "जन्म स्थान: {0}", + "BirthLocation": "जन्म स्थान", + "BirthDateValue": "जन्म: {0}", + "Banner": "झंडा", + "Backdrops": "पृष्ठभूमि", + "Backdrop": "पृष्ठभूमि", + "AutoBasedOnLanguageSetting": "ऑटो (भाषा सेटिंग के आधार पर)", + "Auto": "ऑटो", + "AuthProviderHelp": "इस उपयोगकर्ता के पासवर्ड को प्रमाणित करने के लिए एक प्रमाणीकरण प्रदाता का उपयोग करें।", + "Audio": "नया", + "AttributeNew": "नया", + "AspectRatio": "आस्पेक्ट अनुपात", + "AskAdminToCreateLibrary": "लाइब्रेरी बनाने के लिए किसी व्यवस्थापक से पूछें।", + "Ascending": "आरोही", + "AsManyAsPossible": "जितने अधिक संभव हों", + "Artists": "कलाकार की", + "Artist": "कलाकार", + "Art": "कला", + "AroundTime": "लगभग", + "Anytime": "किसी भी समय", + "AnyLanguage": "कोई भी भाषा", + "AlwaysPlaySubtitlesHelp": "भाषा की वरीयता से मेल खाने वाले उपशीर्षक ऑडियो भाषा की परवाह किए बिना लोड किए जाएंगे।", + "AlwaysPlaySubtitles": "हमेशा खेलो", + "AllowedRemoteAddressesHelp": "कोमा ने नेटवर्क के लिए आईपी पते या आईपी / नेटमास्क प्रविष्टियों की सूची को अलग कर दिया है जिन्हें दूरस्थ रूप से कनेक्ट करने की अनुमति दी जाएगी। यदि खाली छोड़ दिया जाता है, तो सभी दूरस्थ पते की अनुमति दी जाएगी।", + "AllowRemoteAccessHelp": "अनियंत्रित होने पर, सभी दूरस्थ कनेक्शन अवरुद्ध हो जाएंगे।", + "AllowRemoteAccess": "इस जेलिफ़िन सर्वर को दूरस्थ कनेक्शन की अनुमति दें।", + "AllowFfmpegThrottlingHelp": "जब एक ट्रांसकोड या रीमूक्स वर्तमान प्लेबैक स्थिति से काफी आगे हो जाता है, तो प्रक्रिया को रोकें ताकि यह कम संसाधनों का उपभोग करेगा। अक्सर मांग किए बिना देखने पर यह सबसे उपयोगी है। यदि आप प्लेबैक समस्याओं का अनुभव करते हैं तो इसे बंद कर दें।", + "AllowFfmpegThrottling": "थ्रोटल ट्रांसकोड", + "AllowOnTheFlySubtitleExtractionHelp": "वीडियो ट्रांसकोडिंग को रोकने में मदद करने के लिए एंबेडेड सबटाइटल वीडियो से निकाले जा सकते हैं और सादे पाठ में ग्राहकों तक पहुंचाए जाते हैं। कुछ प्रणालियों पर यह एक लंबा समय ले सकता है और निष्कर्षण प्रक्रिया के दौरान वीडियो प्लेबैक को स्टाल करने का कारण बन सकता है। जब वे क्लाइंट डिवाइस द्वारा मूल रूप से समर्थित नहीं होते हैं, तो वीडियो ट्रांसकोडिंग के साथ जले हुए एम्बेडेड उपशीर्षक को अक्षम करें।", + "AlbumArtist": "चित्राधार कलाकार", + "AllowOnTheFlySubtitleExtraction": "मक्खी पर उपशीर्षक निष्कर्षण की अनुमति दें", + "Album": "एल्बम", + "AddItemToCollectionHelp": "उनके लिए खोज करके संग्रह में आइटम जोड़ें और उन्हें संग्रह में जोड़ने के लिए उनके राइट-क्लिक या टैप मेनू का उपयोग करें।" } From de753abe40dcc12de89eafcf4a239c35e465c3bb Mon Sep 17 00:00:00 2001 From: millallo Date: Sat, 23 May 2020 10:08:54 +0000 Subject: [PATCH 055/181] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/it.json b/src/strings/it.json index b862fc1175..effc731639 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -671,7 +671,7 @@ "LabelNumberOfGuideDays": "Numero di giorni per i quali scaricare i dati della guida:", "LabelNumberOfGuideDaysHelp": "Scaricando più giorni si avrà la possibilità di pianificare in anticipo più programmi e vedere più liste, ma il tempo di download si allungherà. 'Auto': MB sceglierà automaticamente in base al numero di canali.", "LabelOptionalNetworkPath": "Cartella condivisa (Opzionale):", - "LabelOptionalNetworkPathHelp": "Se questa cartella è condivisa sulla rete, fornendo il percorso di condivisione di rete si può consentire alle applicazioni Jellyfin su altri dispositivi di accedere direttamente ai file multimediali.", + "LabelOptionalNetworkPathHelp": "Se questa cartella è condivisa sulla rete, fornendo il percorso di condivisione di rete si può consentire alle applicazioni Jellyfin su altri dispositivi di accedere direttamente ai file multimediali. Ad esempio {0} oppure {1}.", "LabelOriginalAspectRatio": "Aspetto originale:", "LabelOriginalTitle": "Titolo originale:", "LabelOverview": "Trama:", From 899c352a34e5e2b06815184c05a4c64101159269 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 23 May 2020 09:34:18 +0000 Subject: [PATCH 056/181] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index 736c3b5b4f..3329cb7c3e 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -731,5 +731,8 @@ "HeaderServerAddressSettings": "Cài Đặt Địa Chỉ Máy Chủ", "HeaderRemoteAccessSettings": "Cài Đặt Truy Cập Từ Xa", "HeaderHttpsSettings": "Cài Đặt HTTPS", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "LabelExtractChaptersDuringLibraryScanHelp": "Trích xuất hình ảnh của video được nhập vào trong lúc quét thư viện. Nếu không thì hình này này sẽ được trích xuất thông qua những tác vụ định kì, giúp cho quá trình quét thư viện diễn ra nhanh hơn.", + "LabelExtractChaptersDuringLibraryScan": "Trích xuất hình ảnh từng chương khi quét thư viện", + "LabelBaseUrlHelp": "Thêm một thư mục tuỳ chọn vào đường dẫn máy chủ. Ví dụ: http://example.com/<baseurl>" } From 91ecfe9da5c72c37c88ed4dda3cf9eba4282a500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=9E=E5=94=AF?= Date: Sat, 23 May 2020 09:35:58 +0000 Subject: [PATCH 057/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 45191405f7..9a3d55e218 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -548,7 +548,7 @@ "LabelEmbedAlbumArtDidl": "在DIDL中嵌入专辑封面", "LabelEmbedAlbumArtDidlHelp": "有些设备首选这种方式获取专辑封面。启用该选项可能导致其他设备播放失败。", "LabelEnableAutomaticPortMap": "开启自动端口映射", - "LabelEnableAutomaticPortMapHelp": "尝试通过UPnP将公共端口自动映射到本地端口。这可能不适用于某些型号的路由器。需要服务器重新启动后才会应用更改。", + "LabelEnableAutomaticPortMapHelp": "尝试通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", "LabelEnableBlastAliveMessages": "爆发活动信号", "LabelEnableBlastAliveMessagesHelp": "如果该服务器不能被网络中的其他UPnP设备检测到,请启用此选项。", "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔(秒)", @@ -670,7 +670,7 @@ "LabelNumberOfGuideDays": "下载几天的节目指南:", "LabelNumberOfGuideDaysHelp": "下载更多天的节目指南可以帮你进一步查看节目列表并做出提前安排,但下载过程也将耗时更久。它将基于频道数量自动选择。", "LabelOptionalNetworkPath": "(可选的)共享的网络文件夹:", - "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件。", + "LabelOptionalNetworkPathHelp": "如果这个文件夹在你的网络上是共享的,提供这个网络共享地址能够允许其他设备上的 Jellyfin 应用程序直接访问媒体文件,例如 {0} 或者 {1}。", "LabelOriginalAspectRatio": "原始长宽比:", "LabelOriginalTitle": "原标题:", "LabelOverview": "内容概述:", @@ -1020,7 +1020,7 @@ "OptionMissingEpisode": "缺少的剧集", "OptionMonday": "星期一", "OptionNameSort": "名字", - "OptionNew": "更新...", + "OptionNew": "新建…", "OptionNone": "没有", "OptionOnAppStartup": "在程序启动时", "OptionOnInterval": "在一个期间", @@ -1455,7 +1455,7 @@ "ButtonAddImage": "添加图片", "LabelPlayer": "播放器:", "LabelBaseUrl": "基础 URL:", - "LabelBaseUrlHelp": "您可以在此处添加自定义子目录,以便从更唯一的 URL 访问服务器。", + "LabelBaseUrlHelp": "在此处添加自定义子目录到服务器的 URL,例如:http://example.com/<baseurl>。", "MusicLibraryHelp": "重播 {0}音乐命名指南{1}。", "HeaderFavoritePeople": "最喜欢的人物", "OptionRandom": "随机", @@ -1514,5 +1514,12 @@ "HeaderServerAddressSettings": "服务器地址设置", "HeaderRemoteAccessSettings": "远程访问设置", "HeaderHttpsSettings": "HTTPS 设置", - "ApiKeysCaption": "当前启用的 API 密钥" + "ApiKeysCaption": "当前启用的 API 密钥", + "TabDVR": "DVR", + "SaveChanges": "保存更改", + "LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。", + "LabelRequireHttps": "强制 HTTPS", + "LabelStable": "稳定版", + "LabelEnableHttpsHelp": "开启服务器监听 HTTPS 端口。必须配置有效的证书才会生效。", + "LabelEnableHttps": "启用 HTTPS" } From 5af71c79a95bf873a8d5253d85d3b28f7faf49e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=9E=E5=94=AF?= Date: Sat, 23 May 2020 09:37:47 +0000 Subject: [PATCH 058/181] Translated using Weblate (English) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en/ --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 07ba4eb1d0..94b27a1b54 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -646,7 +646,7 @@ "LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play content.", "LabelEnableHardwareDecodingFor": "Enable hardware decoding for:", "LabelEnableHttps": "Enable HTTPS", - "LabelEnableHttpsHelp": "Enables the server to listen on the configured HTTPS post. A valid certificate must also be configured in order for this to take effect.", + "LabelEnableHttpsHelp": "Enables the server to listen on the configured HTTPS port. A valid certificate must also be configured in order for this to take effect.", "LabelEnableRealtimeMonitor": "Enable real time monitoring", "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately, on supported file systems.", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", From c14f72342d56bda904136b3fa8f1fffa67717055 Mon Sep 17 00:00:00 2001 From: DINESH REDDY Date: Sat, 23 May 2020 10:39:46 +0000 Subject: [PATCH 059/181] Translated using Weblate (English) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en/ --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 94b27a1b54..8ee4695533 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -35,7 +35,7 @@ "AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", "AnyLanguage": "Any Language", "Anytime": "Anytime", - "AroundTime": "Around {0}", + "AroundTime": "Around", "Art": "Art", "Artist": "Artist", "Artists": "Artists", From b7a6429b51223407d462c61c872580d7c83d0ef6 Mon Sep 17 00:00:00 2001 From: Viperinius Date: Sat, 23 May 2020 13:33:35 +0000 Subject: [PATCH 060/181] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 87eaaaaf84..1ca5e91102 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -33,7 +33,7 @@ "AlwaysPlaySubtitlesHelp": "Untertitel die den Spracheinstellungen entsprechen werden unabhängig von der Tonspursprache geladen.", "AnyLanguage": "Jede Sprache", "Anytime": "Jederzeit", - "AroundTime": "Um {0}", + "AroundTime": "Um", "Artists": "Interpreten", "AsManyAsPossible": "So viele wie möglich", "Ascending": "Aufsteigend", @@ -675,7 +675,7 @@ "LabelNumberOfGuideDays": "Anzahl von Tagen für die Programminformationen geladen werden sollen:", "LabelNumberOfGuideDaysHelp": "Das laden von zusätzlichen Programmdaten bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Aber es wird länger dauern alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", "LabelOptionalNetworkPath": "(Optionaler) Gemeinsamer 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.", + "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:", "LabelOverview": "Übersicht:", @@ -1535,7 +1535,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-Post zu beobachten. Damit dies geschehen kann, muss ein gültiges Zertifikat konfiguriert sein.", + "LabelEnableHttpsHelp": "Erlaubt es dem Server, den konfigurierten HTTPS-Port zu beobachten. Damit dies geschehen kann, muss ein gültiges Zertifikat konfiguriert sein.", "LabelEnableHttps": "Aktiviere HTTPS", "HeaderServerAddressSettings": "Server-Adresseinstellungen", "HeaderRemoteAccessSettings": "Fernzugriffs-Einstellungen", From a03613756153081cd3962cb8aeecafb8cbf67d68 Mon Sep 17 00:00:00 2001 From: Adam Bokor Date: Sat, 23 May 2020 15:47:42 +0000 Subject: [PATCH 061/181] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index c1ee03e090..6bd8bc82cd 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -592,7 +592,7 @@ "AlwaysPlaySubtitles": "Mindig jelenjen meg", "AnyLanguage": "Bármelyik nyelv", "Anytime": "Bármikor", - "AroundTime": "{0} körül", + "AroundTime": "kb.", "AsManyAsPossible": "Amennyi lehetséges", "AspectRatio": "Képarány", "Auto": "Auto", @@ -933,7 +933,7 @@ "LabelNewName": "Új név:", "LabelNewsCategories": "Hírek kategóriái:", "LabelNumber": "Szám:", - "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a Jellyfin alkalmazások más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz.", + "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a Jellyfin alkalmazások más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0{ vagy {1}.", "LabelPasswordConfirm": "Jelszó (megerősítés):", "LabelPlaceOfBirth": "Születési hely:", "LabelPostProcessor": "A feldolgozás utáni alkalmazás:", From 1647cb5f11cc9b314b24cf25acacf7164ef8573f Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Sat, 23 May 2020 12:39:15 +0000 Subject: [PATCH 062/181] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index e1017a20b8..3d3b12f0d7 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -14,7 +14,7 @@ "AllowRemoteAccessHelp": "Nezaškrtnuté znamená, že všetky vzdialené pripojenia budú blokované.", "AlwaysPlaySubtitles": "Vždy prehrať", "AnyLanguage": "Akýkoľvek jazyk", - "AroundTime": "Okolo {0}", + "AroundTime": "Okolo", "Artists": "Umelci", "AsManyAsPossible": "Najviac ako je možné", "Ascending": "Vzostupne", @@ -1389,7 +1389,7 @@ "LabelPersonRoleHelp": "Príklad: Vodič nákladiaku so zmrzlinou", "LabelPasswordResetProvider": "Poskytovateľ obnovy hesla:", "LabelParentNumber": "Číslo rodiča:", - "LabelOptionalNetworkPathHelp": "Pokiaľ je tento priečinok zdielaný vo vašej sieti, môže poskytovanie cesty k zdielanému priečinku umožniť Jellyfin aplikáciám priamy prístup k mediálnym súborom.", + "LabelOptionalNetworkPathHelp": "Pokiaľ je tento priečinok zdielaný vo vašej sieti, môže poskytovanie cesty k zdielanému priečinku umožniť Jellyfin aplikáciám priamy prístup k mediálnym súborom. Napríklad, {0} alebo {1}.", "LabelNumberOfGuideDaysHelp": "Stiahnutím viacerých dní umožní sprievodca naplánovať a zobraziť viac vecí do budúcnosti, sťahovanie však môže trvať dlhšie. Auto vyberie možnosť podľa počtu kanálov.", "LabelNumberOfGuideDays": "Počet dní pre stiahnutie dát sprievodcu:", "LabelMusicStreamingTranscodingBitrateHelp": "Špecifikujte maximálny dátový tok pre streamovanie hudby.", From c3cbba0a42da7de7ae88aee955e2675c453b2f22 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 23 May 2020 14:02:38 +0000 Subject: [PATCH 063/181] Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/vi/ --- src/strings/vi.json | 56 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/strings/vi.json b/src/strings/vi.json index 3329cb7c3e..c7e5c61d84 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -188,7 +188,7 @@ "Ascending": "Tăng dần", "AsManyAsPossible": "Càng nhiều càng tốt", "Artists": "Nghệ Sĩ", - "AroundTime": "Khoảng {0}", + "AroundTime": "Khoảng", "Anytime": "Bất cứ lúc nào", "AnyLanguage": "Ngôn Ngữ Bất Kỳ", "AlwaysPlaySubtitlesHelp": "Phụ đề phù hợp với sở thích ngôn ngữ sẽ được tải bất kể ngôn ngữ âm thanh.", @@ -715,7 +715,7 @@ "LabelEnableSingleImageInDidlLimit": "Giới hạn chỉ một hình ảnh nhúng", "LabelEnableRealtimeMonitorHelp": "Thay đổi để nội dung sẽ được xử lý ngay lập tức trên các hệ thống được hỗ trợ.", "LabelEnableRealtimeMonitor": "Bật tính năng theo dõi thời gian thực", - "LabelEnableHttpsHelp": "Cho phép máy chủ theo dõi thiết lập HTTPS. Cần phải có chứng chỉ hợp lệ để tính năng này có hiệu quả.", + "LabelEnableHttpsHelp": "Cho phép máy chủ theo dõi port HTTPS đã được thiết lập. Cần phải có chứng chỉ hợp lệ để tính năng này có hiệu quả.", "LabelEnableHttps": "Bật HTTPS", "LabelEnableHardwareDecodingFor": "Bật tính năng giãi mã phần cứng cho:", "LabelEnableDlnaServerHelp": "Cho phép các thiết bị UPnP trong mạng của bản để duyệt và phát nội dung.", @@ -734,5 +734,55 @@ "HeaderDVR": "DVR", "LabelExtractChaptersDuringLibraryScanHelp": "Trích xuất hình ảnh của video được nhập vào trong lúc quét thư viện. Nếu không thì hình này này sẽ được trích xuất thông qua những tác vụ định kì, giúp cho quá trình quét thư viện diễn ra nhanh hơn.", "LabelExtractChaptersDuringLibraryScan": "Trích xuất hình ảnh từng chương khi quét thư viện", - "LabelBaseUrlHelp": "Thêm một thư mục tuỳ chọn vào đường dẫn máy chủ. Ví dụ: http://example.com/<baseurl>" + "LabelBaseUrlHelp": "Thêm một thư mục tuỳ chọn vào đường dẫn máy chủ. Ví dụ: http://example.com/<baseurl>", + "LabelLoginDisclaimerHelp": "Một tin nhắn sẽ hiển thị ở phía cuối của trang đăng nhập.", + "LabelLoginDisclaimer": "Hiển thị khi đăng nhập:", + "LabelLockItemToPreventChanges": "Khoá mục này để ngăn những thay đổi trong tương lai", + "LabelLocalHttpServerPortNumberHelp": "TCP port mà máy chủ Jellyfin HTTP nên kết nối.", + "LabelLocalHttpServerPortNumber": "HTTP port nội bộ:", + "LabelLineup": "Danh sách diễn viên:", + "LabelLibraryPageSizeHelp": "Cài đặt số lượng mục hiển thị trong một trang thư viện. Cài đặt 0 để vô hiệu hoá việc phân trang.", + "LabelLibraryPageSize": "Kích thước trang của thư viện:", + "LabelLanNetworks": "Mạng nội bộ:", + "LabelKodiMetadataUserHelp": "Lưu dữ liệu xem vào tập tin NFO dành cho những ứng dụng khác sử dụng.", + "LabelKodiMetadataUser": "Lưu thông tin người xem vào tập tin NFO dành cho:", + "LabelKodiMetadataSaveImagePathsHelp": "Cài đặt này được khuyến cáo nếu bạn có những hình ảnh đặt tên không đúng với hướng dẫn của Kodi.", + "LabelKodiMetadataSaveImagePaths": "Lưu đường dẫn hình ảnh trong tập tin NFO", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Kích hoạt thay thế đường dẫn hình ảnh sử dụng cài đặt của máy chủ.", + "LabelKodiMetadataEnablePathSubstitution": "Kích hoạt thay thế đường dẫn", + "LabelKodiMetadataEnableExtraThumbsHelp": "Khi tải hình ảnh, chúng có thể được lưu vào cả extrafanart và extrathumbs để tối ưu hoá khả năng tương thích với giao diện Kodi.", + "LabelKodiMetadataEnableExtraThumbs": "Sao chép từ mục extrafanart đến mục extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Toàn bộ ngày trong tập tin NFO sẽ được đọc sử dụng định dạng này.", + "LabelKodiMetadataDateFormat": "Định dạng của ngày phát hành:", + "LabelKidsCategories": "Những thể loại của trẻ em:", + "LabelKeepUpTo": "Theo kịp:", + "LabelInternetQuality": "Chất lượng Internet:", + "LabelInNetworkSignInWithEasyPasswordHelp": "Sử dụng mã PIN đơn giản để đăng nhập thiết bị phát trong mạng nội bộ. Mật khẩu thông thường sẽ chỉ cần khi không truy cập nội mạng. Nếu mã PIN để trống, bạn sex không cần mật khẩu trong mạng nội bộ.", + "LabelInNetworkSignInWithEasyPassword": "Kích hoạt đăng nhập nội mạng bằng mã PIN đơn giản", + "LabelImportOnlyFavoriteChannels": "Giới hạn để chỉ nhập vào những kênh yêu thích", + "LabelImageType": "Loại hình ảnh:", + "LabelImageFetchersHelp": "Kích hoạt và xếp hạng chương trình tải hình ảnh theo thứ tự ưu tiên.", + "LabelIdentificationFieldHelp": "Một phần chuỗi không phân biệt viết hoa/thường hoặc regex expression.", + "LabelIconMaxWidthHelp": "Độ phân giải tối đa của biểu tượng hiển thị thông qua upnp:icon.", + "LabelIconMaxWidth": "Chiều ngang tối đa của biểu tượng:", + "LabelIconMaxHeightHelp": "Độ phân giải tối đa của biểu tượng hiển thị thông qua upnp:icon.", + "LabelIconMaxHeight": "Chiều cao tối đa của biểu tượng:", + "LabelHttpsPortHelp": "TCP port mà máy chủ Jellyfin HTTPS nên kết nối vào.", + "LabelHttpsPort": "HTTPS port trên máy chủ:", + "LabelHomeScreenSectionValue": "Mục trên trang chủ {0}:", + "LabelHomeNetworkQuality": "Chất lượng mạng trong nhà:", + "LabelHardwareAccelerationTypeHelp": "Hỗ trợ phần cần những thiết lập bổ sung.", + "LabelHardwareAccelerationType": "Hỗ trợ phần cứng:", + "LabelEncoderPreset": "Thiết lập cài sẵn của mã H264 và H265:", + "LabelH264Crf": "CRF của mã H264:", + "LabelGroupMoviesIntoCollectionsHelp": "Khi hiển thị danh sách phim, các bộ phim thuộc về một bộ sưu tập sẽ hiển thị trong một nhóm.", + "LabelGroupMoviesIntoCollections": "Nhóm phim vào bộ sưu tập", + "LabelServerNameHelp": "Tên này sẽ được sử dụng để phân biệt máy chủ và giá trị mặc định là tên của máy tính chủ.", + "LabelFriendlyName": "Tên thân thiện:", + "LabelFormat": "Định dạng:", + "LabelForgotPasswordUsernameHelp": "Nhập vào tên tài khoản nếu bạn nhớ nó.", + "LabelFont": "Kiểu chữ:", + "LabelFolder": "Thư mục:", + "LabelFileOrUrl": "Tệp hoặc URL:", + "LabelFailed": "Thất bại" } From c9555c94ce3c65eeeb249aee2b5cabcb6f7b99c9 Mon Sep 17 00:00:00 2001 From: amirmasoud Date: Sun, 24 May 2020 04:08:37 +0000 Subject: [PATCH 064/181] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fa/ --- src/strings/fa.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/fa.json b/src/strings/fa.json index 76f8ec4e2b..b91a2c82f1 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -212,7 +212,7 @@ "AskAdminToCreateLibrary": "از کاربر مدیر بخواهید که یک کتابخانه ایجاد کند.", "Ascending": "بالا رونده", "AsManyAsPossible": "تا حدی که ممکن است", - "AroundTime": "حدود {0}", + "AroundTime": "حدود", "Anytime": "هر زمانی", "AnyLanguage": "هر زبانی", "AlwaysPlaySubtitles": "همیشه پخش کن", @@ -458,7 +458,7 @@ "BoxSet": "جعبه ست", "Art": "هنر", "Artist": "هنرمند", - "AllComplexFormats": "تمام فرمت‌های پیچیده (ASS, SSA, VOBSUB, PGS, SUB, IDX)", + "AllComplexFormats": "کلیه فرمت‌های پیچیده (ASS ، SSA ، VOBSUB ، PGS ، SUB ، IDX ، ...)", "GuideProviderLogin": "ورود", "Guide": "راهنما", "GuestStar": "ستاره‌ی مهمان", @@ -641,7 +641,7 @@ "OptionPlainStorageFolders": "نمایش همه پوشه‌ها به عنوان پوشه‌های ذخیره سازی ساده", "OptionParentalRating": "رتبه بندی والدین", "OptionOnInterval": "در یک فاصله", - "BookLibraryHelp": "کتاب‌های صوتی و متنی پشتیبانی می‌شوند. {0}راهنمای نامگذاری کتاب{1} را مرور کنید.", + "BookLibraryHelp": "کتاب‌های صوتی و متنی پشتیبانی می‌شوند. {0} راهنمای نامگذاری کتاب {1} را مرور کنید.", "TabInfo": "اطلاعات", "TabGuide": "راهنما", "TabFavorites": "مورد علاقه‌ها", From 3b6fa7e97201a5121975e6280013455e79f26edc Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 24 May 2020 18:30:35 +0900 Subject: [PATCH 065/181] add display preference for details banner and update some defaults --- src/components/displaySettings/displaySettings.js | 2 ++ .../displaySettings/displaySettings.template.html | 8 ++++++++ src/controllers/itemDetails.js | 2 +- src/scripts/settings/userSettings.js | 13 +++++++++++-- src/strings/en-gb.json | 2 +- src/strings/en-us.json | 8 +++++--- 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index b6938bc95d..4e068960a3 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -182,6 +182,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); + context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); context.querySelector('#selectLanguage').value = userSettings.language() || ''; context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; @@ -223,6 +224,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', ' userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); + userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); if (user.Id === apiClient.getCurrentUserId()) { skinManager.setTheme(userSettingsInstance.theme()); diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index b8ab1a9ba5..d37c24b49d 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -156,6 +156,14 @@
${EnableFastImageFadeInHelp}
+
+ +
${EnableDetailsBannerHelp}
+
+
-
'; html += ''; From 04e27d98d5c89582351f3de9efd955e120a47aae Mon Sep 17 00:00:00 2001 From: Influence365 Date: Tue, 26 May 2020 14:32:47 +0100 Subject: [PATCH 086/181] Update to imageDownloader.js for bug fix jellyfin#1207 --- src/components/imageDownloader/imageDownloader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 220e4148cc..c989011e8d 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -203,9 +203,9 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image html += '
'; if (layoutManager.tv || !appHost.supports('externallinks')) { - html += '
'; + html += '
'; } else { - html += ''; + html += ''; } html += '
'; From 5f7729b239fd95d37a42e44ef114e4a8c5e1c8d9 Mon Sep 17 00:00:00 2001 From: lldsolitude Date: Tue, 26 May 2020 14:08:21 +0000 Subject: [PATCH 087/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 9a3d55e218..1c9c681de7 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -29,7 +29,7 @@ "AlwaysPlaySubtitles": "总是显示", "AlwaysPlaySubtitlesHelp": "无论音频为何种语言,都将加载与语言偏好匹配的字幕。", "Anytime": "任何时间", - "AroundTime": "{0} 左右", + "AroundTime": "大约", "Artists": "艺术家", "AsManyAsPossible": "尽可能多", "Ascending": "升序", @@ -548,7 +548,7 @@ "LabelEmbedAlbumArtDidl": "在DIDL中嵌入专辑封面", "LabelEmbedAlbumArtDidlHelp": "有些设备首选这种方式获取专辑封面。启用该选项可能导致其他设备播放失败。", "LabelEnableAutomaticPortMap": "开启自动端口映射", - "LabelEnableAutomaticPortMapHelp": "尝试通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", + "LabelEnableAutomaticPortMapHelp": "通过UPnP将路由器端口自动转发到服务器端口。这可能不适用于某些型号的路由器和网络配置。需要服务器重新启动后才会应用更改。", "LabelEnableBlastAliveMessages": "爆发活动信号", "LabelEnableBlastAliveMessagesHelp": "如果该服务器不能被网络中的其他UPnP设备检测到,请启用此选项。", "LabelEnableDlnaClientDiscoveryInterval": "客户端搜寻时间间隔(秒)", @@ -1455,7 +1455,7 @@ "ButtonAddImage": "添加图片", "LabelPlayer": "播放器:", "LabelBaseUrl": "基础 URL:", - "LabelBaseUrlHelp": "在此处添加自定义子目录到服务器的 URL,例如:http://example.com/<baseurl>。", + "LabelBaseUrlHelp": "为服务器 URL添加自定义子目录,例如:http://example.com/<baseurl>。", "MusicLibraryHelp": "重播 {0}音乐命名指南{1}。", "HeaderFavoritePeople": "最喜欢的人物", "OptionRandom": "随机", @@ -1520,6 +1520,8 @@ "LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。", "LabelRequireHttps": "强制 HTTPS", "LabelStable": "稳定版", - "LabelEnableHttpsHelp": "开启服务器监听 HTTPS 端口。必须配置有效的证书才会生效。", - "LabelEnableHttps": "启用 HTTPS" + "LabelEnableHttpsHelp": "开启服务器对所配置HTTPS 端口的监听。必须配置有效的证书才会生效。", + "LabelEnableHttps": "启用 HTTPS", + "LabelChromecastVersion": "Chromecast版本", + "HeaderDVR": "DVR" } From 59dd84b2ee3e729d97092002447f1c38f20b8f69 Mon Sep 17 00:00:00 2001 From: lldsolitude Date: Tue, 26 May 2020 15:10:27 +0000 Subject: [PATCH 088/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 1c9c681de7..0b51170196 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1523,5 +1523,6 @@ "LabelEnableHttpsHelp": "开启服务器对所配置HTTPS 端口的监听。必须配置有效的证书才会生效。", "LabelEnableHttps": "启用 HTTPS", "LabelChromecastVersion": "Chromecast版本", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "LabelNightly": "Nightly" } From 6dc722cac4cca034a79ee8c37d8001c606bb9463 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Tue, 26 May 2020 16:56:49 -0400 Subject: [PATCH 089/181] Include explicit yarn version To avoid confusion, specify an explicit version of Yarn and link to classic install docs. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2aac6b155..f06e461320 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user ### Dependencies -- Yarn +- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install) - Gulp-cli ### Getting Started @@ -78,4 +78,4 @@ Jellyfin Web is the frontend used for most of the clients available for end user ```sh yarn build:standalone - ``` \ No newline at end of file + ``` From 67da5e49ab98be81d899cde645983b9a2963977f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Tue, 26 May 2020 23:07:19 +0200 Subject: [PATCH 090/181] Transfer whole playlist when transfer playback to another device --- .../playback/remotecontrolautoplay.js | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/components/playback/remotecontrolautoplay.js b/src/components/playback/remotecontrolautoplay.js index 90a872cc6e..39a0db8286 100644 --- a/src/components/playback/remotecontrolautoplay.js +++ b/src/components/playback/remotecontrolautoplay.js @@ -1,47 +1,45 @@ -define(['events', 'playbackManager'], function (events, playbackManager) { - 'use strict'; +import events from 'events'; +import playbackManager from 'playbackManager'; - function transferPlayback(oldPlayer, newPlayer) { +function transferPlayback(oldPlayer, newPlayer) { + const state = playbackManager.getPlayerState(oldPlayer); + const item = state.NowPlayingItem; - var state = playbackManager.getPlayerState(oldPlayer); - - var item = state.NowPlayingItem; - - if (!item) { - return; - } - - var playState = state.PlayState || {}; - var resumePositionTicks = playState.PositionTicks || 0; - - playbackManager.stop(oldPlayer).then(function () { - - playbackManager.play({ - ids: [item.Id], - serverId: item.ServerId, - startPositionTicks: resumePositionTicks - - }, newPlayer); - }); + if (!item) { + return; } - events.on(playbackManager, 'playerchange', function (e, newPlayer, newTarget, oldPlayer) { + const playlist = playbackManager.getPlaylist(oldPlayer).then(playlist => { + const playlistIds = playlist.map(x => x.Id); + const playState = state.PlayState || {}; + const resumePositionTicks = playState.PositionTicks || 0; + const playlistIndex = playlistIds.indexOf(item.Id) || 0; - if (!oldPlayer || !newPlayer) { - return; - } - - if (!oldPlayer.isLocalPlayer) { - console.debug('Skipping remote control autoplay because oldPlayer is not a local player'); - return; - } - - if (newPlayer.isLocalPlayer) { - console.debug('Skipping remote control autoplay because newPlayer is a local player'); - return; - } - - transferPlayback(oldPlayer, newPlayer); + playbackManager.stop(oldPlayer).then(() => { + playbackManager.play({ + ids: playlistIds, + serverId: item.ServerId, + startPositionTicks: resumePositionTicks, + startIndex: playlistIndex + }, newPlayer); + }); }); +} +events.on(playbackManager, 'playerchange', (e, newPlayer, newTarget, oldPlayer) => { + if (!oldPlayer || !newPlayer) { + return; + } + + if (!oldPlayer.isLocalPlayer) { + console.debug('Skipping remote control autoplay because oldPlayer is not a local player'); + return; + } + + if (newPlayer.isLocalPlayer) { + console.debug('Skipping remote control autoplay because newPlayer is a local player'); + return; + } + + transferPlayback(oldPlayer, newPlayer); }); From 10470e24a5bb28b41937f1bf8fe089cda1e8614b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Tue, 26 May 2020 23:09:08 +0200 Subject: [PATCH 091/181] Add ES6 module to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 14ff38d351..8111552d28 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "src/components/images/imageLoader.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/playback/mediasession.js", + "src/components/playback/remotecontrolautoplay.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/scripts/dfnshelper.js", From ffa9f2093baeb09f6e719b79ab24934ec20b0664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Tue, 26 May 2020 23:16:33 +0200 Subject: [PATCH 092/181] Fixed code smell --- src/components/playback/remotecontrolautoplay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/remotecontrolautoplay.js b/src/components/playback/remotecontrolautoplay.js index 39a0db8286..c0adb57a45 100644 --- a/src/components/playback/remotecontrolautoplay.js +++ b/src/components/playback/remotecontrolautoplay.js @@ -9,7 +9,7 @@ function transferPlayback(oldPlayer, newPlayer) { return; } - const playlist = playbackManager.getPlaylist(oldPlayer).then(playlist => { + playbackManager.getPlaylist(oldPlayer).then(playlist => { const playlistIds = playlist.map(x => x.Id); const playState = state.PlayState || {}; const resumePositionTicks = playState.PositionTicks || 0; From 5ef5f10795a2a4ef179c2c0563711045274ffb26 Mon Sep 17 00:00:00 2001 From: artiume Date: Tue, 26 May 2020 17:56:54 -0400 Subject: [PATCH 093/181] Add Genres to Home Screen --- .../homeScreenSettings/homeScreenSettings.js | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/components/homeScreenSettings/homeScreenSettings.js b/src/components/homeScreenSettings/homeScreenSettings.js index b07e6de3cc..9eae944d26 100644 --- a/src/components/homeScreenSettings/homeScreenSettings.js +++ b/src/components/homeScreenSettings/homeScreenSettings.js @@ -37,18 +37,19 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa var list = []; if (type === 'movies') { - list.push({ name: globalize.translate('Movies'), value: 'movies', isDefault: true }); - list.push({ name: globalize.translate('Suggestions'), value: 'suggestions' }); - + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); list.push({ name: globalize.translate('Favorites'), value: 'favorites' @@ -58,7 +59,6 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa value: 'collections' }); } else if (type === 'tvshows') { - list.push({ name: globalize.translate('Shows'), value: 'shows', @@ -68,49 +68,45 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa name: globalize.translate('Suggestions'), value: 'suggestions' }); - list.push({ name: globalize.translate('Latest'), value: 'latest' }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); list.push({ name: globalize.translate('Favorites'), value: 'favorites' }); } else if (type === 'music') { - list.push({ name: globalize.translate('Suggestions'), value: 'suggestions', isDefault: true }); - list.push({ name: globalize.translate('Albums'), value: 'albums' }); - list.push({ name: globalize.translate('HeaderAlbumArtists'), value: 'albumartists' }); - list.push({ name: globalize.translate('Artists'), value: 'artists' }); - list.push({ name: globalize.translate('Playlists'), value: 'playlists' }); - list.push({ name: globalize.translate('Genres'), value: 'genres' }); } else if (type === 'livetv') { - list.push({ name: globalize.translate('Suggestions'), value: 'suggestions', From 1926e4b9b7665234ae1a20f7a15a4f5defb7a99e Mon Sep 17 00:00:00 2001 From: Kunio Date: Tue, 26 May 2020 18:54:58 +0000 Subject: [PATCH 094/181] Translated using Weblate (Swedish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sv/ --- src/strings/sv.json | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/strings/sv.json b/src/strings/sv.json index 443549e797..282fec7b7e 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -13,7 +13,7 @@ "Albums": "Album", "All": "Alla", "AllChannels": "Alla kanaler", - "AllComplexFormats": "Alla komplexa format (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Alla komplexa format (ASS, SSA, VOBSUB, PGS, SUB/IDX, ...)", "AllEpisodes": "Alla avsnitt", "AllLanguages": "Alla språk", "AllLibraries": "Alla bibliotek", @@ -26,7 +26,7 @@ "AlwaysPlaySubtitlesHelp": "Undertexter på det önskade språket kommer att laddas oavsett ljudspårets språk.", "AnyLanguage": "Alla språk", "Anytime": "När som helst", - "AroundTime": "Runt {0}", + "AroundTime": "Runt", "Art": "Grafik", "Artists": "Artister", "AsManyAsPossible": "Så många som möjligt", @@ -40,13 +40,13 @@ "BirthDateValue": "Född: {0}", "BirthLocation": "Födelseort", "BirthPlaceValue": "Födelseort:{0}", - "BookLibraryHelp": "Ljud- och textböcker stöds. Läs {0}boknamngivningsguide{1}.", + "BookLibraryHelp": "Ljud- och textböcker stöds. Läs {0} boknamngivningsguiden {1}.", "Books": "Böcker", "Box": "Omslag", "BoxRear": "Omslag (baksida)", "Browse": "Bläddra", "BrowsePluginCatalogMessage": "Besök katalogen för att se tillgängliga tillägg.", - "BurnSubtitlesHelp": "Avgör ifall servern ska \"bränna in\" undertexterna under transkodning. Att undvika detta förbättrar prestandan avsevärt. Välj \"Automatisk\" för att bränna bild-baserade format (ex. VOBSUB, PGS, SUB/IDX, etc.) och vissa ASS/SSA-undertexter.", + "BurnSubtitlesHelp": "Avgör ifall servern ska \"bränna in\" undertexterna under transkodning. Att undvika detta förbättrar prestandan avsevärt. Välj \"Automatisk\" för att bränna bild-baserade format (ex. VOBSUB, PGS, SUB/IDX, ...) och vissa ASS/SSA-undertexter.", "ButtonAdd": "Lägg till", "ButtonAddMediaLibrary": "Lägg till mediabibliotek", "ButtonAddScheduledTaskTrigger": "Lägg till utlösare", @@ -988,7 +988,7 @@ "OptionMissingEpisode": "Saknade avsnitt", "OptionMonday": "Måndag", "OptionNameSort": "Namn", - "OptionNew": "Ny...", + "OptionNew": "Ny…", "OptionNone": "Inga", "OptionOnAppStartup": "När servern startar", "OptionOnInterval": "Med visst intervall", @@ -1507,5 +1507,15 @@ "UnsupportedPlayback": "Jellyfin kan inte dekryptera inehåll skyddat av DRM men allt inehåll kommer ändå försökas, även skyddade titlar. Vissa filer kan se helt svart ut på grund av kryptering eller andra funktioner som inte stöds, till exempel interaktiva titlar.", "LabelLibraryPageSizeHelp": "Sätter en begränsad sidstorlek i bibliotek. Sätt 0 för att avaktivera begränsad sidstorlek.", "ApiKeysCaption": "Lista av aktiva API-nycklar", - "DeinterlaceMethodHelp": "Välj metod för borttagning av inflätning vid konvertering av inflätat inehåll." + "DeinterlaceMethodHelp": "Välj metod för borttagning av inflätning vid konvertering av inflätat inehåll.", + "TabDVR": "PVR", + "SaveChanges": "Spara ändringar", + "LabelRequireHttps": "Kräv HTTPS", + "LabelChromecastVersion": "Chromecast-version", + "LabelEnableHttpsHelp": "Gör det möjligt för servern att lyssna på den konfigurerade HTTPS-porten. Ett giltigt certifikat måste också konfigureras för att detta ska fungera.", + "LabelEnableHttps": "Aktivera HTTPS", + "HeaderServerAddressSettings": "Serveradressinställningar", + "HeaderRemoteAccessSettings": "Inställningar för fjärråtkomst", + "HeaderHttpsSettings": "HTTPS-inställningar", + "HeaderDVR": "PVR" } From ef0b4b5f51c978861a4f4acd02f5fa8bc3afded7 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Wed, 27 May 2020 07:15:26 +0100 Subject: [PATCH 095/181] Update to imageDownloader.js for bug fix #1207 --- src/components/imageDownloader/imageDownloader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index c989011e8d..a3965279cf 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -203,9 +203,9 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image html += '
'; if (layoutManager.tv || !appHost.supports('externallinks')) { - html += '
'; + html += '
'; } else { - html += ''; + html += ''; } html += '
'; From 9cc10c32ab666aa0d687143e8310130e14ad922d Mon Sep 17 00:00:00 2001 From: Influence365 Date: Wed, 27 May 2020 07:16:24 +0100 Subject: [PATCH 096/181] Update to imageeditor.js for bug fix #1207 --- src/components/imageeditor/imageeditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index 502539fae6..c13d207892 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -132,7 +132,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', var imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize }); - html += '
'; + html += '
'; html += ''; html += ''; From c9a6753aba031a14bb1571f6da5747020abf47c3 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Wed, 27 May 2020 07:17:06 +0100 Subject: [PATCH 097/181] Update imageeditor.js --- src/components/imageeditor/imageeditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index c13d207892..2927a0b120 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -132,7 +132,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', var imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize }); - html += '
'; + html += '
'; html += ''; html += ''; From 0d7d4c7d15f5083b06477d8cb16e901ab3596d01 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Wed, 27 May 2020 07:17:45 +0100 Subject: [PATCH 098/181] Reverted previous commit --- src/components/cardbuilder/card.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/cardbuilder/card.css b/src/components/cardbuilder/card.css index 059c1d5986..c24fcf6ba6 100644 --- a/src/components/cardbuilder/card.css +++ b/src/components/cardbuilder/card.css @@ -157,7 +157,7 @@ button::-moz-focus-inner { } .cardImageContainer { - background-size: contain; + background-size: cover; background-repeat: no-repeat; background-position: center center; display: -webkit-flex; From b27832f930d194af9467d062e3f7e341eef5c918 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Wed, 27 May 2020 10:41:42 +0300 Subject: [PATCH 099/181] Enable babel for date-fns --- webpack.dev.js | 2 +- webpack.prod.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.dev.js b/webpack.dev.js index 716cfb2d07..579f356df3 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -15,7 +15,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!date-fns|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: { diff --git a/webpack.prod.js b/webpack.prod.js index eb39f82cd4..a3e4341173 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -8,7 +8,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!date-fns|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: { From f9a2e9ac38835ef32b3d0daada503092eb3f2212 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 27 May 2020 11:12:15 +0200 Subject: [PATCH 100/181] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2eae7e6933..73f40aaca1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -35,6 +35,7 @@ - [Thibault Nocchi](https://github.com/ThibaultNocchi) - [MrTimscampi](https://github.com/MrTimscampi) - [Sarab Singh](https://github.com/sarab97) + - [Andrei Oanca](https://github.com/OancaAndrei) # Emby Contributors From 696776f175bec4ef186ff4582d1cc503bc231de6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 27 May 2020 21:12:33 +0900 Subject: [PATCH 101/181] add code suggestions Co-authored-by: Julien Machiels --- src/components/bookPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 9b33ee5ab6..44153d4438 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -122,7 +122,7 @@ export class BookPlayer { tocElement.id = 'dialogToc'; let tocHtml = '
'; - tocHtml += ''; + tocHtml += ''; tocHtml += '
'; tocHtml += '
    '; rendition.book.navigation.forEach((chapter) => { From a66f40d805c8293f42e44382f3073ad78a500dbe Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 28 May 2020 02:33:10 +0900 Subject: [PATCH 102/181] invert backdrop default setting --- src/scripts/settings/userSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 819b7867ca..06c5fa40a6 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -134,7 +134,7 @@ import events from 'events'; } val = this.get('enableBackdrops', false); - return val === 'true'; + return val !== 'false'; } export function detailsBanner(val) { From 91de4da786f7ca17fd6eadf3c6c14775c436fe80 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 28 May 2020 02:34:45 +0900 Subject: [PATCH 103/181] update image fade animation strings --- src/strings/en-us.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index c9a8ff8909..e14678d2f6 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -824,8 +824,8 @@ "LabelSaveLocalMetadataHelp": "Saving artwork into media folders will put them in a place where they can be easily edited.", "LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.", "LabelScreensaver": "Screensaver:", - "EnableFastImageFadeIn": "Image Fade Animations", - "EnableFastImageFadeInHelp": "Show posters and other images with a fade animation once they load.", + "EnableFastImageFadeIn": "Fast Image Fade Animations", + "EnableFastImageFadeInHelp": "Show posters and other images with a quicker fade animation when they finish loading.", "LabelSeasonNumber": "Season number:", "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:", "LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.", From 417230a86d5d8b24bd131b2b9d7289aad7949f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 27 May 2020 12:33:48 +0000 Subject: [PATCH 104/181] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index d5527faa76..e0444c615a 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1601,5 +1601,35 @@ "LabelRequireHttps": "Vyžadovat HTTPS", "TabDVR": "Nahrávání", "HeaderDVR": "Nahrávání", - "SaveChanges": "Uložit změny" + "SaveChanges": "Uložit změny", + "LabelSyncPlayPlaybackDiff": "Rozdíl v době přehrávání:", + "SyncPlayAccessHelp": "Určuje úroveň přístupu k synchronizaci přehrávání, kterou tento uživatel bude mít. Tato funkce umožňuje synchronizovat přehrávání s dalšími uživateli.", + "MessageSyncPlayErrorMedia": "Zapnutí synchronizace přehrávání se nezdařilo. Chyba média.", + "MessageSyncPlayErrorMissingSession": "Zapnutí synchronizace přehrávání se nezdařilo. Nebyla nalezena relace.", + "MessageSyncPlayErrorNoActivePlayer": "Nebyl nalezen žádný aktivní přehrávač. Synchronizace přehrávání byla vypnuta.", + "MessageSyncPlayErrorAccessingGroups": "Při načítání seznamu skupin došlo k chybě.", + "MessageSyncPlayLibraryAccessDenied": "Přístup k tomuto obsahu je omezen.", + "MessageSyncPlayJoinGroupDenied": "K použití synchronizace přehrávání je vyžadováno povolení.", + "MessageSyncPlayCreateGroupDenied": "K vytvoření skupiny je vyžadováno povolení.", + "MessageSyncPlayGroupDoesNotExist": "Připojení ke skupině se nezdařilo, protože skupina neexistuje.", + "MessageSyncPlayPlaybackPermissionRequired": "K přehrávání je vyžadováno povolení.", + "MessageSyncPlayNoGroupsAvailable": "Neexistují žádné skupiny. Začněte něco přehrávat.", + "MessageSyncPlayGroupWait": "Přehrávání uživatele {0} se načítá…", + "MessageSyncPlayUserLeft": "Uživatel {0} opustil skupinu.", + "MessageSyncPlayUserJoined": "Uživatel {0} se připojil do skupiny.", + "MessageSyncPlayDisabled": "Synchronizace přehrávání zakázána.", + "MessageSyncPlayEnabled": "Synchronizace přehrávání povolena.", + "LabelSyncPlayAccess": "Přístup k funkci synchronizace přehrávání", + "LabelSyncPlayAccessNone": "Zakázáno pro tohoto uživatele", + "LabelSyncPlayAccessJoinGroups": "Povolit uživateli připojovat se do skupin", + "LabelSyncPlayAccessCreateAndJoinGroups": "Povolit uživateli vytvářet a připojovat se do skupin", + "LabelSyncPlayLeaveGroupDescription": "Zakázat synchronizaci přehrávání", + "LabelSyncPlayLeaveGroup": "Opustit skupinu", + "LabelSyncPlayNewGroupDescription": "Vytvořit skupinu", + "LabelSyncPlayNewGroup": "Nová skupina", + "LabelSyncPlaySyncMethod": "Způsob synchronizace:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Časový rozdíl mezi serverem:", + "HeaderSyncPlayEnabled": "Synchronizace přehrávání povolena", + "HeaderSyncPlaySelectGroup": "Připojit ke skupině" } From fb38fb6c4af246b99637709f1b5aa47452bb3ecf Mon Sep 17 00:00:00 2001 From: KGT1 Date: Wed, 27 May 2020 13:03:39 +0000 Subject: [PATCH 105/181] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 1ca5e91102..115e85f84d 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1539,5 +1539,35 @@ "LabelEnableHttps": "Aktiviere HTTPS", "HeaderServerAddressSettings": "Server-Adresseinstellungen", "HeaderRemoteAccessSettings": "Fernzugriffs-Einstellungen", - "HeaderHttpsSettings": "HTTPS-Einstellungen" + "HeaderHttpsSettings": "HTTPS-Einstellungen", + "SyncPlayAccessHelp": "Wähle die Berechtigungsstufe, die dieser Benutzer auf das SyncPlay-Feature hat. SyncPlay ermöglicht die Synchronisierung der Wiedergabe mit anderen Benutzern.", + "MessageSyncPlayErrorMedia": "SyncPlay konnte nicht aktiviert werden! Medienfehler.", + "MessageSyncPlayErrorMissingSession": "SyncPlay konnte nicht aktiviert werden! Fehlende Sitzung.", + "MessageSyncPlayErrorNoActivePlayer": "Keine aktive Wiedergabe gefunden. SyncPlay wurde deaktiviert.", + "MessageSyncPlayErrorAccessingGroups": "Beim Zugriff auf die Gruppen ist ein Fehler aufgetreten.", + "MessageSyncPlayLibraryAccessDenied": "Der Zugang zu diesem Inhalt ist beschränkt.", + "MessageSyncPlayJoinGroupDenied": "Eine Berechtigung ist erforderlich um SyncPlay zu benutzen.", + "MessageSyncPlayCreateGroupDenied": "Zum Erstellen einer Gruppe ist eine Genehmigung erforderlich.", + "MessageSyncPlayGroupDoesNotExist": "Konnte der Gruppe nicht beitreten, da sie nicht existiert.", + "MessageSyncPlayPlaybackPermissionRequired": "Wiedergabegenehmigung erforderlich.", + "MessageSyncPlayNoGroupsAvailable": "Keine Gruppen verfügbar. Fange an, etwas abzuspielen.", + "MessageSyncPlayGroupWait": "{0} ist am laden...", + "MessageSyncPlayUserLeft": "{0} hat die Gruppe verlassen.", + "MessageSyncPlayUserJoined": "{0} ist der Gruppe beigetreten.", + "MessageSyncPlayDisabled": "SyncPlay deaktiviert.", + "MessageSyncPlayEnabled": "SyncPlay aktiviert.", + "LabelSyncPlayAccess": "SyncPlay-Zugriff", + "LabelSyncPlayAccessNone": "Deaktiviert für diesen Benutzer", + "LabelSyncPlayAccessJoinGroups": "Erlaube dem Benutzer, Gruppen beizutreten", + "LabelSyncPlayAccessCreateAndJoinGroups": "Erlaube dem Benutzer, Gruppen zu erstellen und beizutreten", + "LabelSyncPlayLeaveGroupDescription": "Deaktiviere SyncPlay", + "LabelSyncPlayLeaveGroup": "Gruppe verlassen", + "LabelSyncPlayNewGroupDescription": "Erstelle eine neue Gruppe", + "LabelSyncPlayNewGroup": "Neue Gruppe", + "LabelSyncPlaySyncMethod": "Sync-Methode:", + "LabelSyncPlayPlaybackDiff": "Zeitversatz bei der Wiedergabe:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Zeitversatz mit dem Server:", + "HeaderSyncPlayEnabled": "SyncPlay aktiviert", + "HeaderSyncPlaySelectGroup": "Tritt einer Gruppe bei" } From 3c5fe3cf6acd4d4d99e2569d894c662010afd8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mur=C3=A1ncsik=20Sebesty=C3=A9n?= Date: Wed, 27 May 2020 12:24:53 +0000 Subject: [PATCH 106/181] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index 6bd8bc82cd..d4709b2600 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -1524,5 +1524,20 @@ "HeaderHttpsSettings": "HTTPS Beállítások", "TabDVR": "DVR", "HeaderDVR": "DVR", - "SaveChanges": "Változtatások mentése" + "SaveChanges": "Változtatások mentése", + "MessageSyncPlayGroupWait": "{0} bufferel...", + "MessageSyncPlayUserLeft": "{0} elhagyta a csoportot.", + "MessageSyncPlayUserJoined": "{0} csatlakozott a csoporthoz.", + "MessageSyncPlayDisabled": "SyncPlay letiltva.", + "MessageSyncPlayEnabled": "SyncPlay engedélyezve.", + "LabelSyncPlayAccess": "SyncPlay hozzáférés", + "LabelSyncPlayAccessCreateAndJoinGroups": "A felhasználó létrehozhat csoportokat és csatlakozhat hozzájuk", + "LabelSyncPlayLeaveGroupDescription": "SyncPlay letiltása", + "LabelSyncPlayLeaveGroup": "Csoport elhagyása", + "LabelSyncPlayNewGroupDescription": "Új csoport létrehozása", + "LabelSyncPlayNewGroup": "Új csoport", + "LabelSyncPlaySyncMethod": "Szinkronizálási mód:", + "MillisecondsUnit": "ms", + "HeaderSyncPlayEnabled": "SyncPlay engedélyezve", + "HeaderSyncPlaySelectGroup": "Csatlakozás csoporthoz" } From 46d0f2d0dfe84b874451412be4f116caed75f60a Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Wed, 27 May 2020 15:38:24 +0000 Subject: [PATCH 107/181] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index ff89d00307..d546b6671c 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -30,7 +30,7 @@ "AlwaysPlaySubtitlesHelp": "As legendas que combinarem com a preferência de idioma serão carregadas independente do idioma do áudio.", "AnyLanguage": "Qualquer idioma", "Anytime": "A qualquer momento", - "AroundTime": "Aproximadamente {0}", + "AroundTime": "Aproximadamente", "Art": "Arte", "Artists": "Artistas", "AsManyAsPossible": "Quantos forem possíveis", @@ -1515,10 +1515,40 @@ "LabelNightly": "Nightly", "LabelStable": "Estável", "LabelChromecastVersion": "Versão do Chromecast", - "LabelEnableHttpsHelp": "Habilita que o servidor escute na localização HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", + "LabelEnableHttpsHelp": "Habilita que o servidor escute na porta HTTPS configurada. Um certificado válido também deve ser configurado para que isso entre em vigor.", "LabelEnableHttps": "Habilitar HTTPS", "HeaderServerAddressSettings": "Configurações da localização do servidor", "HeaderRemoteAccessSettings": "Configurações de acesso remoto", "HeaderHttpsSettings": "Configurações HTTPS", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "LabelSyncPlayTimeOffset": "Diferença de tempo com o servidor:", + "SyncPlayAccessHelp": "Selecione o nível de acesso desse usuário aos recursos do SyncPlay. SyncPlay habilita a reprodução sincronizada com outros usuários.", + "MessageSyncPlayErrorMedia": "Falha ao ativar SyncPlay! Erro de mídia.", + "MessageSyncPlayErrorMissingSession": "Falha ao ativar SyncPlay! Sessão em falta.", + "MessageSyncPlayErrorNoActivePlayer": "Nenhum reprodutor ativo encontrado. SyncPlay foi desativado.", + "MessageSyncPlayErrorAccessingGroups": "Ocorreu um erro ao acessar a lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "O acesso a esse conteúdo é restrito.", + "MessageSyncPlayJoinGroupDenied": "Permissão necessária para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permissão necessária para criar um grupo.", + "MessageSyncPlayGroupDoesNotExist": "Falha ao participar de grupo pois o mesmo não existe.", + "MessageSyncPlayPlaybackPermissionRequired": "É necessária permissão de reprodução.", + "MessageSyncPlayNoGroupsAvailable": "Nenhum grupo disponível. Comece a reproduzir algo primeiro.", + "MessageSyncPlayGroupWait": "{0} está carregando...", + "MessageSyncPlayUserLeft": "{0} deixou o grupo.", + "MessageSyncPlayUserJoined": "{0} se juntou ao grupo.", + "MessageSyncPlayDisabled": "SyncPlay desativado.", + "MessageSyncPlayEnabled": "SyncPlay ativado.", + "LabelSyncPlayAccess": "Acesso ao SyncPlay", + "LabelSyncPlayAccessNone": "Desativado para esse usuário", + "LabelSyncPlayAccessJoinGroups": "Permitir que o usuário participe de grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir que o usuário crie e participe em grupos", + "LabelSyncPlayLeaveGroupDescription": "Desativar SyncPlay", + "LabelSyncPlayLeaveGroup": "Deixar grupo", + "LabelSyncPlayNewGroupDescription": "Criar novo grupo", + "LabelSyncPlayNewGroup": "Novo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronização:", + "LabelSyncPlayPlaybackDiff": "Diferença no tempo de reprodução:", + "MillisecondsUnit": "ms", + "HeaderSyncPlayEnabled": "SyncPlay ativado", + "HeaderSyncPlaySelectGroup": "Entrar em um grupo" } From ab854f412ab54cde2bb9e409fecc758bc7e59fb4 Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Wed, 27 May 2020 16:33:37 +0000 Subject: [PATCH 108/181] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 3d3b12f0d7..9e6305b183 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -1521,5 +1521,34 @@ "HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu", "HeaderHttpsSettings": "Nastavenia HTTPS", "HeaderDVR": "DVR", - "SaveChanges": "Uložiť zmeny" + "SaveChanges": "Uložiť zmeny", + "MessageSyncPlayErrorMedia": "Povolenie synchronizácie prehrávania zlyhalo! Chyba média.", + "MessageSyncPlayErrorMissingSession": "Zapnutie synchronizácie prehrávania zlyhalo! Aktívna relácia nebola nájdená.", + "MessageSyncPlayErrorNoActivePlayer": "Nebol nájdený žiadny aktívny prehrávač. Synchronizácia prehrávania bola vypnutá.", + "MessageSyncPlayErrorAccessingGroups": "Pri načítaní zoznamu skupín sa vyskytla chyba.", + "MessageSyncPlayLibraryAccessDenied": "Prístup k tomuto obsahuje je obmedzený.", + "MessageSyncPlayJoinGroupDenied": "K použitiu synchronizácie prehrávania je vyžadované povolenie.", + "MessageSyncPlayCreateGroupDenied": "K vytvoreniu skupiny je požadované povolenie.", + "MessageSyncPlayGroupDoesNotExist": "Pripojenie ku skupine zlyhalo, pretože skupina neexistuje.", + "MessageSyncPlayPlaybackPermissionRequired": "K prehrávaniu je potrebné povolenie.", + "MessageSyncPlayNoGroupsAvailable": "Nie je dostupná žiadna skupina. Skúste najskôr začať niečo prehrávať.", + "MessageSyncPlayGroupWait": "Prehrávanie používateľa {0} sa načítava...", + "MessageSyncPlayUserLeft": "Používateľ {0} opustil skupinu.", + "MessageSyncPlayUserJoined": "Používateľ {0} sa pripojil k skupine.", + "MessageSyncPlayDisabled": "Synchronizácia prehrávania zakázana.", + "MessageSyncPlayEnabled": "Synchronizácia prehrávania povolená.", + "LabelSyncPlayAccess": "Prístup k synchronizácií prehrávania", + "LabelSyncPlayAccessNone": "Zakázať pre tohoto používateľa", + "LabelSyncPlayAccessJoinGroups": "Povoliť použivateľovi pripájať sa do skupín", + "LabelSyncPlayAccessCreateAndJoinGroups": "Povoliť používateľovi vytvárať a pripájať sa do skupín", + "LabelSyncPlayLeaveGroupDescription": "Zakázať synchronizáciu prehrávania", + "LabelSyncPlayLeaveGroup": "Opustiť skupinu", + "LabelSyncPlayNewGroupDescription": "Vytvoriť novú skupinu", + "LabelSyncPlayNewGroup": "Nová skupina", + "LabelSyncPlaySyncMethod": "Spôsob synchronizácie:", + "LabelSyncPlayPlaybackDiff": "Rozdiel v dobe prehrávania:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Časový rozdiel so serverom:", + "HeaderSyncPlayEnabled": "Synchronizácia prehrávania je povolená", + "HeaderSyncPlaySelectGroup": "Pripojiť sa k skupine" } From 1f2a9d186569edfe0902a496d34aedd4750f5d73 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 28 May 2020 03:08:04 +0900 Subject: [PATCH 109/181] fix details banner string --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index e14678d2f6..9e57106df3 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -239,7 +239,7 @@ "EnableThemeSongsHelp": "Play theme songs in the background while browsing the library.", "EnableThemeVideos": "Theme videos", "EnableThemeVideosHelp": "Play theme videos in the background while browsing the library.", - "EnableDetailsBanner": "Enable Details Banner", + "EnableDetailsBanner": "Details Banner", "EnableDetailsBannerHelp": "Display a banner image at the top of the item details page.", "Ended": "Ended", "EndsAtValue": "Ends at {0}", From 89a3e5de095f998f1c247fe8422b864d4865b2e9 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Wed, 27 May 2020 20:34:26 +0200 Subject: [PATCH 110/181] Update apiclient --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ccea0f43c7..749c62d39c 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "hls.js": "^0.13.1", "howler": "^2.2.0", "intersection-observer": "^0.10.0", - "jellyfin-apiclient": "^1.1.2", + "jellyfin-apiclient": "^1.2.0", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", "jstree": "^3.3.7", diff --git a/yarn.lock b/yarn.lock index 3270990337..20fdef5de1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6539,10 +6539,10 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -jellyfin-apiclient@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.1.2.tgz#e9983f3c515d0f6fbf7d57b89b4801dd9f83d12c" - integrity sha512-pJ/X4oY6EycFeRuR2Ui41ukCB9jNfPHZLtciZlInYVtselZpEG/d6oqH91lp4wIIql4vXRWi2pMFciS+sHpTsA== +jellyfin-apiclient@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.2.0.tgz#a892985ccfcd9798fe67455ee39cd0869adb14d5" + integrity sha512-7l2dXpVU+nvDVYJA/RwJPzZy99RtP89iIooZdRZ9gGF4tSCQe1Gf/fNIcTPBdMjXDBhiEZc1wytz4iYR1y2E/Q== "jellyfin-noto@https://github.com/jellyfin/jellyfin-noto": version "1.0.3" From 5b647fc1261c54f8c1de4fe102fda17a6419a80a Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 19:50:23 +0000 Subject: [PATCH 111/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index add033a3e6..fd86ff2104 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1521,5 +1521,6 @@ "LabelRequireHttpsHelp": "Si activé, le serveur va automatiquement rediriger toutes les requêtes en HTTP vers HTTPS. Cette option n'a aucun effet si le serveur n'écoute pas HTTPS.", "LabelRequireHttps": "Nécessite HTTPS", "LabelNightly": "De nuit", - "LabelStable": "Stable" + "LabelStable": "Stable", + "EnableDetailsBanner": "Bannière des détails" } From e6e6a7deb8d3834a84309d88a207157d9ef96c51 Mon Sep 17 00:00:00 2001 From: Lumenol Date: Wed, 27 May 2020 19:50:41 +0000 Subject: [PATCH 112/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index fd86ff2104..2d941723bb 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1522,5 +1522,6 @@ "LabelRequireHttps": "Nécessite HTTPS", "LabelNightly": "De nuit", "LabelStable": "Stable", - "EnableDetailsBanner": "Bannière des détails" + "EnableDetailsBanner": "Bannière des détails", + "EnableDetailsBannerHelp": "Affichez une image de bannière en haut de la page de détails de l'article." } From f40a695f53ac72d88616b5de6de4887f3574a7b3 Mon Sep 17 00:00:00 2001 From: Lumenol Date: Wed, 27 May 2020 19:51:36 +0000 Subject: [PATCH 113/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 2d941723bb..6e36e486dc 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1523,5 +1523,6 @@ "LabelNightly": "De nuit", "LabelStable": "Stable", "EnableDetailsBanner": "Bannière des détails", - "EnableDetailsBannerHelp": "Affichez une image de bannière en haut de la page de détails de l'article." + "EnableDetailsBannerHelp": "Affichez une image de bannière en haut de la page de détails de l'article.", + "HeaderSyncPlaySelectGroup": "Rejoindre un groupe" } From 09e2518862c8b6d1fe907ec9dc27a40388942235 Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 19:52:09 +0000 Subject: [PATCH 114/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 6e36e486dc..be32320668 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -692,7 +692,7 @@ "LabelNumberOfGuideDays": "Nombre de jours de données du guide à télécharger :", "LabelNumberOfGuideDaysHelp": "Télécharger plus de journées du guide permet de programmer des enregistrements plus longtemps à l'avance et de visualiser plus de contenus, mais prendra également plus de temps. Automatique permettra une sélection automatique basée sur le nombre de chaînes.", "LabelOptionalNetworkPath": "(Optionnel) Dossier réseau partagé :", - "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner accès au chemin du dossier réseau peut autoriser les applications Jellyfin sur d'autres appareils à avoir accès à ses fichiers directement.", + "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner accès au chemin du dossier réseau peut autoriser les applications Jellyfin sur d'autres appareils à avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", "LabelOriginalAspectRatio": "Ratio d'aspect original :", "LabelOriginalTitle": "Titre original :", "LabelOverview": "Synopsis :", @@ -1458,7 +1458,7 @@ "MessageConfirmAppExit": "Voulez-vous quitter ?", "LabelVideoResolution": "Résolution vidéo :", "LabelStreamType": "Type de flux :", - "EnableFastImageFadeInHelp": "Activer un fondu plus rapide pour l'animation des images chargées", + "EnableFastImageFadeInHelp": "Activer un fondu plus rapide pour l'animation des images chargées.", "EnableFastImageFadeIn": "Fondu d'image rapide", "LabelPlayerDimensions": "Dimension du lecteur :", "LabelDroppedFrames": "Images perdues :", @@ -1524,5 +1524,15 @@ "LabelStable": "Stable", "EnableDetailsBanner": "Bannière des détails", "EnableDetailsBannerHelp": "Affichez une image de bannière en haut de la page de détails de l'article.", - "HeaderSyncPlaySelectGroup": "Rejoindre un groupe" + "HeaderSyncPlaySelectGroup": "Rejoindre un groupe", + "LabelSyncPlayAccessCreateAndJoinGroups": "Autoriser l'utilisateur à créer un ou rejoindre un groupe", + "LabelSyncPlayLeaveGroupDescription": "Désactiver SyncPlay", + "LabelSyncPlayLeaveGroup": "Quitter le groupe", + "LabelSyncPlayNewGroupDescription": "Créer un nouveau groupe", + "LabelSyncPlayNewGroup": "Nouveau groupe", + "LabelSyncPlaySyncMethod": "Méthode de synchronisation :", + "LabelSyncPlayPlaybackDiff": "Décalage de la lecture :", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Décalage de temps avec le serveur  :", + "HeaderSyncPlayEnabled": "SyncPlay activé" } From e1b441a7fe77460c019894019867d727f532456d Mon Sep 17 00:00:00 2001 From: Lumenol Date: Wed, 27 May 2020 19:54:31 +0000 Subject: [PATCH 115/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index be32320668..e9acdbf3bc 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1510,7 +1510,7 @@ "HeaderFavoritePlaylists": "Listes de lecture favorites", "TabDVR": "DVR", "LabelChromecastVersion": "Version de Chromecast", - "LabelEnableHttpsHelp": "Autorise le serveur à écouter les requêtes HTTPS configurées. Un certificat valide doit être configuré pour permettre ce mode de fonctionnement.", + "LabelEnableHttpsHelp": "Autorise le serveur à écouter les requêtes HTTPS sur le port configurée. Un certificat valide doit être configuré pour permettre ce mode de fonctionnement.", "LabelEnableHttps": "Activer HTTPS", "HeaderServerAddressSettings": "Paramètres adresses serveur", "HeaderRemoteAccessSettings": "Paramètres d'accès distant", From 06cc7077ac224409957d84e0f3fa684a5723b1e2 Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 19:57:07 +0000 Subject: [PATCH 116/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index e9acdbf3bc..d2a7f3b5ed 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1534,5 +1534,19 @@ "LabelSyncPlayPlaybackDiff": "Décalage de la lecture :", "MillisecondsUnit": "ms", "LabelSyncPlayTimeOffset": "Décalage de temps avec le serveur  :", - "HeaderSyncPlayEnabled": "SyncPlay activé" + "HeaderSyncPlayEnabled": "SyncPlay activé", + "MessageSyncPlayLibraryAccessDenied": "L'accès à ce contenu est restreint.", + "MessageSyncPlayJoinGroupDenied": "Permission requise pour utiliser SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permission requise pour créer un groupe.", + "MessageSyncPlayGroupDoesNotExist": "Impossible de rejoindre le groupe car il n'existe pas.", + "MessageSyncPlayPlaybackPermissionRequired": "Autorisation de lecture requise.", + "MessageSyncPlayNoGroupsAvailable": "Aucun groupe disponible. Commencez par lancer quelque chose.", + "MessageSyncPlayGroupWait": "{0} est en train de charger...", + "MessageSyncPlayUserLeft": "{0} a quitté le groupe.", + "MessageSyncPlayUserJoined": "{0} a rejoint le groupe.", + "MessageSyncPlayDisabled": "SyncPlay désactivé.", + "MessageSyncPlayEnabled": "SyncPlay activé.", + "LabelSyncPlayAccess": "Accès SyncPlay", + "LabelSyncPlayAccessNone": "Désactivé pour cet utilisateur", + "LabelSyncPlayAccessJoinGroups": "Autoriser l'utilisateur à rejoindre un groupe" } From 9d6c319cf43842dc974845aea1e7d87dee03fb5c Mon Sep 17 00:00:00 2001 From: Lumenol Date: Wed, 27 May 2020 19:57:16 +0000 Subject: [PATCH 117/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index d2a7f3b5ed..b44af7ffa6 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -692,7 +692,7 @@ "LabelNumberOfGuideDays": "Nombre de jours de données du guide à télécharger :", "LabelNumberOfGuideDaysHelp": "Télécharger plus de journées du guide permet de programmer des enregistrements plus longtemps à l'avance et de visualiser plus de contenus, mais prendra également plus de temps. Automatique permettra une sélection automatique basée sur le nombre de chaînes.", "LabelOptionalNetworkPath": "(Optionnel) Dossier réseau partagé :", - "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner accès au chemin du dossier réseau peut autoriser les applications Jellyfin sur d'autres appareils à avoir accès à ses fichiers directement. Par exemple, {0} ou {1}.", + "LabelOptionalNetworkPathHelp": "Si le dossier est partagé sur votre réseau, donner le chemin d'accès au dossier réseau peut permettre aux 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 :", "LabelOverview": "Synopsis :", From e192da22b8dab132d7c8840fc228416e4b50af43 Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 20:01:46 +0000 Subject: [PATCH 118/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index b44af7ffa6..66746b9bc5 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -31,7 +31,7 @@ "AlwaysPlaySubtitlesHelp": "Les sous-titres correspondant à la préférence linguistique seront chargés indépendamment de la langue de l'audio.", "AnyLanguage": "N'importe quel langage", "Anytime": "N'importe quand", - "AroundTime": "Aux environs de {0}", + "AroundTime": "Aux environs de", "Artists": "Artistes", "AsManyAsPossible": "Autant que possible", "Ascending": "Croissant", @@ -274,7 +274,7 @@ "HeaderAddUser": "Ajouter un utilisateur", "HeaderAdditionalParts": "Parties additionelles", "HeaderAdmin": "Administrateur", - "HeaderAlbumArtists": "Artistes de l'album", + "HeaderAlbumArtists": "Artistes", "HeaderAlert": "Alerte", "HeaderAllowMediaDeletionFrom": "Autoriser la suppression de médias à partir de", "HeaderApiKey": "Clé API", @@ -1448,7 +1448,7 @@ "FetchingData": "Récuperer des données suplémentaires", "CopyStreamURLSuccess": "URL copiée avec succès.", "CopyStreamURL": "Copier l'URL du flux", - "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple: http://example.com/<baseurl>", + "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple : http ://example.com/< ;baseurl> ;", "HeaderFavoritePeople": "Personnes préférées", "OptionRandom": "Aléatoire", "ButtonSplit": "Séparer", @@ -1548,5 +1548,10 @@ "MessageSyncPlayEnabled": "SyncPlay activé.", "LabelSyncPlayAccess": "Accès SyncPlay", "LabelSyncPlayAccessNone": "Désactivé pour cet utilisateur", - "LabelSyncPlayAccessJoinGroups": "Autoriser l'utilisateur à rejoindre un groupe" + "LabelSyncPlayAccessJoinGroups": "Autoriser l'utilisateur à rejoindre un groupe", + "SyncPlayAccessHelp": "Sélectionner le niveau d'accès de cet utilisateur pour la fonctionnalité SyncPlay. SyncPlay permet de synchroniser la lecture avec d'autres utilisateurs.", + "MessageSyncPlayErrorMedia": "Impossible d'activer SyncPlay ! Erreur média.", + "MessageSyncPlayErrorMissingSession": "Impossible d'activer SyncPlay ! Session manquante.", + "MessageSyncPlayErrorNoActivePlayer": "Aucun player actif trouvé. SyncPlay a été désactivé.", + "MessageSyncPlayErrorAccessingGroups": "Une erreur s'est produite pendant l'accès à la liste de groupes." } From 161a2c29e2379298909fd505bc1f0787a7b61317 Mon Sep 17 00:00:00 2001 From: Lumenol Date: Wed, 27 May 2020 20:04:48 +0000 Subject: [PATCH 119/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 66746b9bc5..67ae0dc672 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1438,8 +1438,8 @@ "LabelTranscodingProgress": "Progression du transcodage :", "LabelTranscodingFramerate": "Taux de rafraîchissement du transcodage :", "LabelPleaseRestart": "Les changements prendront effet après un rechargement manuel du client web.", - "LabelPlayMethod": "Méthode de lecture :", - "LabelPlayer": "Lecteur :", + "LabelPlayMethod": "Méthode de lecture:", + "LabelPlayer": "Lecteur:", "LabelBaseUrl": "URL de base :", "LabelAudioSampleRate": "Taux d’échantillonnage audio :", "LabelAudioCodec": "Codec audio :", @@ -1456,8 +1456,8 @@ "HeaderNavigation": "Navigation", "OptionForceRemoteSourceTranscoding": "Transcodage forcé pour sources de media à distance (ex: TV en direct)", "MessageConfirmAppExit": "Voulez-vous quitter ?", - "LabelVideoResolution": "Résolution vidéo :", - "LabelStreamType": "Type de flux :", + "LabelVideoResolution": "Résolution vidéo:", + "LabelStreamType": "Type de flux:", "EnableFastImageFadeInHelp": "Activer un fondu plus rapide pour l'animation des images chargées.", "EnableFastImageFadeIn": "Fondu d'image rapide", "LabelPlayerDimensions": "Dimension du lecteur :", From dba358735a7894abc451f3515280cf01519e6808 Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 20:12:43 +0000 Subject: [PATCH 120/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 67ae0dc672..66746b9bc5 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1438,8 +1438,8 @@ "LabelTranscodingProgress": "Progression du transcodage :", "LabelTranscodingFramerate": "Taux de rafraîchissement du transcodage :", "LabelPleaseRestart": "Les changements prendront effet après un rechargement manuel du client web.", - "LabelPlayMethod": "Méthode de lecture:", - "LabelPlayer": "Lecteur:", + "LabelPlayMethod": "Méthode de lecture :", + "LabelPlayer": "Lecteur :", "LabelBaseUrl": "URL de base :", "LabelAudioSampleRate": "Taux d’échantillonnage audio :", "LabelAudioCodec": "Codec audio :", @@ -1456,8 +1456,8 @@ "HeaderNavigation": "Navigation", "OptionForceRemoteSourceTranscoding": "Transcodage forcé pour sources de media à distance (ex: TV en direct)", "MessageConfirmAppExit": "Voulez-vous quitter ?", - "LabelVideoResolution": "Résolution vidéo:", - "LabelStreamType": "Type de flux:", + "LabelVideoResolution": "Résolution vidéo :", + "LabelStreamType": "Type de flux :", "EnableFastImageFadeInHelp": "Activer un fondu plus rapide pour l'animation des images chargées.", "EnableFastImageFadeIn": "Fondu d'image rapide", "LabelPlayerDimensions": "Dimension du lecteur :", From c471a01e609b783d3c1d4a45eccb29ff1d627e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 27 May 2020 19:02:54 +0000 Subject: [PATCH 121/181] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index e0444c615a..8b631caf5a 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1540,8 +1540,8 @@ "CopyStreamURLError": "Při kopírování URL došlo k chybě.", "LabelVideoResolution": "Rozlišení videa:", "LabelStreamType": "Typ streamu:", - "EnableFastImageFadeInHelp": "Povolte rychlejší animaci pro načtené obrázky", - "EnableFastImageFadeIn": "Rychlé zmizení obrazu", + "EnableFastImageFadeInHelp": "Zobrazí plakáty a další obrázky s rychlejší animací přechodu po dokončení načítání.", + "EnableFastImageFadeIn": "Rychlé animace přechodů obrazu", "LabelPlayerDimensions": "Zobrazené rozlišení:", "LabelDroppedFrames": "Vynechané snímky:", "LabelCorruptedFrames": "Poškozené snímky:", @@ -1631,5 +1631,7 @@ "MillisecondsUnit": "ms", "LabelSyncPlayTimeOffset": "Časový rozdíl mezi serverem:", "HeaderSyncPlayEnabled": "Synchronizace přehrávání povolena", - "HeaderSyncPlaySelectGroup": "Připojit ke skupině" + "HeaderSyncPlaySelectGroup": "Připojit ke skupině", + "EnableDetailsBannerHelp": "Zobrazí obrázek ve vrchní části detailu položky.", + "EnableDetailsBanner": "Obrázek detailu" } From 3b963493a088d991d339c27d409a70cc6feff773 Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 20:36:54 +0000 Subject: [PATCH 122/181] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index 66746b9bc5..01c8ae4394 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1448,7 +1448,7 @@ "FetchingData": "Récuperer des données suplémentaires", "CopyStreamURLSuccess": "URL copiée avec succès.", "CopyStreamURL": "Copier l'URL du flux", - "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple : http ://example.com/< ;baseurl> ;", + "LabelBaseUrlHelp": "Ajoute un sous-répertoire personnalisé à l'adresse URL du serveur. Par exemple : http://example.com/< ;baseurl> ;", "HeaderFavoritePeople": "Personnes préférées", "OptionRandom": "Aléatoire", "ButtonSplit": "Séparer", From 53fecd15bff9bcac98f3539dd1cc2f53f433168c Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 27 May 2020 18:10:21 +0000 Subject: [PATCH 123/181] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 64 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 0b67a184d1..23a563a8f0 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -547,7 +547,7 @@ "LabelEmbedAlbumArtDidl": "Insluiten van albumhoezen in Didl", "LabelEmbedAlbumArtDidlHelp": "Sommige apparaten prefereren deze methode voor het verkrijgen van albumhoezen. Anderen kunnen falen om af te spelen met deze optie ingeschakeld.", "LabelEnableAutomaticPortMap": "Schakel automatisch poort vertalen in", - "LabelEnableAutomaticPortMapHelp": "Poging om de publieke poort automatisch om te zetten naar een lokale poort via UPnP. Dit werkt niet op alle routers. De wijzigingen worden pas actief na een herstart van de server.", + "LabelEnableAutomaticPortMapHelp": "Publieke poort automatisch doorsturen naar een lokale poort via UPnP. Dit werkt niet op alle routers en netwerk configuraties. De wijzigingen worden pas actief na een herstart van de server.", "LabelEnableBlastAliveMessages": "Alive berichten zenden", "LabelEnableBlastAliveMessagesHelp": "Zet dit aan als de server niet betrouwbaar door andere UPnP-apparaten op uw netwerk wordt gedetecteerd.", "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients (seconden)", @@ -666,7 +666,7 @@ "LabelNumberOfGuideDays": "Aantal dagen van de gids om te downloaden:", "LabelNumberOfGuideDaysHelp": "Het downloaden van meer dagen van de gids gegevens biedt de mogelijkheid verder vooruit te plannen en een beter overzicht geven, maar het zal ook langer duren om te downloaden. Auto kiest op basis van het aantal kanalen.", "LabelOptionalNetworkPath": "(Optioneel) Gedeelde netwerkmap:", - "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen.", + "LabelOptionalNetworkPathHelp": "Als deze map wordt gedeeld op uw netwerk, kunnen middels het netwerkpad Jellyfin apps op andere apparaten rechtstreeks toegang tot mediabestanden krijgen. Bijvoorbeeld {0} or {1}.", "LabelOriginalAspectRatio": "Originele aspect ratio:", "LabelOriginalTitle": "Orginele titel:", "LabelOverview": "Overzicht:", @@ -980,7 +980,7 @@ "OptionMissingEpisode": "Ontbrekende Afleveringen", "OptionMonday": "Maandag", "OptionNameSort": "Naam", - "OptionNew": "Nieuw ...", + "OptionNew": "Nieuw…", "OptionNone": "Geen", "OptionOnAppStartup": "Op applicatie start", "OptionOnInterval": "Op interval", @@ -1406,14 +1406,14 @@ "LabelAudioCodec": "Audio codec:", "LabelAudioChannels": "Audio kanalen:", "LabelBitrate": "Bitrate:", - "LabelBaseUrlHelp": "Hier kunt u een eigen subdirectory toevoegen om de server te bereiken doormiddel van een meer unieke URL.", + "LabelBaseUrlHelp": "Voegt een aangepaste submap toe aan de server-URL. Bijvoorbeeld: http://example.com/<baseurl>", "LabelFolder": "Folder:", "LabelLineup": "Lineup:", "LabelPlayer": "Speler:", "LabelPlayMethod": "Afspeel methode:", "LabelPleaseRestart": "De wijzigingen zullen worden toegepast na het handmatig herladen van de web cliënt.", "LabelStatus": "Status:", - "LabelTagline": "Label lijn:", + "LabelTagline": "Label tekst:", "LabelTranscodingContainer": "Container:", "LabelTranscodePath": "Transcodeer pad:", "LabelTranscodes": "Transcoderen:", @@ -1471,8 +1471,8 @@ "Artist": "Artiest", "AllowFfmpegThrottlingHelp": "Wanneer een transcode of remux ver genoeg voorloopt op de huidige afspeelpositie, pauzeer het proces, zodat het minder middelen verbruikt. Dit is vooral handig wanneer u kijkt zonder vaak te zoeken. Schakel dit uit als u afspeelproblemen ondervindt.", "AllowFfmpegThrottling": "Throttle Transcodes", - "EnableFastImageFadeInHelp": "Schakel snellere vervagings-animatie in voor ingeladen afbeeldingen", - "EnableFastImageFadeIn": "Snelle afbeeldingsvervaging", + "EnableFastImageFadeInHelp": "Toon posters en andere afbeeldingen met een snellere fade-animatie wanneer ze klaar zijn met laden.", + "EnableFastImageFadeIn": "Fast Image Fade Animaties", "LabelPlayerDimensions": "Afspeellengte:", "LabelLibraryPageSizeHelp": "Kies het aantal artikelen dat wordt weergegeven op een bibliotheekpagina. Kies 0 om dit te verbergen.", "LabelLibraryPageSize": "Bibliotheekpagina grootte:", @@ -1514,5 +1514,53 @@ "SelectAdminUsername": "Selecteer een gebruikersnaam voor het beheerder account.", "HeaderFavoritePlaylists": "Favoriete afspeellijsten", "ButtonTogglePlaylist": "Afspeellijst", - "ButtonToggleContextMenu": "Meer" + "ButtonToggleContextMenu": "Meer", + "LabelRequireHttpsHelp": "Indien aangevinkt, zal de server alle verzoeken via HTTP automatisch omleiden naar HTTPS. Dit heeft geen effect als de server niet luistert op HTTPS.", + "EnableDetailsBanner": "Details Banner", + "MessageSyncPlayNoGroupsAvailable": "Geen groepen beschikbaar. Begin eerst iets te spelen.", + "EnableDetailsBannerHelp": "Toon een bannerafbeelding bovenaan de pagina met itemdetails.", + "TabDVR": "DVR", + "SyncPlayAccessHelp": "Selecteer het toegangsniveau dat deze gebruiker heeft tot de SyncPlay-functie. SyncPlay maakt het mogelijk om het afspelen met andere gebruikers te synchroniseren.", + "Filter": "Filter", + "New": "Nieuw", + "SaveChanges": "Wijzigingen opslaan", + "MessageSyncPlayErrorMedia": "Kan SyncPlay niet inschakelen! Media fout.", + "MessageSyncPlayErrorMissingSession": "Kan SyncPlay niet inschakelen! Ontbrekende sessie.", + "MessageSyncPlayErrorNoActivePlayer": "Geen actieve speler gevonden. SyncPlay is uitgeschakeld.", + "MessageSyncPlayErrorAccessingGroups": "Er is een fout opgetreden bij het openen van de groepslijst.", + "MessageSyncPlayLibraryAccessDenied": "Toegang tot deze inhoud is beperkt.", + "MessageSyncPlayJoinGroupDenied": "Toestemming vereist om SyncPlay te gebruiken.", + "MessageSyncPlayCreateGroupDenied": "Toestemming vereist om een groep te maken.", + "MessageSyncPlayGroupDoesNotExist": "Kan niet deelnemen aan de groep omdat deze niet bestaat.", + "MessageSyncPlayPlaybackPermissionRequired": "Afspeelrechten vereist.", + "MessageSyncPlayGroupWait": "{0} is aan het bufferen...", + "MessageSyncPlayUserLeft": "{0} i heeft de groep verlaten.", + "MessageSyncPlayUserJoined": "{0} is lid geworden van de groep.", + "MessageSyncPlayDisabled": "SyncPlay uitgeschakeld.", + "MessageSyncPlayEnabled": "SyncPlay ingeschakeld.", + "LabelSyncPlayAccess": "SyncPlay toegang", + "LabelSyncPlayAccessNone": "Uitgeschakeld voor deze gebruiker", + "LabelSyncPlayAccessJoinGroups": "Sta de gebruiker toe om groepen te maken", + "LabelSyncPlayAccessCreateAndJoinGroups": "Sta de gebruiker toe om groepen te maken en eraan deel te nemen", + "LabelSyncPlayLeaveGroupDescription": "SyncPlay uitschakelen", + "LabelSyncPlayLeaveGroup": "Groep verlaten", + "LabelSyncPlayNewGroupDescription": "Maak een nieuwe groep", + "LabelSyncPlayNewGroup": "Nieuwe groep", + "LabelSyncPlaySyncMethod": "Sync methode:", + "LabelSyncPlayPlaybackDiff": "Verschil in afspeeltijd:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tijd offset met de server:", + "LabelRequireHttps": "HTTPS verplichten", + "LabelNightly": "Nightly", + "LabelStable": "Stabiel", + "LabelChromecastVersion": "Chromecast versie", + "LabelEnableHttpsHelp": "Hiermee kan de server luisteren op de geconfigureerde HTTPS-poort. Hiervoor moet ook een geldig certificaat worden geconfigureerd.", + "LabelEnableHttps": "HTTPS inschakelen", + "HeaderSyncPlayEnabled": "SyncPlay ingeschakeld", + "HeaderSyncPlaySelectGroup": "Word lid van een groep", + "HeaderServerAddressSettings": "Server adres instellingen", + "HeaderRemoteAccessSettings": "Externe toegang instellingen", + "HeaderHttpsSettings": "HTTPS instellingen", + "HeaderDVR": "DVR", + "ApiKeysCaption": "Lijst met de momenteel ingeschakelde API-sleutels" } From bb27d4e0555f3b6e687e09a935793d419808486c Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Wed, 27 May 2020 18:40:35 +0000 Subject: [PATCH 124/181] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index d546b6671c..e1e8acfc31 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1121,7 +1121,7 @@ "RefreshMetadata": "Atualizar metadados", "RefreshQueued": "Atualização enfileirada.", "ReleaseDate": "Data de lançamento", - "RememberMe": "Lembre-me", + "RememberMe": "Lembrar-me", "RemoveFromCollection": "Remover da coletânea", "RemoveFromPlaylist": "Remover da lista de reprodução", "Repeat": "Repetir", @@ -1465,7 +1465,7 @@ "AskAdminToCreateLibrary": "Peça a um administrador para criar uma biblioteca.", "AllowFfmpegThrottling": "Transcodes do Acelerador", "PlaybackErrorNoCompatibleStream": "Este cliente não é compatível com a media e o servidor não está enviando um formato de mídia compatível.", - "EnableFastImageFadeInHelp": "Habilitar animações rápidas de aparecimento para imagens carregadas", + "EnableFastImageFadeInHelp": "Mostrar pôsteres e outras imagens com uma animação mais rápida ao terminar de carregar.", "LabelDroppedFrames": "Quadros caídos:", "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução.", "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações dos episódios incorporadas nos arquivos ao invés dos nomes", @@ -1550,5 +1550,7 @@ "LabelSyncPlayPlaybackDiff": "Diferença no tempo de reprodução:", "MillisecondsUnit": "ms", "HeaderSyncPlayEnabled": "SyncPlay ativado", - "HeaderSyncPlaySelectGroup": "Entrar em um grupo" + "HeaderSyncPlaySelectGroup": "Entrar em um grupo", + "EnableDetailsBanner": "Banner de detalhes", + "EnableDetailsBannerHelp": "Exibe um banner na parte superior da página de detalhes do item." } From c8197f1dfe6210e9442bb8877a665089474570b9 Mon Sep 17 00:00:00 2001 From: Gabriel Gil Pinto Date: Thu, 28 May 2020 03:00:18 +0000 Subject: [PATCH 125/181] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/strings/es.json b/src/strings/es.json index 8020c71006..d9d595431e 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1458,8 +1458,8 @@ "ButtonSplit": "Dividir", "HeaderNavigation": "Navegación", "MessageConfirmAppExit": "¿Quieres salir?", - "EnableFastImageFadeInHelp": "Las imágenes que hayan terminado de cargarse mostrarán una pequeña animación", - "EnableFastImageFadeIn": "Cargar las imágenes suavemente", + "EnableFastImageFadeInHelp": "Mostrar carteles y otras imágenes con difuminado rápido cuando termine la carga.", + "EnableFastImageFadeIn": "Difuminado rápido de imágenes", "CopyStreamURLError": "Ha habido un error copiando la dirección.", "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o un remux se adelanta lo suficiente desde la posición de reproducción actual, pause el proceso para que consuma menos recursos. Esto es más útil cuando se reproduce de forma linear, sin saltar de posición de reproducción a menudo. Desactívelo si experimenta problemas de reproducción.", "PlaybackErrorNoCompatibleStream": "Este contenido no es compatible con este dispositivo y no se puede reproducir: No se puede obtener del servidor en un formato compatible.", @@ -1524,5 +1524,37 @@ "LabelEnableHttps": "Activar HTTPS", "TabDVR": "DVR", "SaveChanges": "Guardar cambios", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso que posee este usuario al SyncPlay. SyncPlay permite sincronizar reproductores con otros usuarios.", + "MessageSyncPlayErrorMedia": "¡No se pudo activar SyncPlay! Error de medio.", + "MessageSyncPlayErrorMissingSession": "¡No se pudo activar SyncPlay! Sesión desconectada.", + "MessageSyncPlayErrorNoActivePlayer": "No hay reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Ocurrió un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "Acceso restringido a este contenido.", + "MessageSyncPlayJoinGroupDenied": "Requiere permiso para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Requiere permiso para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "No se pudo unir al grupo porque no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Requiere permiso para reproducir.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Reproduce algo primero.", + "MessageSyncPlayGroupWait": "{0} se está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay inactivo.", + "MessageSyncPlayEnabled": "SyncPlay activo.", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Inactivo para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir a usuarios unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir a usuarios crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Inhabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia del tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Huso horario de el servidor:", + "HeaderSyncPlayEnabled": "Syncplay activo", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "EnableDetailsBannerHelp": "Mostrar imagen de banner en el tope de la página de detalles del elemento.", + "EnableDetailsBanner": "Barra de Detalles" } From 2201d693e22d3b29299a7d05b94811bb27691510 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 28 May 2020 18:38:13 +1000 Subject: [PATCH 126/181] Make book player plugin exit gracefully --- src/components/bookPlayer/plugin.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 44153d4438..3893d7ada6 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -25,14 +25,22 @@ export class BookPlayer { stop() { let elem = this._mediaElement; + let tocElement = this._tocElement; let rendition = this._rendition; - if (elem && rendition) { - rendition.destroy(); - - elem.remove(); + if (elem) { + dialogHelper.close(elem); this._mediaElement = null; } + + if (tocElement) { + dialogHelper.close(tocElement); + this._tocElement = null; + } + + if (rendition) { + rendition.destroy(); + } } onWindowKeyUp(e) { From c7a89ae74be89ffea13e92a087d5959c7bd56cf2 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 28 May 2020 18:39:49 +1000 Subject: [PATCH 127/181] Make book player display an error on non-epub books --- src/components/bookPlayer/plugin.js | 23 +++++++++++++++++++++-- src/components/bookPlayer/style.css | 4 ++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 3893d7ada6..55b358306d 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -170,13 +170,32 @@ export class BookPlayer { } setCurrentSrc(elem, options) { - let serverId = options.items[0].ServerId; + let item = options.items[0]; + if (!item.Path.endsWith('.epub')) { + return new Promise((resolve, reject) => { + let errorDialog = dialogHelper.createDialog({ + size: 'small', + autoFocus: false, + removeOnClose: true + }); + + errorDialog.innerHTML = '

    This book type is not supported yet

    '; + + this.stop(); + + dialogHelper.open(errorDialog); + loading.hide(); + + return resolve(); + }); + } + let serverId = item.ServerId; let apiClient = connectionManager.getApiClient(serverId); const self = this; return new Promise(function (resolve, reject) { require(['epubjs'], function (epubjs) { - let downloadHref = apiClient.getItemDownloadUrl(options.items[0].Id); + let downloadHref = apiClient.getItemDownloadUrl(item.Id); let book = epubjs.default(downloadHref, {openAs: 'epub'}); let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); diff --git a/src/components/bookPlayer/style.css b/src/components/bookPlayer/style.css index 581438a003..e37b995f31 100644 --- a/src/components/bookPlayer/style.css +++ b/src/components/bookPlayer/style.css @@ -33,3 +33,7 @@ .toc li { margin-bottom: 5px; } + +.bookplayerErrorMsg { + text-align: center; +} From 063ef77ff1c3241f35ba38edae46e81f42a48709 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 28 May 2020 18:45:28 +1000 Subject: [PATCH 128/181] Refactor book player to make use of lambdas and get rid of `self` --- src/components/bookPlayer/plugin.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 55b358306d..6c111843fd 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -78,7 +78,7 @@ export class BookPlayer { links.forEach((link) => { let href = link.getAttribute('href'); - link.onclick = function () { + link.onclick = () => { f(href); return false; }; @@ -115,11 +115,11 @@ export class BookPlayer { elem.innerHTML = html; - elem.querySelector('.btnBookplayerExit').addEventListener('click', function () { + elem.querySelector('.btnBookplayerExit').addEventListener('click', () => { dialogHelper.close(elem); }); - elem.querySelector('.btnBookplayerToc').addEventListener('click', function () { + elem.querySelector('.btnBookplayerToc').addEventListener('click', () => { let rendition = this._rendition; if (rendition) { let tocElement = dialogHelper.createDialog({ @@ -143,7 +143,7 @@ export class BookPlayer { tocHtml += '
'; tocElement.innerHTML = tocHtml; - tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', function () { + tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', () => { dialogHelper.close(tocElement); }); @@ -157,7 +157,7 @@ export class BookPlayer { dialogHelper.open(tocElement); } - }.bind(this)); + }); dialogHelper.open(elem); @@ -192,24 +192,23 @@ export class BookPlayer { let serverId = item.ServerId; let apiClient = connectionManager.getApiClient(serverId); - const self = this; - return new Promise(function (resolve, reject) { - require(['epubjs'], function (epubjs) { + return new Promise((resolve, reject) => { + require(['epubjs'], (epubjs) => { let downloadHref = apiClient.getItemDownloadUrl(item.Id); let book = epubjs.default(downloadHref, {openAs: 'epub'}); let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); - self._currentSrc = downloadHref; - self._rendition = rendition; - return rendition.display().then(function () { - document.addEventListener('keyup', self.onWindowKeyUp.bind(self)); + this._currentSrc = downloadHref; + this._rendition = rendition; + return rendition.display().then(() => { + document.addEventListener('keyup', this.onWindowKeyUp.bind(this)); // FIXME: I don't really get why document keyup event is not triggered when epub is in focus - self._rendition.on('keyup', self.onWindowKeyUp.bind(self)); + this._rendition.on('keyup', this.onWindowKeyUp.bind(this)); loading.hide(); return resolve(); - }, function () { + }, () => { console.error('Failed to display epub'); return reject(); }); From 70ab8af6e76e412799e619f854c591a6b424878a Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 28 May 2020 18:32:53 +0900 Subject: [PATCH 129/181] update imports for photo player --- src/components/photoPlayer/plugin.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/photoPlayer/plugin.js b/src/components/photoPlayer/plugin.js index aa5464d77c..bdb0214da3 100644 --- a/src/components/photoPlayer/plugin.js +++ b/src/components/photoPlayer/plugin.js @@ -1,11 +1,3 @@ -import browser from 'browser'; -import require from 'require'; -import events from 'events'; -import appHost from 'apphost'; -import loading from 'loading'; -import dom from 'dom'; -import playbackManager from 'playbackManager'; -import appRouter from 'appRouter'; import connectionManager from 'connectionManager'; export class PhotoPlayer { @@ -20,7 +12,7 @@ export class PhotoPlayer { return new Promise(function (resolve, reject) { - require(['slideshow'], function (slideshow) { + import('slideshow').then(slideshow => { var index = options.startIndex || 0; From a711c8e9f7a6693cf86a8579e828618a03a4ce4c Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 28 May 2020 21:14:25 +1000 Subject: [PATCH 130/181] Refactor table of contents into its own class --- package.json | 1 + src/components/bookPlayer/plugin.js | 111 +++++++++----------- src/components/bookPlayer/tableOfContent.js | 90 ++++++++++++++++ 3 files changed, 140 insertions(+), 62 deletions(-) create mode 100644 src/components/bookPlayer/tableOfContent.js diff --git a/package.json b/package.json index 40e59e9c98..fdb2d7f15a 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/components/bookPlayer/plugin.js", + "src/components/bookPlayer/tableOfContent.js", "src/components/syncplay/playbackPermissionManager.js", "src/components/syncplay/groupSelectionMenu.js", "src/components/syncplay/timeSyncManager.js", diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 6c111843fd..9e997efdef 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -9,12 +9,18 @@ import 'css!./style'; import 'material-icons'; import 'paper-icon-button-light'; +import TableOfContent from './tableOfContent'; + export class BookPlayer { constructor() { this.name = 'Book Player'; this.type = 'mediaplayer'; this.id = 'bookplayer'; this.priority = 1; + + this.onDialogClosed = this.onDialogClosed.bind(this); + this.openTableOfContents = this.openTableOfContents.bind(this); + this.onWindowKeyUp = this.onWindowKeyUp.bind(this); } play(options) { @@ -24,6 +30,8 @@ export class BookPlayer { } stop() { + this.unbindEvents(); + let elem = this._mediaElement; let tocElement = this._tocElement; let rendition = this._rendition; @@ -34,7 +42,7 @@ export class BookPlayer { } if (tocElement) { - dialogHelper.close(tocElement); + tocElement.destroy(); this._tocElement = null; } @@ -60,9 +68,12 @@ export class BookPlayer { book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); break; case 'Escape': - dialogHelper.close(this._mediaElement); if (this._tocElement) { - dialogHelper.close(this._tocElement); + // Close table of contents on ESC if it is open + this._tocElement.destroy(); + } else { + // Otherwise stop the entire book player + this.stop(); } break; } @@ -72,17 +83,42 @@ export class BookPlayer { this.stop(); } - replaceLinks(contents, f) { - let links = contents.querySelectorAll('a[href]'); + bindMediaElementEvents() { + let elem = this._mediaElement; - links.forEach((link) => { - let href = link.getAttribute('href'); + elem.addEventListener('close', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerToc').addEventListener('click', this.openTableOfContents); + } - link.onclick = () => { - f(href); - return false; - }; - }); + bindEvents() { + this.bindMediaElementEvents(); + + document.addEventListener('keyup', this.onWindowKeyUp); + // FIXME: I don't really get why document keyup event is not triggered when epub is in focus + this._rendition.on('keyup', this.onWindowKeyUp); + } + + unbindMediaElementEvents() { + let elem = this._mediaElement; + + elem.removeEventListener('close', this.onDialogClosed); + elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed); + elem.querySelector('.btnBookplayerToc').removeEventListener('click', this.openTableOfContents); + } + + unbindEvents() { + if (this._mediaElement) { + this.unbindMediaElementEvents(); + } + document.removeEventListener('keyup', this.onWindowKeyUp); + if (this._rendition) { + this._rendition.off('keyup', this.onWindowKeyUp); + } + } + + openTableOfContents() { + this._tocElement = new TableOfContent(this); } createMediaElement() { @@ -115,53 +151,7 @@ export class BookPlayer { elem.innerHTML = html; - elem.querySelector('.btnBookplayerExit').addEventListener('click', () => { - dialogHelper.close(elem); - }); - - elem.querySelector('.btnBookplayerToc').addEventListener('click', () => { - let rendition = this._rendition; - if (rendition) { - let tocElement = dialogHelper.createDialog({ - size: 'small', - autoFocus: false, - removeOnClose: true - }); - tocElement.id = 'dialogToc'; - - let tocHtml = '
'; - tocHtml += ''; - tocHtml += '
'; - tocHtml += '
    '; - rendition.book.navigation.forEach((chapter) => { - tocHtml += '
  • '; - // Remove '../' from href - let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; - tocHtml += `${chapter.label}`; - tocHtml += '
  • '; - }); - tocHtml += '
'; - tocElement.innerHTML = tocHtml; - - tocElement.querySelector('.btnBookplayerTocClose').addEventListener('click', () => { - dialogHelper.close(tocElement); - }); - - this.replaceLinks(tocElement, (href) => { - let relative = rendition.book.path.relative(href); - rendition.display(relative); - dialogHelper.close(tocElement); - }); - - this._tocElement = tocElement; - - dialogHelper.open(tocElement); - } - }); - dialogHelper.open(elem); - - elem.addEventListener('close', this.onDialogClosed.bind(this)); } this._mediaElement = elem; @@ -201,10 +191,7 @@ export class BookPlayer { this._currentSrc = downloadHref; this._rendition = rendition; return rendition.display().then(() => { - document.addEventListener('keyup', this.onWindowKeyUp.bind(this)); - - // FIXME: I don't really get why document keyup event is not triggered when epub is in focus - this._rendition.on('keyup', this.onWindowKeyUp.bind(this)); + this.bindEvents(); loading.hide(); return resolve(); diff --git a/src/components/bookPlayer/tableOfContent.js b/src/components/bookPlayer/tableOfContent.js new file mode 100644 index 0000000000..6a35966b1b --- /dev/null +++ b/src/components/bookPlayer/tableOfContent.js @@ -0,0 +1,90 @@ +import dialogHelper from 'dialogHelper'; + +export default class TableOfContent { + constructor(bookPlayer) { + this._bookPlayer = bookPlayer; + this._rendition = bookPlayer._rendition; + + this.onDialogClosed = this.onDialogClosed.bind(this); + + this.createMediaElement(); + } + + destroy() { + let elem = this._elem; + if (elem) { + this.unbindEvents(); + dialogHelper.close(elem); + } + + this._bookPlayer._tocElement = null; + } + + bindEvents() { + let elem = this._elem; + + elem.addEventListener('close', this.onDialogClosed, {once: true}); + elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); + } + + unbindEvents() { + let elem = this._elem; + + elem.removeEventListener('close', this.onDialogClosed); + elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); + } + + onDialogClosed() { + this.destroy(); + } + + replaceLinks(contents, f) { + let links = contents.querySelectorAll('a[href]'); + + links.forEach((link) => { + let href = link.getAttribute('href'); + + link.onclick = () => { + f(href); + return false; + }; + }); + } + + createMediaElement() { + let rendition = this._rendition; + + let elem = dialogHelper.createDialog({ + size: 'small', + autoFocus: false, + removeOnClose: true + }); + elem.id = 'dialogToc'; + + let tocHtml = '
'; + tocHtml += ''; + tocHtml += '
'; + tocHtml += '
    '; + rendition.book.navigation.forEach((chapter) => { + tocHtml += '
  • '; + // Remove '../' from href + let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + tocHtml += `${chapter.label}`; + tocHtml += '
  • '; + }); + tocHtml += '
'; + elem.innerHTML = tocHtml; + + this.replaceLinks(elem, (href) => { + let relative = rendition.book.path.relative(href); + rendition.display(relative); + this.destroy(); + }); + + this._elem = elem; + + this.bindEvents(); + + dialogHelper.open(elem); + } +} From 8d51ae6fb48d78d2e4bbb1359767e8bb7cfca69d Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Thu, 28 May 2020 21:19:19 +1000 Subject: [PATCH 131/181] Remove unused imports from book player --- src/components/bookPlayer/plugin.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 9e997efdef..302ed6bd18 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -1,10 +1,7 @@ import connectionManager from 'connectionManager'; -import dom from 'dom'; import loading from 'loading'; -import playbackManager from 'playbackManager'; import keyboardnavigation from 'keyboardnavigation'; import dialogHelper from 'dialogHelper'; -import appHost from 'apphost'; import 'css!./style'; import 'material-icons'; import 'paper-icon-button-light'; From 3312aa09e8f03c1fd601c6194d5c0b70042b733c Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 28 May 2020 23:27:37 +0200 Subject: [PATCH 132/181] Use babel-parser and add class property support --- .eslintrc.js | 1 + package.json | 7 ++- yarn.lock | 174 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 173 insertions(+), 9 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8b9a7dea13..fa1d440d46 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,6 +50,7 @@ module.exports = { files: [ './src/**/*.js' ], + parser: "babel-eslint", env: { node: false, amd: true, diff --git a/package.json b/package.json index 749c62d39c..463847089f 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,13 @@ "license": "GPL-2.0-or-later", "devDependencies": { "@babel/core": "^7.9.6", + "@babel/plugin-proposal-class-properties": "^7.10.1", + "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.9.6", "@babel/polyfill": "^7.8.7", "@babel/preset-env": "^7.8.6", "autoprefixer": "^9.8.0", + "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", "browser-sync": "^2.26.7", "clean-webpack-plugin": "^3.0.0", @@ -116,7 +119,9 @@ "src/scripts/settings/webSettings.js" ], "plugins": [ - "@babel/plugin-transform-modules-amd" + "@babel/plugin-transform-modules-amd", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-private-methods" ] } ] diff --git a/yarn.lock b/yarn.lock index 20fdef5de1..5eefeae72e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@babel/highlight" "^7.8.3" +"@babel/code-frame@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" + integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== + dependencies: + "@babel/highlight" "^7.10.1" + "@babel/compat-data@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" @@ -40,6 +47,16 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.1.tgz#4d14458e539bcb04ffe34124143f5c489f2dbca9" + integrity sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA== + dependencies: + "@babel/types" "^7.10.1" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" @@ -76,6 +93,18 @@ levenary "^1.1.1" semver "^5.5.0" +"@babel/helper-create-class-features-plugin@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.1.tgz#6d8a45aafe492378d0e6fc0b33e5dea132eae21c" + integrity sha512-bwhdehBJZt84HuPUcP1HaTLuc/EywVS8rc3FgsEPDcivg+DCW+SHuLHVkYOmcBA1ZfI+Z/oZjQc/+bPmIO7uAA== + dependencies: + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" @@ -102,6 +131,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-function-name@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4" + integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" + "@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" @@ -111,6 +149,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.9.5" +"@babel/helper-get-function-arity@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d" + integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw== + dependencies: + "@babel/types" "^7.10.1" + "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" @@ -125,6 +170,13 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-member-expression-to-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15" + integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g== + dependencies: + "@babel/types" "^7.10.1" + "@babel/helper-member-expression-to-functions@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" @@ -152,6 +204,13 @@ "@babel/types" "^7.9.0" lodash "^4.17.13" +"@babel/helper-optimise-call-expression@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543" + integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg== + dependencies: + "@babel/types" "^7.10.1" + "@babel/helper-optimise-call-expression@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" @@ -164,6 +223,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== +"@babel/helper-plugin-utils@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" + integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== + "@babel/helper-regex@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" @@ -182,6 +246,16 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-replace-supers@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" + integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + "@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" @@ -200,6 +274,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-split-export-declaration@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" + integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== + dependencies: + "@babel/types" "^7.10.1" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -207,6 +288,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" + integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== + "@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" @@ -231,6 +317,15 @@ "@babel/traverse" "^7.9.6" "@babel/types" "^7.9.6" +"@babel/highlight@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" + integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== + dependencies: + "@babel/helper-validator-identifier" "^7.10.1" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/highlight@^7.8.3": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" @@ -240,6 +335,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.1.tgz#2e142c27ca58aa2c7b119d09269b702c8bbad28c" + integrity sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg== + "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" @@ -254,6 +354,14 @@ "@babel/helper-remap-async-to-generator" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" +"@babel/plugin-proposal-class-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01" + integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-proposal-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" @@ -311,6 +419,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" +"@babel/plugin-proposal-private-methods@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598" + integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": version "7.8.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" @@ -718,6 +834,15 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" + integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" + "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -727,6 +852,21 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/traverse@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" + integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.1" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" @@ -742,6 +882,15 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/types@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921" + integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.1" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" @@ -1592,6 +1741,15 @@ axios@0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +babel-eslint@^11.0.0-beta.2: + version "11.0.0-beta.2" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-11.0.0-beta.2.tgz#de1f06795aa0d8cedcf6ac943e63056f5b4a7048" + integrity sha512-D2tunrOu04XloEdU2XVUminUu25FILlGruZmffqH5OSnLDhCheKNvUoM1ihrexdUvhizlix8bjqRnsss4V/UIQ== + dependencies: + eslint-scope "5.0.0" + eslint-visitor-keys "^1.1.0" + semver "^6.3.0" + babel-loader@^8.0.6: version "8.1.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" @@ -3983,18 +4141,18 @@ eslint-plugin-promise@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== +eslint-scope@5.0.0, eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" - integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" From 487bcfbd77a31f1bec88bc474da40c1f23975904 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 28 May 2020 23:34:05 +0200 Subject: [PATCH 133/181] Remove unused dependencies and cleanup webpack output --- package.json | 4 - webpack.common.js | 1 + yarn.lock | 1042 +++------------------------------------------ 3 files changed, 69 insertions(+), 978 deletions(-) diff --git a/package.json b/package.json index 463847089f..e0c01b4449 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", "browser-sync": "^2.26.7", - "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^5.1.1", "css-loader": "^3.4.2", "cssnano": "^4.1.10", @@ -50,9 +49,6 @@ "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.0.0", "webpack": "^4.41.5", - "webpack-cli": "^3.3.10", - "webpack-concat-plugin": "^3.0.0", - "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2", "webpack-stream": "^5.2.1" }, diff --git a/webpack.common.js b/webpack.common.js index 03beb63a73..d870b1046e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -16,6 +16,7 @@ const Assets = [ module.exports = { context: path.resolve(__dirname, 'src'), entry: './bundle.js', + stats: 'errors-only', resolve: { modules: [ path.resolve(__dirname, 'node_modules') diff --git a/yarn.lock b/yarn.lock index 5eefeae72e..510c523634 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,7 +1084,7 @@ "@types/source-list-map" "*" source-map "^0.6.1" -"@types/webpack@^4.4.31", "@types/webpack@^4.41.8": +"@types/webpack@^4.41.8": version "4.41.12" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.12.tgz#0386ee2a2814368e2f2397abb036c0bf173ff6c3" integrity sha512-BpCtM4NnBen6W+KEhrL9jKuZCXVtiH6+0b6cxdvNt2EwU949Al334PjQSl2BeAyvAX9mgoNNG21wvjP3xZJJ5w== @@ -1256,7 +1256,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@~1.3.4: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -1363,11 +1363,6 @@ ansi-gray@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -1514,16 +1509,6 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - array-includes@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" @@ -1693,13 +1678,6 @@ async@1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1945,34 +1923,6 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" - boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -2192,11 +2142,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2224,11 +2169,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2429,15 +2369,6 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2449,6 +2380,15 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -2555,14 +2495,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -clean-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz#a99d8ec34c1c628a4541567aa7b457446460c62b" - integrity sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A== - dependencies: - "@types/webpack" "^4.4.31" - del "^4.1.1" - cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2752,11 +2684,6 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== - commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -2794,26 +2721,6 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2829,7 +2736,7 @@ concat-stream@^1.5.0, concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-with-sourcemaps@^1.0.0, concat-with-sourcemaps@^1.0.5: +concat-with-sourcemaps@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== @@ -2844,7 +2751,7 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" -connect-history-api-fallback@^1, connect-history-api-fallback@^1.6.0: +connect-history-api-fallback@^1: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== @@ -2884,18 +2791,13 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= -content-disposition@0.5.3, content-disposition@^0.5.2: +content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - convert-source-map@1.X, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -2903,21 +2805,11 @@ convert-source-map@1.X, convert-source-map@^1.5.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -3036,17 +2928,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -3064,6 +2945,17 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3339,7 +3231,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.X, debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: +debug@3.X: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3438,18 +3330,6 @@ decompress@^4.0.0, decompress@^4.2.0: pify "^2.3.0" strip-dirs "^2.0.0" -deep-equal@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3462,14 +3342,6 @@ default-compare@^1.0.0: dependencies: kind-of "^5.0.2" -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== - dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" - default-resolution@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" @@ -3504,19 +3376,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - del@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" @@ -3569,11 +3428,6 @@ detect-newline@2.X: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - dev-ip@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0" @@ -3588,14 +3442,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" - integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== - dependencies: - arrify "^1.0.1" - path-type "^3.0.0" - dir-glob@^2.0.0, dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -3610,26 +3456,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - dependencies: - buffer-indexof "^1.0.0" - doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -3867,11 +3693,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3957,15 +3778,6 @@ engine.io@~3.2.0: engine.io-parser "~2.1.0" ws "~3.3.1" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - enhanced-resolve@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" @@ -4278,23 +4090,11 @@ eventemitter3@3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== -eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== - events@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" - evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -4381,42 +4181,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -4507,7 +4271,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== -fast-glob@^2.0.2, fast-glob@^2.2.6: +fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== @@ -4553,20 +4317,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== - dependencies: - websocket-driver ">=0.5.1" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -4700,19 +4450,6 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -4759,16 +4496,6 @@ find-versions@^3.0.0: dependencies: semver-regex "^2.0.0" -findup-sync@3.0.0, findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -4779,6 +4506,16 @@ findup-sync@^2.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + fined@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" @@ -4837,13 +4574,6 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" - integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA== - dependencies: - debug "^3.0.0" - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4875,11 +4605,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5137,13 +4862,6 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@2.0.0, global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -5153,6 +4871,13 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -5211,17 +4936,6 @@ globby@^11.0.0: merge2 "^1.3.0" slash "^3.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globby@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" @@ -5234,19 +4948,6 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globby@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" - integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== - dependencies: - array-union "^1.0.1" - dir-glob "2.0.0" - fast-glob "^2.0.2" - glob "^7.1.2" - ignore "^3.3.5" - pify "^3.0.0" - slash "^1.0.0" - globby@^9.0.0: version "9.2.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" @@ -5561,11 +5262,6 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -5751,16 +5447,6 @@ howler@^2.2.0: resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.0.tgz#0e2c780997ae65ab9a1a186333031beac1c63c6b" integrity sha512-sGPkrAQy7jh5mNDbkRNG0F82R2HFDYNsQXBcX4smXQT0y0F4UMsa/+jXaGwWvcrajWr2tDB7JUkH7G5qSnuIyQ== -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -5776,11 +5462,6 @@ html-comment-regex@^1.1.0, html-comment-regex@^1.1.2: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== - html-minifier-terser@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#95d3df037f04835e9d1a09d1767c0e361a7de916" @@ -5849,23 +5530,7 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@1.7.3, http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -5886,21 +5551,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= - -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - http-proxy@1.15.2: version "1.15.2" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.15.2.tgz#642fdcaffe52d3448d2bda3b0079e9409064da31" @@ -5909,15 +5559,6 @@ http-proxy@1.15.2: eventemitter3 "1.x.x" requires-port "1.x.x" -http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -6064,14 +5705,6 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -import-local@2.0.0, import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -6161,15 +5794,7 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" - -interpret@1.2.0, interpret@^1.1.0: +interpret@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== @@ -6199,26 +5824,6 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - irregular-plurals@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" @@ -6234,11 +5839,6 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" @@ -6279,11 +5879,6 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6489,25 +6084,11 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= -is-path-cwd@^2.0.0, is-path-cwd@^2.2.0: +is-path-cwd@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" - is-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" @@ -6540,7 +6121,7 @@ is-promise@^2.1: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.4, is-regex@^1.0.5: +is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -6774,11 +6355,6 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -6834,11 +6410,6 @@ keyv@3.0.0: dependencies: json-buffer "3.0.0" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -6902,13 +6473,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - lead@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" @@ -7010,15 +6574,6 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -7243,11 +6798,6 @@ logalot@^2.0.0, logalot@^2.1.0: figures "^1.3.5" squeak "^1.0.0" -loglevel@^1.6.8: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== - longest-streak@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" @@ -7356,13 +6906,6 @@ make-iterator@^1.0.0: dependencies: kind-of "^6.0.2" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -7467,20 +7010,6 @@ mdn-data@2.0.6: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memoizee@0.4.X: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -7495,7 +7024,7 @@ memoizee@0.4.X: next-tick "1" timers-ext "^0.1.5" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -7561,11 +7090,6 @@ meow@^7.0.1: type-fest "^0.13.1" yargs-parser "^18.1.3" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -7576,11 +7100,6 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -7616,7 +7135,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: +mime-db@1.44.0, mime-db@^1.28.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== @@ -7633,17 +7152,7 @@ mime@1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.4: - version "2.4.5" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.5.tgz#d8de2ecb92982dedbb6541c9b6841d7f218ea009" - integrity sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w== - -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -7763,29 +7272,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" - multipipe@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" @@ -7875,11 +7366,6 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - node-gyp@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" @@ -8114,14 +7600,6 @@ object-inspect@^1.7.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== -object-is@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -8200,11 +7678,6 @@ object.values@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -8212,11 +7685,6 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -8243,13 +7711,6 @@ opn@5.3.0: dependencies: is-wsl "^1.1.0" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -8278,13 +7739,6 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -8309,15 +7763,6 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-locale@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -8341,11 +7786,6 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-event@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" @@ -8370,11 +7810,6 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -8417,11 +7852,6 @@ p-map-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - p-map@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" @@ -8439,13 +7869,6 @@ p-reduce@^1.0.0: resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" @@ -8607,7 +8030,7 @@ parseuri@0.0.5: dependencies: better-assert "~1.0.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.2: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -8657,11 +8080,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -8684,11 +8102,6 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - path-to-regexp@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.2.1.tgz#b33705c140234d873c8721c7b9fd8b541ed3aff9" @@ -8822,15 +8235,6 @@ plur@^3.0.1: dependencies: irregular-plurals "^2.0.0" -portfinder@^1.0.26: - version "1.0.26" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" - integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.1" - portscanner@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.1.1.tgz#eabb409e4de24950f5a2a516d35ae769343fbb96" @@ -9656,14 +9060,6 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.9.1" - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -9741,11 +9137,6 @@ qs@6.2.3: resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" integrity sha1-HPyyXBCpsrSDBT/zn138kjOQjP4= -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -9779,11 +9170,6 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -9809,21 +9195,11 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.0, range-parser@~1.2.1: +range-parser@~1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@^2.3.2: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -9924,7 +9300,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -10016,14 +9392,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -10274,7 +9642,7 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@1.x.x, requires-port@^1.0.0: +requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= @@ -10284,13 +9652,6 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -10361,11 +9722,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -10451,7 +9807,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -10493,14 +9849,6 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -schema-utils@^0.4.5: - version "0.4.7" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" - integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== - dependencies: - ajv "^6.1.0" - ajv-keywords "^3.1.0" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -10538,18 +9886,6 @@ seek-bzip@^1.0.5: dependencies: commander "~2.8.1" -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== - dependencies: - node-forge "0.9.0" - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -10608,31 +9944,12 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== -serve-index@1.9.1, serve-index@^1.9.1: +serve-index@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= @@ -10655,16 +9972,6 @@ serve-static@1.13.2: parseurl "~1.3.2" send "0.16.2" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - server-destroy@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" @@ -10875,27 +10182,6 @@ socket.io@2.1.1: socket.io-client "2.1.1" socket.io-parser "~3.2.0" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== - dependencies: - debug "^3.2.5" - eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" - -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== - dependencies: - faye-websocket "^0.10.0" - uuid "^3.4.0" - websocket-driver "0.6.5" - sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -10999,29 +10285,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - specificity@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" @@ -11108,7 +10371,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -11584,13 +10847,6 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -supports-color@6.1.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -11610,6 +10866,13 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -11794,11 +11057,6 @@ through@^2.3.6, through@^2.3.8, through@~2.3.4: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -12009,14 +11267,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" @@ -12044,14 +11294,6 @@ ua-parser-js@0.7.17: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" integrity sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g== -uglify-es@^3.3.9: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" - uglify-js@3.4.x: version "3.4.10" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" @@ -12309,7 +11551,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.0, upath@^1.1.1: +upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== @@ -12345,14 +11587,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" @@ -12423,16 +11657,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.1, uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== - v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -12458,11 +11687,6 @@ value-or-function@^3.0.0: resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" @@ -12609,96 +11833,11 @@ watchpack@^1.6.1: graceful-fs "^4.1.2" neo-async "^2.5.0" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - webcomponents.js@^0.7.24: version "0.7.24" resolved "https://registry.yarnpkg.com/webcomponents.js/-/webcomponents.js-0.7.24.tgz#2116fbfa1468ec416a7befdaa333e1d118f69c04" integrity sha1-IRb7+hRo7EFqe+/aozPh0Rj2nAQ= -webpack-cli@^3.3.10: - version "3.3.11" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" - integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== - dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" - -webpack-concat-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/webpack-concat-plugin/-/webpack-concat-plugin-3.0.0.tgz#db34ae230794b634061bc2944053ed407619f138" - integrity sha512-DLdDbZXyrFR99wyAVC9P06HLjr2XujBmQdSbnQMK2o01H9U2NHsN5W76jeTVeXDq5OLvZf8r/se65ftRo3Prow== - dependencies: - concat-with-sourcemaps "^1.0.5" - globby "^8.0.1" - schema-utils "^0.4.5" - uglify-es "^3.3.9" - upath "^1.1.0" - -webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" - -webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== - dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.7" - semver "^6.3.0" - serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" - spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - webpack-log@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" @@ -12766,27 +11905,6 @@ webpack@^4.26.1, webpack@^4.41.5: watchpack "^1.6.1" webpack-sources "^1.4.1" -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== - dependencies: - http-parser-js ">=0.4.0 <0.4.11" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== - webworkify@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/webworkify/-/webworkify-1.5.0.tgz#734ad87a774de6ebdd546e1d3e027da5b8f4a42c" @@ -12882,13 +12000,6 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - ws@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -12954,7 +12065,7 @@ yargs-parser@^10.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^13.1.0, yargs-parser@^13.1.2: +yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -12984,23 +12095,6 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.4.0.tgz#816e1a866d5598ccf34e5596ddce22d92da490d4" From 41c4becbeb929f9755edf713efe9809586af8841 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 28 May 2020 08:42:05 +0000 Subject: [PATCH 134/181] Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es/ --- src/strings/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/es.json b/src/strings/es.json index d9d595431e..ab46562f94 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -1525,7 +1525,7 @@ "TabDVR": "DVR", "SaveChanges": "Guardar cambios", "HeaderDVR": "DVR", - "SyncPlayAccessHelp": "Selecciona el nivel de acceso que posee este usuario al SyncPlay. SyncPlay permite sincronizar reproductores con otros usuarios.", + "SyncPlayAccessHelp": "Selecciona los permisos de este usuario para utilizar SyncPlay. SyncPlay te permite sincroniza la reproducción entre varios dispositivos.", "MessageSyncPlayErrorMedia": "¡No se pudo activar SyncPlay! Error de medio.", "MessageSyncPlayErrorMissingSession": "¡No se pudo activar SyncPlay! Sesión desconectada.", "MessageSyncPlayErrorNoActivePlayer": "No hay reproductor activo. SyncPlay ha sido desactivado.", From da6a4772245f952b75195230d2b90ede2b7a96e9 Mon Sep 17 00:00:00 2001 From: Adam Bokor Date: Thu, 28 May 2020 19:25:58 +0000 Subject: [PATCH 135/181] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index d4709b2600..6d6307285d 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -1393,7 +1393,7 @@ "ButtonSplit": "Szétvág", "Absolute": "Abszolút", "LabelSkipIfAudioTrackPresentHelp": "Vedd ki a pipát, ha minden videóhoz szeretnél feliratot az audio nyelvétől függetlenül.", - "EnableFastImageFadeInHelp": "Gyorsabb előtűnés animáció a betöltött képekhez", + "EnableFastImageFadeInHelp": "Poszterek és más képek megjelenítése gyorsabb animációkkal.", "EnableFastImageFadeIn": "Gyors kép-előtűnés", "SubtitleOffset": "Felirat eltolása", "SeriesDisplayOrderHelp": "Rakd sorba az epizódokat az adásba kerülésük dátuma, a DVD sorszám, vagy az abszolút számozás szerint.", @@ -1539,5 +1539,22 @@ "LabelSyncPlaySyncMethod": "Szinkronizálási mód:", "MillisecondsUnit": "ms", "HeaderSyncPlayEnabled": "SyncPlay engedélyezve", - "HeaderSyncPlaySelectGroup": "Csatlakozás csoporthoz" + "HeaderSyncPlaySelectGroup": "Csatlakozás csoporthoz", + "SyncPlayAccessHelp": "Válaszd ki, hogy ez a felhasználó milyen szinten férhet hozzá a SyncPlay funkcióhoz. A SyncPlay lehetőséget biztosít a lejátszások közötti szinkronizációra.", + "MessageSyncPlayErrorMedia": "Nem sikerült a SyncPlay engedélyezése! Média hiba.", + "MessageSyncPlayErrorMissingSession": "A SyncPlay lejátszása sikertelen! Hiányzó munkamenet.", + "MessageSyncPlayErrorNoActivePlayer": "Nem található aktív lejátszó. A SyncPlay letiltásra került.", + "MessageSyncPlayErrorAccessingGroups": "Hiba történt a csoportok listájának betöltésekor.", + "MessageSyncPlayLibraryAccessDenied": "A tartalomhoz való hozzáférés korlátozva van.", + "MessageSyncPlayJoinGroupDenied": "A SyncPlay használatához jogosultság szükséges.", + "MessageSyncPlayCreateGroupDenied": "Jogosultság szükséges a csoportok létrehozásához.", + "MessageSyncPlayGroupDoesNotExist": "Nem sikerült csatlakozni a csoporthoz, mivel az nem létezik.", + "MessageSyncPlayPlaybackPermissionRequired": "Lejátszási jogosultság szükséges.", + "MessageSyncPlayNoGroupsAvailable": "Nincsenek elérhető csoportok. Először kezdj el lejátszani valamit.", + "LabelSyncPlayAccessNone": "Letiltva ennél a felhasználónál", + "LabelSyncPlayAccessJoinGroups": "A felhasználó csoportokhoz való csatlakozásának engedélyezése", + "LabelSyncPlayPlaybackDiff": "Lejátszási időkülönbség:", + "LabelSyncPlayTimeOffset": "Időeltolás a szerverhez képest:", + "EnableDetailsBannerHelp": "Megjelenít egy banner képet a részletes információoldal tetején.", + "EnableDetailsBanner": "Banner a részletes oldalon" } From 0f9db16069e4005803aae70eef402dcee1a7dae9 Mon Sep 17 00:00:00 2001 From: 4d1m Date: Thu, 28 May 2020 08:44:57 +0000 Subject: [PATCH 136/181] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index 7aa2f26d3f..89aeda2ef2 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -1238,7 +1238,7 @@ "Repeat": "Repetă", "RemoveFromPlaylist": "Scoateți din lista de redare", "RemoveFromCollection": "Scoateți din colecție", - "RememberMe": "Ține-mă minte", + "RememberMe": "Ține-mă Minte", "ReleaseDate": "Data lansării", "RefreshQueued": "Actualizare adăugată în coadă.", "RefreshMetadata": "Actualizați metadatele", @@ -1454,8 +1454,8 @@ "HeaderNavigation": "Navigare", "MessageConfirmAppExit": "Vrei să ieși?", "CopyStreamURLError": "A apărut o eroare la copierea adresei URL.", - "EnableFastImageFadeInHelp": "Activați animația mai rapidă de tranziție pentru imaginile încărcate", - "EnableFastImageFadeIn": "Tranziție a imaginii rapidă", + "EnableFastImageFadeInHelp": "Arătați postere și alte imagini cu o animație de tranziție rapidă când sunt deja încărcate.", + "EnableFastImageFadeIn": "Animație de Tranziție a Imaginii Rapidă", "LabelVideoResolution": "Rezoluția video:", "LabelStreamType": "Tipul streamului:", "LabelPlayerDimensions": "Dimensiunile soft redare:", @@ -1519,5 +1519,37 @@ "HeaderHttpsSettings": "Setări https", "TabDVR": "DVR", "SaveChanges": "Salvează modificările", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "SyncPlayAccessHelp": "Selectați nivelul de acces pe care îl are acest utilizator la funcția SyncPlay. SyncPlay permite sincronizarea redării cu alte dispozitive.", + "MessageSyncPlayErrorMedia": "Eroare la activarea SyncPlay! Eroare media.", + "MessageSyncPlayErrorMissingSession": "Eroare la activarea SyncPlay! Sesiune lipsă.", + "MessageSyncPlayErrorNoActivePlayer": "Nu a fost găsit niciun soft de redare activ. SyncPlay a fost dezactivat.", + "MessageSyncPlayErrorAccessingGroups": "A apărut o eroare la accesarea listei de grupuri.", + "MessageSyncPlayLibraryAccessDenied": "Accesul la acest conținut este restricționat.", + "MessageSyncPlayJoinGroupDenied": "Permisiune necesară pentru a utiliza SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permisiune necesară pentru crearea unui grup.", + "MessageSyncPlayGroupDoesNotExist": "Nu a reușit să se alăture grupului, deoarece nu există.", + "MessageSyncPlayPlaybackPermissionRequired": "Este necesară permisiunea de redare.", + "MessageSyncPlayNoGroupsAvailable": "Nu există grupuri disponibile. Începe să redai ceva mai întâi.", + "MessageSyncPlayGroupWait": "{0} se încarcă...", + "MessageSyncPlayUserLeft": "{0} a părăsit grupul.", + "MessageSyncPlayUserJoined": "{0} s-a alăturat grupului.", + "MessageSyncPlayDisabled": "SyncPlay dezactivat.", + "MessageSyncPlayEnabled": "SyncPlay activat.", + "LabelSyncPlayAccess": "Acces SyncPlay", + "LabelSyncPlayAccessNone": "Dezactivat pentru acest utilizator", + "LabelSyncPlayAccessJoinGroups": "Permiteți utilizatorului să se alăture grupurilor", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permiteți utilizatorului să creeze și să se alăture grupurilor", + "LabelSyncPlayLeaveGroupDescription": "Dezactivează SyncPlay", + "LabelSyncPlayLeaveGroup": "Parăsește grup", + "LabelSyncPlayNewGroupDescription": "Crează un grup nou", + "LabelSyncPlayNewGroup": "Grup nou", + "LabelSyncPlaySyncMethod": "Metoda de sincronizare:", + "LabelSyncPlayPlaybackDiff": "Diferența de timp de redare:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Decalare de timp cu serverul:", + "HeaderSyncPlayEnabled": "SyncPlay activat", + "HeaderSyncPlaySelectGroup": "Alăturați-vă unui grup", + "EnableDetailsBannerHelp": "Afișați o imagine de bandou în partea de sus a paginii cu detalii ale articolului.", + "EnableDetailsBanner": "Detalii Bandou" } From 54b022791a0f333a9f59d26f5435f1da1f4c6b2c Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 28 May 2020 21:13:52 +0000 Subject: [PATCH 137/181] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index d3a38d34d3..7ef148e7dc 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -1460,8 +1460,8 @@ "HeaderNavigation": "Навигация", "LabelVideoResolution": "Разрешение видео:", "LabelStreamType": "Тип потока:", - "EnableFastImageFadeInHelp": "Включить быстрое появление анимации для загруженных изображений", - "EnableFastImageFadeIn": "Быстрое появление изображения", + "EnableFastImageFadeInHelp": "Показывать постеры и другие рисунки анимацией побыстрее , когда они закончат загружаться.", + "EnableFastImageFadeIn": "Быстрое анимация рисунка", "LabelPlayerDimensions": "Размеры проигрывателя:", "LabelDroppedFrames": "Пропущенные кадры:", "LabelCorruptedFrames": "Испорченные кадры:", @@ -1520,5 +1520,33 @@ "HeaderServerAddressSettings": "Параметры адреса сервера", "HeaderRemoteAccessSettings": "Параметры удалённого доступа", "HeaderHttpsSettings": "Параметры HTTPS", - "HeaderDVR": "DVR" + "HeaderDVR": "DVR", + "MessageSyncPlayJoinGroupDenied": "Требуется разрешение для использования SyncPlay.", + "MessageSyncPlayDisabled": "SyncPlay отключен.", + "MessageSyncPlayEnabled": "SyncPlay включён.", + "LabelSyncPlayAccess": "Доступ к SyncPlay", + "LabelSyncPlayLeaveGroupDescription": "Отключить SyncPlay", + "HeaderSyncPlayEnabled": "SyncPlay включён", + "HeaderSyncPlaySelectGroup": "Присоединить группу", + "EnableDetailsBanner": "Баннер подробностей", + "EnableDetailsBannerHelp": "Отображает рисунок баннера вверху страницы подробностей элемента.", + "MessageSyncPlayErrorAccessingGroups": "Произошла ошибка при попытке доступа к списку групп.", + "MessageSyncPlayLibraryAccessDenied": "Доступ к данному содержанию ограничен.", + "MessageSyncPlayCreateGroupDenied": "Требуется разрешение для создания группы.", + "MessageSyncPlayGroupDoesNotExist": "Не удалось присоединиться к группе, поскольку она не существует.", + "MessageSyncPlayPlaybackPermissionRequired": "Требуется разрешение на воспроизведение.", + "MessageSyncPlayNoGroupsAvailable": "Никакие группы не доступны. Сначала начните воспроизводить что-нибудь.", + "MessageSyncPlayGroupWait": "{0} буферизуется...", + "MessageSyncPlayUserLeft": "{0} покинул группу.", + "MessageSyncPlayUserJoined": "{0} присоединил группу.", + "LabelSyncPlayAccessNone": "Отключено для данного пользователя", + "LabelSyncPlayAccessJoinGroups": "Разрешить пользователю присоединяться к группам", + "LabelSyncPlayAccessCreateAndJoinGroups": "Разрешить пользователю создавать и присоединять группы", + "LabelSyncPlayLeaveGroup": "Покинуть группу", + "LabelSyncPlayNewGroupDescription": "Создание новой группы", + "LabelSyncPlayNewGroup": "Новая группа", + "LabelSyncPlaySyncMethod": "Метод синхронизации:", + "LabelSyncPlayPlaybackDiff": "Разница времени воспроизведения:", + "MillisecondsUnit": "мс", + "LabelSyncPlayTimeOffset": "Сдвиг времени относительно сервера:" } From d0fc6f43719bc257ab9c8df26acf2d9d03e77e0d Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Thu, 28 May 2020 20:07:07 +0000 Subject: [PATCH 138/181] Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sk/ --- src/strings/sk.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/strings/sk.json b/src/strings/sk.json index 9e6305b183..e99883d24e 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -721,7 +721,7 @@ "Refresh": "Obnoviť", "RefreshMetadata": "Obnoviť metadáta", "ReleaseDate": "Dátum vydania", - "RememberMe": "Zapamätať si ma", + "RememberMe": "Zapamätaj si ma", "RemoveFromCollection": "Odobrať z kolekcie", "Repeat": "Opakovať", "RepeatAll": "Opakovať všetko", @@ -1457,8 +1457,8 @@ "MessageConfirmAppExit": "Chceli by ste odísiť?", "LabelVideoResolution": "Rozlíšenie videa:", "LabelStreamType": "Typ streamu:", - "EnableFastImageFadeInHelp": "Povoliť animáciu rýchleho rozjasnenia pre nahrané obrázky", - "EnableFastImageFadeIn": "Rýchle rozjasnenie obrázku", + "EnableFastImageFadeInHelp": "Zobrazí plagát a ostatné obrázky s rýchlejšou animáciou prechodu po dokončení načítania.", + "EnableFastImageFadeIn": "Rýchla animácia prechodu obrázku", "LabelPlayerDimensions": "Rozmery prehrávača:", "LabelDroppedFrames": "Vynechané snímky:", "LabelCorruptedFrames": "Poškodené snímky:", @@ -1550,5 +1550,8 @@ "MillisecondsUnit": "ms", "LabelSyncPlayTimeOffset": "Časový rozdiel so serverom:", "HeaderSyncPlayEnabled": "Synchronizácia prehrávania je povolená", - "HeaderSyncPlaySelectGroup": "Pripojiť sa k skupine" + "HeaderSyncPlaySelectGroup": "Pripojiť sa k skupine", + "SyncPlayAccessHelp": "Vyberte úroveň prístupu pre tohto používateľa k funkcií synchronizácie prehrávania. Synchronizácia prehrávania umožňuje zosynchronizovať prehrávanie s ostatnými zariadeniami.", + "EnableDetailsBannerHelp": "Zobrazí banner na vrchnej časti detailu položky.", + "EnableDetailsBanner": "Detail banneru" } From aae276dad18967be52c909640693661c8b66d865 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 28 May 2020 08:43:50 +0000 Subject: [PATCH 139/181] Translated using Weblate (English) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/en/ --- src/strings/en-us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 46005ca237..f26ba16c85 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1403,7 +1403,7 @@ "Suggestions": "Suggestions", "Sunday": "Sunday", "Sync": "Sync", - "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other users.", + "SyncPlayAccessHelp": "Select the level of access this user has to the SyncPlay feature. SyncPlay enables to sync playback with other devices.", "SystemDlnaProfilesHelp": "System profiles are read-only. Changes to a system profile will be saved to a new custom profile.", "TV": "TV", "TabAccess": "Access", From aa2315f355e26a66d84a14ea5d1cf6c619c8bbb0 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Fri, 29 May 2020 00:25:30 +0200 Subject: [PATCH 140/181] Update dependencies --- src/components/notifications/notifications.js | 2 +- src/components/playback/mediasession.js | 8 + src/scripts/site.js | 1 + yarn.lock | 1081 +++++++++-------- 4 files changed, 599 insertions(+), 493 deletions(-) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 3ac891b5e7..c8480e4f15 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -46,7 +46,7 @@ define(['serverNotifications', 'playbackManager', 'events', 'globalize', 'requir function showNonPersistentNotification(title, options, timeoutMs) { try { - var notif = new Notification(title, options); + var notif = new Notification(title, options); /* eslint-disable-line compat/compat */ if (notif.show) { notif.show(); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 2dd10b3484..5eac56b5ce 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -119,6 +119,7 @@ import connectionManager from 'connectionManager'; const canSeek = playState.CanSeek || false; if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.metadata = new MediaMetadata({ title: title, artist: artist, @@ -179,6 +180,7 @@ import connectionManager from 'connectionManager'; function hideMediaControls() { if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.metadata = null; } else { window.NativeShell.hideMediaSession(); @@ -210,26 +212,32 @@ import connectionManager from 'connectionManager'; } if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('previoustrack', function () { execute('previousTrack'); }); + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('nexttrack', function () { execute('nextTrack'); }); + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('play', function () { execute('unpause'); }); + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('pause', function () { execute('pause'); }); + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('seekbackward', function () { execute('rewind'); }); + /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('seekforward', function () { execute('fastForward'); }); diff --git a/src/scripts/site.js b/src/scripts/site.js index aeb651d888..07536728e9 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -561,6 +561,7 @@ var AppInfo = {}; require(['components/playback/volumeosd']); } + /* eslint-disable-next-line compat/compat */ if (navigator.mediaSession || window.NativeShell) { require(['mediaSession']); } diff --git a/yarn.lock b/yarn.lock index 20fdef5de1..0401be73ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,23 +2,30 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== dependencies: "@babel/highlight" "^7.8.3" -"@babel/compat-data@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" - integrity sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g== +"@babel/code-frame@^7.10.1", "@babel/code-frame@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" + integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== dependencies: - browserslist "^4.11.1" + "@babel/highlight" "^7.10.1" + +"@babel/compat-data@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db" + integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw== + dependencies: + browserslist "^4.12.0" invariant "^2.2.4" semver "^5.5.0" -"@babel/core@>=7.2.2", "@babel/core@>=7.9.0", "@babel/core@^7.9.6": +"@babel/core@>=7.2.2", "@babel/core@>=7.9.0": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== @@ -40,284 +47,334 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" - integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== +"@babel/core@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.1.tgz#2a0ad0ea693601820defebad2140206503d89af3" + integrity sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg== dependencies: - "@babel/types" "^7.9.6" + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.1" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helpers" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.10.1", "@babel/generator@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.1.tgz#4d14458e539bcb04ffe34124143f5c489f2dbca9" + integrity sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA== + dependencies: + "@babel/types" "^7.10.1" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" - integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== +"@babel/helper-annotate-as-pure@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268" + integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059" + integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw== dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-explode-assignable-expression" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-compilation-targets@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" - integrity sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw== +"@babel/helper-compilation-targets@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.1.tgz#ad6f69b4c3bae955081ef914a84e5878ffcaca63" + integrity sha512-YuF8IrgSmX/+MV2plPkjEnzlC2wf+gaok8ehMNN0jodF3/sejZauExqpEVGbJua62oaWoNYIXwz4RmAsVcGyHw== dependencies: - "@babel/compat-data" "^7.9.6" - browserslist "^4.11.1" + "@babel/compat-data" "^7.10.1" + browserslist "^4.12.0" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" - integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== +"@babel/helper-create-class-features-plugin@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.1.tgz#6d8a45aafe492378d0e6fc0b33e5dea132eae21c" + integrity sha512-bwhdehBJZt84HuPUcP1HaTLuc/EywVS8rc3FgsEPDcivg+DCW+SHuLHVkYOmcBA1ZfI+Z/oZjQc/+bPmIO7uAA== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-regex" "^7.8.3" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + +"@babel/helper-create-regexp-features-plugin@^7.10.1", "@babel/helper-create-regexp-features-plugin@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd" + integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-regex" "^7.10.1" regexpu-core "^4.7.0" -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== +"@babel/helper-define-map@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d" + integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-function-name" "^7.10.1" + "@babel/types" "^7.10.1" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== +"@babel/helper-explode-assignable-expression@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e" + integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg== dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" - integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== +"@babel/helper-function-name@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4" + integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ== dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.9.5" + "@babel/helper-get-function-arity" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-get-function-arity@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" - integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== +"@babel/helper-get-function-arity@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d" + integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== +"@babel/helper-hoist-variables@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077" + integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== +"@babel/helper-member-expression-to-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15" + integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-module-imports@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" - integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== +"@babel/helper-module-imports@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876" + integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-module-transforms@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" - integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== +"@babel/helper-module-transforms@^7.10.1", "@babel/helper-module-transforms@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" + integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.9.0" + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-simple-access" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" lodash "^4.17.13" -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== +"@babel/helper-optimise-call-expression@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543" + integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" - integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" + integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== -"@babel/helper-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" - integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== +"@babel/helper-regex@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96" + integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g== dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== +"@babel/helper-remap-async-to-generator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432" + integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-wrap-function" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" - integrity sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA== +"@babel/helper-replace-supers@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" + integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.9.6" - "@babel/types" "^7.9.6" + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-simple-access@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" - integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== +"@babel/helper-simple-access@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e" + integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw== dependencies: - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helper-split-export-declaration@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" - integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== +"@babel/helper-split-export-declaration@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" + integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.1" -"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" - integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== +"@babel/helper-validator-identifier@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" + integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== +"@babel/helper-wrap-function@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9" + integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-function-name" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/helpers@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" - integrity sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw== +"@babel/helpers@^7.10.1", "@babel/helpers@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973" + integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw== dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.6" - "@babel/types" "^7.9.6" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/highlight@^7.8.3": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" - integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== +"@babel/highlight@^7.10.1", "@babel/highlight@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" + integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== dependencies: - "@babel/helper-validator-identifier" "^7.9.0" + "@babel/helper-validator-identifier" "^7.10.1" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.8.6", "@babel/parser@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" - integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== +"@babel/parser@^7.10.1", "@babel/parser@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.1.tgz#2e142c27ca58aa2c7b119d09269b702c8bbad28c" + integrity sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg== -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== +"@babel/plugin-proposal-async-generator-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55" + integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-remap-async-to-generator" "^7.10.1" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== +"@babel/plugin-proposal-class-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01" + integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-proposal-dynamic-import@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" + integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-dynamic-import" "^7.8.0" -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== +"@babel/plugin-proposal-json-strings@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09" + integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78" + integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== +"@babel/plugin-proposal-numeric-separator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123" + integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-numeric-separator" "^7.10.1" -"@babel/plugin-proposal-object-rest-spread@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz#7a093586fcb18b08266eb1a7177da671ac575b63" - integrity sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A== +"@babel/plugin-proposal-object-rest-spread@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6" + integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.9.5" + "@babel/plugin-transform-parameters" "^7.10.1" -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== +"@babel/plugin-proposal-optional-catch-binding@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2" + integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== +"@babel/plugin-proposal-optional-chaining@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c" + integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.8.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" - integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== +"@babel/plugin-proposal-private-methods@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598" + integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.8" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f" + integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-async-generators@^7.8.0": version "7.8.4" @@ -326,6 +383,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-class-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5" + integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-dynamic-import@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -347,12 +411,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" - integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== +"@babel/plugin-syntax-numeric-separator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99" + integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-object-rest-spread@^7.8.0": version "7.8.3" @@ -375,160 +439,160 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== +"@babel/plugin-syntax-top-level-await@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362" + integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== +"@babel/plugin-transform-arrow-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b" + integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== +"@babel/plugin-transform-async-to-generator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062" + integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg== dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-remap-async-to-generator" "^7.10.1" -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== +"@babel/plugin-transform-block-scoped-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d" + integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== +"@babel/plugin-transform-block-scoping@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e" + integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" - integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg== +"@babel/plugin-transform-classes@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f" + integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-define-map" "^7.10.1" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== +"@babel/plugin-transform-computed-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07" + integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-destructuring@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" - integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q== +"@babel/plugin-transform-destructuring@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907" + integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" - integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== +"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee" + integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== +"@babel/plugin-transform-duplicate-keys@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9" + integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== +"@babel/plugin-transform-exponentiation-operator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3" + integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-for-of@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== +"@babel/plugin-transform-for-of@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5" + integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== +"@babel/plugin-transform-function-name@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d" + integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== +"@babel/plugin-transform-literals@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a" + integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== +"@babel/plugin-transform-member-expression-literals@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39" + integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-modules-amd@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" - integrity sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw== +"@babel/plugin-transform-modules-amd@^7.10.1", "@babel/plugin-transform-modules-amd@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a" + integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" - integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== +"@babel/plugin-transform-modules-commonjs@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301" + integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-simple-access" "^7.10.1" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" - integrity sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg== +"@babel/plugin-transform-modules-systemjs@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6" + integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-hoist-variables" "^7.10.1" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" - integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== +"@babel/plugin-transform-modules-umd@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595" + integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": version "7.8.3" @@ -537,164 +601,175 @@ dependencies: "@babel/helper-create-regexp-features-plugin" "^7.8.3" -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== +"@babel/plugin-transform-new-target@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324" + integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== +"@babel/plugin-transform-object-super@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde" + integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" -"@babel/plugin-transform-parameters@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" - integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA== +"@babel/plugin-transform-parameters@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd" + integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg== dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-get-function-arity" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== +"@babel/plugin-transform-property-literals@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d" + integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-regenerator@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" - integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== +"@babel/plugin-transform-regenerator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490" + integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== +"@babel/plugin-transform-reserved-words@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86" + integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== +"@babel/plugin-transform-shorthand-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" + integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== +"@babel/plugin-transform-spread@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8" + integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== +"@babel/plugin-transform-sticky-regex@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00" + integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-regex" "^7.10.1" -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== +"@babel/plugin-transform-template-literals@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628" + integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== +"@babel/plugin-transform-typeof-symbol@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e" + integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== +"@babel/plugin-transform-unicode-escapes@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940" + integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-unicode-regex@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f" + integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" "@babel/polyfill@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.7.tgz#151ec24c7135481336168c3bd8b8bf0cf91c032f" - integrity sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w== + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.10.1.tgz#d56d4c8be8dd6ec4dce2649474e9b707089f739f" + integrity sha512-TviueJ4PBW5p48ra8IMtLXVkDucrlOZAIZ+EXqS3Ot4eukHbWiqcn7DcqpA1k5PcKtmJ4Xl9xwdv6yQvvcA+3g== dependencies: core-js "^2.6.5" regenerator-runtime "^0.13.4" "@babel/preset-env@^7.8.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.6.tgz#df063b276c6455ec6fcfc6e53aacc38da9b0aea6" - integrity sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ== + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.1.tgz#099e1b76379739bdcbfab3d548dc7e7edb2ac808" + integrity sha512-bGWNfjfXRLnqbN2T4lB3pMfoic8dkRrmHpVZamSFHzGy5xklyHTobZ28TVUD2grhE5WDnu67tBj8oslIhkiOMQ== dependencies: - "@babel/compat-data" "^7.9.6" - "@babel/helper-compilation-targets" "^7.9.6" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.6" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/compat-data" "^7.10.1" + "@babel/helper-compilation-targets" "^7.10.1" + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-proposal-async-generator-functions" "^7.10.1" + "@babel/plugin-proposal-class-properties" "^7.10.1" + "@babel/plugin-proposal-dynamic-import" "^7.10.1" + "@babel/plugin-proposal-json-strings" "^7.10.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1" + "@babel/plugin-proposal-numeric-separator" "^7.10.1" + "@babel/plugin-proposal-object-rest-spread" "^7.10.1" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" + "@babel/plugin-proposal-optional-chaining" "^7.10.1" + "@babel/plugin-proposal-private-methods" "^7.10.1" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.1" "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.1" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-json-strings" "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.1" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.5" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.9.5" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.6" - "@babel/plugin-transform-modules-commonjs" "^7.9.6" - "@babel/plugin-transform-modules-systemjs" "^7.9.6" - "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-syntax-top-level-await" "^7.10.1" + "@babel/plugin-transform-arrow-functions" "^7.10.1" + "@babel/plugin-transform-async-to-generator" "^7.10.1" + "@babel/plugin-transform-block-scoped-functions" "^7.10.1" + "@babel/plugin-transform-block-scoping" "^7.10.1" + "@babel/plugin-transform-classes" "^7.10.1" + "@babel/plugin-transform-computed-properties" "^7.10.1" + "@babel/plugin-transform-destructuring" "^7.10.1" + "@babel/plugin-transform-dotall-regex" "^7.10.1" + "@babel/plugin-transform-duplicate-keys" "^7.10.1" + "@babel/plugin-transform-exponentiation-operator" "^7.10.1" + "@babel/plugin-transform-for-of" "^7.10.1" + "@babel/plugin-transform-function-name" "^7.10.1" + "@babel/plugin-transform-literals" "^7.10.1" + "@babel/plugin-transform-member-expression-literals" "^7.10.1" + "@babel/plugin-transform-modules-amd" "^7.10.1" + "@babel/plugin-transform-modules-commonjs" "^7.10.1" + "@babel/plugin-transform-modules-systemjs" "^7.10.1" + "@babel/plugin-transform-modules-umd" "^7.10.1" "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.9.5" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.10.1" + "@babel/plugin-transform-object-super" "^7.10.1" + "@babel/plugin-transform-parameters" "^7.10.1" + "@babel/plugin-transform-property-literals" "^7.10.1" + "@babel/plugin-transform-regenerator" "^7.10.1" + "@babel/plugin-transform-reserved-words" "^7.10.1" + "@babel/plugin-transform-shorthand-properties" "^7.10.1" + "@babel/plugin-transform-spread" "^7.10.1" + "@babel/plugin-transform-sticky-regex" "^7.10.1" + "@babel/plugin-transform-template-literals" "^7.10.1" + "@babel/plugin-transform-typeof-symbol" "^7.10.1" + "@babel/plugin-transform-unicode-escapes" "^7.10.1" + "@babel/plugin-transform-unicode-regex" "^7.10.1" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.6" - browserslist "^4.11.1" + "@babel/types" "^7.10.1" + browserslist "^4.12.0" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -711,43 +786,50 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.8.4": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.1.tgz#b6eb75cac279588d3100baecd1b9894ea2840822" + integrity sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.9.2": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.8.3", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== +"@babel/template@^7.10.1", "@babel/template@^7.8.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" + integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/code-frame" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" -"@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" - integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== +"@babel/traverse@^7.10.1", "@babel/traverse@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" + integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.6" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.6" - "@babel/types" "^7.9.6" + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.1" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" - integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== +"@babel/types@^7.10.1", "@babel/types@^7.4.4", "@babel/types@^7.9.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921" + integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA== dependencies: - "@babel/helper-validator-identifier" "^7.9.5" + "@babel/helper-validator-identifier" "^7.10.1" lodash "^4.17.13" to-fast-properties "^2.0.0" @@ -1492,10 +1574,10 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-metadata-inferer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.1.1.tgz#66e24fae9d30ca961fac4880b7fc466f09b25165" - integrity sha512-hc9w8Qrgg9Lf9iFcZVhNjUnhrd2BBpTlyCnegPVvCe6O0yMrF57a6Cmh7k+xUsfUOMh9wajOL5AsGOBNEyTCcw== +ast-metadata-inferer@^0.2.0-0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ast-metadata-inferer/-/ast-metadata-inferer-0.2.0.tgz#a470e5d1d7402b18c6f7a1f3d6900723cfa07392" + integrity sha512-6yPph2NeCHNxoI/ZmjklYaLOSZDAx+0L0+wsXnF56FxmjxvUlYZSWcj1KXtXO8IufruQTzVFOjg1+IzdDazSPg== astral-regex@^1.0.0: version "1.0.0" @@ -1981,7 +2063,7 @@ browserslist@^1.1.3: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.12.0, browserslist@^4.6.4, browserslist@^4.8.2, browserslist@^4.8.5: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.4, browserslist@^4.8.5: version "4.12.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== @@ -2241,16 +2323,21 @@ caniuse-db@^1.0.30000639: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001036.tgz#8761fb6cd423ef2d3f8d96a21d898932252dc477" integrity sha512-plRkihXQyiDaFUXC7x/jAIXXTKiiaWvfAagsruh/vmstnRQ+a2a95HyENxiTr5WrkPSvmFUIvsRUalVFyeh2/w== -caniuse-db@^1.0.30001017: - version "1.0.30001039" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001039.tgz#b5e8c3bb07a144341644729fa2a5eb2c0deaf47d" - integrity sha512-XVk5KMAi8/DI28tQXKuq1PDyuPoD9Ypnda3ctF04TlB+LYIb+bgHq0ZDfNOn0+4cwLENJC0093Vuf0dhkjXQ7Q== +caniuse-db@^1.0.30001059: + version "1.0.30001068" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001068.tgz#79fa671a063f03485c663f4165252f039c312c36" + integrity sha512-FF4o1nUDSnYY8rPCldTJ1486rqcgSZasQtWIMvSC3WOllFJNvmwhuBcApuWC1CD2TKTRnIBXqM4d4lJsCdRJzQ== -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001061: version "1.0.30001061" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001061.tgz#80ca87ef14eb543a7458e7fd2b5e2face3458c9f" integrity sha512-SMICCeiNvMZnyXpuoO+ot7FHpMVPlrsR+HmfByj6nY4xYDHXLqMTbgH7ecEkDNXWkH1vaip+ZS0D7VTXwM1KYQ== +caniuse-lite@^1.0.30001043: + version "1.0.30001066" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz#0a8a58a10108f2b9bf38e7b65c237b12fd9c5f04" + integrity sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -3676,11 +3763,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.413: +electron-to-chromium@^1.2.7: version "1.3.427" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.427.tgz#ea43d02908a8c71f47ebb46e09de5a3cf8236f04" integrity sha512-/rG5G7Opcw68/Yrb4qYkz07h3bESVRJjUl4X/FrKLXzoUJleKm6D7K7rTTz8V5LUWnd+BbTOyxJX2XprRqHD8A== +electron-to-chromium@^1.3.413: + version "1.3.453" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.453.tgz#758a8565a64b7889b27132a51d2abb8b135c9d01" + integrity sha512-IQbCfjJR0NDDn/+vojTlq7fPSREcALtF8M1n01gw7nQghCtfFYrJ2dfhsp8APr8bANoFC8vRTFVXMOGpT0eetw== + elliptic@^6.0.0: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" @@ -3940,17 +4032,17 @@ eslint-module-utils@^2.4.1: pkg-dir "^2.0.0" eslint-plugin-compat@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-3.5.1.tgz#09f9c05dcfa9b5cd69345d7ab333749813ed8b14" - integrity sha512-dhfW12vZxxKLEVhrPoblmEopgwpYU2Sd4GdXj5OSfbQ+as9+1aY+S5pqnJYJvXXNWFFJ6aspLkCyk4NMQ/pgtA== + version "3.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-compat/-/eslint-plugin-compat-3.7.0.tgz#03f1ebb350a3c7eb93b6f461e200048e6008594b" + integrity sha512-A3uzSYqUjNj6rMyaBuU3l8wSCadZjeZRZ7WF3eU9vUT0JItiqRysjmYELkHHCpH8l7wRprUu4MZPr37lFCw7iA== dependencies: - "@babel/runtime" "^7.7.7" - ast-metadata-inferer "^0.1.1" - browserslist "^4.8.2" - caniuse-db "^1.0.30001017" + ast-metadata-inferer "^0.2.0-0" + browserslist "^4.12.0" + caniuse-db "^1.0.30001059" + core-js "^3.6.5" lodash.memoize "4.1.2" - mdn-browser-compat-data "^1.0.3" - semver "^6.3.0" + mdn-browser-compat-data "^1.0.21" + semver "7.3.2" eslint-plugin-eslint-comments@^3.1.2: version "3.1.2" @@ -6540,9 +6632,9 @@ isurl@^1.0.0-alpha5: is-object "^1.0.1" jellyfin-apiclient@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.2.0.tgz#a892985ccfcd9798fe67455ee39cd0869adb14d5" - integrity sha512-7l2dXpVU+nvDVYJA/RwJPzZy99RtP89iIooZdRZ9gGF4tSCQe1Gf/fNIcTPBdMjXDBhiEZc1wytz4iYR1y2E/Q== + version "1.2.1" + resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.2.1.tgz#1da577f7e22c37be8ec23c139b9ddab2c36da5a2" + integrity sha512-5aNtUq7YsoDPZ0LL6cq55HDnSTVfECfw05hbPFxNsFlUogEiHwaoIz+ahWRO13OUFQJuiu8f3fy16glcGzrBIQ== "jellyfin-noto@https://github.com/jellyfin/jellyfin-noto": version "1.0.3" @@ -7292,10 +7384,10 @@ mdast-util-compact@^2.0.0: dependencies: unist-util-visit "^2.0.0" -mdn-browser-compat-data@^1.0.3: - version "1.0.19" - resolved "https://registry.yarnpkg.com/mdn-browser-compat-data/-/mdn-browser-compat-data-1.0.19.tgz#f4542aea7bce4231c95c5bdab04f999298b58903" - integrity sha512-S1i9iILAsFi/C17NADg2cBT6D6Xcd5Ub+GSMQa5ScLhuVyUrRKjCNmFEED9mQ2G/lrKtvU9SGUrpPpXB8SXhCg== +mdn-browser-compat-data@^1.0.21: + version "1.0.23" + resolved "https://registry.yarnpkg.com/mdn-browser-compat-data/-/mdn-browser-compat-data-1.0.23.tgz#52e21d74e52d40bacf1cc3377755897ef1639033" + integrity sha512-qzabBf9lN1UG6Ju6am5j4bsy8PJSxlE8zQEyDXzKqD+nAQsAnA8apvbkgTSIA/ZpKgz/7qOtpJgtgGN00MEsIg== dependencies: extend "3.0.2" @@ -7770,9 +7862,9 @@ node-libs-browser@^2.2.1: vm-browserify "^1.0.1" node-releases@^1.1.53: - version "1.1.53" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" - integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== + version "1.1.57" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.57.tgz#f6754ce225fad0611e61228df3e09232e017ea19" + integrity sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw== node-sass@^4.13.1: version "4.14.1" @@ -9884,9 +9976,9 @@ regexpu-core@^4.7.0: unicode-match-property-value-ecmascript "^1.2.0" regjsgen@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== regjsparser@^0.6.4: version "0.6.4" @@ -10421,6 +10513,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" From f230d3e177d69f539aa827a7803a37198feb7260 Mon Sep 17 00:00:00 2001 From: Viperinius Date: Thu, 28 May 2020 22:13:37 +0000 Subject: [PATCH 141/181] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/strings/de.json b/src/strings/de.json index 115e85f84d..4e9934e9cc 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1478,8 +1478,8 @@ "MessageConfirmAppExit": "Wirklich verlassen?", "LabelVideoResolution": "Videoauflösung:", "LabelStreamType": "Streamtyp:", - "EnableFastImageFadeInHelp": "Aktiviere schnellere Einblendeanimation für geladene Bilder", - "EnableFastImageFadeIn": "Schnelle Bildeinblendung", + "EnableFastImageFadeInHelp": "Zeige Poster und andere Bilder mit einer schnelleren Einblendeanimation, wenn diese fertig geladen sind.", + "EnableFastImageFadeIn": "Schnelle Bildeinblendungsanimationen", "LabelPlayerDimensions": "Playerabmessungen:", "LabelDroppedFrames": "Verlorene Frames:", "LabelCorruptedFrames": "Fehlerhafte Frames:", @@ -1540,7 +1540,7 @@ "HeaderServerAddressSettings": "Server-Adresseinstellungen", "HeaderRemoteAccessSettings": "Fernzugriffs-Einstellungen", "HeaderHttpsSettings": "HTTPS-Einstellungen", - "SyncPlayAccessHelp": "Wähle die Berechtigungsstufe, die dieser Benutzer auf das SyncPlay-Feature hat. SyncPlay ermöglicht die Synchronisierung der Wiedergabe mit anderen Benutzern.", + "SyncPlayAccessHelp": "Wähle die Berechtigungsstufe, die dieser Benutzer auf das SyncPlay-Feature hat. SyncPlay ermöglicht die Synchronisierung der Wiedergabe mit anderen Geräten.", "MessageSyncPlayErrorMedia": "SyncPlay konnte nicht aktiviert werden! Medienfehler.", "MessageSyncPlayErrorMissingSession": "SyncPlay konnte nicht aktiviert werden! Fehlende Sitzung.", "MessageSyncPlayErrorNoActivePlayer": "Keine aktive Wiedergabe gefunden. SyncPlay wurde deaktiviert.", @@ -1569,5 +1569,7 @@ "MillisecondsUnit": "ms", "LabelSyncPlayTimeOffset": "Zeitversatz mit dem Server:", "HeaderSyncPlayEnabled": "SyncPlay aktiviert", - "HeaderSyncPlaySelectGroup": "Tritt einer Gruppe bei" + "HeaderSyncPlaySelectGroup": "Tritt einer Gruppe bei", + "EnableDetailsBannerHelp": "Zeigt ein Bannerbild im oberen Bereich der Seite Item-Details.", + "EnableDetailsBanner": "Detailbanner" } From c60011010e579915a5c5a99ee2f2c2325d0e51a4 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Thu, 28 May 2020 21:36:39 +0000 Subject: [PATCH 142/181] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index 7ef148e7dc..4a7e2aa8ab 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -1548,5 +1548,9 @@ "LabelSyncPlaySyncMethod": "Метод синхронизации:", "LabelSyncPlayPlaybackDiff": "Разница времени воспроизведения:", "MillisecondsUnit": "мс", - "LabelSyncPlayTimeOffset": "Сдвиг времени относительно сервера:" + "LabelSyncPlayTimeOffset": "Сдвиг времени относительно сервера:", + "SyncPlayAccessHelp": "Выберите уровень доступа данного пользователя к функциональности SyncPlay. SyncPlay позволяет синхронизировать воспроизведение с другими устройствами.", + "MessageSyncPlayErrorMedia": "Не удалось включить SyncPlay! Ошибка медиаданных.", + "MessageSyncPlayErrorMissingSession": "Не удалось включить SyncPlay! Отсутствует сеанс.", + "MessageSyncPlayErrorNoActivePlayer": "Активный проигрыватель не найден. SyncPlay был отключен." } From c13fc65dd024246aafc4775d33215d2c03a95911 Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 29 May 2020 16:47:21 +0900 Subject: [PATCH 143/181] fix import statement in photo player Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/photoPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/photoPlayer/plugin.js b/src/components/photoPlayer/plugin.js index bdb0214da3..f658e4c702 100644 --- a/src/components/photoPlayer/plugin.js +++ b/src/components/photoPlayer/plugin.js @@ -12,7 +12,7 @@ export class PhotoPlayer { return new Promise(function (resolve, reject) { - import('slideshow').then(slideshow => { + import('slideshow').then(({default: slideshow}) => { var index = options.startIndex || 0; From fe9a825aa15fc8f0538aa1b1f93fb156345b0ced Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Fri, 29 May 2020 19:13:02 +1000 Subject: [PATCH 144/181] Make book player report percentage progress based on CFI location --- src/components/bookPlayer/plugin.js | 90 +++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 302ed6bd18..66bcb46973 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -2,6 +2,7 @@ import connectionManager from 'connectionManager'; import loading from 'loading'; import keyboardnavigation from 'keyboardnavigation'; import dialogHelper from 'dialogHelper'; +import events from 'events'; import 'css!./style'; import 'material-icons'; import 'paper-icon-button-light'; @@ -21,6 +22,9 @@ export class BookPlayer { } play(options) { + this._progress = 0; + this._loaded = false; + loading.show(); let elem = this.createMediaElement(); return this.setCurrentSrc(elem, options); @@ -46,6 +50,45 @@ export class BookPlayer { if (rendition) { rendition.destroy(); } + + // Hide loader in case player was not fully loaded yet + loading.hide(); + this._cancellationToken.shouldCancel = true; + } + + currentItem() { + return this._currentItem; + } + + currentTime() { + return this._progress * 1000; + } + + duration() { + return 1000; + } + + getBufferedRanges() { + return [{ + start: 0, + end: 10000000 + }]; + } + + volume() { + return 100; + } + + isMuted() { + return false; + } + + paused() { + return false; + } + + seekable() { + return true; } onWindowKeyUp(e) { @@ -57,12 +100,16 @@ export class BookPlayer { case 'l': case 'ArrowRight': case 'Right': - book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + if (this._loaded) { + book.package.metadata.direction === 'rtl' ? rendition.prev() : rendition.next(); + } break; case 'j': case 'ArrowLeft': case 'Left': - book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + if (this._loaded) { + book.package.metadata.direction === 'rtl' ? rendition.next() : rendition.prev(); + } break; case 'Escape': if (this._tocElement) { @@ -115,7 +162,9 @@ export class BookPlayer { } openTableOfContents() { - this._tocElement = new TableOfContent(this); + if (this._loaded) { + this._tocElement = new TableOfContent(this); + } } createMediaElement() { @@ -158,6 +207,14 @@ export class BookPlayer { setCurrentSrc(elem, options) { let item = options.items[0]; + this._currentItem = item; + this.streamInfo = { + started: true, + ended: false, + mediaSource: { + Id: item.Id + } + }; if (!item.Path.endsWith('.epub')) { return new Promise((resolve, reject) => { let errorDialog = dialogHelper.createDialog({ @@ -187,11 +244,34 @@ export class BookPlayer { this._currentSrc = downloadHref; this._rendition = rendition; + let cancellationToken = { + shouldCancel: false + }; + this._cancellationToken = cancellationToken; + return rendition.display().then(() => { + let epubElem = document.querySelector('.epub-container'); + epubElem.style.display = 'none'; + this.bindEvents(); - loading.hide(); - return resolve(); + return this._rendition.book.locations.generate(1024).then(() => { + if (cancellationToken.shouldCancel) { + return reject(); + } + + this._loaded = true; + epubElem.style.display = 'block'; + rendition.on('relocated', (locations) => { + this._progress = book.locations.percentageFromCfi(locations.start.cfi); + + events.trigger(this, 'timeupdate'); + }); + + loading.hide(); + + return resolve(); + }); }, () => { console.error('Failed to display epub'); return reject(); From 0908cbb64009bc9fc98b52e141d54d467e36c829 Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 29 May 2020 21:59:54 +0000 Subject: [PATCH 145/181] Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_MX/ --- src/strings/es-mx.json | 54 +++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 140addc8f9..3a711a558e 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -32,7 +32,7 @@ "AnamorphicVideoNotSupported": "Video anamorfico no soportado", "AnyLanguage": "Cualquier idioma", "Anytime": "En cualquier momento", - "AroundTime": "Alrededor de {0}", + "AroundTime": "Alrededor de", "Art": "Arte", "Artists": "Artistas", "AsManyAsPossible": "Tantos como sea posible", @@ -103,7 +103,7 @@ "ButtonRemove": "Remover", "ButtonRename": "Renombrar", "ButtonRepeat": "Repetir", - "ButtonResetEasyPassword": "Reiniciar el código pin sencillo", + "ButtonResetEasyPassword": "Restablecer código PIN sencillo", "ButtonResetPassword": "Restablecer contraseña", "ButtonRestart": "Reiniciar", "ButtonResume": "Continuar", @@ -203,7 +203,7 @@ "EnableBackdrops": "Imágenes de fondo", "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", "EnableCinemaMode": "Modo cine", - "EnableColorCodedBackgrounds": "Fondos de color codificados", + "EnableColorCodedBackgrounds": "Fondos de colores codificados", "EnableDisplayMirroring": "Duplicado de pantalla", "EnableExternalVideoPlayers": "Reproductores de video externos", "EnableExternalVideoPlayersHelp": "Un menú de reproductor externo se mostrara cuando inicie la reproducción de un video.", @@ -410,8 +410,8 @@ "HeaderRecordingOptions": "Opciones de grabación", "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", "HeaderRemoteControl": "Control remoto", - "HeaderRemoveMediaFolder": "Eliminar carpeta de medios", - "HeaderRemoveMediaLocation": "Eliminar ubicación de medios", + "HeaderRemoveMediaFolder": "Remover carpeta de medios", + "HeaderRemoveMediaLocation": "Remover ubicación de medios", "HeaderResponseProfile": "Perfil de respuesta", "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", "HeaderRestart": "Reiniciar", @@ -697,7 +697,7 @@ "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", - "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente.", + "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", "LabelOriginalAspectRatio": "Relación de aspecto original:", "LabelOriginalTitle": "Título original:", "LabelOverview": "Resumen:", @@ -878,7 +878,7 @@ "MessageConfirmDeleteTunerDevice": "¿Estás seguro de querer eliminar este dispositivo?", "MessageConfirmProfileDeletion": "¿Estás seguro de querer eliminar este perfil?", "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", - "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer eliminar esta ubicación?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer remover esta ubicación?", "MessageConfirmRestart": "¿Estás seguro de que deseas reiniciar el servidor Jellyfin?", "MessageConfirmRevokeApiKey": "¿Estás seguro de querer revocar esta clave API? La conexión de la aplicación con el servidor Jellyfin será terminada abruptamente.", "MessageConfirmShutdown": "¿Estás seguro de que deseas apagar el servidor?", @@ -912,7 +912,7 @@ "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar tu experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor Jellyfin, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", "MessageReenableUser": "Ver abajo para volver a habilitar", "MessageSettingsSaved": "Configuraciones guardadas.", - "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se eliminarán de tu biblioteca:", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se removerán de tu biblioteca:", "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utiliza el administrador de metadatos para establecer los tipos de contenido para las subcarpetas.", "MessageYouHaveVersionInstalled": "Actualmente cuentas con la versión {0} instalada.", @@ -1453,7 +1453,7 @@ "LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:", "LabelSize": "Tamaño:", "SelectAdminUsername": "Por favor, selecciona un nombre de usuario para la cuenta de administrador.", - "EnableFastImageFadeInHelp": "Habilita una animación más rápida de desvanecimiento para las imágenes cargadas", + "EnableFastImageFadeInHelp": "Habilita una animación más rápida de desvanecimiento para las imágenes cargadas.", "LabelDroppedFrames": "Cuadros saltados:", "CopyStreamURLError": "Hubo un error al copiar la URL.", "ButtonSplit": "Dividir", @@ -1484,7 +1484,7 @@ "MessageConfirmAppExit": "¿Deseas salir?", "LabelVideoResolution": "Resolución de video:", "LabelStreamType": "Tipo de transmisión:", - "EnableFastImageFadeIn": "Desvanecimiento rápido de las imágenes", + "EnableFastImageFadeIn": "Desvanecimiento rápido de animaciones", "LabelPlayerDimensions": "Dimensiones del reproductor:", "LabelCorruptedFrames": "Cuadros corruptos:", "HeaderNavigation": "Navegación", @@ -1524,5 +1524,37 @@ "HeaderRemoteAccessSettings": "Opciones de acceso remoto", "HeaderHttpsSettings": "Opciones HTTPS", "HeaderDVR": "DVR", - "ApiKeysCaption": "Lista de claves API actualmente habilitadas" + "ApiKeysCaption": "Lista de claves API actualmente habilitadas", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso que este usuario tiene a la función SyncPlay. SyncPlay permite sincronizar la reproducción con otros dispositivos.", + "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", + "MessageSyncPlayErrorMissingSession": "¡Fallo al activar SyncPlay! Falta la sesión.", + "MessageSyncPlayErrorNoActivePlayer": "No se ha encontrado ningún reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Se produjo un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "El acceso a este contenido está restringido.", + "MessageSyncPlayJoinGroupDenied": "Permiso requerido para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permiso requerido para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "Fallo al unirse al grupo porque éste no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Permiso de reproducción requerido.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Empieza a reproducir algo primero.", + "MessageSyncPlayGroupWait": "{0} está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Deshabilitado para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir al usuario unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir al usuario crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia de tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tiempo compensado respecto al servidor:", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "EnableDetailsBannerHelp": "Mostrar una imagen banner en la parte superior de la página de detalles del elemento.", + "EnableDetailsBanner": "Banner de detalles" } From 47bb80cd63c90850dcae4c06d77bcbbbc6a5606c Mon Sep 17 00:00:00 2001 From: artiume Date: Sat, 30 May 2020 01:36:55 +0000 Subject: [PATCH 146/181] Added translation using Weblate (Spanish (Latin America)) --- src/strings/es_419.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/strings/es_419.json diff --git a/src/strings/es_419.json b/src/strings/es_419.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/strings/es_419.json @@ -0,0 +1 @@ +{} From 719824a06c4e1a613ad99194c202a71359504e6e Mon Sep 17 00:00:00 2001 From: artiume Date: Sat, 30 May 2020 01:48:27 +0000 Subject: [PATCH 147/181] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 0967ef424b..90fa57533b 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -1 +1,28 @@ -{} +{ + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronizar", + "Songs": "Canciones", + "Shows": "Programas", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "Movies": "Películas", + "HeaderNextUp": "A continuación", + "HeaderLiveTV": "TV en vivo", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "Collections": "Colecciones", + "Channels": "Canales", + "Books": "Libros", + "Artists": "Artistas", + "Albums": "Álbumes", + "TabLatest": "Recientes", + "HeaderUser": "Usuario" +} From a9efe0200630f3d35c0073780d04d39d13dd02f9 Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:29:03 +0000 Subject: [PATCH 148/181] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 90fa57533b..d15d630b7e 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -24,5 +24,19 @@ "Artists": "Artistas", "Albums": "Álbumes", "TabLatest": "Recientes", - "HeaderUser": "Usuario" + "HeaderUser": "Usuario", + "AlbumArtist": "Artista del álbum", + "Album": "Álbum", + "Aired": "Transmitido", + "AirDate": "Fecha de emisión", + "AdditionalNotificationServices": "Explora el catálogo de complementos para instalar servicios de notificaciones adicionales.", + "AddedOnValue": "Agregado {0}", + "AddToPlaylist": "Agregar a lista de reproducción", + "AddToPlayQueue": "Agregar a la cola de reproducción", + "AddToCollection": "Agregar a colección", + "AddItemToCollectionHelp": "Agrega elementos a las colecciones buscándolos y utilizando sus menúes al hacer clic derecho o al tocarlos para agregarlos a una colección.", + "Add": "Agregar", + "Actor": "Actor", + "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", + "Absolute": "Absoluto" } From 4d8761593c9f0210edbfd42ae850c4884841085c Mon Sep 17 00:00:00 2001 From: Influence365 Date: Sat, 30 May 2020 09:43:30 +0100 Subject: [PATCH 149/181] Update dashboardgeneral.html --- src/dashboardgeneral.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboardgeneral.html b/src/dashboardgeneral.html index 03bfa9af8d..9ac0c0b0ca 100644 --- a/src/dashboardgeneral.html +++ b/src/dashboardgeneral.html @@ -62,7 +62,7 @@
${LabelLoginDisclaimerHelp}
-
+
${LabelCustomCssHelp}
From a032f6a35da5ad1430fbedaf246992e864d0e454 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Sat, 30 May 2020 09:44:23 +0100 Subject: [PATCH 150/181] Update emby-textarea.js --- src/elements/emby-textarea/emby-textarea.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index e0ce77aa51..8d5a272656 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -55,6 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } + $('.customCSS1').css("height",newHeight + 'px'); textarea.style.height = newHeight + 'px'; } From 6ce8c77d8ec3cc547589fe22685d68c50cab5853 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Sat, 30 May 2020 09:51:49 +0100 Subject: [PATCH 151/181] Update emby-textarea.js --- src/elements/emby-textarea/emby-textarea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 8d5a272656..8d01ff950b 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -55,7 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } - $('.customCSS1').css("height",newHeight + 'px'); + $('.customCSS').css("height",newHeight + 'px'); textarea.style.height = newHeight + 'px'; } From f0d337ee37c8542a7cc297af4232a7e4ccd9af7d Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 30 May 2020 19:51:03 +0900 Subject: [PATCH 152/181] fix default export for photo player --- src/components/photoPlayer/plugin.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/photoPlayer/plugin.js b/src/components/photoPlayer/plugin.js index f658e4c702..d8d4ee70ef 100644 --- a/src/components/photoPlayer/plugin.js +++ b/src/components/photoPlayer/plugin.js @@ -1,6 +1,6 @@ import connectionManager from 'connectionManager'; -export class PhotoPlayer { +export default class PhotoPlayer { constructor() { this.name = 'Photo Player'; this.type = 'mediaplayer'; @@ -43,5 +43,3 @@ export class PhotoPlayer { return (mediaType || '').toLowerCase() === 'photo'; } } - -export default PhotoPlayer; From 0c302adbb5970a94d498599ad434b407a3cd4ecc Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 13:00:51 +0200 Subject: [PATCH 153/181] Add format checking to EPUB player --- src/components/bookPlayer/plugin.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/bookPlayer/plugin.js b/src/components/bookPlayer/plugin.js index 66bcb46973..b655b038a8 100644 --- a/src/components/bookPlayer/plugin.js +++ b/src/components/bookPlayer/plugin.js @@ -215,24 +215,7 @@ export class BookPlayer { Id: item.Id } }; - if (!item.Path.endsWith('.epub')) { - return new Promise((resolve, reject) => { - let errorDialog = dialogHelper.createDialog({ - size: 'small', - autoFocus: false, - removeOnClose: true - }); - errorDialog.innerHTML = '

This book type is not supported yet

'; - - this.stop(); - - dialogHelper.open(errorDialog); - loading.hide(); - - return resolve(); - }); - } let serverId = item.ServerId; let apiClient = connectionManager.getApiClient(serverId); @@ -283,6 +266,13 @@ export class BookPlayer { canPlayMediaType(mediaType) { return (mediaType || '').toLowerCase() === 'book'; } + + canPlayItem(item) { + if (item.Path && (item.Path.endsWith('epub'))) { + return true; + } + return false; + } } export default BookPlayer; From 9535d94ab770089d7122d8702102340b6d85e2cf Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 13:15:49 +0200 Subject: [PATCH 154/181] Add Path field to List query Fixes the lack of play button on books --- src/components/playback/playbackmanager.js | 2 -- src/controllers/list.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 053088ef2f..88d4e0d599 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1129,7 +1129,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } self.canPlay = function (item) { - var itemType = item.Type; if (itemType === 'PhotoAlbum' || itemType === 'MusicGenre' || itemType === 'Season' || itemType === 'Series' || itemType === 'BoxSet' || itemType === 'MusicAlbum' || itemType === 'MusicArtist' || itemType === 'Playlist') { @@ -1143,7 +1142,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } if (itemType === 'Program') { - if (!item.EndDate || !item.StartDate) { return false; } diff --git a/src/controllers/list.js b/src/controllers/list.js index edd4469007..d05616ec9d 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -308,7 +308,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { StartIndex: startIndex, Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName', + Fields: 'PrimaryImageAspectRatio,SortName,Path', ImageTypeLimit: 1, ParentId: item.Id, SortBy: sortBy From d8375efc6918017a5fa4d3bb6a009c5b40c3641d Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 13:33:38 +0200 Subject: [PATCH 155/181] Add Path to home sections Fixes play button on home sections for books --- src/components/homesections/homesections.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 3cd30893b9..535f1cd687 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -229,7 +229,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var options = { Limit: limit, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', ImageTypeLimit: 1, EnableImageTypes: 'Primary,Backdrop,Thumb', ParentId: parentId @@ -667,7 +667,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la var apiClient = connectionManager.getApiClient(serverId); return apiClient.getNextUpEpisodes({ Limit: enableScrollX() ? 24 : 15, - Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo', + Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo,Path', UserId: apiClient.getCurrentUserId(), ImageTypeLimit: 1, EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', From 8a1ca94760db89e7f848a3e782b6505199fd2e75 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Sat, 30 May 2020 13:26:22 +0100 Subject: [PATCH 156/181] Update src/dashboardgeneral.html Co-authored-by: Julien Machiels --- src/dashboardgeneral.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboardgeneral.html b/src/dashboardgeneral.html index 9ac0c0b0ca..6c0f43eb34 100644 --- a/src/dashboardgeneral.html +++ b/src/dashboardgeneral.html @@ -62,7 +62,7 @@
${LabelLoginDisclaimerHelp}
-
+
${LabelCustomCssHelp}
From e6b7c8a6915540c7ef57ba833655b60174f46c63 Mon Sep 17 00:00:00 2001 From: Influence365 Date: Sat, 30 May 2020 13:27:06 +0100 Subject: [PATCH 157/181] Update emby-textarea.js --- src/elements/emby-textarea/emby-textarea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 8d01ff950b..58402843e9 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -55,7 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } - $('.customCSS').css("height",newHeight + 'px'); + $('.customCssContainer').css("height",newHeight + 'px'); textarea.style.height = newHeight + 'px'; } From 429f0bfe95ba0fe27dd85431fd4374af5ca21812 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Sat, 30 May 2020 18:45:40 +0200 Subject: [PATCH 158/181] Fix spacing --- src/elements/emby-textarea/emby-textarea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 58402843e9..e68b1dd149 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -55,7 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } - $('.customCssContainer').css("height",newHeight + 'px'); + $('.customCssContainer').css("height", newHeight + 'px'); textarea.style.height = newHeight + 'px'; } From 42b7fd9e4aa579d48bfbd18c41998339083f8a21 Mon Sep 17 00:00:00 2001 From: Dumitrache Alex Date: Sat, 30 May 2020 08:19:25 +0000 Subject: [PATCH 159/181] Translated using Weblate (Romanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ro/ --- src/strings/ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/ro.json b/src/strings/ro.json index 89aeda2ef2..4e1227d85b 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -250,7 +250,7 @@ "ButtonSubmit": "Trimite", "Collections": "Colecții", "AllowRemoteAccess": "Permite conexiuni externe către acest server Jellyfin.", - "AllowRemoteAccessHelp": "Dacă este nebifat, toate conexiunile externe vor fi blocate.", + "AllowRemoteAccessHelp": "Dacă este neselectat, toate conexiunile externe vor fi blocate.", "AlwaysPlaySubtitles": "Întotdeauna arată", "AnyLanguage": "Orice Limbă", "Anytime": "Oricând", From c52a1c0ae4236ed1200b7e828fe79631633c4a75 Mon Sep 17 00:00:00 2001 From: Julien Machiels Date: Sat, 30 May 2020 10:06:20 +0000 Subject: [PATCH 160/181] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/eo/ --- src/strings/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/eo.json b/src/strings/eo.json index 3151c9e434..5f0e658efa 100644 --- a/src/strings/eo.json +++ b/src/strings/eo.json @@ -1,5 +1,5 @@ { "AddToCollection": "Aldoni al kolekto", "Actor": "Aktoro", - "Absolute": "Absoluto" + "Absolute": "Absoluta" } From 7281b1dbbba742878807317b3b0eb5733a0f0ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A6=99=E5=91=80?= Date: Sat, 30 May 2020 15:41:11 +0000 Subject: [PATCH 161/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 0b51170196..d57c46395d 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1,5 +1,5 @@ { - "AccessRestrictedTryAgainLater": "目前访问受限。请稍后再试。", + "AccessRestrictedTryAgainLater": "目前访问受限,请稍后再试。", "Actor": "演员", "Add": "添加", "AddItemToCollectionHelp": "通过搜索项目并右键或轻触得到的弹出菜单来将项目添加到集合中。", @@ -1464,7 +1464,7 @@ "HeaderNavigation": "导航", "CopyStreamURLError": "复制URL地址时发生错误。", "MessageConfirmAppExit": "你要退出吗?", - "EnableFastImageFadeIn": "快速图片淡入", + "EnableFastImageFadeIn": "快速图片淡入淡出", "EnableFastImageFadeInHelp": "为已加载的图片启用更快的图片淡入动画", "OptionForceRemoteSourceTranscoding": "强制远程转码(像电视直播一样)", "NoCreatedLibraries": "看上去您还未创建任何资料库。{0} 您想现在创建一个吗? {1}", @@ -1524,5 +1524,25 @@ "LabelEnableHttps": "启用 HTTPS", "LabelChromecastVersion": "Chromecast版本", "HeaderDVR": "DVR", - "LabelNightly": "Nightly" + "LabelNightly": "Nightly", + "MessageSyncPlayErrorAccessingGroups": "访问群组列表时发生错误。", + "MessageSyncPlayLibraryAccessDenied": "搜限制的访问权限。", + "MessageSyncPlayCreateGroupDenied": "需要权限创建该组。", + "MessageSyncPlayGroupDoesNotExist": "无法加入群组,该群组不存在。", + "MessageSyncPlayPlaybackPermissionRequired": "需要播放权限。", + "MessageSyncPlayGroupWait": "{0} 正在缓冲...", + "MessageSyncPlayUserLeft": "{0} 已离开小组。", + "MessageSyncPlayUserJoined": "{0} 已加入该小组。", + "LabelSyncPlayAccessNone": "禁用此用户", + "LabelSyncPlayAccessJoinGroups": "允许用户加入群组", + "LabelSyncPlayAccessCreateAndJoinGroups": "允许用户创建和加入群组", + "LabelSyncPlayLeaveGroup": "离开组", + "LabelSyncPlayNewGroupDescription": "创建一个新组", + "LabelSyncPlayNewGroup": "新组", + "LabelSyncPlaySyncMethod": "同步方式:", + "MillisecondsUnit": "毫秒", + "LabelSyncPlayTimeOffset": "服务器时间偏移:", + "HeaderSyncPlayEnabled": "同步播放已启用", + "HeaderSyncPlaySelectGroup": "加入群组", + "EnableDetailsBannerHelp": "在项目详细信息页面的顶部显示横幅图片。" } From f32a1da9c8ef54dfc858916f39064a1b23cd0ffc Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:29:07 +0000 Subject: [PATCH 162/181] Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_419/ --- src/strings/es_419.json | 1510 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 1509 insertions(+), 1 deletion(-) diff --git a/src/strings/es_419.json b/src/strings/es_419.json index d15d630b7e..895c107e90 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -38,5 +38,1513 @@ "Add": "Agregar", "Actor": "Actor", "AccessRestrictedTryAgainLater": "El acceso está restringido actualmente. Por favor, inténtalo más tarde.", - "Absolute": "Absoluto" + "Absolute": "Absoluto", + "YadifBob": "YADIF Bob", + "Trailers": "Trailers", + "TabTrailers": "Trailers", + "ReleaseGroup": "Grupo que lo estrenó", + "OptionThumbCard": "Tarjeta miniatura", + "OptionResElement": "elemento res", + "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionBluray": "Blu-ray", + "OptionBlockTrailers": "Trailers", + "LabelNightly": "Nocturno", + "HeaderVideos": "Videos", + "Director": "Director", + "Depressed": "Presionado", + "BoxSet": "Box Set", + "UnsupportedPlayback": "Jellyfin no puede desencriptar contenido protegido por DRM de todas formas todo el contenido será intentado, incluyendo los títulos protegidos. Algunos archivos pueden aparecer completamente en negro debido al encriptado o características no soportadas, como títulos interactivos.", + "OnApplicationStartup": "Cuando se inicia la aplicación", + "EveryXHours": "Cada {0} horas", + "EveryHour": "Cada hora", + "EveryXMinutes": "Cada {0} minutos", + "OnWakeFromSleep": "Al despertar de la suspensión", + "WeeklyAt": "{0}s a las {1}", + "DailyAt": "Diariamente a las {0}", + "LastSeen": "Ultima vez visto {0}", + "PersonRole": "como {0}", + "ListPaging": "{0}-{1} de {2}", + "WriteAccessRequired": "El servidor Jellyfin requiere permiso de escritura en esta carpeta. Por favor, asegúrate de tener acceso de escritura e inténtalo de nuevo.", + "PathNotFound": "No se pudo encontrar la ruta. Por favor, asegúrate de que la ruta es válida e inténtalo de nuevo.", + "Yesterday": "Ayer", + "Yes": "Sí", + "Yadif": "YADIF", + "XmlTvSportsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas deportivos. Separa varios con un «|».", + "XmlTvPathHelp": "Una ruta a un archivo XMLTV. Jellyfin leerá este archivo y lo revisará periódicamente en busca de actualizaciones. Tú eres responsable de crear y actualizar el archivo.", + "XmlTvNewsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas de noticias. Separa varios con un «|».", + "XmlTvMovieCategoriesHelp": "Los programas con estas categorías serán mostrados como películas. Separa varios con un «|».", + "XmlTvKidsCategoriesHelp": "Los programas con estas categorías serán mostrados como programas infantiles. Separa varios con un «|».", + "XmlDocumentAttributeListHelp": "Estos atributos se aplican al elemento raíz de cada respuesta XML.", + "Writer": "Escritor", + "WizardCompleted": "Eso es todo lo que necesitamos por ahora. Jellyfin ha comenzado a recolectar información sobre tu biblioteca de medios. Revisa alguna de nuestras aplicaciones, y luego haz clic en Finalizar para ver el Panel de control.", + "Whitelist": "Lista blanca", + "WelcomeToProject": "¡Bienvenido a Jellyfin!", + "Wednesday": "Miércoles", + "Watched": "Visto", + "ViewPlaybackInfo": "Ver información de reproducción", + "ViewArtist": "Ver artista", + "ViewAlbum": "Ver álbum", + "VideoRange": "Rango de video", + "Vertical": "Vertical", + "ValueVideoCodec": "Códec de video: {0}", + "ValueTimeLimitSingleHour": "Límite de tiempo: 1 hora", + "ValueTimeLimitMultiHour": "Límite de tiempo: {0} horas", + "ValueSongCount": "{0} canciones", + "ValueSeriesCount": "{0} series", + "ValueSeconds": "{0} segundos", + "ValueOneSong": "1 canción", + "ValueOneSeries": "1 serie", + "ValueOneMusicVideo": "1 video musical", + "ValueOneMovie": "1 película", + "ValueOneEpisode": "1 episodio", + "ValueOneAlbum": "1 álbum", + "ValueMusicVideoCount": "{0} videos musicales", + "ValueMovieCount": "{0} películas", + "ValueMinutes": "{0} min", + "ValueEpisodeCount": "{0} episodios", + "ValueDiscNumber": "Disco {0}", + "ValueContainer": "Contenedor: {0}", + "ValueConditions": "Condiciones: {0}", + "ValueCodec": "Códec: {0}", + "ValueAudioCodec": "Códec de audio: {0}", + "ValueAlbumCount": "{0} álbumes", + "UserProfilesIntro": "Jellyfin incluye soporte para perfiles de usuario con detalladas configuraciones de pantalla, estado de reproducción y controles parentales.", + "UserAgentHelp": "Proporciona un encabezado HTTP de agente de usuario personalizado.", + "Upload": "Subir", + "Up": "Arriba", + "Unrated": "Sin clasificar", + "Unplayed": "No reproducido", + "Unmute": "Activar sonido", + "UninstallPluginHeader": "Desinstalar complemento", + "UninstallPluginConfirmation": "¿Estás seguro de querer desinstalar {0}?", + "Uniform": "Uniforme", + "TvLibraryHelp": "Revisa la {0}guía de nombrado de series de TV{1}.", + "Tuesday": "Martes", + "Transcoding": "Transcodificando", + "TrackCount": "{0} pistas", + "Track": "Pista", + "TitlePlayback": "Reproducción", + "TitleHostingSettings": "Configuraciones de alojamiento", + "TitleHardwareAcceleration": "Aceleración por hardware", + "Thursday": "Jueves", + "Thumb": "Miniatura", + "ThisWizardWillGuideYou": "Este asistente te guiará a través del proceso de instalación. Para comenzar, por favor, selecciona tu idioma preferido.", + "TheseSettingsAffectSubtitlesOnThisDevice": "Estas configuraciones afectan a los subtítulos en este dispositivo", + "ThemeVideos": "Videos temáticos", + "ThemeSongs": "Canciones temáticas", + "TellUsAboutYourself": "Háblanos de ti", + "TagsValue": "Etiquetas: {0}", + "Tags": "Etiquetas", + "TabUsers": "Usuarios", + "TabUpcoming": "Próximamente", + "TabTranscoding": "Transcodificación", + "TabSuggestions": "Sugerencias", + "TabStreaming": "Transmisión", + "TabSongs": "Canciones", + "TabShows": "Programas", + "TabSettings": "Configuración", + "TabServer": "Servidor", + "TabSeries": "Series", + "TabScheduledTasks": "Tareas programadas", + "TabResumeSettings": "Reanudar", + "TabResponses": "Respuestas", + "TabRecordings": "Grabaciones", + "TabProfiles": "Perfiles", + "TabProfile": "Perfil", + "TabPlugins": "Complementos", + "TabPlaylists": "Listas de reproducción", + "TabPlaylist": "Lista de reproducción", + "TabPlayback": "Reproducción", + "TabPassword": "Contraseña", + "TabParentalControl": "Control parental", + "TabOther": "Otros", + "TabNotifications": "Notificaciones", + "TabNfoSettings": "Configuración de NFO", + "TabNetworking": "Redes", + "TabNetworks": "Cadenas", + "TabMyPlugins": "Mis complementos", + "TabMusicVideos": "Videos musicales", + "TabMusic": "Música", + "TabMovies": "Películas", + "TabMetadata": "Metadatos", + "TabLogs": "Registros", + "TabLiveTV": "TV en vivo", + "TabInfo": "Información", + "TabGuide": "Guía", + "TabGenres": "Géneros", + "TabFavorites": "Favoritos", + "TabEpisodes": "Episodios", + "TabDVR": "DVR", + "TabDisplay": "Mostrar", + "TabDirectPlay": "Reproducción directa", + "TabDevices": "Dispositivos", + "TabDashboard": "Panel de control", + "TabContainers": "Contenedores", + "TabCollections": "Colecciones", + "TabCodecs": "Códecs", + "TabChannels": "Canales", + "TabCatalog": "Catálogo", + "OptionPoster": "Póster", + "OptionPlayed": "Reproducido", + "OptionPlayCount": "Contador de reproducciones", + "OptionPlainVideoItemsHelp": "Si se habilita, todos los videos serán representados en DIDL como «object.item.videoItem» en lugar de un tipo más específico, como «object.item.videoItem.movie».", + "OptionPlainVideoItems": "Mostrar todos los videos como elementos de video simples", + "OptionPlainStorageFoldersHelp": "Si se habilita, todos las carpetas serán representadas en DIDL como «object.container.storageFolder» en lugar de un tipo más específico, como «object.container.person.musicArtist».", + "OptionPlainStorageFolders": "Mostrar todas las carpetas como carpetas de almacenamiento simples", + "OptionParentalRating": "Clasificación parental", + "OptionOnInterval": "En un intervalo", + "OptionOnAppStartup": "Al iniciar la aplicación", + "OptionNone": "Ninguno", + "OptionNew": "Nuevo…", + "OptionNameSort": "Nombre", + "OptionMonday": "Lunes", + "OptionMissingEpisode": "Episodios faltantes", + "OptionMax": "Máximo", + "OptionLoginAttemptsBeforeLockoutHelp": "Un valor de cero significa heredar el valor predeterminado de tres intentos para los usuarios normales y cinco para los administradores. Ajustar esto a -1 deshabilitará la función.", + "OptionLoginAttemptsBeforeLockout": "Determina cuantos intentos de inicio de sesión incorrectos se pueden hacer antes de que ocurra el bloqueo.", + "OptionList": "Lista", + "OptionLikes": "Me gusta", + "OptionIsSD": "SD", + "OptionIsHD": "HD", + "OptionImdbRating": "Calificación de IMDb", + "OptionIgnoreTranscodeByteRangeRequestsHelp": "Si se habilita, estas solicitudes serán honradas pero se ignorará el encabezado de rango de bytes.", + "OptionIgnoreTranscodeByteRangeRequests": "Ignorar solicitudes de transcodificación de rango de bytes", + "OptionHomeVideos": "Fotos", + "OptionHlsSegmentedSubtitles": "Subtítulos segmentados HLS", + "OptionHideUserFromLoginHelp": "Útil para cuentas privadas o de administrador ocultas. El usuario tendrá que iniciar sesión manualmente introduciendo su nombre de usuario y contraseña.", + "OptionHideUser": "Ocultar este usuario de las pantallas de inicio de sesión", + "OptionHasTrailer": "Trailer", + "OptionHasThemeVideo": "Video temático", + "OptionHasThemeSong": "Canción temática", + "OptionHasSubtitles": "Subtítulos", + "OptionHasSpecialFeatures": "Características especiales", + "OptionFriday": "Viernes", + "OptionFavorite": "Favoritos", + "OptionExtractChapterImage": "Habilitar la extracción de imágenes de los capítulos", + "OptionExternallyDownloaded": "Descarga externa", + "OptionEveryday": "Todos los días", + "OptionEstimateContentLength": "Estimar la duración del contenido cuando se transcodifica", + "OptionEquals": "Igual a", + "OptionEnded": "Finalizado", + "OptionEnableM2tsModeHelp": "Habilita el modo m2ts cuando se codifican mpegts.", + "OptionEnableM2tsMode": "Habilitar modo M2TS", + "OptionEnableForAllTuners": "Habilitar para todos los dispositivos sintonizadores", + "OptionEnableExternalContentInSuggestionsHelp": "Permitir que los trailers de Internet y los programas de televisión en vivo se incluyan en el contenido sugerido.", + "OptionEnableExternalContentInSuggestions": "Habilitar contenido externo en las sugerencias", + "OptionEnableAccessToAllLibraries": "Habilitar acceso a todas las bibliotecas", + "OptionEnableAccessToAllChannels": "Habilitar acceso a todos los canales", + "OptionEnableAccessFromAllDevices": "Habilitar acceso desde todos los dispositivos", + "OptionEmbedSubtitles": "Incrustar dentro del contenedor", + "OptionDvd": "DVD", + "OptionDownloadThumbImage": "Miniatura", + "OptionDownloadPrimaryImage": "Principal", + "OptionDownloadMenuImage": "Menú", + "OptionDownloadLogoImage": "Logo", + "OptionDownloadImagesInAdvanceHelp": "Por defecto, la mayoría de las imágenes solo son descargadas cuando son solicitadas por una aplicación Jellyfin. Habilita esta opción para descargar todas las imágenes por adelantado, a medida que se agreguen nuevos medios. Esto podría causar escaneos de bibliotecas significativamente más largos.", + "OptionDownloadImagesInAdvance": "Descargar las imágenes con antelación", + "OptionDownloadDiscImage": "Disco", + "OptionDownloadBoxImage": "Caja", + "OptionDownloadBannerImage": "Banner", + "OptionDownloadBackImage": "Parte trasera", + "OptionDownloadArtImage": "Arte", + "OptionDisplayFolderViewHelp": "Muestra las carpetas junto con sus otras bibliotecas de medios. Esto puede ser útil si deseas tener una vista simple de carpeta.", + "OptionDisplayFolderView": "Mostrar una vista de carpetas para mostrar las carpetas simples de los medios", + "OptionDislikes": "No me gusta", + "OptionDisableUserHelp": "Si se desactiva, el servidor no aceptará conexiones de este usuario. Las conexiones existentes serán finalizadas abruptamente.", + "OptionDisableUser": "Desactivar este usuario", + "OptionDescending": "Descendente", + "OptionDatePlayed": "Fecha de reproducción", + "OptionDateAddedImportTime": "Usar la fecha de escaneo en la biblioteca", + "OptionDateAddedFileTime": "Usar la fecha de creación del archivo", + "OptionDateAdded": "Fecha de adición", + "OptionDaily": "Diario", + "OptionCustomUsers": "Personalizado", + "OptionCriticRating": "Calificación de los críticos", + "OptionContinuing": "Continuando", + "OptionCommunityRating": "Calificación de la comunidad", + "OptionBlockTvShows": "Programas de TV", + "OptionBlockMusic": "Música", + "OptionBlockMovies": "Películas", + "OptionBlockLiveTvChannels": "Canales de TV en vivo", + "OptionBlockChannelContent": "Contenido de canales de Internet", + "OptionBlockBooks": "Libros", + "OptionBanner": "Banner", + "OptionAutomaticallyGroupSeriesHelp": "Si se habilita, las series que se reparten a través de múltiples carpetas dentro de esta biblioteca serán fusionadas en una sola serie.", + "OptionAutomaticallyGroupSeries": "Fusionar automáticamente series esparcidas a través de múltiples carpetas", + "OptionAutomatic": "Automático", + "OptionAuto": "Automático", + "OptionAscending": "Ascendente", + "OptionArtist": "Artista", + "OptionAllowVideoPlaybackTranscoding": "Permitir la reproducción de video que requiera de transcodificación", + "OptionAllowVideoPlaybackRemuxing": "Permitir reproducción de video que requiera conversión sin recodificar", + "OptionAllowUserToManageServer": "Permitir a este usuario administrar el servidor", + "OptionAllowSyncTranscoding": "Permitir descarga y sincronización de medios que requieran transcodificación", + "OptionAllowRemoteSharedDevicesHelp": "Los dispositivos DLNA se considerarán compartidos hasta que un usuario comience a controlarlos.", + "OptionAllowRemoteSharedDevices": "Permitir control remoto de dispositivos compartidos", + "OptionAllowRemoteControlOthers": "Permitir control remoto de otros usuarios", + "OptionAllowMediaPlaybackTranscodingHelp": "Restringir el acceso a la transcodificación podría causar fallas en la reproducción en las aplicaciones Jellyfin debido a los formatos de medios no soportados.", + "OptionAllowMediaPlayback": "Permitir reproducción de medios", + "OptionAllowManageLiveTv": "Permitir gestión de grabación de TV en vivo", + "OptionAllowLinkSharingHelp": "Solo son compartidas páginas web que contienen información sobre los medios. Los archivos de medios nunca son compartidos públicamente. Los compartidos tienen un límite de tiempo y expirarán después de {0} días.", + "OptionAllowLinkSharing": "Permitir compartir medios en redes sociales", + "OptionAllowContentDownloading": "Permitir descarga y sincronización de medios", + "OptionAllowBrowsingLiveTv": "Permitir acceso a TV en vivo", + "OptionForceRemoteSourceTranscoding": "Forzar transcodificación de fuentes remotas (como TV en vivo)", + "OptionAllowAudioPlaybackTranscoding": "Permitir la reproducción de audio que requiera transcodificación", + "OptionAllUsers": "Todos los usuarios", + "OptionAlbumArtist": "Artista del álbum", + "OptionAlbum": "Álbum", + "OptionAdminUsers": "Administradores", + "Option3D": "3D", + "OnlyImageFormats": "Solo formatos de imagen (VOBSUB, PGS, SUB)", + "OnlyForcedSubtitlesHelp": "Solo se cargarán subtítulos marcados como forzados.", + "OnlyForcedSubtitles": "Solo forzados", + "OneChannel": "Un canal", + "Off": "Apagar", + "NumLocationsValue": "{0} carpetas", + "Normal": "Normal", + "None": "Ninguno", + "NoSubtitlesHelp": "Los subtítulos no serán cargados por defecto. Pueden ser activados manualmente durante la reproducción.", + "NoSubtitles": "Ninguno", + "NoSubtitleSearchResultsFound": "No se encontraron resultados.", + "NoPluginConfigurationMessage": "Este complemento no tiene configuraciones disponibles.", + "NoNextUpItemsMessage": "No se encontró nada. ¡Comienza a ver tus programas!", + "NoNewDevicesFound": "No se encontraron nuevos dispositivos. Para agregar un sintonizador nuevo, cierra este diálogo e introduce la información del dispositivo manualmente.", + "NoCreatedLibraries": "Parece que no has creado ninguna biblioteca todavía. {0}¿Quisieras crear una ahora?{1}", + "No": "No", + "NextUp": "A continuación", + "Next": "Siguiente", + "News": "Noticias", + "NewEpisodesOnly": "Solo episodios nuevos", + "NewEpisodes": "Episodios nuevos", + "NewCollectionNameExample": "Ejemplo: Colección Guerra de las Galaxias", + "NewCollectionHelp": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas y otros contenidos de la biblioteca.", + "NewCollection": "Nueva colección", + "Never": "Nunca", + "Name": "Nombre", + "MySubtitles": "Mis subtítulos", + "Mute": "Silenciar", + "MusicVideo": "Video musical", + "MusicLibraryHelp": "Revisar la {0}guía de nombrado de música{1}.", + "MusicArtist": "Artista musical", + "MusicAlbum": "Álbum de música", + "Movie": "Película", + "MovieLibraryHelp": "Revisar la {0}guía de nombrado de películas{1}.", + "MoveRight": "Mover a la derecha", + "MoveLeft": "Mover a la izquierda", + "MoreMediaInfo": "Información multimedia", + "MoreUsersCanBeAddedLater": "Más usuarios pueden ser añadidos más tarde desde el panel de control.", + "MoreFromValue": "Más de {0}", + "Monday": "Lunes", + "Mobile": "Móvil", + "MinutesBefore": "minutos antes", + "MinutesAfter": "minutos después", + "MetadataSettingChangeHelp": "Cambiar la configuración de los metadatos afectará al nuevo contenido que se añada en el futuro. Para actualizar el contenido existente, abre la pantalla de detalles y haz clic en el botón actualizar, o realiza actualizaciones masivas usando el administrador de metadatos.", + "MetadataManager": "Administrador de metadatos", + "Metadata": "Metadatos", + "MessageSyncPlayErrorMedia": "¡Fallo al activar SyncPlay! Error en el archivo de medios.", + "MessageSyncPlayErrorMissingSession": "¡Fallo al activar SyncPlay! Falta la sesión.", + "MessageSyncPlayErrorNoActivePlayer": "No se ha encontrado ningún reproductor activo. SyncPlay ha sido desactivado.", + "MessageSyncPlayErrorAccessingGroups": "Se produjo un error al acceder a la lista de grupos.", + "MessageSyncPlayLibraryAccessDenied": "El acceso a este contenido está restringido.", + "MessageSyncPlayJoinGroupDenied": "Permiso requerido para usar SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "Permiso requerido para crear un grupo.", + "MessageSyncPlayGroupDoesNotExist": "Fallo al unirse al grupo porque éste no existe.", + "MessageSyncPlayPlaybackPermissionRequired": "Permiso de reproducción requerido.", + "MessageSyncPlayNoGroupsAvailable": "No hay grupos disponibles. Empieza a reproducir algo primero.", + "MessageSyncPlayGroupWait": "{0} está cargando...", + "MessageSyncPlayUserLeft": "{0} abandonó el grupo.", + "MessageSyncPlayUserJoined": "{0} se ha unido al grupo.", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "MessageYouHaveVersionInstalled": "Actualmente cuentas con la versión {0} instalada.", + "MessageUnsetContentHelp": "El contenido será mostrado como carpetas simples. Para mejores resultados utiliza el administrador de metadatos para establecer los tipos de contenido para las subcarpetas.", + "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", + "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se removerán de tu biblioteca:", + "MessageSettingsSaved": "Configuraciones guardadas.", + "MessageReenableUser": "Ver abajo para volver a habilitar", + "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad Jellyfin son una gran forma de mejorar tu experiencia con Jellyfin con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor Jellyfin, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", + "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor, inicia sesión en tu servidor local directamente.", + "MessagePleaseWait": "Por favor, espera. Esto podría tomar un minuto.", + "MessagePleaseEnsureInternetMetadata": "Por favor, asegúrate de que la descarga de metadatos de Internet está habilitada.", + "MessagePlayAccessRestricted": "La reproducción de este contenido está actualmente restringida. Por favor, contacta al administrador del servidor para obtener más información.", + "MessagePasswordResetForUsers": "Los siguientes usuarios han restablecido sus contraseñas. Ahora pueden iniciar sesión con los códigos PIN que se usaron para realizar el restablecimiento.", + "MessageNothingHere": "Nada aquí.", + "MessageNoTrailersFound": "No se encontraron trailers. Instala el canal de trailers para mejorar tu experiencia con películas al agregar una biblioteca de trailers desde Internet.", + "MessageNoServersAvailable": "No se encontraron servidores utilizando el descubrimiento automático de servidores.", + "MessageNoPluginsInstalled": "No tienes complementos instalados.", + "MessageNoMovieSuggestionsAvailable": "No hay sugerencias de películas disponibles en este momento. Comienza a ver y a calificar tus películas, y luego regresa para ver tus recomendaciones.", + "MessageNoCollectionsAvailable": "Las colecciones te permiten disfrutar de agrupaciones personalizadas de películas, series y álbumes. Haz clic en el botón + para comenzar a crear colecciones.", + "MessageNoAvailablePlugins": "No hay complementos disponibles.", + "MessageLeaveEmptyToInherit": "Dejar vacío para heredar la configuración de un elemento superior o del valor predeterminado global.", + "MessageItemsAdded": "Elementos agregados.", + "MessageItemSaved": "Elemento guardado.", + "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en este momento. Por favor, contacta al administrador del servidor para más información.", + "MessageInvalidUser": "Nombre de usuario o contraseña inválidos. Por favor, intenta de nuevo.", + "MessageInvalidForgotPasswordPin": "Se ha introducido un código PIN inválido o expirado. Por favor, inténtalo de nuevo.", + "MessageInstallPluginFromApp": "Este complemento debe ser instalado desde dentro de la aplicación en la que deseas usarlo.", + "MessageImageTypeNotSelected": "Por favor, selecciona un tipo de imagen del menú desplegable.", + "MessageImageFileTypeAllowed": "Solo son soportados archivos JPEG y PNG.", + "MessageForgotPasswordInNetworkRequired": "Por favor, intenta de nuevo dentro de tu red local para iniciar el proceso de restablecimiento de contraseña.", + "MessageForgotPasswordFileCreated": "El siguiente archivo fue creado en tu servidor y contiene instrucciones de como proceder:", + "MessageFileReadError": "Hubo un error al leer el archivo. Por favor, intenta de nuevo.", + "MessageEnablingOptionLongerScans": "Habilitar esta opción podría resultar en escaneos de bibliotecas significativamente más largos.", + "MessageDownloadQueued": "Descarga puesta en la cola.", + "MessageDirectoryPickerLinuxInstruction": "Para Linux en Arch Linux, CentOS, Debian, Fedora, openSUSE o Ubuntu, debes conceder al usuario del servicio al menos permisos de lectura a tus ubicaciones de almacenamiento.", + "MessageDirectoryPickerBSDInstruction": "Para BSD, quizás necesites configurar el almacenamiento dentro de tu «FreeNAS Jail» de manera que permita a Jellyfin accederlo.", + "List": "Lista", + "LinksValue": "Enlaces: {0}", + "Like": "Me gusta", + "LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", + "LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.", + "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LaunchWebAppOnStartupHelp": "Abre el cliente web en su navegador web predeterminado cuando se inicia el servidor. Esto no ocurrirá cuando se utilice la función de reinicio del servidor.", + "LaunchWebAppOnStartup": "Iniciar la interfaz web al iniciar el servidor", + "LatestFromLibrary": "Últimas - {0}", + "Large": "Grande", + "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", + "LabelffmpegPathHelp": "La ruta hacia el archivo de la aplicación ffmpeg, o la carpeta que contenga ffmpeg.", + "LabelffmpegPath": "Ruta del FFmpeg:", + "LabelZipCode": "Código postal:", + "LabelYoureDone": "¡Has terminado!", + "LabelYourFirstName": "Tu nombre:", + "LabelYear": "Año:", + "LabelXDlnaDocHelp": "Determina el contenido del elemento X_DLNADOC en el namespace urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaDoc": "Documento X-DLNA:", + "LabelXDlnaCapHelp": "Determina el contenido del elemento X_DLNACAP en el namespace urn:schemas-dlna-org:device-1-0.", + "LabelXDlnaCap": "X-DLNA límite:", + "LabelWeb": "Web:", + "LabelVideoResolution": "Resolución de video:", + "LabelVideoCodec": "Códec de video:", + "LabelVideoBitrate": "Velocidad de bits de video:", + "LabelVideo": "Video", + "DashboardArchitecture": "Arquitectura: {0}", + "DashboardOperatingSystem": "Sistema operativo: {0}", + "DashboardServerName": "Servidor: {0}", + "DashboardVersionNumber": "Versión: {0}", + "LabelVersionInstalled": "{0} instalado", + "LabelVersion": "Versión:", + "LabelValue": "Valor:", + "LabelVaapiDeviceHelp": "Este es el nodo de renderizado que es usado para la aceleración por hardware.", + "LabelVaapiDevice": "Dispositivo VA API:", + "LabelUsername": "Nombre de usuario:", + "LabelUserRemoteClientBitrateLimitHelp": "Sobrescribe el valor global predeterminado establecido en la configuración de reproducción del servidor.", + "LabelUserLoginAttemptsBeforeLockout": "Intentos fallidos de inicio de sesión antes de que el usuario sea bloqueado:", + "LabelUserLibraryHelp": "Selecciona la biblioteca de usuario que se mostrará en el dispositivo. Déjalo vacío para heredar la configuración por defecto.", + "LabelUserLibrary": "Biblioteca de usuario:", + "LabelUserAgent": "Agente de usuario:", + "LabelUser": "Usuario:", + "LabelUseNotificationServices": "Usar los siguientes servicios:", + "LabelTypeText": "Texto", + "LabelTypeMetadataDownloaders": "Recolectores de metadatos para {0}:", + "LabelType": "Tipo:", + "LabelTunerType": "Tipo de sintonizador:", + "LabelTunerIpAddress": "Dirección IP del sintonizador:", + "LabelTriggerType": "Tipo de disparador:", + "LabelTranscodingVideoCodec": "Códec de video:", + "LabelTranscodingThreadCountHelp": "Selecciona el número máximo de hilos a utilizar al transcodificar. Reducir el número de hilos reducirá el uso de la CPU, pero es posible que no se convierta lo suficientemente rápido como para que la experiencia de reproducción sea fluida.", + "LabelTranscodingThreadCount": "Conteo de hilos de la transcodificación:", + "LabelTranscodingProgress": "Progreso de la transcodificación:", + "LabelTranscodingFramerate": "Velocidad de cuadros de la transcodificación:", + "LabelTranscodes": "Transcodificaciones:", + "LabelTranscodingTempPathHelp": "Especifica una ruta personalizada para los archivos de transcodificación servidos a los clientes. Deja en blanco para utilizar el predeterminado del servidor.", + "LabelTranscodePath": "Ruta de transcodificación:", + "LabelTranscodingContainer": "Contenedor:", + "LabelTranscodingAudioCodec": "Códec de audio:", + "LabelTrackNumber": "Número de pista:", + "LabelTitle": "Título:", + "LabelTimeLimitHours": "Límite de tiempo (horas):", + "LabelTime": "Hora:", + "LabelTheme": "Tema:", + "LabelTextSize": "Tamaño del texto:", + "LabelTextColor": "Color del texto:", + "LabelTextBackgroundColor": "Color de fondo para el texto:", + "LabelTagline": "Eslogan:", + "LabelTag": "Etiqueta:", + "LabelTVHomeScreen": "Modo de pantalla de TV:", + "LabelSyncPlayAccess": "Acceso a SyncPlay", + "LabelSyncPlayAccessNone": "Deshabilitado para este usuario", + "LabelSyncPlayAccessJoinGroups": "Permitir al usuario unirse a grupos", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permitir al usuario crear y unirse a grupos", + "LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay", + "LabelSyncPlayLeaveGroup": "Abandonar grupo", + "LabelSyncPlayNewGroupDescription": "Crear un nuevo grupo", + "LabelSyncPlayNewGroup": "Nuevo grupo", + "LabelSyncPlaySyncMethod": "Método de sincronización:", + "LabelSyncPlayPlaybackDiff": "Diferencia de tiempo de reproducción:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Tiempo compensado respecto al servidor:", + "LabelSupportedMediaTypes": "Tipos de medios soportados:", + "LabelSubtitles": "Subtítulos", + "LabelSubtitlePlaybackMode": "Modo de subtítulo:", + "LabelSubtitleFormatHelp": "Ejemplo: srt", + "LabelSubtitleDownloaders": "Recolectores de subtítulos:", + "LabelStreamType": "Tipo de transmisión:", + "LabelStopping": "Deteniendo", + "LabelStopWhenPossible": "Detener cuando sea posible:", + "LabelStatus": "Estado:", + "LabelStartWhenPossible": "Iniciar cuando sea posible:", + "LabelSportsCategories": "Categorías de deportes:", + "LabelSpecialSeasonsDisplayName": "Nombre de la temporada de especiales:", + "LabelSource": "Fuente:", + "LabelSoundEffects": "Efectos de sonido:", + "LabelSortTitle": "Título para ordenar:", + "LabelSortOrder": "Clasificar ordenado:", + "LabelSortBy": "Ordenar por:", + "LabelSonyAggregationFlagsHelp": "Determina el contenido del elemento aggregationFlags en el namespace urn:schemas-sonycom:av.", + "LabelSonyAggregationFlags": "Marcas de agregación Sony:", + "LabelSkipIfGraphicalSubsPresentHelp": "Mantener versiones de texto de subtítulos resultará en una entrega más eficiente y disminuirá las posibilidades de que un video sea transcodificado.", + "LabelSkipIfGraphicalSubsPresent": "Omitir si el video ya contiene subtítulos incrustados", + "LabelSkipIfAudioTrackPresentHelp": "Desactiva esto para asegurarte de que todos los videos tengan subtítulos, independientemente del idioma del audio.", + "LabelSkipIfAudioTrackPresent": "Omitir si la pista de audio por defecto coincide con el idioma de descarga", + "LabelSkipForwardLength": "Longitud de salto hacia adelante:", + "LabelSkipBackLength": "Longitud de salto hacia atrás:", + "Sunday": "Domingo", + "Suggestions": "Sugerencias", + "Subtitles": "Subtítulos", + "SubtitleOffset": "Desplazamiento de subtítulos", + "SubtitleDownloadersHelp": "Habilita y prioriza tus recolectores de subtítulos en orden de prioridad.", + "SubtitleAppearanceSettingsDisclaimer": "Estos ajustes no se aplicarán a los subtítulos gráficos (PGS, DVD, etc.) o a los subtítulos ASS/SSA que incorporen sus propios estilos.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Estos ajustes también aplican a cualquier reproducción en Chromecast iniciada por este dispositivo.", + "Studios": "Estudios", + "StopRecording": "Detener grabación", + "Sports": "Deportes", + "SortName": "Nombre para ordenar", + "SortChannelsBy": "Ordenar canales por:", + "SortByValue": "Ordenar por {0}", + "Sort": "Ordenar", + "SmartSubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados cuando el audio se encuentre en un idioma extranjero.", + "Smart": "Inteligente", + "Smaller": "Más pequeño", + "SmallCaps": "Mayúsculas pequeñas", + "Small": "Pequeño", + "SkipEpisodesAlreadyInMyLibraryHelp": "Los episodios serán comparados usando el numero de temporada y de episodio, cuando estén disponibles.", + "SkipEpisodesAlreadyInMyLibrary": "No grabar episodios que ya se encuentran en mi biblioteca", + "SimultaneousConnectionLimitHelp": "El numero máximo de transmisiones simultáneas permitidas. Ingresa 0 para sin límite.", + "Filter": "Filtro", + "New": "Nuevo", + "Shuffle": "Aleatorio", + "ShowYear": "Mostrar año", + "ShowTitle": "Mostrar título", + "ShowIndicatorsFor": "Mostrar indicadores para:", + "ShowAdvancedSettings": "Mostrar configuraciones avanzadas", + "Share": "Compartir", + "SettingsWarning": "Cambiar estos valores podría causar inestabilidad o fallas de conexión. Si experimentas cualquier problema, recomendamos volver a los valores por defecto.", + "SettingsSaved": "Configuración guardada.", + "Settings": "Configuración", + "ServerUpdateNeeded": "Este servidor Jellyfin necesita ser actualizado. Para descargar la última versión, por favor, visita {0}", + "ServerRestartNeededAfterPluginInstall": "El servidor Jellyfin necesitará reiniciarse después de instalar un complemento.", + "ServerNameIsShuttingDown": "El servidor Jellyfin - {0} se está apagando.", + "ServerNameIsRestarting": "El servidor Jellyfin - {0} se está reiniciando.", + "SeriesYearToPresent": "{0} - Actualidad", + "SeriesSettings": "Configuración de la serie", + "SeriesRecordingScheduled": "Grabación de series programadas.", + "SeriesDisplayOrderHelp": "Ordenar los episodios por fecha de emisión, orden del DVD o numeración absoluta.", + "SeriesCancelled": "Serie cancelada.", + "Series": "Series", + "SendMessage": "Enviar mensaje", + "SelectAdminUsername": "Por favor, selecciona un nombre de usuario para la cuenta de administrador.", + "Season": "Temporada", + "SearchResults": "Resultados de la búsqueda", + "SearchForSubtitles": "Buscar subtítulos", + "SearchForMissingMetadata": "Buscar metadatos faltantes", + "SearchForCollectionInternetMetadata": "Buscar en Internet por ilustraciones y metadatos", + "Search": "Buscar", + "Screenshots": "Capturas de pantalla", + "Screenshot": "Captura de pantalla", + "Schedule": "Programación", + "ScanLibrary": "Escanear biblioteca", + "ScanForNewAndUpdatedFiles": "Escanear por archivos nuevos y actualizados", + "SaveSubtitlesIntoMediaFoldersHelp": "Almacenar los subtítulos junto a los archivos de video permitirá administrarlos con más facilidad.", + "SaveSubtitlesIntoMediaFolders": "Guardar subtítulos en las carpetas de los medios", + "SaveChanges": "Guardar cambios", + "Save": "Guardar", + "Saturday": "Sábado", + "Runtime": "Duración", + "RunAtStartup": "Ejecutar al iniciar", + "Rewind": "Rebobinar", + "ResumeAt": "Reanudar desde {0}", + "RestartPleaseWaitMessage": "Por favor, espera mientras el servidor Jellyfin se apaga y reinicia. Esto puede tomar un minuto o dos.", + "ReplaceExistingImages": "Reemplazar imágenes existentes", + "ReplaceAllMetadata": "Reemplazar todos los metadatos", + "RepeatOne": "Repetir uno", + "RepeatMode": "Modo de repetición", + "RepeatEpisodes": "Repetir episodios", + "RepeatAll": "Repetir todos", + "Repeat": "Repetir", + "RemoveFromPlaylist": "Remover de la lista de reproducción", + "RemoveFromCollection": "Remover de la colección", + "RememberMe": "Recuérdame", + "ReleaseDate": "Fecha de estreno", + "RefreshQueued": "Actualización puesta en la cola.", + "RefreshMetadata": "Actualizar metadatos", + "RefreshDialogHelp": "Los metadatos son actualizados basándose en las configuraciones y servicios de Internet que estén activados en el panel de control de tu servidor Jellyfin.", + "Refresh": "Actualizar", + "Recordings": "Grabaciones", + "RecordingScheduled": "Grabación programada.", + "RecordingPathChangeMessage": "Cambiar la carpeta de grabaciones no moverá las grabaciones existentes de la antigua ubicación a la nueva. Necesitan moverse manualmente si se desea.", + "RecordingCancelled": "Grabación cancelada.", + "RecordSeries": "Grabar series", + "Record": "Grabar", + "RecommendationStarring": "Protagonizado por {0}", + "RecommendationDirectedBy": "Dirigido por {0}", + "RecommendationBecauseYouWatched": "Porque viste {0}", + "RecommendationBecauseYouLike": "Porque te gusta {0}", + "RecentlyWatched": "Visto recientemente", + "Rate": "Calificación", + "Raised": "Elevado", + "QueueAllFromHere": "Encolar todos desde aquí", + "Quality": "Calidad", + "Programs": "Programas", + "ProductionLocations": "Lugares de producción", + "Producer": "Productor", + "Primary": "Principal", + "Previous": "Anterior", + "Premieres": "Estrenos", + "Premiere": "Estreno", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir información del episodio incrustada a los nombres de archivo", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Esto utiliza la información del episodio desde los metadatos incrustados si están disponibles.", + "PreferEmbeddedTitlesOverFileNamesHelp": "Esto determina el título mostrado por defecto cuando no hay disponibles metadatos en Internet o localmente.", + "PreferEmbeddedTitlesOverFileNames": "Preferir títulos incrustados a los nombres de archivo", + "PluginInstalledMessage": "El complemento ha sido instalado con éxito. El servidor Jellyfin necesitará ser reiniciado para que los cambios surtan efecto.", + "PleaseSelectTwoItems": "Por favor, selecciona al menos dos elementos.", + "PleaseRestartServerName": "Por favor, reinicia el servidor Jellyfin - {0}.", + "PleaseEnterNameOrId": "Por favor, introduce un nombre o ID externo.", + "PleaseConfirmPluginInstallation": "Por favor, haz clic en OK para confirmar que has leído lo que se encuentra arriba y que deseas proceder con la instalación del complemento.", + "PleaseAddAtLeastOneFolder": "Por favor, agrega al menos una carpeta a esta biblioteca dando clic al botón Agregar.", + "Played": "Reproducido", + "PlaybackErrorNoCompatibleStream": "Este cliente no es compatible con los medios y el servidor no está enviando un formato de medios compatible.", + "PlayNextEpisodeAutomatically": "Reproducir el siguiente episodio automáticamente", + "PlayNext": "Reproducir siguiente", + "PlayFromBeginning": "Reproducir desde el inicio", + "PlayCount": "Cantidad de reproducciones", + "PlaybackData": "Datos de reproducción", + "PlayAllFromHere": "Reproducir todos desde aquí", + "Play": "Reproducir", + "PlaceFavoriteChannelsAtBeginning": "Colocar canales favoritos al inicio", + "PinCodeResetConfirmation": "¿Estás seguro de que quieres restablecer el código PIN?", + "PinCodeResetComplete": "El código PIN ha sido restablecido.", + "PictureInPicture": "Pantalla en pantalla", + "Person": "Persona", + "PerfectMatch": "Coincidencia perfecta", + "People": "Personas", + "PasswordSaved": "Contraseña guardada.", + "PasswordResetProviderHelp": "Elige un proveedor de restablecimiento de contraseña para usar cuando este usuario solicite un restablecimiento de contraseña", + "PasswordResetHeader": "Restablecer contraseña", + "PasswordResetConfirmation": "¿Estás seguro de querer restablecer la contraseña?", + "PasswordResetComplete": "La contraseña ha sido restablecida.", + "PasswordMatchError": "La contraseña y la confirmación de la contraseña deben coincidir.", + "ParentalRating": "Clasificación parental", + "PackageInstallFailed": "Instalación fallida de {0} (versión {1}).", + "PackageInstallCompleted": "Instalación completada de {0} (versión {1}).", + "PackageInstallCancelled": "Instalación cancelada de {0} (versión {1}).", + "Overview": "Resumen", + "OtherArtist": "Otro artista", + "OriginalAirDateValue": "Fecha de emisión original: {0}", + "OptionWeekly": "Semanal", + "OptionWeekends": "Fines de semana", + "OptionWeekdays": "Días de semana", + "OptionWednesday": "Miércoles", + "OptionWakeFromSleep": "Despertar de la suspensión", + "OptionUnplayed": "No reproducido", + "OptionUnairedEpisode": "Episodios no emitidos", + "OptionTvdbRating": "Calificación de TVDB", + "OptionTuesday": "Martes", + "OptionTrackName": "Nombre de la pista", + "OptionThumb": "Miniatura", + "OptionThursday": "Jueves", + "OptionSunday": "Domingo", + "OptionSubstring": "Subcadena", + "OptionSpecialEpisode": "Especiales", + "OptionSaveMetadataAsHiddenHelp": "Cambiar esto se aplicará a los nuevos metadatos guardados en el futuro. Los archivos de metadatos existentes serán actualizados la próxima vez que sean guardados por el servidor Jellyfin.", + "OptionSaveMetadataAsHidden": "Guardar metadatos e imágenes como archivos ocultos", + "OptionSaturday": "Sábado", + "OptionRuntime": "Duración", + "OptionResumable": "Reanudable", + "OptionRequirePerfectSubtitleMatchHelp": "Solicitar una coincidencia perfecta filtrará los subtítulos para incluir solo aquellos que han sido probados y verificados exactamente con tu archivo de video. Desmarcar esta opción incrementará las probabilidades de que se descarguen subtítulos, pero incrementará las posibilidades de obtener subtítulos mal sincronizados o con texto incorrecto.", + "OptionRequirePerfectSubtitleMatch": "Solo descargar subtítulos que coincidan perfectamente con mis archivos de video", + "OptionReportByteRangeSeekingWhenTranscodingHelp": "Esto es requerido para algunos dispositivos que no pueden hacer búsquedas de tiempo muy bien.", + "OptionReportByteRangeSeekingWhenTranscoding": "Reportar que el servidor soporta la búsqueda de bytes cuando se transcodifica", + "OptionReleaseDate": "Fecha de estreno", + "OptionRegex": "Expresión regular", + "OptionRandom": "Aleatorio", + "OptionProtocolHttp": "HTTP", + "OptionProtocolHls": "Transmisión en vivo por HTTP", + "OptionProfileVideoAudio": "Audio del video", + "OptionProfileVideo": "Video", + "OptionProfilePhoto": "Foto", + "OptionProfileAudio": "Audio", + "OptionPremiereDate": "Fecha de estreno", + "OptionPosterCard": "Ficha de póster", + "LabelSkin": "Apariencia:", + "LabelSize": "Tamaño:", + "LabelSimultaneousConnectionLimit": "Límite de transmisiones simultáneas:", + "LabelServerName": "Nombre del servidor:", + "LabelServerHostHelp": "192.168.1.100:8096 o https://miservidor.com", + "LabelServerHost": "Servidor:", + "LabelSeriesRecordingPath": "Ruta para las grabaciones de series (opcional):", + "LabelSerialNumber": "Número de serie", + "LabelSendNotificationToUsers": "Enviar la notificación a:", + "LabelSelectVersionToInstall": "Seleccionar versión a instalar:", + "LabelSelectUsers": "Seleccionar usuarios:", + "LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.", + "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:", + "LabelSeasonNumber": "Temporada número:", + "EnableFastImageFadeInHelp": "Habilita una animación más rápida de desvanecimiento para las imágenes cargadas.", + "EnableFastImageFadeIn": "Desvanecimiento rápido de animaciones", + "LabelScreensaver": "Protector de pantalla:", + "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", + "LabelSaveLocalMetadataHelp": "Guardar ilustraciones en las carpetas de los medios los colocará en un lugar donde se pueden editar fácilmente.", + "LabelSaveLocalMetadata": "Guardar las ilustraciones en las carpetas de los medios", + "LabelRuntimeMinutes": "Duración (minutos):", + "LabelRequireHttpsHelp": "Si se marca, el servidor redirigirá automáticamente todas las solicitudes a través de HTTP a HTTPS. Esto no tiene efecto si el servidor no está escuchando en HTTPS.", + "LabelRequireHttps": "Requerir HTTPS", + "LabelRemoteClientBitrateLimitHelp": "Un límite opcional de velocidad de bits por transmisión para todos los dispositivos fuera de la red. Esto es útil para evitar que los dispositivos soliciten una tasa de bits más alta de la que puede manejar tu conexión a Internet. Esto puede provocar un aumento de la carga de la CPU en el servidor para transcodificar los videos sobre la marcha a una velocidad de bits inferior.", + "LabelRemoteClientBitrateLimit": "Limite de velocidad de bits para la transmisión por Internet (Mbps):", + "LabelReleaseDate": "Fecha de estreno:", + "LabelRefreshMode": "Modo de actualización:", + "LabelRecordingPathHelp": "Especifica la ubicación por defecto para guardar las grabaciones. Si se deja en blanco, se usará la carpeta de datos de programa del servidor.", + "LabelRecordingPath": "Ruta por defecto para las grabaciones:", + "LabelRecord": "Grabar:", + "LabelReasonForTranscoding": "Motivo para transcodificar:", + "LabelReadHowYouCanContribute": "Aprende cómo puedes contribuir.", + "LabelPublicHttpsPortHelp": "El número de puerto público que debe asignarse al puerto HTTPS local.", + "LabelPublicHttpsPort": "Número de puerto HTTPS público:", + "LabelPublicHttpPortHelp": "El número de puerto público que debe asignarse al puerto HTTP local.", + "LabelPublicHttpPort": "Número de puerto HTTP público:", + "LabelProtocolInfoHelp": "El valor que será utilizado cuando se responda a solicitudes GetProtocolInfo desde el dispositivo.", + "LabelProtocolInfo": "Información del protocolo:", + "LabelProtocol": "Protocolo:", + "LabelProfileVideoCodecs": "Códecs de video:", + "LabelProfileContainersHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los contenedores.", + "LabelProfileContainer": "Contenedor:", + "LabelProfileCodecsHelp": "Separados por comas. Puede dejarse vacío para aplicarlo a todos los códecs.", + "LabelProfileCodecs": "Códecs:", + "LabelProfileAudioCodecs": "Códecs de audio:", + "LabelPrevious": "Anterior", + "LabelPreferredSubtitleLanguage": "Idioma preferido para los subtítulos:", + "LabelPreferredDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", + "LabelPreferredDisplayLanguage": "Idioma de pantalla preferido:", + "LabelPostProcessorArgumentsHelp": "Usar {path} como la ruta del archivo grabado.", + "LabelPostProcessorArguments": "Argumentos de la línea de comandos del post-procesador:", + "LabelPostProcessor": "Aplicación de postprocesamiento:", + "LabelPleaseRestart": "Los cambios tendrán efecto después de recargar manualmente el cliente web.", + "LabelPlayMethod": "Método de reproducción:", + "LabelPlaylist": "Lista de reproducción:", + "LabelPlayerDimensions": "Dimensiones del reproductor:", + "LabelPlayer": "Reproductor:", + "LabelPlayDefaultAudioTrack": "Reproducir la pista de audio por defecto independientemente del idioma", + "LabelPlaceOfBirth": "Lugar de nacimiento:", + "LabelPersonRoleHelp": "Ejemplo: Conductor de camión de helados", + "LabelPersonRole": "Rol:", + "LabelPath": "Ruta:", + "LabelPasswordRecoveryPinCode": "Código PIN:", + "LabelPasswordResetProvider": "Proveedor de restablecimiento de contraseña:", + "LabelPasswordConfirm": "Contraseña (confirmar):", + "LabelPassword": "Contraseña:", + "LabelParentalRating": "Clasificación parental:", + "LabelParentNumber": "Número antecesor:", + "LabelOverview": "Resumen:", + "LabelOriginalTitle": "Título original:", + "LabelOriginalAspectRatio": "Relación de aspecto original:", + "LabelOptionalNetworkPathHelp": "Si esta carpeta es compartida en su red, proveer la ruta del recurso compartido de red puede permitir a las aplicaciones Jellyfin en otros dispositivos acceder a los archivos de medios directamente. Por ejemplo, {0} o {1}.", + "LabelOptionalNetworkPath": "(Opcional) Carpeta de red compartida:", + "LabelNumberOfGuideDaysHelp": "Descargar más días de datos de programación permite programar con mayor anticipación y ver más listados, pero tomará más tiempo en descargar. Auto hará la selección basada en el número de canales.", + "LabelNumberOfGuideDays": "Número de días de datos de la programación a descargar:", + "LabelNumber": "Número:", + "LabelNotificationEnabled": "Habilitar esta notificación", + "LabelNext": "Siguiente", + "LabelNewsCategories": "Categorías de noticias:", + "LabelNewPasswordConfirm": "Confirmación de contraseña nueva:", + "LabelNewPassword": "Contraseña nueva:", + "LabelNewName": "Nuevo nombre:", + "LabelStable": "Estable", + "LabelChromecastVersion": "Versión de Chromecast", + "LabelName": "Nombre:", + "LabelMusicStreamingTranscodingBitrateHelp": "Especifica la velocidad de bits máxima al transmitir música.", + "LabelMusicStreamingTranscodingBitrate": "Velocidad de bits de transcodificación de música:", + "LabelMovieRecordingPath": "Ruta para las grabaciones de películas (opcional):", + "LabelMoviePrefixHelp": "Si un prefijo es aplicado al título de las películas, introdúcelo aquí para que el servidor pueda manejarlo correctamente.", + "LabelMoviePrefix": "Prefijo de la película:", + "LabelMovieCategories": "Categorías de películas:", + "LabelMonitorUsers": "Monitorear actividad desde:", + "LabelModelUrl": "URL del modelo", + "LabelModelNumber": "Número del modelo", + "LabelModelName": "Nombre del modelo", + "LabelModelDescription": "Descripción del modelo", + "LabelMinScreenshotDownloadWidth": "Anchura mínima de descarga de capturas de pantalla:", + "LabelMinResumePercentageHelp": "Los medios se asumen como no reproducidos si se detienen antes de este tiempo.", + "LabelMinResumePercentage": "Porcentaje mínimo para reanudar:", + "LabelMinResumeDurationHelp": "La duración de video más corta en segundos que guardará la ubicación de reproducción y te permitirá reanudarla.", + "LabelMinResumeDuration": "Duración mínima para la reanudación:", + "LabelMinBackdropDownloadWidth": "Anchura mínima de descarga de imágenes de fondo:", + "LabelMethod": "Método:", + "LabelMetadataSaversHelp": "Selecciona los formatos de archivo con los que se guardarán tus metadatos.", + "LabelMetadataSavers": "Grabadores de metadatos:", + "LabelMetadataReadersHelp": "Ordena tus fuentes de metadatos locales por prioridad. El primer archivo encontrado será leído.", + "LabelMetadataReaders": "Lectores de metadatos:", + "LabelMetadataPathHelp": "Especifique una ubicación personalizada para las ilustraciones y los metadatos descargados.", + "LabelKidsCategories": "Categorías infantiles:", + "LabelKeepUpTo": "Mantener hasta:", + "LabelInternetQuality": "Calidad en Internet:", + "LabelInNetworkSignInWithEasyPasswordHelp": "Utiliza el código PIN sencillo para acceder a los clientes en tu red local. Tu contraseña regular solo se necesitará fuera de casa. Si el código PIN se deja en blanco, no necesitarás una contraseña dentro de tu red local.", + "LabelInNetworkSignInWithEasyPassword": "Habilitar inicio de sesión con mi código PIN sencillo para conexiones dentro de la red", + "LabelImportOnlyFavoriteChannels": "Restringir a canales marcados como favoritos", + "LabelImageType": "Tipo de imagen:", + "LabelImageFetchersHelp": "Habilita y prioriza tus recolectores de imágenes preferidos.", + "LabelIdentificationFieldHelp": "Una subcadena indiferente a las mayúsculas y minúsculas o una expresión regular (regex).", + "LabelIconMaxWidthHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxWidth": "Ancho máximo del ícono:", + "LabelIconMaxHeightHelp": "Resolución máxima de los íconos expuestos vía upnp:icon.", + "LabelIconMaxHeight": "Altura máxima del ícono:", + "LabelHttpsPortHelp": "El número de puerto TCP al que el servidor HTTPS de Jellyfin debería enlazar.", + "LabelHttpsPort": "Número de puerto local HTTPS:", + "LabelHomeScreenSectionValue": "Sección {0} de la pantalla de inicio:", + "LabelHomeNetworkQuality": "Calidad en red local:", + "LabelHardwareAccelerationTypeHelp": "La aceleración por hardware requiere configuración adicional.", + "LabelHardwareAccelerationType": "Aceleración por hardware:", + "LabelEncoderPreset": "Codificación H264 y H265 preestablecida:", + "LabelH264Crf": "CRF de codificación H264:", + "LabelGroupMoviesIntoCollectionsHelp": "Cuando se muestran listados de películas, las películas que pertenecen a una colección serán mostradas agrupadas en un solo artículo.", + "LabelGroupMoviesIntoCollections": "Agrupar películas en colecciones", + "LabelServerNameHelp": "Este nombre se usará para identificar el servidor y se predeterminará al nombre de la computadora del servidor.", + "LabelFriendlyName": "Nombre amistoso:", + "LabelFormat": "Formato:", + "LabelForgotPasswordUsernameHelp": "Introduce tu nombre de usuario, si lo recuerdas.", + "LabelFont": "Fuente:", + "LabelFolder": "Carpeta:", + "LabelFinish": "Terminar", + "LabelFileOrUrl": "Archivo o URL:", + "LabelFailed": "Fallido", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera imágenes de los capítulos cuando se importan videos durante el escaneo de la biblioteca. De lo contrario, se extraerán durante la tarea programada imágenes de capítulos, lo que permitirá que el escaneado normal de la biblioteca se complete más rápidamente.", + "LabelExtractChaptersDuringLibraryScan": "Extraer las imágenes de los capítulos durante el escaneo de la biblioteca", + "LabelBaseUrlHelp": "Añade un subdirectorio personalizado a la URL del servidor. Por ejemplo: http://ejemplo.com/<urlbase>", + "LabelBaseUrl": "URL base:", + "LabelEveryXMinutes": "Cada:", + "LabelEvent": "Evento:", + "LabelEpisodeNumber": "Episodio número:", + "LabelEndDate": "Fecha de fin:", + "LabelEnableSingleImageInDidlLimitHelp": "Algunos dispositivos no se renderizarán correctamente si se incrustan varias imágenes en DIDL.", + "LabelEnableSingleImageInDidlLimit": "Limitar a una sola imagen incrustada", + "LabelEnableRealtimeMonitorHelp": "Los cambios en los archivos serán procesados inmediatamente, en los sistemas de archivo soportados.", + "LabelEnableRealtimeMonitor": "Activar monitoreo en tiempo real", + "LabelEnableHttpsHelp": "Permite al servidor escuchar en el puerto HTTPS configurado. Un certificado válido también debe ser configurado para que esto tenga efecto.", + "LabelEnableHttps": "Habilitar HTTPS", + "LabelEnableHardwareDecodingFor": "Habilitar decodificación por hardware para:", + "LabelEnableDlnaServerHelp": "Permite a dispositivos UPnP en tu red explorar y reproducir contenido.", + "LabelEnableDlnaServer": "Habilitar servidor DLNA", + "LabelEnableDlnaPlayToHelp": "Detecta dispositivos dentro de tu red y ofrece la capacidad de controlarlos remotamente.", + "LabelEnableDlnaPlayTo": "Habilitar Reproducir En mediante DLNA", + "LabelEnableDlnaDebugLoggingHelp": "Crea grandes archivos de registro y solo se debe usar cuando se requiera para solucionar problemas.", + "LabelEnableDlnaDebugLogging": "Habilitar el registro de depuración de DLNA", + "LabelEnableDlnaClientDiscoveryIntervalHelp": "Determina la duración en segundos entre búsquedas SSDP realizadas por Jellyfin.", + "LabelEnableDlnaClientDiscoveryInterval": "Intervalo de descubrimiento de clientes (segundos)", + "LabelEnableBlastAliveMessagesHelp": "Habilita esto si el servidor no es detectado de manera confiable por otros dispositivos UPnP en tu red.", + "LabelEnableBlastAliveMessages": "Bombardeo de mensajes de vida", + "LabelEnableAutomaticPortMapHelp": "Redirecciona automáticamente los puertos públicos de tu router a los puertos locales de tu servidor a través de UPnP. Esto puede no funcionar con algunos modelos de routers o configuraciones de red. Los cambios no se aplicarán hasta después de reiniciar el servidor.", + "LabelEnableAutomaticPortMap": "Habilitar mapeo automático de puertos", + "LabelEmbedAlbumArtDidlHelp": "Algunos dispositivos prefieren este método para obtener arte de álbumes. Otros podrían fallar al reproducir con esta opción habilitada.", + "LabelEmbedAlbumArtDidl": "Incrustar arte del álbum en DIDL", + "LabelEasyPinCode": "Código PIN sencillo:", + "LabelDynamicExternalId": "{0} Id:", + "LabelDropShadow": "Mostrar sombra:", + "LabelDroppedFrames": "Cuadros saltados:", + "LabelDropImageHere": "Arrastre la imagen aquí o haz para explorar.", + "LabelDownloadLanguages": "Descargar idiomas:", + "LabelDownMixAudioScaleHelp": "Incrementa el audio cuando se hace downmix. Un valor de 1 preservará el volumen original.", + "LabelDownMixAudioScale": "Incremento del audio cuando se hace downmix:", + "LabelDisplaySpecialsWithinSeasons": "Mostrar especiales dentro de las temporadas en las que fueron transmitidas", + "LabelDisplayOrder": "Orden para mostrar:", + "LabelDisplayName": "Nombre a mostrar:", + "LabelDisplayMode": "Modo de pantalla:", + "LabelDisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "LabelDisplayLanguageHelp": "La traducción de Jellyfin es un proyecto en curso.", + "LabelDisplayLanguage": "Idioma de pantalla:", + "LabelDiscNumber": "Número de disco:", + "LabelDidlMode": "Modo DIDL:", + "LabelDeviceDescription": "Descripción del dispositivo", + "LabelDeinterlaceMethod": "Método de desentrelazado:", + "LabelDefaultUserHelp": "Determina qué biblioteca de usuario será mostrada en los dispositivos conectados. Esto puede ser reemplazado para cada dispositivo empleando perfiles.", + "LabelDefaultUser": "Usuario por defecto:", + "LabelDefaultScreen": "Pantalla por defecto:", + "LabelDeathDate": "Fecha de defunción:", + "LabelDay": "Día:", + "LabelDateTimeLocale": "Configuración regional de fecha y hora:", + "LabelDateAddedBehaviorHelp": "Si un valor de metadatos está presente, siempre se utilizará antes de cualquiera de estas opciones.", + "LabelDateAddedBehavior": "Comportamiento de la fecha de adición para nuevo contenido:", + "LabelDateAdded": "Fecha de adición:", + "LabelDashboardTheme": "Tema del panel de control del servidor:", + "LabelCustomRating": "Calificación personalizada:", + "LabelCustomDeviceDisplayNameHelp": "Proporcione un nombre personalizado para mostrar o déjalo vacío para usar el nombre reportado por el dispositivo.", + "LabelCustomDeviceDisplayName": "Nombre a mostrar:", + "LabelCustomCssHelp": "Aplica tu propio estilo personalizado a la interfaz web.", + "LabelCustomCss": "CSS personalizado:", + "LabelCustomCertificatePathHelp": "Ruta a un archivo PKCS #12 que contiene un certificado y una clave privada para habilitar el soporte TLS en un dominio personalizado.", + "LabelCustomCertificatePath": "Ruta del certificado SSL personalizado:", + "LabelCurrentPassword": "Contraseña actual:", + "LabelCriticRating": "Calificación de los críticos:", + "LabelCountry": "País:", + "LabelCorruptedFrames": "Cuadros corruptos:", + "LabelContentType": "Tipo de contenido:", + "LabelCommunityRating": "Calificación de la comunidad:", + "LabelCollection": "Colección:", + "LabelChannels": "Canales:", + "LabelCertificatePasswordHelp": "Si tu certificado requiere una contraseña, por favor, introdúcela aquí.", + "LabelCertificatePassword": "Contraseña del certificado:", + "TabArtists": "Artistas", + "TabAlbums": "Álbumes", + "TabAlbumArtists": "Artistas del álbum", + "TabAdvanced": "Avanzado", + "TabAccess": "Acceso", + "TV": "TV", + "SystemDlnaProfilesHelp": "Los perfiles del sistema son de solo lectura. Los cambios a un perfil del sistema serán guardados en un nuevo perfil personalizado.", + "SyncPlayAccessHelp": "Selecciona el nivel de acceso que este usuario tiene a la función SyncPlay. SyncPlay permite sincronizar la reproducción con otros dispositivos.", + "MessageDeleteTaskTrigger": "¿Estás seguro de querer eliminar este disparador de tarea?", + "MessageCreateAccountAt": "Crear una cuenta en {0}", + "MessageContactAdminToResetPassword": "Por favor, contacta a tu administrador para restablecer tu contraseña.", + "MessageConfirmShutdown": "¿Estás seguro de que deseas apagar el servidor?", + "MessageConfirmRevokeApiKey": "¿Estás seguro de querer revocar esta clave API? La conexión de la aplicación con el servidor Jellyfin será terminada abruptamente.", + "MessageConfirmRestart": "¿Estás seguro de que deseas reiniciar el servidor Jellyfin?", + "MessageConfirmRemoveMediaLocation": "¿Estás seguro de querer remover esta ubicación?", + "MessageConfirmRecordingCancellation": "¿Cancelar grabación?", + "MessageConfirmProfileDeletion": "¿Estás seguro de querer eliminar este perfil?", + "MessageConfirmDeleteTunerDevice": "¿Estás seguro de querer eliminar este dispositivo?", + "MessageConfirmDeleteGuideProvider": "¿Estás seguro de querer eliminar este proveedor de guía?", + "MessageConfirmAppExit": "¿Deseas salir?", + "MessageAreYouSureYouWishToRemoveMediaFolder": "¿Estás seguro de querer remover esta carpeta de medios?", + "MessageAreYouSureDeleteSubtitles": "¿Estás seguro de querer eliminar este subtítulo?", + "MessageAlreadyInstalled": "Esta versión ya se encuentra instalada.", + "Menu": "Menú", + "MediaIsBeingConverted": "Los medios están siendo convertidos a un formato compatible con el dispositivo que está reproduciendo el medio.", + "MediaInfoStreamTypeVideo": "Video", + "MediaInfoStreamTypeSubtitle": "Subtítulo", + "MediaInfoStreamTypeEmbeddedImage": "Imagen incrustada", + "MediaInfoStreamTypeData": "Dato", + "MediaInfoStreamTypeAudio": "Audio", + "MediaInfoSoftware": "Software", + "MediaInfoTimestamp": "Fecha y hora", + "MediaInfoSize": "Tamaño", + "MediaInfoSampleRate": "Tasa de muestreo", + "MediaInfoResolution": "Resolución", + "MediaInfoRefFrames": "Tramas de referencia", + "MediaInfoProfile": "Perfil", + "MediaInfoPixelFormat": "Formato de pixel", + "MediaInfoPath": "Ruta", + "MediaInfoLevel": "Nivel", + "MediaInfoLayout": "Esquema", + "MediaInfoLanguage": "Idioma", + "MediaInfoInterlaced": "Entrelazado", + "MediaInfoFramerate": "Velocidad de cuadros", + "MediaInfoForced": "Forzado", + "MediaInfoExternal": "Externo", + "MediaInfoDefault": "Por defecto", + "MediaInfoContainer": "Contenedor", + "MediaInfoCodecTag": "Etiqueta de códec", + "MediaInfoCodec": "Códec", + "MediaInfoChannels": "Canales", + "MediaInfoBitrate": "Velocidad de bits", + "MediaInfoBitDepth": "Profundidad de bit", + "MediaInfoAspectRatio": "Relación de aspecto", + "MediaInfoAnamorphic": "Anamórfico", + "MaxParentalRatingHelp": "El contenido con una calificación más alta será ocultado a este usuario.", + "MarkUnplayed": "Marcar como no reproducido", + "MarkPlayed": "Marcar como reproducido", + "MapChannels": "Mapear canales", + "ManageRecording": "Administrar grabación", + "ManageLibrary": "Administrar biblioteca", + "Logo": "Logo", + "LiveTV": "TV en vivo", + "LiveBroadcasts": "Emisiones en vivo", + "Live": "En vivo", + "LabelCancelled": "Cancelado", + "LabelCachePathHelp": "Especifica una ubicación personalizada para los archivos caché del servidor como las imágenes. Dejar en blanco para utilizar la configuración por defecto del servidor.", + "LabelCachePath": "Ruta de la caché:", + "LabelCache": "Caché:", + "LabelBurnSubtitles": "Quemar subtítulos:", + "LabelBlockContentWithTags": "Bloquear elementos con las etiquetas:", + "LabelBlastMessageIntervalHelp": "Determina la duración en segundos del intervalo entre mensajes de vida.", + "LabelBlastMessageInterval": "Intervalo de mensajes de vida (segundos)", + "LabelBitrate": "Velocidad de bits:", + "LabelBirthYear": "Año de nacimiento:", + "LabelBirthDate": "Fecha de nacimiento:", + "LabelBindToLocalNetworkAddressHelp": "Opcional. Sobrescribe la dirección IP local a la que se vincula el servidor http. Si se deja vacío, el servidor se vinculará a todas las direcciones disponibles. Cambiar este valor requiere reiniciar el servidor Jellyfin.", + "LabelBindToLocalNetworkAddress": "Vincular a la dirección de red local:", + "LabelAutomaticallyRefreshInternetMetadataEvery": "Actualizar automáticamente los metadatos desde Internet:", + "LabelAuthProvider": "Proveedor de autenticación:", + "LabelAudioSampleRate": "Frecuencia de muestreo de audio:", + "LabelAudioLanguagePreference": "Idioma preferido de audio:", + "LabelAudioCodec": "Códec de audio:", + "LabelAudioChannels": "Canales de audio:", + "LabelAudioBitrate": "Velocidad de bits de audio:", + "LabelAudioBitDepth": "Profundidad de bits de audio:", + "LabelAudio": "Audio", + "LabelArtistsHelp": "Separar múltiples empleando ;", + "LabelArtists": "Artistas:", + "LabelAppNameExample": "Ejemplo: Sickbeard, Sonarr", + "LabelAppName": "Nombre de la aplicación", + "LabelAllowedRemoteAddressesMode": "Modo de filtrado de direcciones IP remotas:", + "LabelAllowedRemoteAddresses": "Filtro de direcciones IP remotas:", + "LabelAllowServerAutoRestartHelp": "El servidor solo se reiniciará durante los períodos de inactividad cuando no haya usuarios activos.", + "LabelAllowServerAutoRestart": "Permite al servidor reiniciarse automáticamente para aplicar actualizaciones", + "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "LabelAll": "Todos", + "LabelAlbumArtists": "Artistas del álbum:", + "LabelAlbumArtPN": "PN del arte del álbum:", + "LabelAlbumArtMaxWidthHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxWidth": "Ancho máximo del arte del álbum:", + "LabelAlbumArtMaxHeightHelp": "Resolución máxima del arte del álbum expuesta vía upnp:albumArtURI.", + "LabelAlbumArtMaxHeight": "Altura máxima del arte del álbum:", + "LabelAlbumArtHelp": "PN usado para el arte del álbum, dentro del atributo dlna:profileID en upnp:albumArtURI. Algunos dispositivos requieren valores específicos, independientemente del tamaño de la imagen.", + "LabelAlbum": "Álbum:", + "LabelAirsBeforeSeason": "Transmisión antes de la temporada:", + "LabelAirsBeforeEpisode": "Transmisión antes del episodio:", + "LabelAirsAfterSeason": "Transmisión después de la temporada:", + "LabelAirTime": "Duración:", + "LabelAirDays": "Se emite los días:", + "LabelAccessStart": "Hora de inicio:", + "LabelAccessEnd": "Hora de finalización:", + "LabelAccessDay": "Día de la semana:", + "LabelAbortedByServerShutdown": "(Abortado por apagado del servidor)", + "Label3DFormat": "Formato 3D:", + "Kids": "Niños", + "Items": "Elementos", + "ItemCount": "{0} elementos", + "InstantMix": "Mix instantáneo", + "InstallingPackage": "Instalando {0} (versión {1})", + "ImportMissingEpisodesHelp": "Si se habilita, la información sobre los episodios faltantes se importará a la base de datos de Jellyfin y se mostrarán dentro de las temporadas y series. Esto puede causar escaneos de biblioteca significativamente más largos.", + "ImportFavoriteChannelsHelp": "Si se habilita, solo los canales marcados como favoritos en el dispositivo sintonizador serán importados.", + "Images": "Imágenes", + "Identify": "Identificar", + "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", + "Horizontal": "Horizontal", + "Home": "Inicio", + "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de últimos medios", + "Hide": "Ocultar", + "Help": "Ayuda", + "HeadersFolders": "Carpetas", + "HeaderYears": "Años", + "HeaderXmlSettings": "Configuración XML", + "HeaderXmlDocumentAttributes": "Atributos del documento XML", + "HeaderXmlDocumentAttribute": "Atributo del documento XML", + "HeaderVideoTypes": "Tipos de video", + "HeaderVideoType": "Tipo de video", + "HeaderVideoQuality": "Calidad de video", + "HeaderUsers": "Usuarios", + "HeaderUploadImage": "Subir imagen", + "HeaderUpcomingOnTV": "Próximamente en TV", + "HeaderTypeText": "Introducir texto", + "HeaderTypeImageFetchers": "Recolectores de imágenes para {0}", + "HeaderTuners": "Sintonizador", + "HeaderTunerDevices": "Sintonizadores", + "HeaderTranscodingProfileHelp": "Agrega perfiles de transcodificación para indicar qué formatos deben ser usados cuando se requiere transcodificar.", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTracks": "Pistas", + "HeaderThisUserIsCurrentlyDisabled": "Este usuario se encuentra actualmente deshabilitado", + "HeaderTaskTriggers": "Disparadores de tarea", + "HeaderTags": "Etiquetas", + "HeaderSystemDlnaProfiles": "Perfiles del sistema", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "HeaderSyncPlaySelectGroup": "Unirse a un grupo", + "HeaderSubtitleProfilesHelp": "Los perfiles de subtítulo describen los formatos de subtítulo soportados por el dispositivo.", + "HeaderSubtitleProfiles": "Perfiles de subtítulo", + "HeaderSubtitleProfile": "Perfil de subtítulo", + "HeaderSubtitleDownloads": "Descarga de subtítulos", + "HeaderSubtitleAppearance": "Apariencia de subtítulos", + "HeaderStopRecording": "Detener grabación", + "HeaderStatus": "Estado", + "HeaderStartNow": "Iniciar ahora", + "HeaderSpecialFeatures": "Características especiales", + "HeaderSpecialEpisodeInfo": "Información del episodio especial", + "HeaderSortOrder": "Clasificar ordenado", + "HeaderSortBy": "Ordenar por", + "HeaderShutdown": "Apagar", + "HeaderSetupLibrary": "Configura tus bibliotecas de medios", + "HeaderSettings": "Configuración", + "HeaderServerSettings": "Configuración del servidor", + "HeaderSelectServerCachePathHelp": "Explora o introduce la ruta a utilizar para los archivos caché del servidor. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectServerCachePath": "Seleccionar ruta para la caché del servidor", + "HeaderSelectServer": "Seleccionar servidor", + "HeaderSelectPath": "Seleccionar ruta", + "HeaderSelectMetadataPathHelp": "Explora o introduce la ruta donde deseas almacenar los metadatos. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectMetadataPath": "Selecciona la ruta para los metadatos", + "HeaderSelectCertificatePath": "Selecciona la ruta del certificado", + "HeaderSecondsValue": "{0} segundos", + "HeaderSeasons": "Temporadas", + "HeaderSchedule": "Programación", + "HeaderScenes": "Escenas", + "HeaderRunningTasks": "Tareas en ejecución", + "HeaderRevisionHistory": "Historial de versiones", + "HeaderRestartingServer": "Reiniciando servidor", + "HeaderRestart": "Reiniciar", + "HeaderResponseProfileHelp": "Los perfiles de respuesta proporcionan un medio para personalizar la información enviada al dispositivo cuando se reproducen ciertos tipos de medios.", + "HeaderResponseProfile": "Perfil de respuesta", + "HeaderRemoveMediaLocation": "Remover ubicación de medios", + "HeaderRemoveMediaFolder": "Remover carpeta de medios", + "HeaderRemoteControl": "Control remoto", + "HeaderRemoteAccessSettings": "Opciones de acceso remoto", + "HeaderRecordingPostProcessing": "Post procesado de las grabaciones", + "HeaderRecordingOptions": "Opciones de grabación", + "HeaderRecentlyPlayed": "Reproducido recientemente", + "HeaderProfileServerSettingsHelp": "Estos valores controlan como el servidor Jellyfin se presentará al dispositivo.", + "HeaderProfileInformation": "Información del perfil", + "HeaderProfile": "Perfil", + "HeaderPreferredMetadataLanguage": "Idioma preferido para los metadatos", + "HeaderPluginInstallation": "Instalación de complemento", + "HeaderPleaseSignIn": "Por favor, inicia sesión", + "HeaderPlaybackError": "Error de reproducción", + "HeaderPlayback": "Reproducción de medios", + "HeaderPlayOn": "Reproducir en", + "HeaderPlayAll": "Reproducir todo", + "HeaderPinCodeReset": "Restablecer código PIN", + "HeaderPhotoAlbums": "Álbumes de fotos", + "HeaderPeople": "Personas", + "HeaderPendingInvitations": "Invitaciones pendientes", + "HeaderPaths": "Rutas", + "HeaderPasswordReset": "Restablecer contraseña", + "HeaderPassword": "Contraseña", + "HeaderParentalRatings": "Clasificación parental", + "HeaderOtherItems": "Otros elementos", + "HeaderOnNow": "Transmitiendo ahora", + "HeaderNextVideoPlayingInValue": "El siguiente video se reproducirá en {0}", + "HeaderNextEpisodePlayingInValue": "El siguiente episodio se reproducirá en {0}", + "HeaderNewDevices": "Nuevos dispositivos", + "HeaderNewApiKey": "Nueva clave API", + "HeaderNavigation": "Navegación", + "HeaderMyMediaSmall": "Mis medios (pequeño)", + "HeaderMyMedia": "Mis medios", + "HeaderMyDevice": "Mi dispositivo", + "HeaderMusicVideos": "Videos musicales", + "HeaderMusicQuality": "Calidad de la música", + "HeaderMovies": "Películas", + "HeaderMoreLikeThis": "Más como esto", + "HeaderMetadataSettings": "Configuración de metadatos", + "HeaderMediaInfo": "Info del medio", + "HeaderMediaFolders": "Carpetas de medios", + "HeaderMedia": "Medios", + "HeaderLoginFailure": "Falló el inicio de sesión", + "HeaderLiveTvTunerSetup": "Configuración del sintonizador de TV", + "HeaderLiveTv": "TV en vivo", + "HeaderLibrarySettings": "Configuraciones de biblioteca", + "HeaderLibraryOrder": "Orden de las bibliotecas", + "HeaderLibraryFolders": "Carpetas de bibliotecas", + "HeaderLibraryAccess": "Acceso a bibliotecas", + "HeaderLibraries": "Bibliotecas", + "HeaderLatestRecordings": "Últimas grabaciones", + "HeaderLatestMusic": "Última música", + "HeaderLatestMovies": "Últimas películas", + "HeaderLatestMedia": "Últimos medios", + "HeaderLatestEpisodes": "Últimos episodios", + "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edite una biblioteca en la configuración de bibliotecas de Jellyfin y ubica la sección grabadores de metadatos.", + "HeaderKeepSeries": "Conservar serie", + "HeaderKeepRecording": "Conservar grabación", + "HeaderItems": "Elementos", + "HeaderInstantMix": "Mix instantáneo", + "HeaderInstall": "Instalar", + "HeaderImageSettings": "Configuración de imagen", + "HeaderImageOptions": "Opciones de imagen", + "HeaderIdentifyItemHelp": "Introduce uno o más criterios de búsqueda. Elimina criterios para expandir los resultados.", + "HeaderIdentificationHeader": "Encabezado de identificación", + "HeaderIdentificationCriteriaHelp": "Introduzca, al menos, un criterio de identificación.", + "HeaderIdentification": "Identificación", + "HeaderHttpsSettings": "Opciones HTTPS", + "HeaderHttpHeaders": "Encabezados HTTP", + "HeaderHome": "Inicio", + "HeaderGuideProviders": "Proveedores de Guías de TV", + "HeaderGenres": "Géneros", + "HeaderFrequentlyPlayed": "Reproducido frecuentemente", + "HeaderForgotPassword": "Olvidé mi contraseña", + "HeaderForKids": "Para niños", + "HeaderFilters": "Filtros", + "HeaderFetcherSettings": "Configuración del recolector", + "HeaderFetchImages": "Obtener imágenes:", + "HeaderFeatures": "Características", + "HeaderFeatureAccess": "Acceso a características", + "HeaderFavoritePlaylists": "Listas de reproducción favoritas", + "HeaderFavoriteVideos": "Videos favoritos", + "LabelMetadataPath": "Ruta para los metadatos:", + "LabelMetadataDownloadersHelp": "Habilita y prioriza tus recolectores de metadatos preferidos. Los recolectores de metadatos de menor prioridad solo serán utilizados para llenar información faltante.", + "LabelMetadataDownloadLanguage": "Idioma preferido para las descargas:", + "LabelMetadata": "Metadatos:", + "LabelMessageTitle": "Título del mensaje:", + "LabelMessageText": "Texto del mensaje:", + "LabelMaxStreamingBitrateHelp": "Especifique una velocidad de bits máxima cuando se transmite.", + "LabelMaxStreamingBitrate": "Calidad máxima de transmisión:", + "LabelMaxScreenshotsPerItem": "Número máximo de capturas de pantalla por elemento:", + "LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.", + "LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:", + "LabelMaxParentalRating": "Máxima clasificación parental permitida:", + "LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:", + "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:", + "LabelMatchType": "Tipo de coincidencia:", + "LabelManufacturerUrl": "URL del fabricante", + "LabelManufacturer": "Fabricante:", + "LabelLogs": "Registros:", + "LabelLoginDisclaimerHelp": "Un mensaje que se mostrará en la parte inferior de la página de inicio de sesión.", + "LabelLoginDisclaimer": "Aviso legal:", + "LabelLockItemToPreventChanges": "Bloquear este elemento para evitar cambios futuros", + "LabelLocalHttpServerPortNumberHelp": "El número de puerto TCP al que el servidor HTTP de Jellyfin debería enlazar.", + "LabelLocalHttpServerPortNumber": "Número de puerto local HTTP:", + "LabelLineup": "Programación:", + "LabelLibraryPageSizeHelp": "Establece el número de elementos a mostrar en una página de biblioteca. Establece en 0 para deshabilitar el paginado.", + "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", + "LabelLanguage": "Idioma:", + "LabelLanNetworks": "Redes LAN:", + "LabelKodiMetadataUserHelp": "Guarda los datos de visto en archivos NFO para que otras aplicaciones los utilicen.", + "LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:", + "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.", + "LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO", + "LabelKodiMetadataEnablePathSubstitutionHelp": "Habilita la sustitución de rutas de imágenes usando la configuración de sustitución de rutas del servidor.", + "LabelKodiMetadataEnablePathSubstitution": "Habilitar sustitución de ruta", + "LabelKodiMetadataEnableExtraThumbsHelp": "Cuando se descargan imágenes estas pueden ser almacenadas tanto en extrafanart como extrathumb para maximizar la compatibilidad con las pieles de Kodi.", + "LabelKodiMetadataEnableExtraThumbs": "Copiar extrafanart al campo extrathumbs", + "LabelKodiMetadataDateFormatHelp": "Todas las fechas dentro de los archivos NFO serán analizadas usando este formato.", + "LabelKodiMetadataDateFormat": "Formato de fecha de estreno:", + "HeaderFavoritePeople": "Personas favoritas", + "HeaderFavoriteMovies": "Películas favoritas", + "HeaderFavoriteBooks": "Libros favoritos", + "HeaderExternalIds": "IDs externos:", + "HeaderError": "Error", + "HeaderEpisodes": "Episodios", + "HeaderEnabledFieldsHelp": "Desmarca un campo para bloquearlo y prevenir que sus datos sean cambiados.", + "HeaderEnabledFields": "Campos habilitados", + "HeaderEditImages": "Editar imágenes", + "HeaderEasyPinCode": "Código PIN sencillo", + "HeaderDVR": "DVR", + "HeaderDownloadSync": "Descargar y sincronizar", + "HeaderDisplay": "Pantalla", + "HeaderDirectPlayProfileHelp": "Agrega perfiles de reproducción directa para indicar qué formatos puede manejar el dispositivo de forma nativa.", + "HeaderDirectPlayProfile": "Perfil de reproducción directa", + "HeaderDevices": "Dispositivos", + "HeaderDeviceAccess": "Acceso a dispositivos", + "HeaderDeveloperInfo": "Info del desarrollador", + "HeaderDetectMyDevices": "Detectar mis dispositivos", + "HeaderDeleteTaskTrigger": "Borrar disparador de tarea", + "HeaderDeleteProvider": "Eliminar proveedor", + "HeaderDeleteItems": "Eliminar elementos", + "HeaderDeleteItem": "Eliminar elemento", + "HeaderDeleteDevice": "Eliminar dispositivo", + "HeaderDefaultRecordingSettings": "Configuración predeterminada de grabaciones", + "HeaderDateIssued": "Fecha de emisión", + "HeaderCustomDlnaProfiles": "Perfiles personalizados", + "HeaderContinueListening": "Continuar escuchando", + "HeaderContainerProfileHelp": "Los perfiles de contenedor indican las limitaciones de un dispositivo al reproducir formatos específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el formato ha sido configurado para reproducción directa.", + "HeaderContainerProfile": "Perfil del contenedor", + "HeaderConnectionFailure": "Falla de conexión", + "HeaderConnectToServer": "Conectarse al servidor", + "HeaderConfirmRevokeApiKey": "Revocar clave API", + "HeaderConfirmProfileDeletion": "Confirmar eliminación de perfil", + "HeaderConfirmPluginInstallation": "Confirmar instalación de complemento", + "HeaderConfigureRemoteAccess": "Configurar acceso remoto", + "HeaderCodecProfileHelp": "Los perfiles de códecs indican las limitaciones de un dispositivo al reproducir códecs específicos. Si una limitación se aplica entonces el medio será transcodificado, incluso si el códec ha sido configurado para reproducción directa.", + "HeaderCodecProfile": "Perfil de códec", + "HeaderChapterImages": "Imágenes de los capítulos", + "HeaderChannels": "Canales", + "HeaderChannelAccess": "Acceso a los canales", + "HeaderCastCrew": "Reparto y equipo", + "HeaderCastAndCrew": "Reparto y equipo", + "HeaderCancelSeries": "Cancelar serie", + "HeaderCancelRecording": "Cancelar grabación", + "HeaderBranding": "Establecer marca", + "HeaderBooks": "Libros", + "HeaderBlockItemsWithNoRating": "Bloquear elementos sin clasificación o con información de clasificación desconocida:", + "HeaderAutomaticUpdates": "Actualizaciones automáticas", + "HeaderAudioSettings": "Configuración de audio", + "HeaderAudioBooks": "Audiolibros", + "HeaderAppearsOn": "Aparece en", + "HeaderApp": "Aplicación", + "ApiKeysCaption": "Lista de claves API actualmente habilitadas", + "HeaderApiKeysHelp": "Las aplicaciones externas deben tener una clave API para poder comunicarse con el servidor Jellyfin. Las claves se emiten al iniciar sesión con una cuenta Jellyfin, o al otorgar manualmente una clave a la aplicación.", + "HeaderApiKeys": "Claves API", + "HeaderApiKey": "Clave API", + "HeaderAllowMediaDeletionFrom": "Permitir eliminación de medios de", + "HeaderAlert": "Alerta", + "HeaderAlbums": "Álbumes", + "HeaderAdmin": "Administrador", + "HeaderAdditionalParts": "Partes adicionales", + "HeaderAddUser": "Agregar usuario", + "HeaderAddUpdateImage": "Agregar/Actualizar Imagen", + "HeaderAddToPlaylist": "Agregar a lista de reproducción", + "HeaderAddToCollection": "Agregar a colección", + "HeaderAddScheduledTaskTrigger": "Agregar disparador", + "HeaderActivity": "Actividad", + "HeaderActiveRecordings": "Grabaciones activas", + "HeaderActiveDevices": "Dispositivos activos", + "HeaderAccessScheduleHelp": "Crea una programación de acceso para limitar el acceso a ciertos horarios.", + "HeaderAccessSchedule": "Programación de acceso", + "HardwareAccelerationWarning": "Habilitar la aceleración por hardware podría causar inestabilidad en algunos entornos. Asegúrate de que tu sistema operativo y controladores de video están actualizados. Si tienes dificultades reproduciendo videos después de habilitar esto, necesitarás volver a cambiar la configuración a Ninguno.", + "HDPrograms": "Programas en HD", + "EncoderPresetHelp": "Elige un valor más rápido para mejorar el rendimiento, o uno más lento para mejorar la calidad.", + "H264CrfHelp": "El «Factor de transferencia constante» (CRF) es la configuración de calidad por defecto para el codificador x264. Puedes establecer valores entre 0 y 51, donde los valores más bajos dan como resultado mejor calidad (a expensas de archivos más grandes). Los valores comunes son entre 18 y 28. El valor por defecto para x264 es 23, así que puedes usar este valor como punto de referencia.", + "GuideProviderSelectListings": "Elegir listados", + "GuideProviderLogin": "Iniciar sesión", + "Guide": "Guía", + "GuestStar": "Estrella invitada", + "GroupVersions": "Agrupar versiones", + "GroupBySeries": "Agrupar por series", + "Genre": "Género", + "General": "General", + "Fullscreen": "Pantalla completa", + "Friday": "Viernes", + "FormatValue": "Formato: {0}", + "FolderTypeUnset": "Contenido variado", + "FolderTypeTvShows": "Series de TV", + "FolderTypeMusicVideos": "Videos musicales", + "FolderTypeMusic": "Música", + "FolderTypeMovies": "Películas", + "FolderTypeBooks": "Libros", + "Filters": "Filtros", + "FileReadError": "Ha ocurrido un error al leer el archivo.", + "FileReadCancelled": "La lectura del archivo ha sido cancelada.", + "FileNotFound": "Archivo no encontrado.", + "File": "Archivo", + "FetchingData": "Obteniendo datos adicionales", + "Features": "Características", + "Favorite": "Favorito", + "FastForward": "Avance rápido", + "FFmpegSavePathNotFound": "No fue posible localizar FFmpeg usando la ruta que has introducido. FFprobe también es requerido y debe de estar en la misma carpeta. Estos componentes normalmente están empaquetados en la misma descarga. Por favor, verifica la ruta e inténtalo de nuevo.", + "Extras": "Extras", + "ExtractChapterImagesHelp": "La extracción de imágenes de capítulos permitirá a los clientes mostrar menús gráficos de selección de escenas. El proceso puede ser lento, intensivo en recursos y puede requerir varios gigabytes de espacio. Se ejecuta cuando se descubren los videos y también como una tarea programada cada noche. La programación es configurable en el área de tareas programadas. No se recomienda ejecutar esta tarea durante las horas de mayor uso.", + "ExtraLarge": "Extra grande", + "ExitFullscreen": "Salir de la pantalla completa", + "EveryNDays": "Cada {0} días", + "ErrorSavingTvProvider": "Hubo un error al guardar el proveedor de TV. Por favor, asegúrate de que sea accesible e inténtalo de nuevo.", + "ErrorPleaseSelectLineup": "Por favor, selecciona una programación e inténtalo de nuevo. Si no hay disponible ninguna, entonces, por favor, verifica que tu nombre de usuario, contraseña, y código postal sean correctos.", + "ErrorMessageStartHourGreaterThanEnd": "La hora de finalización debe ser mayor que la hora de inicio.", + "ErrorGettingTvLineups": "Hubo un error al descargar la programación de TV. Por favor, asegúrate de que tu información sea correcta e inténtalo de nuevo.", + "ErrorDeletingItem": "Hubo un error eliminando el elemento del servidor Jellyfin. Por favor, verifica que el servidor Jellyfin tiene permisos de escritura en la carpeta del medio e inténtalo de nuevo.", + "ErrorAddingXmlTvFile": "Hubo un error accediendo al archivo XMLTV. Por favor, asegúrate de que el archivo existe e inténtalo de nuevo.", + "ErrorAddingTunerDevice": "Hubo un error al agregar el dispositivo sintonizador. Por favor, asegúrate de que esté disponible e inténtalo de nuevo.", + "ErrorAddingMediaPathToVirtualFolder": "Hubo un error agregando la ruta de medios. Por favor, asegúrate de que la ruta es válida y que el proceso del servidor Jellyfin tiene acceso a ese destino.", + "ErrorAddingListingsToSchedulesDirect": "Hubo un error agregando la programación de tu cuenta de Schedules Direct. Schedules Direct solo permite un numero limitado de programaciones por cuenta. Tal vez necesites acceder al sitio web de Schedules Direct y eliminar otras programaciones de tu cuenta antes de continuar.", + "Episodes": "Episodios", + "Episode": "Episodio", + "EndsAtValue": "Termina a las {0}", + "Ended": "Finalizado", + "EnableDetailsBannerHelp": "Mostrar una imagen banner en la parte superior de la página de detalles del elemento.", + "EnableDetailsBanner": "Banner de detalles", + "EnableThemeVideosHelp": "Reproducir videos temáticos en el fondo mientras se navega por la biblioteca.", + "EnableThemeVideos": "Videos temáticos", + "EnableThemeSongsHelp": "Reproducir canciones temáticas en el fondo mientras se navega por la biblioteca.", + "EnableThemeSongs": "Canciones temáticas", + "EnableStreamLoopingHelp": "Habilita esta opción si las transmisiones en vivo contienen solo unos pocos segundos de datos y necesitan ser solicitadas continuamente. Habilitar esto cuando no es requerido puede causar problemas.", + "EnableStreamLooping": "Repetir automáticamente las transmisiones en vivo", + "EnablePhotosHelp": "Las imágenes serán detectadas y mostradas junto con otros archivos multimedia.", + "EnablePhotos": "Mostrar fotografías", + "EnableNextVideoInfoOverlayHelp": "Al finalizar un video, mostrar información sobre el siguiente video a reproducir en la lista de reproducción actual.", + "EnableNextVideoInfoOverlay": "Mostrar la información del siguiente video durante la reproducción", + "EnableHardwareEncoding": "Habilitar codificación por hardware", + "EnableExternalVideoPlayersHelp": "Un menú de reproductor externo se mostrara cuando inicie la reproducción de un video.", + "EnableExternalVideoPlayers": "Reproductores de video externos", + "EnableDisplayMirroring": "Duplicado de pantalla", + "EnableColorCodedBackgrounds": "Fondos de colores codificados", + "EnableCinemaMode": "Modo cine", + "EnableBackdropsHelp": "Muestra imágenes de fondo en el fondo de algunas páginas mientras se navega por la biblioteca.", + "EnableBackdrops": "Imágenes de fondo", + "EditSubtitles": "Editar subtítulos", + "EditMetadata": "Editar metadatos", + "EditImages": "Editar imágenes", + "Edit": "Editar", + "EasyPasswordHelp": "Tu código PIN fácil se utiliza para el acceso sin conexión en los clientes soportados y también puede utilizarse para acceder fácilmente cuando se está en la misma red.", + "DropShadow": "Sombra paralela", + "DrmChannelsNotImported": "Los canales con DRM no serán importados.", + "DownloadsValue": "{0} descargas", + "Download": "Descargar", + "Down": "Abajo", + "DoNotRecord": "No grabar", + "DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.", + "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", + "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", + "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como recientes o continuar viendo", + "DisplayInMyMedia": "Mostrar en la pantalla de inicio", + "Display": "Pantalla", + "Dislike": "No me gusta", + "Disconnect": "Desconectar", + "Disc": "DIsco", + "Disabled": "Desactivado", + "Directors": "Directores", + "DirectStreaming": "Transmisión directa", + "DirectStreamHelp2": "Transmitir directamente un archivo usa muy poco poder de procesamiento sin ninguna perdida en la calidad de video.", + "DirectStreamHelp1": "El medio es compatible con el dispositivo en cuanto a la resolución y tipo de medio (H.264, AC3, etc.), pero está en un contenedor de archivo incompatible (mkv, avi, wmv, etc.). El video será reempaquetado sobre la marcha antes de transmitirlo al dispositivo.", + "DirectPlaying": "Reproducción directa", + "DeviceAccessHelp": "Esto solo se aplica a los dispositivos que pueden ser identificados de manera única y no impedirá el acceso desde navegadores. Filtrar el acceso a los dispositivos de los usuarios les impedirá usar nuevos dispositivos hasta que hayan sido aprobados aquí.", + "DetectingDevices": "Detectando dispositivos", + "Desktop": "Escritorio", + "Descending": "Descendente", + "DeleteUserConfirmation": "¿Estás seguro de querer eliminar este usuario?", + "DeleteUser": "Eliminar usuario", + "DeleteMedia": "Eliminar medios", + "DeleteImageConfirmation": "¿Estás seguro de querer eliminar esta imagen?", + "DeleteImage": "Eliminar imagen", + "DeleteDeviceConfirmation": "¿Estás seguro de querer eliminar este dispositivo? Volverá a aparecer la siguiente vez que un usuario inicie sesión con él.", + "Delete": "Eliminar", + "DeinterlaceMethodHelp": "Seleccione el método de desentrelazado que se usará al transcodificar contenido entrelazado.", + "DefaultSubtitlesHelp": "Los subtítulos se cargan basados en los indicadores «por defecto» y «forzado» incluidos en los metadatos. Las preferencias de idioma son consideradas cuando hay múltiples opciones disponibles.", + "DefaultMetadataLangaugeDescription": "Estos son sus valores por defecto y pueden ser personalizados en cada biblioteca.", + "DefaultErrorMessage": "Ha ocurrido un error al procesar la solicitud. Por favor, inténtalo de nuevo más tarde.", + "Default": "Por defecto", + "DeathDateValue": "Falleció: {0}", + "DatePlayed": "Fecha de reproducción", + "DateAdded": "Fecha de adición", + "CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.", + "CriticRating": "Calificación de los críticos", + "CopyStreamURLError": "Hubo un error al copiar la URL.", + "CopyStreamURLSuccess": "URL copiada con éxito.", + "CopyStreamURL": "Copiar la URL de la transmisión", + "Continuing": "Continuando", + "ContinueWatching": "Continuar viendo", + "Connect": "Conectar", + "ConfirmEndPlayerSession": "¿Deseas apagar Jellyfin en {0}?", + "ConfirmDeletion": "Confirmar eliminación", + "HeaderServerAddressSettings": "Configuración de la dirección del servidor", + "HeaderSeriesStatus": "Estado de la serie", + "HeaderSeriesOptions": "Opciones de serie", + "HeaderSeries": "Series", + "HeaderSendMessage": "Enviar mensaje", + "HeaderSelectTranscodingPathHelp": "Explora o introduce la ruta a utilizar para los archivos temporales de transcodificación. Se debe tener permisos de escritura en dicha carpeta.", + "HeaderSelectTranscodingPath": "Selecciona la ruta para los archivos temporales de transcodificación", + "ConfirmDeleteItems": "Eliminar estos elementos los eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteItem": "Eliminar este elemento lo eliminará tanto del sistema como de tu biblioteca de medios. ¿Estás seguro de querer continuar?", + "ConfirmDeleteImage": "¿Eliminar imagen?", + "ConfigureDateAdded": "Configura cómo se determina la fecha de adición en el panel de control del servidor Jellyfin en la configuración de la biblioteca", + "Composer": "Compositor", + "CommunityRating": "Calificación de la comunidad", + "ColorTransfer": "Transferencia de color", + "ColorSpace": "Espacio de color", + "ColorPrimaries": "Colores primarios", + "ClientSettings": "Configuración del cliente", + "CinemaModeConfigurationHelp": "El modo cine lleva la experiencia del cine directamente a tu sala de estar con la capacidad de reproducir trailers e introducciones personalizadas antes de la función principal.", + "ChannelNumber": "Número de canal", + "ChannelNameOnly": "Canal {0} solamente", + "ChannelAccessHelp": "Selecciona los canales a compartir con este usuario. Los administradores podrán editar todos los canales empleando el administrador de metadatos.", + "ChangingMetadataImageSettingsNewContent": "Cambios en las configuraciones de descarga de metadatos o arte solo se aplicarán a contenido nuevo agregado a tu biblioteca. Para aplicar los cambios a los títulos existentes, necesitarás actualizar sus metadatos manualmente.", + "Categories": "Categorías", + "CancelSeries": "Cancelar serie", + "CancelRecording": "Cancelar grabación", + "ButtonWebsite": "Sitio web", + "ButtonViewWebsite": "Ver sitio web", + "ButtonUp": "Arriba", + "ButtonUninstall": "Desinstalar", + "ButtonTrailer": "Trailer", + "ButtonTogglePlaylist": "Lista de reproducción", + "ButtonToggleContextMenu": "Más", + "ButtonSubtitles": "Subtítulos", + "ButtonSubmit": "Enviar", + "ButtonSplit": "Dividir", + "ButtonStop": "Detener", + "ButtonStart": "Iniciar", + "ButtonSort": "Ordenar", + "ButtonSignOut": "Cerrar sesión", + "ButtonSignIn": "Iniciar sesión", + "ButtonShutdown": "Apagar", + "ButtonShuffle": "Aleatorio", + "ButtonSettings": "Configuración", + "ButtonSend": "Enviar", + "ButtonSelectView": "Seleccionar vista", + "ButtonSelectServer": "Seleccionar servidor", + "ButtonSelectDirectory": "Seleccionar directorio", + "ButtonSearch": "Búsqueda", + "ButtonScanAllLibraries": "Escanear todas las bibliotecas", + "ButtonSave": "Guardar", + "ButtonRevoke": "Revocar", + "ButtonResume": "Continuar", + "ButtonRestart": "Reiniciar", + "ButtonResetPassword": "Restablecer contraseña", + "ButtonResetEasyPassword": "Restablecer código PIN sencillo", + "ButtonRepeat": "Repetir", + "ButtonRename": "Renombrar", + "ButtonRemove": "Remover", + "ButtonRefreshGuideData": "Actualizar datos de la guía", + "ButtonRefresh": "Actualizar", + "ButtonQuickStartGuide": "Guía de inicio rápido", + "ButtonProfile": "Perfil", + "ButtonPreviousTrack": "Pista anterior", + "ButtonPlay": "Reproducir", + "ButtonPause": "Pausar", + "ButtonParentalControl": "Control parental", + "ButtonOpen": "Abrir", + "ButtonOk": "OK", + "ButtonOff": "Apagar", + "ButtonNextTrack": "Pista siguiente", + "ButtonNew": "Nuevo", + "ButtonNetwork": "Red", + "ButtonMore": "Más", + "ButtonManualLogin": "Inicio de sesión manual", + "ButtonLibraryAccess": "Acceso a biblioteca(s)", + "ButtonLearnMore": "Aprender más", + "ButtonInfo": "Info", + "ButtonHome": "Inicio", + "ButtonHelp": "Ayuda", + "ButtonGuide": "Guía", + "ButtonGotIt": "Hecho", + "ButtonFullscreen": "Pantalla completa", + "ButtonForgotPassword": "Olvidé mi contraseña", + "ButtonFilter": "Filtro", + "ButtonEditOtherUserPreferences": "Editar el perfil, la imagen y las preferencias personales de este usuario.", + "ButtonEditImages": "Editar imágenes", + "ButtonEdit": "Editar", + "ButtonDownload": "Descargar", + "ButtonDown": "Abajo", + "ButtonDeleteImage": "Eliminar imagen", + "ButtonDelete": "Eliminar", + "ButtonConnect": "Conectar", + "ButtonChangeServer": "Cambiar servidor", + "ButtonCancel": "Cancelar", + "ButtonBack": "Atrás", + "ButtonAudioTracks": "Pistas de audio", + "ButtonArrowUp": "Arriba", + "ButtonArrowRight": "Derecha", + "ButtonArrowLeft": "Izquierda", + "ButtonArrowDown": "Abajo", + "ButtonAddUser": "Agregar usuario", + "ButtonAddServer": "Agregar servidor", + "ButtonAddScheduledTaskTrigger": "Agregar disparador", + "ButtonAddMediaLibrary": "Agregar biblioteca de medios", + "ButtonAddImage": "Agregar imagen", + "ButtonAdd": "Agregar", + "BurnSubtitlesHelp": "Determina si el servidor debería quemar los subtítulos al transcodificar videos. Evitar esto mejorará altamente el rendimiento del servidor. Seleccione Auto para grabar formatos basados en imágenes (VOBSUB, PGS, SUB, IDX...) y ciertos subtítulos ASS o SSA.", + "BrowsePluginCatalogMessage": "Explora nuestro catálogo de complementos para ver los complementos disponibles.", + "Browse": "Explorar", + "BoxRear": "Caja (parte trasera)", + "Box": "Caja", + "BookLibraryHelp": "Los libros de texto y audiolibros están soportados. Revisa la {0} guía de nombrado de libros {1}.", + "Blacklist": "Lista negra", + "BirthPlaceValue": "Lugar de nacimiento: {0}", + "BirthLocation": "Lugar de nacimiento", + "BirthDateValue": "Nacimiento: {0}", + "Banner": "Banner", + "Backdrops": "Imágenes de fondo", + "Backdrop": "Imagen de fondo", + "AutoBasedOnLanguageSetting": "Auto (basado en la configuración del idioma)", + "Auto": "Auto", + "AuthProviderHelp": "Selecciona un proveedor de autenticación que se utilizará para autenticar la contraseña de este usuario.", + "Audio": "Audio", + "AttributeNew": "Nuevo", + "AspectRatio": "Relación de aspecto", + "AskAdminToCreateLibrary": "Pide a un administrador crear una biblioteca.", + "Ascending": "Ascendente", + "AsManyAsPossible": "Tantos como sea posible", + "Artist": "Artista", + "Art": "Arte", + "AroundTime": "Alrededor de", + "Anytime": "En cualquier momento", + "AnyLanguage": "Cualquier idioma", + "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados independientemente del idioma del audio.", + "AlwaysPlaySubtitles": "Siempre reproducir", + "AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP/máscaras de red para las redes a las que se les permitirá conectarse remotamente. Si se deja en blanco, se les permitirá a todas las direcciones remotas.", + "AllowRemoteAccessHelp": "Si no se marca, se bloquearán todas las conexiones remotas.", + "AllowRemoteAccess": "Permitir conexiones remotas a este servidor Jellyfin.", + "AllowFfmpegThrottlingHelp": "Cuando una transcodificación o remuxeado se adelanta lo suficiente de la posición de reproducción actual, se pausa el proceso para que consuma menos recursos. Esto es más útil cuando se mira sin buscar con frecuencia. Apaga esto si experimentas problemas de reproducción.", + "AllowFfmpegThrottling": "Regular transcodificaciones", + "AllowOnTheFlySubtitleExtractionHelp": "Los subtítulos incrustados pueden extraerse de los videos y entregarse a los clientes en texto plano para ayudar a evitar la transcodificación de video. En algunos sistemas, esto puede tardar mucho tiempo y provocar que la reproducción de video se detenga durante el proceso de extracción. Deshabilite esta opción para que los subtítulos incrustados se graben con transcodificación de video cuando no estén soportados de forma nativa por el dispositivo cliente.", + "AllowOnTheFlySubtitleExtraction": "Permitir la extracción de subtítulos sobre la marcha", + "AllowMediaConversionHelp": "Permitir o denegar acceso a la función de convertir medios.", + "AllowMediaConversion": "Permitir conversión de medios", + "AllowHWTranscodingHelp": "Permite al sintonizador transcodificar las transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.", + "AllLibraries": "Todas las bibliotecas", + "AllLanguages": "Todos los idiomas", + "AllEpisodes": "Todos los episodios", + "AllComplexFormats": "Todos los formatos complejos (ASS, SSA, VOBSUB, PGS, SUB, IDX...)", + "AllChannels": "Todos los canales", + "All": "Todo", + "Alerts": "Alertas" } From d1f327966ce0bfebe17cbf3b64de19c508efb47a Mon Sep 17 00:00:00 2001 From: millallo Date: Sat, 30 May 2020 16:01:03 +0000 Subject: [PATCH 163/181] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index 71d09eced7..d678cf6e0f 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -1455,8 +1455,8 @@ "MessageConfirmAppExit": "Vuoi uscire?", "HeaderNavigation": "Navigazione", "CopyStreamURLError": "Si è verificato un errore nel copiare l'indirizzo.", - "EnableFastImageFadeInHelp": "Abilita la dissolvenza veloce per le immagini caricate", - "EnableFastImageFadeIn": "Dissolvenza immagine veloce", + "EnableFastImageFadeInHelp": "Mostra i poster e le altre immagini con una dissolvenza veloce alla fine del caricamento.", + "EnableFastImageFadeIn": "Dissolvenza Immagine Veloce", "PlaybackErrorNoCompatibleStream": "Il client è incompatibile con il media e il server non sta inviando un formato compatibile.", "OptionForceRemoteSourceTranscoding": "Forza la transcodifica da fonti di media remoti (come LiveTV)", "NoCreatedLibraries": "Sembra che tu non abbia ancora creato delle librerie. {0}Vuoi crearne una adesso?{1}", @@ -1520,5 +1520,37 @@ "TabDVR": "DVR", "SaveChanges": "Salva modifiche", "HeaderDVR": "DVR", - "LabelNightly": "Nightly" + "LabelNightly": "Nightly", + "SyncPlayAccessHelp": "Scegli il livello d'accesso di questo utente a SyncPlay. SyncPlay ti permette di riprodurre contemporaneamente su diversi dispositivi.", + "MessageSyncPlayErrorMedia": "Impossibile abilitare SyncPlay! Errore media.", + "MessageSyncPlayErrorMissingSession": "Impossibile abilitare SyncPlay! Sessione mancante.", + "MessageSyncPlayErrorNoActivePlayer": "Nessun player attivo. SyncPlay è stato disabilitato.", + "MessageSyncPlayErrorAccessingGroups": "Errore durante l'accesso alla lista dei gruppi.", + "MessageSyncPlayLibraryAccessDenied": "L'accesso a questo contenuto è negato.", + "MessageSyncPlayJoinGroupDenied": "E' Necessario il permesso per l'utilizzo di SyncPlay.", + "MessageSyncPlayCreateGroupDenied": "E' necessario il permesso di creazione di un gruppo.", + "MessageSyncPlayGroupDoesNotExist": "Impossibile unirsi al gruppo perchè non esiste.", + "MessageSyncPlayPlaybackPermissionRequired": "Permesso di riproduzione necessario.", + "MessageSyncPlayNoGroupsAvailable": "Nessun gruppo disponibile. Inizia a riprodurre qualcosa.", + "MessageSyncPlayGroupWait": "{0} sta bufferizzando...", + "MessageSyncPlayUserLeft": "{0} ha lasciato il gruppo.", + "MessageSyncPlayUserJoined": "{0} si è unito al gruppo.", + "MessageSyncPlayDisabled": "SyncPlay disabilitato.", + "MessageSyncPlayEnabled": "SyncPlay abilitato.", + "LabelSyncPlayAccess": "Accesso SyncPlay", + "LabelSyncPlayAccessNone": "Disabilitato per questo utente", + "LabelSyncPlayAccessJoinGroups": "Permetti all'utente di unirsi ai gruppi", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permetti all'utente di creare e unirsi ai gruppi", + "LabelSyncPlayLeaveGroupDescription": "Disabilita SyncPlay", + "LabelSyncPlayLeaveGroup": "Lascia il gruppo", + "LabelSyncPlayNewGroupDescription": "Crea un nuovo gruppo", + "LabelSyncPlayNewGroup": "Nuovo gruppo", + "LabelSyncPlaySyncMethod": "Metodo Sync:", + "LabelSyncPlayPlaybackDiff": "Differenza oraria nella riproduzione:", + "MillisecondsUnit": "ms", + "LabelSyncPlayTimeOffset": "Differenza temporale con il server:", + "HeaderSyncPlayEnabled": "SyncPlay abilitato", + "HeaderSyncPlaySelectGroup": "Unisciti a un gruppo", + "EnableDetailsBannerHelp": "Mostra il banner nella parte superiore della pagina di dettaglio dell'elemento.", + "EnableDetailsBanner": "Banner Dettagli" } From c44c5556d5a6a0c0bfa248d39133763b1d670b41 Mon Sep 17 00:00:00 2001 From: Alexandre Badalo Date: Sat, 30 May 2020 20:33:38 +0000 Subject: [PATCH 164/181] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt/ --- src/strings/pt.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/pt.json b/src/strings/pt.json index e05e5bf668..370a1343ea 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -1102,13 +1102,13 @@ "AllowedRemoteAddressesHelp": "Lista separada por vírgula de endereços IP ou entradas de máscara de IP/rede para redes que terão permissão para se conectar remotamente. Se deixado em branco, todos os endereços remotos serão permitidos.", "AllowRemoteAccessHelp": "Se desmarcada, todas as conexões remotas serão bloqueadas.", "AllowRemoteAccess": "Permitir ligações remotas a este Servidor Jellyfin.", - "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes para evitar transcodificação. Em certos dispositivos, é uma operação demorada e pode causar paragens de reprodução durante o processo de extracção. Desactive esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", + "AllowOnTheFlySubtitleExtractionHelp": "Legendas integradas podem ser extraídas do vídeo e enviadas como texto simples para os clientes de forma a evitar transcodificação. Em certos dispositivos, esta operação pode demorar algum tempo e causar paragens de reprodução durante o processo de extração. Desative esta opção para que as legendas sejam integradas no vídeo durante a conversão para um formato suportado pelo dispositivo de destino.", "AllowOnTheFlySubtitleExtraction": "Permitir a extração de legendas em tempo real", "AllowHWTranscodingHelp": "Permita que o sintonizador transcodifique os fluxos em tempo real. Isso pode ajudar a reduzir a transcodificação exigida pelo servidor.", "AllLibraries": "Todas as bibliotecas", "AllLanguages": "Todos os idiomas", "AllEpisodes": "Todos os episódios", - "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB/IDX, etc.)", + "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", "AllChannels": "Todos os canais", "All": "Todos", "Alerts": "Alertas", From 55a049678eceac99de6e7f0b005230a9a514377d Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 18:55:57 +0200 Subject: [PATCH 165/181] Remove non-standalone mode --- gulpfile.js | 34 ++++++++-------------------------- package.json | 1 - 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 538497d4d0..552dbf70c1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,7 +16,6 @@ const stream = require('webpack-stream'); const inject = require('gulp-inject'); const postcss = require('gulp-postcss'); const sass = require('gulp-sass'); -const gulpif = require('gulp-if'); const lazypipe = require('lazypipe'); sass.compiler = require('node-sass'); @@ -131,18 +130,12 @@ function javascript(query) { .pipe(browserSync.stream()); } -function apploader(standalone) { - function task() { - return src(options.apploader.query, { base: './src/' }) - .pipe(gulpif(standalone, concat('scripts/apploader.js'))) - .pipe(pipelineJavascript()) - .pipe(dest('dist/')) - .pipe(browserSync.stream()); - } - - task.displayName = 'apploader'; - - return task; +function apploader() { + return src(options.apploader.query, { base: './src/' }) + .pipe(concat('scripts/apploader.js')) + .pipe(pipelineJavascript()) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); } function webpack() { @@ -181,12 +174,6 @@ function copy(query) { .pipe(browserSync.stream()); } -function copyIndex() { - return src(options.injectBundle.query, { base: './src/' }) - .pipe(dest('dist/')) - .pipe(browserSync.stream()); -} - function injectBundle() { return src(options.injectBundle.query, { base: './src/' }) .pipe(inject( @@ -196,10 +183,5 @@ function injectBundle() { .pipe(browserSync.stream()); } -function build(standalone) { - return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); -} - -exports.default = series(build(false), copyIndex); -exports.standalone = series(build(true), injectBundle); -exports.serve = series(exports.standalone, serve); +exports.default = series(clean, parallel(javascript, apploader, webpack, css, html, images, copy), injectBundle); +exports.serve = series(exports.default, serve); diff --git a/package.json b/package.json index d63b870696..a06fad3aea 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,6 @@ "prepare": "gulp --production", "build:development": "gulp --development", "build:production": "gulp --production", - "build:standalone": "gulp standalone --development", "lint": "eslint \".\"", "stylelint": "stylelint \"src/**/*.css\"" } From 88a39a10e5c9e082d1d0e2fe7ca83b9c6ad08585 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 19:49:19 +0200 Subject: [PATCH 166/181] Fix bug reporter by sonarqube --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 552dbf70c1..03826e8b6e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -67,7 +67,7 @@ function serve() { } }); - watch(options.apploader.query, apploader(true)); + watch(options.apploader.query, apploader()); watch('src/bundle.js', webpack); From e2ae0d1e2bd08f4ebcd9d5f51f3a58700e4a4a04 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 23:15:06 +0200 Subject: [PATCH 167/181] Fix linting errors --- .eslintrc.js | 44 ++++++++++----------- src/elements/emby-textarea/emby-textarea.js | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fa1d440d46..27b5c2a237 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,30 +27,30 @@ module.exports = { 'plugin:compat/recommended' ], rules: { - 'block-spacing': ["error"], - 'brace-style': ["error"], - 'comma-dangle': ["error", "never"], - 'comma-spacing': ["error"], - 'eol-last': ["error"], - 'indent': ["error", 4, { "SwitchCase": 1 }], - 'keyword-spacing': ["error"], - 'max-statements-per-line': ["error"], - 'no-floating-decimal': ["error"], - 'no-multi-spaces': ["error"], - 'no-multiple-empty-lines': ["error", { "max": 1 }], - 'no-trailing-spaces': ["error"], - 'one-var': ["error", "never"], - 'quotes': ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": false }], - 'semi': ["error"], - 'space-before-blocks': ["error"], - "space-infix-ops": "error" + 'block-spacing': ['error'], + 'brace-style': ['error'], + 'comma-dangle': ['error', 'never'], + 'comma-spacing': ['error'], + 'eol-last': ['error'], + 'indent': ['error', 4, { 'SwitchCase': 1 }], + 'keyword-spacing': ['error'], + 'max-statements-per-line': ['error'], + 'no-floating-decimal': ['error'], + 'no-multi-spaces': ['error'], + 'no-multiple-empty-lines': ['error', { 'max': 1 }], + 'no-trailing-spaces': ['error'], + 'one-var': ['error', 'never'], + 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], + 'semi': ['error'], + 'space-before-blocks': ['error'], + 'space-infix-ops': 'error' }, overrides: [ { files: [ './src/**/*.js' ], - parser: "babel-eslint", + parser: 'babel-eslint', env: { node: false, amd: true, @@ -98,11 +98,11 @@ module.exports = { }, rules: { // TODO: Fix warnings and remove these rules - 'no-redeclare': ["warn"], - 'no-unused-vars': ["warn"], - 'no-useless-escape': ["warn"], + 'no-redeclare': ['warn'], + 'no-unused-vars': ['warn'], + 'no-useless-escape': ['warn'], // TODO: Remove after ES6 migration is complete - 'import/no-unresolved': ["off"] + 'import/no-unresolved': ['off'] }, settings: { polyfills: [ diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index e68b1dd149..87a3d7fcee 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -55,7 +55,7 @@ define(['layoutManager', 'browser', 'css!./emby-textarea', 'registerElement', 'e newHeight = textarea.scrollHeight/* - offset*/; hasGrown = true; } - $('.customCssContainer').css("height", newHeight + 'px'); + $('.customCssContainer').css('height', newHeight + 'px'); textarea.style.height = newHeight + 'px'; } From 1b69bad510bcf53edc5c6e94264d552fff4d1999 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 30 May 2020 23:18:10 +0200 Subject: [PATCH 168/181] Fix CI --- .ci/azure-pipelines.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 31f00754f5..77263b3ddc 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -21,8 +21,6 @@ jobs: BuildConfiguration: development Production: BuildConfiguration: production - Standalone: - BuildConfiguration: standalone pool: vmImage: 'ubuntu-latest' @@ -52,10 +50,6 @@ jobs: displayName: 'Build Bundle' condition: eq(variables['BuildConfiguration'], 'production') - - script: 'yarn build:standalone' - displayName: 'Build Standalone' - condition: eq(variables['BuildConfiguration'], 'standalone') - - script: 'test -d dist' displayName: 'Check Build' From 76834f5074888eaf7059f2f79733b5808a247b89 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 31 May 2020 13:55:47 +0900 Subject: [PATCH 169/181] update display name for production build --- .ci/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 77263b3ddc..059c39aa56 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -47,7 +47,7 @@ jobs: condition: eq(variables['BuildConfiguration'], 'development') - script: 'yarn build:production' - displayName: 'Build Bundle' + displayName: 'Build Production' condition: eq(variables['BuildConfiguration'], 'production') - script: 'test -d dist' From 5fd58a2fb1f93cbd98092d595debd890ddd6c03c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:42:11 +0000 Subject: [PATCH 170/181] Bump shaka-player from 2.5.11 to 2.5.12 Bumps [shaka-player](https://github.com/google/shaka-player) from 2.5.11 to 2.5.12. - [Release notes](https://github.com/google/shaka-player/releases) - [Changelog](https://github.com/google/shaka-player/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/shaka-player/compare/v2.5.11...v2.5.12) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a06fad3aea..a9a4344e65 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "query-string": "^6.11.1", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.2", - "shaka-player": "^2.5.11", + "shaka-player": "^2.5.12", "sortablejs": "^1.10.2", "swiper": "^5.4.1", "webcomponents.js": "^0.7.24", diff --git a/yarn.lock b/yarn.lock index ac827f401c..ddbe3fea1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10052,10 +10052,10 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shaka-player@^2.5.11: - version "2.5.11" - resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.11.tgz#af550a0ee3aadf7be4e64e1a4d615c8d728e0b0f" - integrity sha512-SiZd/vCUPeKXNPnfWcBdraskdUYLtm+DITWceCZvRP4eoxxQuRI0MekVJTGqu5d7B2yW9TdQh5ojyRAjbQPFGA== +shaka-player@^2.5.12: + version "2.5.12" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.12.tgz#4e8d9b2ab4147368b2a32f537ffed5e884301729" + integrity sha512-mnoMzE5iLnj6HYrovcnd55Lvd8bwV8712Inq3ECMJmBl2nYi7GXDRwYd3fLk6T0EfOsXwSELuHRQ1iyzk4NVNg== dependencies: eme-encryption-scheme-polyfill "^2.0.1" From feee9a174d7961c564cd1406d4070915a9c42b1e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:43:15 +0000 Subject: [PATCH 171/181] Bump eslint-plugin-eslint-comments from 3.1.2 to 3.2.0 Bumps [eslint-plugin-eslint-comments](https://github.com/mysticatea/eslint-plugin-eslint-comments) from 3.1.2 to 3.2.0. - [Release notes](https://github.com/mysticatea/eslint-plugin-eslint-comments/releases) - [Commits](https://github.com/mysticatea/eslint-plugin-eslint-comments/compare/v3.1.2...v3.2.0) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a06fad3aea..39d32f5431 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "del": "^5.1.0", "eslint": "^6.8.0", "eslint-plugin-compat": "^3.5.1", - "eslint-plugin-eslint-comments": "^3.1.2", + "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.20.2", "eslint-plugin-promise": "^4.2.1", "file-loader": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index ac827f401c..3139458275 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3896,10 +3896,10 @@ eslint-plugin-compat@^3.5.1: mdn-browser-compat-data "^1.0.21" semver "7.3.2" -eslint-plugin-eslint-comments@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.2.tgz#4ef6c488dbe06aa1627fea107b3e5d059fc8a395" - integrity sha512-QexaqrNeteFfRTad96W+Vi4Zj1KFbkHHNMMaHZEYcovKav6gdomyGzaxSDSL3GoIyUOo078wRAdYlu1caiauIQ== +eslint-plugin-eslint-comments@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" + integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ== dependencies: escape-string-regexp "^1.0.5" ignore "^5.0.5" From a5ec1509a76c13d53fd0c0ab05715b7418bbe9bc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:44:02 +0000 Subject: [PATCH 172/181] Bump @babel/core from 7.10.1 to 7.10.2 Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.10.1 to 7.10.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.10.2/packages/babel-core) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 81 +++++++++++++++++----------------------------------- 2 files changed, 27 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index a06fad3aea..e953fb6abd 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.9.6", + "@babel/core": "^7.10.2", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.9.6", diff --git a/yarn.lock b/yarn.lock index ac827f401c..a758fd338c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,7 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" - -"@babel/code-frame@^7.10.1", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== @@ -25,41 +18,19 @@ invariant "^2.2.4" semver "^5.5.0" -"@babel/core@>=7.2.2", "@babel/core@>=7.9.0": - version "7.9.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" - integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.6" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.6" - "@babel/parser" "^7.9.6" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.6" - "@babel/types" "^7.9.6" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.9.6": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.1.tgz#2a0ad0ea693601820defebad2140206503d89af3" - integrity sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg== +"@babel/core@>=7.2.2", "@babel/core@>=7.9.0", "@babel/core@^7.10.2": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" + integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== dependencies: "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.1" + "@babel/generator" "^7.10.2" "@babel/helper-module-transforms" "^7.10.1" "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.1" + "@babel/parser" "^7.10.2" "@babel/template" "^7.10.1" "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -69,12 +40,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.10.1", "@babel/generator@^7.9.6": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.1.tgz#4d14458e539bcb04ffe34124143f5c489f2dbca9" - integrity sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA== +"@babel/generator@^7.10.1", "@babel/generator@^7.10.2": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" + integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.2" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -180,7 +151,7 @@ dependencies: "@babel/types" "^7.10.1" -"@babel/helper-module-transforms@^7.10.1", "@babel/helper-module-transforms@^7.9.0": +"@babel/helper-module-transforms@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== @@ -263,7 +234,7 @@ "@babel/traverse" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/helpers@^7.10.1", "@babel/helpers@^7.9.6": +"@babel/helpers@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973" integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw== @@ -272,7 +243,7 @@ "@babel/traverse" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/highlight@^7.10.1", "@babel/highlight@^7.8.3": +"@babel/highlight@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== @@ -281,10 +252,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.1", "@babel/parser@^7.9.6": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.1.tgz#2e142c27ca58aa2c7b119d09269b702c8bbad28c" - integrity sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg== +"@babel/parser@^7.10.1", "@babel/parser@^7.10.2": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" + integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== "@babel/plugin-proposal-async-generator-functions@^7.10.1": version "7.10.1" @@ -800,7 +771,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.1", "@babel/template@^7.8.6": +"@babel/template@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== @@ -809,7 +780,7 @@ "@babel/parser" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/traverse@^7.10.1", "@babel/traverse@^7.9.6": +"@babel/traverse@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== @@ -824,10 +795,10 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.10.1", "@babel/types@^7.4.4", "@babel/types@^7.9.6": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921" - integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA== +"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d" + integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng== dependencies: "@babel/helper-validator-identifier" "^7.10.1" lodash "^4.17.13" From e56d1833c7f477353f92927b2aa7cb7785e0b16b Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Mon, 1 Jun 2020 16:14:06 +0200 Subject: [PATCH 173/181] Fix error in player capabilities checking with syncplay --- src/components/syncplay/syncPlayManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/syncplay/syncPlayManager.js b/src/components/syncplay/syncPlayManager.js index f5c9ac446d..f04d1aeb8c 100644 --- a/src/components/syncplay/syncPlayManager.js +++ b/src/components/syncplay/syncPlayManager.js @@ -265,10 +265,9 @@ class SyncPlayManager { events.on(player, 'timeupdate', this._onTimeUpdate); events.on(player, 'playing', this._onPlaying); events.on(player, 'waiting', this._onWaiting); - this.playbackRateSupported = player.supports('PlaybackRate'); // Save player current PlaybackRate value - if (this.playbackRateSupported) { + if (player.supports && player.supports('PlaybackRate')) { this.localPlayerPlaybackRate = player.getPlaybackRate(); } } From 284bac2544f85f2914c1793faa31b50934f0a053 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 14:59:11 +0000 Subject: [PATCH 174/181] Bump @babel/preset-env from 7.10.1 to 7.10.2 Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.10.1 to 7.10.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.10.2/packages/babel-preset-env) Signed-off-by: dependabot-preview[bot] --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d4b2cf7f09..2c7cda204b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.9.6", "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.8.6", + "@babel/preset-env": "^7.10.2", "autoprefixer": "^9.8.0", "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", diff --git a/yarn.lock b/yarn.lock index 153ff21e74..b3029c91b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -65,10 +65,10 @@ "@babel/helper-explode-assignable-expression" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/helper-compilation-targets@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.1.tgz#ad6f69b4c3bae955081ef914a84e5878ffcaca63" - integrity sha512-YuF8IrgSmX/+MV2plPkjEnzlC2wf+gaok8ehMNN0jodF3/sejZauExqpEVGbJua62oaWoNYIXwz4RmAsVcGyHw== +"@babel/helper-compilation-targets@^7.10.2": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285" + integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA== dependencies: "@babel/compat-data" "^7.10.1" browserslist "^4.12.0" @@ -676,13 +676,13 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/preset-env@^7.8.6": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.1.tgz#099e1b76379739bdcbfab3d548dc7e7edb2ac808" - integrity sha512-bGWNfjfXRLnqbN2T4lB3pMfoic8dkRrmHpVZamSFHzGy5xklyHTobZ28TVUD2grhE5WDnu67tBj8oslIhkiOMQ== +"@babel/preset-env@^7.10.2": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" + integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== dependencies: "@babel/compat-data" "^7.10.1" - "@babel/helper-compilation-targets" "^7.10.1" + "@babel/helper-compilation-targets" "^7.10.2" "@babel/helper-module-imports" "^7.10.1" "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-proposal-async-generator-functions" "^7.10.1" @@ -739,7 +739,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.10.1" "@babel/plugin-transform-unicode-regex" "^7.10.1" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.2" browserslist "^4.12.0" core-js-compat "^3.6.2" invariant "^2.2.2" From fbedfad6f58210a64c3be697760dc92fd67296cd Mon Sep 17 00:00:00 2001 From: Mohammadreza Firoozeh Date: Mon, 1 Jun 2020 09:39:42 +0000 Subject: [PATCH 175/181] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fa/ --- src/strings/fa.json | 46 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/strings/fa.json b/src/strings/fa.json index b91a2c82f1..a2a0f01444 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -612,7 +612,7 @@ "ConfirmDeleteItem": "حذف این مورد، آن را هم از فایل سیستمی و هم از کتابخانه رسانه شما حذف می کند. آیا اطمینان دارید که می‌خواهید ادامه دهید؟", "AlwaysPlaySubtitlesHelp": "زیرنویس‌های متناسب با توجه به اولویت زبان بدون در نظر گرفتن زبان صوتی ویدیو پخش می شوند.", "AllowedRemoteAddressesHelp": "لیستی از آدرس های IP یا ورودی‌های IP/‌netmask برای شبکه هایی که به آنها امکان ارتباط از راه دور داده می‌شود ، با کاما از هم جدا شدند. در صورت خالی ماندن ، تمام آدرسهای راه دور مجاز خواهند بود.", - "AllowOnTheFlySubtitleExtractionHelp": "زیرنویس های جاسازی شده را می‌توان از فیلم‌ها استخراج کرد و به منظور جلوگیری از کدگذاری فیلم ، به صورت متن ساده به بازدید کننده ارسال کرد. در بعضی از سیستم‌ها این می‌تواند مدت زیادی طول بکشد و باعث شود پخش فیلم در طول فرآیند استخراج متوقف شود. این گزینه را غیرفعال کنید تا زیرنویس‌های جاسازی شده با استفاده از رمزگذاری ویدیو در حالی که به طور محلی توسط دستگاه بازدیدکننده پشتیبانی نمی‌شوند ارسال شود.", + "AllowOnTheFlySubtitleExtractionHelp": "زیرنویس های جاسازی شده را می‌توان از ویدئو ها استخراج کرد و به منظور جلوگیری از کدگذاری فیلم ، به صورت متن ساده به بازدید کننده ارسال کرد. در بعضی از سیستم‌ها این می‌تواند مدت زیادی طول بکشد و باعث شود پخش فیلم در طول فرآیند استخراج متوقف شود. این گزینه را غیرفعال کنید تا زیرنویس‌های جاسازی شده با استفاده از رمزگذاری ویدیو در حالی که به طور محلی توسط دستگاه بازدیدکننده پشتیبانی نمی‌شوند ارسال شود.", "AdditionalNotificationServices": "برای نصب سرویس‌های اعلان اضافی، در فروشگاه افزونه‌ها جستجو کنید.", "OptionThumbCard": "کارت بندانگشتی", "OptionThumb": "بندانگشتی", @@ -702,5 +702,47 @@ "MessageUnauthorizedUser": "در حال حاضر مجاز به دسترسی به سرور نیستید. لطفا برای اطلاعات بیشتر با مدیر سرور خود تماس بگیرید.", "MessageInvalidUser": "نام کاربری یا گذرواژه نامعتبر است. لطفا دوباره تلاش کنید.", "MessageInvalidForgotPasswordPin": "کد پین نامعتبر یا منقضی شده وارد شد. لطفا دوباره تلاش کنید.", - "MessageInstallPluginFromApp": "این افزونه باید از داخل برنامه‌ای که قصد استفاده از آن را دارید نصب شود." + "MessageInstallPluginFromApp": "این افزونه باید از داخل برنامه‌ای که قصد استفاده از آن را دارید نصب شود.", + "PasswordResetHeader": "بازنشانی گذرواژه", + "PasswordResetConfirmation": "آیا واقعا تمایل به بازنشانی گذرواژه دارید؟", + "PasswordResetComplete": "گذرواژه بازنشانی شد.", + "PasswordMatchError": "گذرواژه و تکرار گذرواژه باید یکسان باشند.", + "PackageInstallFailed": "{0} (نسخه {1}) نصب به مشکل برخورد.", + "PackageInstallCompleted": "{0} (نسخه {1})نصب به پایان رسید.", + "PackageInstallCancelled": "{0} ( نسخه {1})نصب لغو شد.", + "Overview": "بررسی اجمالی", + "OtherArtist": "هنرمند دیگر", + "OriginalAirDateValue": "زمان پخش اصلی : {0}", + "OptionWeekly": "هفتگی", + "OptionWeekends": "آخر هفته ها", + "OptionWeekdays": "روز های هفته", + "OptionWednesday": "چهارشنبه", + "OptionWakeFromSleep": "از خواب بیدار شدن", + "OptionUnairedEpisode": "قسمت های پخش نشده", + "OptionTvdbRating": "نمره TVDB", + "OptionTuesday": "سه شنبه", + "OptionTrackName": "نام ترک", + "OptionRequirePerfectSubtitleMatchHelp": "نتیجه کامل زیرنویس ها را به صورتی فیلتر می کند که فقط مواردی را که دقیقا با فایل تصویری شما آزمایش و تأیید شده اند ،شامل شود. حذف این گزینه احتمال بارگیری زیرنویس ها را افزایش می دهد ، اما شانس متن زیرنویس ناهماهنگ یا غلط نیز افزایش می یابد.", + "ServerNameIsShuttingDown": "سرور جلی فین - {0} در حال خاموش شدن می باشد.", + "ServerNameIsRestarting": "سرور جلی فین - {0} در حال راه اندازی مجدد می باشد.", + "SeriesYearToPresent": "{0}- در حال حاضر", + "SeriesSettings": "تنظیمات سریال", + "SeriesRecordingScheduled": "ضبط کردن سریال زمانبندی شد.", + "SeriesDisplayOrderHelp": "قسمت ها را بر اساس تاریخ پخش شدن، ترتیب دی وی دی یا فقط شماره مرتب کنید.", + "SeriesCancelled": "سریال متوقف شده است.", + "Series": "سریال ها", + "SelectAdminUsername": "لطفا یک نام کاربری برای حساب مدیر انتخاب کنید.", + "Season": "فصل", + "SearchResults": "نتایج جستجو", + "SearchForSubtitles": "زیرنویس ها را جستجو کنید", + "SearchForMissingMetadata": "فراداده های گم شده را جستجو کنید", + "SearchForCollectionInternetMetadata": "در اینترنت برای پیدا کردن فراداده و عکس جستجو کنید", + "Search": "جستجو کردن", + "Screenshots": "نماگرفت ها", + "Screenshot": "نماگرفت", + "Schedule": "برنامه زمانی", + "ScanLibrary": "کتابخانه را اسکن کنید", + "ScanForNewAndUpdatedFiles": "فایل های جدید و به روز رسانی شده را اسکن کنید", + "ChannelAccessHelp": "کانال هایی که قصد اشتراک گذاری با این کاربر را دارید انتخاب کنید. مدیران توانایی ویرایش همه ی کانال ها را با استفاده از مدیریت فراداده دارند.", + "ChangingMetadataImageSettingsNewContent": "تغییر تنظیمات دانلود فراداده ها و عکس ها فقط بر روی محتواهای جدیدی که به کتابخانه اضافه می شوند، اعمال می شوند. برای اعمال تغییرات بر روی محتوای موجود، شما باید فراداده را به صورت دستی به روزرسانی کنید." } From a0b99f0c0205f6088f1be020ce0e32e45abc6cbb Mon Sep 17 00:00:00 2001 From: 000 Date: Mon, 1 Jun 2020 10:52:44 +0000 Subject: [PATCH 176/181] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index d57c46395d..f62b872c93 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1524,7 +1524,7 @@ "LabelEnableHttps": "启用 HTTPS", "LabelChromecastVersion": "Chromecast版本", "HeaderDVR": "DVR", - "LabelNightly": "Nightly", + "LabelNightly": "开发版", "MessageSyncPlayErrorAccessingGroups": "访问群组列表时发生错误。", "MessageSyncPlayLibraryAccessDenied": "搜限制的访问权限。", "MessageSyncPlayCreateGroupDenied": "需要权限创建该组。", From 021650e6ab278b2205bc25c012608667e3fae71e Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Mon, 1 Jun 2020 19:17:23 +0300 Subject: [PATCH 177/181] Fix userSettings.chromecastVersion call --- src/components/chromecast/chromecastplayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chromecast/chromecastplayer.js b/src/components/chromecast/chromecastplayer.js index 5a9945539a..8edca9010b 100644 --- a/src/components/chromecast/chromecastplayer.js +++ b/src/components/chromecast/chromecastplayer.js @@ -58,7 +58,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' var applicationNightly = '6F511C87'; var applicationID = applicationStable; - if (userSettings.chromecastVersion === 'nightly') { + if (userSettings.chromecastVersion() === 'nightly') { applicationID = applicationNightly; } From a955aeb1cbfb66dad67f62b592c77d91a4afa8a6 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Mon, 1 Jun 2020 19:38:59 +0300 Subject: [PATCH 178/181] Delay access to userSettings --- src/components/chromecast/chromecastplayer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/chromecast/chromecastplayer.js b/src/components/chromecast/chromecastplayer.js index 8edca9010b..b3f75f7a6d 100644 --- a/src/components/chromecast/chromecastplayer.js +++ b/src/components/chromecast/chromecastplayer.js @@ -57,11 +57,6 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' var applicationStable = 'F007D354'; var applicationNightly = '6F511C87'; - var applicationID = applicationStable; - if (userSettings.chromecastVersion() === 'nightly') { - applicationID = applicationNightly; - } - var messageNamespace = 'urn:x-cast:com.connectsdk'; var CastPlayer = function () { @@ -105,6 +100,11 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return; } + var applicationID = applicationStable; + if (userSettings.chromecastVersion() === 'nightly') { + applicationID = applicationNightly; + } + // request session var sessionRequest = new chrome.cast.SessionRequest(applicationID); var apiConfig = new chrome.cast.ApiConfig(sessionRequest, From b8ee2f86b0c00208ddad2e90771b0165da74951c Mon Sep 17 00:00:00 2001 From: Mohammadreza Firoozeh Date: Mon, 1 Jun 2020 16:39:00 +0000 Subject: [PATCH 179/181] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fa/ --- src/strings/fa.json | 49 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/strings/fa.json b/src/strings/fa.json index a2a0f01444..d745abfd93 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -744,5 +744,52 @@ "ScanLibrary": "کتابخانه را اسکن کنید", "ScanForNewAndUpdatedFiles": "فایل های جدید و به روز رسانی شده را اسکن کنید", "ChannelAccessHelp": "کانال هایی که قصد اشتراک گذاری با این کاربر را دارید انتخاب کنید. مدیران توانایی ویرایش همه ی کانال ها را با استفاده از مدیریت فراداده دارند.", - "ChangingMetadataImageSettingsNewContent": "تغییر تنظیمات دانلود فراداده ها و عکس ها فقط بر روی محتواهای جدیدی که به کتابخانه اضافه می شوند، اعمال می شوند. برای اعمال تغییرات بر روی محتوای موجود، شما باید فراداده را به صورت دستی به روزرسانی کنید." + "ChangingMetadataImageSettingsNewContent": "تغییر تنظیمات دانلود فراداده ها و عکس ها فقط بر روی محتواهای جدیدی که به کتابخانه اضافه می شوند، اعمال می شوند. برای اعمال تغییرات بر روی محتوای موجود، شما باید فراداده را به صورت دستی به روزرسانی کنید.", + "Refresh": "به‌روز‌رسانی", + "Recordings": "ضبط شده ها", + "RecordingScheduled": "ضبط برنامه ریزی شد.", + "RecordingPathChangeMessage": "با تغییر محل ذخیره فایل های ضبط شده، فایل های موجود به صورت خودکار منتقل نمی شوند. در صورت نیاز، شما باید خودتان این کار را انجام دهید.", + "RecordingCancelled": "ضبط شدن لغو شد.", + "RecordSeries": "ضبط کردن سریال ها", + "Record": "ضبط کردن", + "RecommendationStarring": "با هنرمندی {0}", + "RecommendationDirectedBy": "کارگردانی شده توسط {0}", + "RecommendationBecauseYouWatched": "برای اینکه تو {0} مشاهده کردی", + "RecommendationBecauseYouLike": "برای اینکه تو {0} را نیز دوست داری", + "RecentlyWatched": "اخیرا مشاهده شده", + "Rate": "ارزیابی کن", + "Raised": "مطرح شده", + "QueueAllFromHere": "همه را از اینجا در صف قرار بده", + "Quality": "کیفیت", + "Programs": "برنامه ها", + "ProductionLocations": "محل تولید", + "Producer": "تولید کننده", + "Primary": "اصلی", + "Previous": "قبلی", + "Premieres": "برتر ها", + "Premiere": "برتر", + "PreferEmbeddedEpisodeInfosOverFileNames": "اطلاعات تعبیه شده در فراداده را به اسم فایل ترجیح بده", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "این از اطلاعات قسمت در فراداده های تعبیه شده در صورت موجود استفاده می کند.", + "PreferEmbeddedTitlesOverFileNamesHelp": "این عنوان نمایش را به صورت پیش فرض تعیین می کند، زمانی که فراداده اینترنتی یا محلی موجود نباشند.", + "PreferEmbeddedTitlesOverFileNames": "عنوان های تعبیه شده را به نام فایل ترجیح بده", + "PluginInstalledMessage": "افزونه با موفقیت نصب شد. برای اعمال تغییرات سرور جلیفین نیاز به بارگذاری مجدد دارد.", + "PleaseSelectTwoItems": "لطفا حداقل دو مورد را انتخاب کنید.", + "PleaseRestartServerName": "لطفا سرور جلیفین را دوباره بارگذاری کنید - {0}.", + "PleaseEnterNameOrId": "لطفا یک نام یا شناسه خارجی را وارد کنید.", + "PleaseConfirmPluginInstallation": "لطفا با کلیک بر روی OK تایید کنید که شما متن بالا را خوانده اید و می خواهید که افزونه را نصب کنید.", + "PleaseAddAtLeastOneFolder": "لطفا حداقل یک پوشه به این کتابخانه با کلیک بر روی \"افزودن\" اضافه کنید.", + "Played": "اجرا شده", + "PlaybackErrorNoCompatibleStream": "سرویس گیرنده با مدیا سازگاری ندارد و سرور یک فرمت سازگار را ارسال نمی کند.", + "PlayNextEpisodeAutomatically": "قسمت بعدی را به صورت خودکار اجرا کن", + "PlayNext": "بعدی را اجرا کن", + "PlayFromBeginning": "از ابتدا اجرا کن", + "PlaybackData": "اجرای مجدد داده", + "PlayAllFromHere": "همه را از اینجا اجرا کن", + "Play": "اجرا کردن", + "PinCodeResetConfirmation": "آیا مطمئن هستید که می خواهید کد پین را باز نشانی کنید؟", + "PinCodeResetComplete": "کد پین بازنشانی شد.", + "PictureInPicture": "تصویر در تصویر", + "Person": "فرد", + "PerfectMatch": "همتای کامل", + "People": "افراد" } From 40481046f454aa69c5c322f0e28f21a7d1d0506e Mon Sep 17 00:00:00 2001 From: artiume Date: Tue, 2 Jun 2020 15:41:58 -0400 Subject: [PATCH 180/181] Change Default for OptionAutomaticallyGroupSeries --- .../libraryoptionseditor/libraryoptionseditor.template.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index caa177108d..92cebc9da4 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -79,7 +79,7 @@
${OptionAutomaticallyGroupSeriesHelp}
From 20eef65e5cfa4619cc94bece5e6706f722000b61 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Wed, 3 Jun 2020 10:49:15 +0300 Subject: [PATCH 181/181] Babel epubjs --- webpack.dev.js | 2 +- webpack.prod.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.dev.js b/webpack.dev.js index 0bb2f1fdb5..17377acf1c 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -15,7 +15,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!date-fns|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!date-fns|epubjs|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: { diff --git a/webpack.prod.js b/webpack.prod.js index 82f1d4d2f9..1b7f4d029e 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -8,7 +8,7 @@ module.exports = merge(common, { rules: [ { test: /\.js$/, - exclude: /node_modules[\\/](?!date-fns|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, + exclude: /node_modules[\\/](?!date-fns|epubjs|jellyfin-apiclient|query-string|split-on-first|strict-uri-encode)/, use: { loader: 'babel-loader', options: {