mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into books
This commit is contained in:
commit
87b9eefc91
54 changed files with 3178 additions and 841 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/**/*.mp3']
|
||||
},
|
||||
injectBundle: {
|
||||
query: 'src/index.html'
|
||||
|
|
14
package.json
14
package.json
|
@ -25,7 +25,7 @@
|
|||
"file-loader": "^6.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-cli": "^2.2.0",
|
||||
"gulp-cli": "^2.2.1",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-if": "^3.0.0",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"stylelint": "^13.4.0",
|
||||
"stylelint": "^13.5.0",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-no-browser-hacks": "^1.2.1",
|
||||
"stylelint-order": "^4.0.0",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"alameda": "^1.4.0",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"core-js": "^3.6.5",
|
||||
"date-fns": "^2.13.0",
|
||||
"date-fns": "^2.14.0",
|
||||
"document-register-element": "^1.14.3",
|
||||
"epubjs": "^0.3.85",
|
||||
"fast-text-encoding": "^1.0.1",
|
||||
|
@ -66,7 +66,7 @@
|
|||
"hls.js": "^0.13.1",
|
||||
"howler": "^2.2.0",
|
||||
"intersection-observer": "^0.10.0",
|
||||
"jellyfin-apiclient": "^1.1.1",
|
||||
"jellyfin-apiclient": "^1.1.2",
|
||||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||
"jquery": "^3.5.1",
|
||||
"jstree": "^3.3.7",
|
||||
|
@ -79,7 +79,7 @@
|
|||
"screenfull": "^5.0.2",
|
||||
"shaka-player": "^2.5.11",
|
||||
"sortablejs": "^1.10.2",
|
||||
"swiper": "^5.4.0",
|
||||
"swiper": "^5.4.1",
|
||||
"webcomponents.js": "^0.7.24",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
|
@ -99,6 +99,10 @@
|
|||
"src/components/sanatizefilename.js",
|
||||
"src/components/scrollManager.js",
|
||||
"src/components/bookPlayer/plugin.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",
|
||||
"src/scripts/filesystem.js",
|
||||
|
|
BIN
src/assets/audio/silence.mp3
Normal file
BIN
src/assets/audio/silence.mp3
Normal file
Binary file not shown.
|
@ -30,7 +30,7 @@
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) {
|
||||
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,6 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
|
|||
var instruction = options.instruction ? options.instruction + '<br/><br/>' : '';
|
||||
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
|
||||
html += instruction;
|
||||
html += globalize.translate('MessageDirectoryPickerInstruction', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
|
||||
if ('bsd' === systemInfo.OperatingSystem.toLowerCase()) {
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
|
@ -126,7 +125,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
|
|||
html += '<div class="inputContainer" style="margin-top:2em;">';
|
||||
html += '<input is="emby-input" id="txtNetworkPath" type="text" label="' + globalize.translate('LabelOptionalNetworkPath') + '"/>';
|
||||
html += '<div class="fieldDescription">';
|
||||
html += globalize.translate('LabelOptionalNetworkPathHelp');
|
||||
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -177,6 +177,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) {
|
||||
|
@ -186,6 +187,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) {
|
||||
|
@ -300,6 +302,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;
|
||||
|
@ -456,6 +462,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) {
|
||||
|
@ -499,5 +520,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;
|
||||
});
|
||||
|
|
|
@ -799,6 +799,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);
|
||||
}
|
||||
|
@ -927,6 +928,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 || '') : '';
|
||||
|
@ -1349,6 +1354,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);
|
||||
if (options.backdropUrl) {
|
||||
videoElement.poster = options.backdropUrl;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -3775,6 +3780,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
}
|
||||
};
|
||||
|
||||
PlaybackManager.prototype.setPlaybackRate = function (value, player = this._currentPlayer) {
|
||||
if (player && player.setPlaybackRate) {
|
||||
player.setPlaybackRate(value);
|
||||
}
|
||||
};
|
||||
|
||||
PlaybackManager.prototype.getPlaybackRate = function (player = this._currentPlayer) {
|
||||
if (player && player.getPlaybackRate) {
|
||||
return player.getPlaybackRate();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
PlaybackManager.prototype.instantMix = function (item, player) {
|
||||
|
||||
player = player || this._currentPlayer;
|
||||
|
@ -3885,6 +3904,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
|||
if (player.supports('SetAspectRatio')) {
|
||||
list.push('SetAspectRatio');
|
||||
}
|
||||
if (player.supports('PlaybackRate')) {
|
||||
list.push('PlaybackRate');
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
|
|
|
@ -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('LabelSyncPlayTimeOffset'),
|
||||
value: stats.TimeOffset + globalize.translate('MillisecondsUnit')
|
||||
});
|
||||
|
||||
syncStats.push({
|
||||
label: globalize.translate('LabelSyncPlayPlaybackDiff'),
|
||||
value: stats.PlaybackDiff + globalize.translate('MillisecondsUnit')
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
189
src/components/syncplay/groupSelectionMenu.js
Normal file
189
src/components/syncplay/groupSelectionMenu.js
Normal file
|
@ -0,0 +1,189 @@
|
|||
import events from 'events';
|
||||
import connectionManager from 'connectionManager';
|
||||
import playbackManager from 'playbackManager';
|
||||
import syncPlayManager from 'syncPlayManager';
|
||||
import loading from 'loading';
|
||||
import toast from 'toast';
|
||||
import actionsheet from 'actionsheet';
|
||||
import globalize from 'globalize';
|
||||
import playbackPermissionManager from 'playbackPermissionManager';
|
||||
|
||||
/**
|
||||
* Gets active player id.
|
||||
* @returns {string} The player's id.
|
||||
*/
|
||||
function getActivePlayerId () {
|
||||
var info = playbackManager.getPlayerInfo();
|
||||
return info ? info.id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, user, apiClient) {
|
||||
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 = '';
|
||||
console.debug('No item is currently playing.');
|
||||
}
|
||||
|
||||
apiClient.sendSyncPlayCommand(sessionId, 'ListGroups').then(function (response) {
|
||||
response.json().then(function (groups) {
|
||||
var menuItems = groups.map(function (group) {
|
||||
return {
|
||||
name: group.PlayingItemName,
|
||||
icon: 'group',
|
||||
id: group.GroupId,
|
||||
selected: false,
|
||||
secondaryText: group.Participants.join(', ')
|
||||
};
|
||||
});
|
||||
|
||||
if (inSession && policy.SyncPlayAccess === 'CreateAndJoinGroups') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('LabelSyncPlayNewGroup'),
|
||||
icon: 'add',
|
||||
id: 'new-group',
|
||||
selected: true,
|
||||
secondaryText: globalize.translate('LabelSyncPlayNewGroupDescription')
|
||||
});
|
||||
}
|
||||
|
||||
if (menuItems.length === 0) {
|
||||
if (inSession && policy.SyncPlayAccess === 'JoinGroups') {
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayCreateGroupDenied')
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayNoGroupsAvailable')
|
||||
});
|
||||
}
|
||||
loading.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
PlayingItemId: playingItemId
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('SyncPlay: unexpected error listing groups:', error);
|
||||
});
|
||||
|
||||
loading.hide();
|
||||
});
|
||||
}).catch(function (error) {
|
||||
console.error(error);
|
||||
loading.hide();
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayErrorAccessingGroups')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, user, apiClient) {
|
||||
const sessionId = getActivePlayerId();
|
||||
if (!sessionId) {
|
||||
syncPlayManager.signalError();
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayErrorNoActivePlayer')
|
||||
});
|
||||
showNewJoinGroupSelection(button, user, apiClient);
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}).catch((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;
|
||||
});
|
||||
|
||||
/**
|
||||
* Shows a menu to handle SyncPlay groups.
|
||||
* @param {HTMLElement} button - Element where to place the menu.
|
||||
*/
|
||||
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) {
|
||||
showLeaveGroupSelection(button, user, apiClient);
|
||||
} else {
|
||||
showNewJoinGroupSelection(button, user, apiClient);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
loading.hide();
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayNoGroupsAvailable')
|
||||
});
|
||||
});
|
||||
}
|
51
src/components/syncplay/playbackPermissionManager.js
Normal file
51
src/components/syncplay/playbackPermissionManager.js
Normal file
|
@ -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.mp3'; // 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();
|
839
src/components/syncplay/syncPlayManager.js
Normal file
839
src/components/syncplay/syncPlayManager.js
Normal file
|
@ -0,0 +1,839 @@
|
|||
/**
|
||||
* Module that manages the SyncPlay feature.
|
||||
* @module components/syncplay/syncPlayManager
|
||||
*/
|
||||
|
||||
import events from 'events';
|
||||
import connectionManager from 'connectionManager';
|
||||
import playbackManager from 'playbackManager';
|
||||
import timeSyncManager from 'timeSyncManager';
|
||||
import toast from 'toast';
|
||||
import globalize from 'globalize';
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
|
||||
/**
|
||||
* Other constants
|
||||
*/
|
||||
const WaitForEventDefaultTimeout = 30000; // milliseconds
|
||||
const WaitForPlayerEventTimeout = 500; // milliseconds
|
||||
|
||||
/**
|
||||
* 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.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;
|
||||
|
||||
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.lastCommand = null;
|
||||
this.queuedCommand = null;
|
||||
|
||||
this.scheduledCommand = null;
|
||||
this.syncTimeout = null;
|
||||
|
||||
this.timeOffsetWithServer = 0; // server time minus local time
|
||||
this.roundTripDuration = 0;
|
||||
this.notifySyncPlayReady = false;
|
||||
|
||||
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) => {
|
||||
this.syncPlaybackTime();
|
||||
});
|
||||
|
||||
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, 'ready');
|
||||
this.notifySyncPlayReady = false;
|
||||
}
|
||||
|
||||
// Report ping
|
||||
if (this.syncEnabled) {
|
||||
const apiClient = connectionManager.currentApiClient();
|
||||
const sessionId = getActivePlayerId();
|
||||
|
||||
if (!sessionId) {
|
||||
this.signalError();
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayErrorMissingSession')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback unpauses.
|
||||
*/
|
||||
onPlayerUnpause () {
|
||||
events.trigger(this, 'unpause', [this.currentPlayer]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback pauses.
|
||||
*/
|
||||
onPlayerPause() {
|
||||
events.trigger(this, 'pause', [this.currentPlayer]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on playback progress.
|
||||
* @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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback is resumed.
|
||||
*/
|
||||
onPlaying () {
|
||||
// TODO: implement group wait
|
||||
this.lastPlaybackWaiting = null;
|
||||
events.trigger(this, 'playing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when playback is buffering.
|
||||
*/
|
||||
onWaiting () {
|
||||
// TODO: implement group wait
|
||||
if (!this.lastPlaybackWaiting) {
|
||||
this.lastPlaybackWaiting = new Date();
|
||||
}
|
||||
events.trigger(this, 'waiting');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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._onPlayerUnpause = () => {
|
||||
self.onPlayerUnpause();
|
||||
};
|
||||
|
||||
this._onPlayerPause = () => {
|
||||
self.onPlayerPause();
|
||||
};
|
||||
|
||||
this._onTimeUpdate = (e) => {
|
||||
self.onTimeUpdate(e);
|
||||
};
|
||||
|
||||
this._onPlaying = () => {
|
||||
self.onPlaying();
|
||||
};
|
||||
|
||||
this._onWaiting = () => {
|
||||
self.onWaiting();
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the bindings to the current player's events.
|
||||
*/
|
||||
releaseCurrentPlayer () {
|
||||
var player = this.currentPlayer;
|
||||
if (player) {
|
||||
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(this.localPlayerPlaybackRate);
|
||||
this.localPlayerPlaybackRate = 1.0;
|
||||
}
|
||||
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':
|
||||
this.prepareSession(apiClient, cmd.GroupId, cmd.Data);
|
||||
break;
|
||||
case 'UserJoined':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayUserJoined', cmd.Data)
|
||||
});
|
||||
break;
|
||||
case 'UserLeft':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayUserLeft', cmd.Data)
|
||||
});
|
||||
break;
|
||||
case 'GroupJoined':
|
||||
this.enableSyncPlay(apiClient, new Date(cmd.Data), true);
|
||||
break;
|
||||
case 'NotInGroup':
|
||||
case 'GroupLeft':
|
||||
this.disableSyncPlay(true);
|
||||
break;
|
||||
case 'GroupWait':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayGroupWait', cmd.Data)
|
||||
});
|
||||
break;
|
||||
case 'GroupDoesNotExist':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayGroupDoesNotExist')
|
||||
});
|
||||
break;
|
||||
case 'CreateGroupDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayCreateGroupDenied')
|
||||
});
|
||||
break;
|
||||
case 'JoinGroupDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayJoinGroupDenied')
|
||||
});
|
||||
break;
|
||||
case 'LibraryAccessDenied':
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayLibraryAccessDenied')
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error('processSyncPlayGroupUpdate: command is not recognised: ' + 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: SyncPlay not enabled, ignoring command', cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.syncPlayReady) {
|
||||
console.debug('SyncPlay processCommand: SyncPlay not ready, queued command', cmd);
|
||||
this.queuedCommand = cmd;
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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('processCommand: command is not recognised: ' + cmd.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const 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(() => {
|
||||
waitForEventOnce(this, 'playbackstart', WaitForEventDefaultTimeout).then(() => {
|
||||
var sessionId = getActivePlayerId();
|
||||
if (!sessionId) {
|
||||
console.error('Missing sessionId!');
|
||||
toast({
|
||||
text: globalize.translate('MessageSyncPlayErrorMissingSession')
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Get playing item id
|
||||
let playingItemId;
|
||||
try {
|
||||
const playState = playbackManager.getPlayerState();
|
||||
playingItemId = playState.NowPlayingItem.Id;
|
||||
} catch (error) {
|
||||
playingItemId = '';
|
||||
}
|
||||
// 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
|
||||
});
|
||||
}).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({
|
||||
text: globalize.translate('MessageSyncPlayErrorMedia')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.injectPlaybackManager();
|
||||
events.trigger(this, 'enabled', [true]);
|
||||
|
||||
waitForEventOnce(this, 'ready').then(() => {
|
||||
this.processCommand(this.queuedCommand, apiClient);
|
||||
this.queuedCommand = null;
|
||||
});
|
||||
|
||||
this.syncPlayReady = false;
|
||||
this.notifySyncPlayReady = true;
|
||||
|
||||
timeSyncManager.forceUpdate();
|
||||
|
||||
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, 'enabled', [false]);
|
||||
this.restorePlaybackManager();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
const currentTime = new Date();
|
||||
const playAtTimeLocal = timeSyncManager.serverDateToLocal(playAtTime);
|
||||
|
||||
if (playAtTimeLocal > currentTime) {
|
||||
const playTimeout = playAtTimeLocal - currentTime;
|
||||
this.localSeek(positionTicks);
|
||||
|
||||
this.scheduledCommand = setTimeout(() => {
|
||||
this.localUnpause();
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.syncEnabled = true;
|
||||
}, SyncMethodThreshold / 2);
|
||||
|
||||
}, playTimeout);
|
||||
|
||||
console.debug('Scheduled play in', playTimeout / 1000.0, 'seconds.');
|
||||
} else {
|
||||
// Group playback already started
|
||||
const serverPositionTicks = positionTicks + (currentTime - playAtTimeLocal) * 10000;
|
||||
waitForEventOnce(this, 'unpause').then(() => {
|
||||
this.localSeek(serverPositionTicks);
|
||||
});
|
||||
this.localUnpause();
|
||||
|
||||
this.syncTimeout = setTimeout(() => {
|
||||
this.syncEnabled = true;
|
||||
}, 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();
|
||||
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) {
|
||||
const pauseTimeout = pauseAtTimeLocal - currentTime;
|
||||
this.scheduledCommand = setTimeout(callback, pauseTimeout);
|
||||
|
||||
console.debug('Scheduled pause in', pauseTimeout / 1000.0, 'seconds.');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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._localUnpause = playbackManager.unpause;
|
||||
playbackManager._localPause = playbackManager.pause;
|
||||
playbackManager._localSeek = 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._localUnpause;
|
||||
playbackManager.pause = playbackManager._localPause;
|
||||
playbackManager.seek = playbackManager._localSeek;
|
||||
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._localUnpause(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides PlaybackManager's seek method.
|
||||
*/
|
||||
seekRequest (PositionTicks, player) {
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var sessionId = getActivePlayerId();
|
||||
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.
|
||||
* 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;
|
||||
|
||||
const playAtTime = this.lastCommand.When;
|
||||
|
||||
const currentPositionTicks = playbackManager.currentTime();
|
||||
// Estimate PositionTicks on server
|
||||
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 diffMillis = (serverPositionTicks - currentPositionTicks) / 10000.0;
|
||||
|
||||
this.playbackDiffMillis = diffMillis;
|
||||
|
||||
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
|
||||
this.localSeek(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets SyncPlay stats.
|
||||
* @returns {Object} The SyncPlay stats.
|
||||
*/
|
||||
getStats () {
|
||||
return {
|
||||
TimeOffset: this.timeOffsetWithServer,
|
||||
PlaybackDiff: this.playbackDiffMillis,
|
||||
SyncMethod: this.syncMethod
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to update the SyncPlay status icon.
|
||||
*/
|
||||
showSyncIcon (syncMethod) {
|
||||
this.syncMethod = syncMethod;
|
||||
events.trigger(this, 'syncing', [true, this.syncMethod]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to clear the SyncPlay status icon.
|
||||
*/
|
||||
clearSyncIcon () {
|
||||
this.syncMethod = 'None';
|
||||
events.trigger(this, 'syncing', [false, this.syncMethod]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals an error state, which disables and resets SyncPlay for a new session.
|
||||
*/
|
||||
signalError () {
|
||||
this.disableSyncPlay();
|
||||
}
|
||||
}
|
||||
|
||||
/** SyncPlayManager singleton. */
|
||||
export default new SyncPlayManager();
|
207
src/components/syncplay/timeSyncManager.js
Normal file
207
src/components/syncplay/timeSyncManager.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
/**
|
||||
* 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} 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(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.requestReceived - this.requestSent) + (this.responseSent - this.responseReceived)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get round-trip delay.
|
||||
*/
|
||||
getDelay () {
|
||||
return (this.responseReceived - this.requestSent) - (this.responseSent - this.requestReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 requestSent = new Date();
|
||||
apiClient.getServerTime().then((response) => {
|
||||
const responseReceived = new Date();
|
||||
response.json().then((data) => {
|
||||
const requestReceived = new Date(data.RequestReceptionTime);
|
||||
const responseSent = new Date(data.ResponseTransmissionTime);
|
||||
|
||||
const measurement = new Measurement(requestSent, requestReceived, responseSent, responseReceived);
|
||||
this.updateTimeOffset(measurement);
|
||||
|
||||
// Avoid overloading server
|
||||
if (this.pings >= GreedyPingCount) {
|
||||
this.pollingInterval = PollingIntervalLowProfile;
|
||||
} else {
|
||||
this.pings++;
|
||||
}
|
||||
|
||||
events.trigger(this, 'update', [null, this.getTimeOffset(), this.getPing()]);
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
events.trigger(this, 'update', [error, null, null]);
|
||||
}).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();
|
|
@ -179,12 +179,6 @@ define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globa
|
|||
view.querySelector('#operatingSystem').innerHTML = globalize.translate('DashboardOperatingSystem', systemInfo.OperatingSystem);
|
||||
view.querySelector('#architecture').innerHTML = globalize.translate('DashboardArchitecture', systemInfo.SystemArchitecture);
|
||||
|
||||
if (systemInfo.CanSelfRestart) {
|
||||
view.querySelector('#btnRestartServer').classList.remove('hide');
|
||||
} else {
|
||||
view.querySelector('#btnRestartServer').classList.add('hide');
|
||||
}
|
||||
|
||||
view.querySelector('#cachePath').innerHTML = systemInfo.CachePath;
|
||||
view.querySelector('#logPath').innerHTML = systemInfo.LogPath;
|
||||
view.querySelector('#transcodePath').innerHTML = systemInfo.TranscodingTempPath;
|
||||
|
|
|
@ -18,11 +18,6 @@ define(['jQuery', 'loading', 'globalize', 'fnchecked', 'emby-checkbox', 'emby-te
|
|||
return '<option value="' + language.Value + '">' + language.Name + '</option>';
|
||||
})).val(config.UICulture);
|
||||
currentLanguage = config.UICulture;
|
||||
if (systemInfo.CanSelfRestart || systemInfo.CanSelfUpdate) {
|
||||
$('.autoUpdatesContainer', page).removeClass('hide');
|
||||
} else {
|
||||
$('.autoUpdatesContainer', page).addClass('hide');
|
||||
}
|
||||
|
||||
loading.hide();
|
||||
}
|
||||
|
|
|
@ -71,13 +71,13 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'e
|
|||
});
|
||||
}
|
||||
|
||||
function performInstallation(page, packageName, guid, updateClass, version) {
|
||||
function performInstallation(page, name, guid, version) {
|
||||
var developer = $('#developer', page).html().toLowerCase();
|
||||
|
||||
var alertCallback = function () {
|
||||
loading.show();
|
||||
page.querySelector('#btnInstall').disabled = true;
|
||||
ApiClient.installPlugin(packageName, guid, updateClass, version).then(function () {
|
||||
ApiClient.installPlugin(name, guid, version).then(function () {
|
||||
loading.hide();
|
||||
alertText(globalize.translate('PluginInstalledMessage'));
|
||||
});
|
||||
|
@ -112,9 +112,8 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'e
|
|||
var installedPlugin = plugins.filter(function (plugin) {
|
||||
return plugin.Name == name;
|
||||
})[0];
|
||||
var vals = $('#selectVersion', page).val().split('|');
|
||||
var version = vals[0];
|
||||
|
||||
var version = $('#selectVersion', page).val();
|
||||
if (installedPlugin) {
|
||||
if (installedPlugin.Version === version) {
|
||||
loading.hide();
|
||||
|
@ -124,7 +123,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize', 'connectionManager', 'e
|
|||
});
|
||||
}
|
||||
} else {
|
||||
performInstallation(page, name, guid, vals[1], version);
|
||||
performInstallation(page, name, guid, version);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
|
||||
<div style="margin-top:1em;">
|
||||
<button is="emby-button" type="button" id="btnRestartServer" class="raised hide" onclick="DashboardPage.restart(this);" style="margin-left:0;">
|
||||
<button is="emby-button" type="button" id="btnRestartServer" class="raised" onclick="DashboardPage.restart(this);" style="margin-left:0;">
|
||||
<span>${ButtonRestart}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnShutdown" class="raised" onclick="DashboardPage.shutdown(this);">
|
||||
|
|
|
@ -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', '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() {
|
||||
|
@ -12,6 +12,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
html += '</div>';
|
||||
html += '<div class="headerRight">';
|
||||
html += '<span class="headerSelectedPlayer"></span>';
|
||||
html += '<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide"><span class="material-icons sync_disabled"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="headerAudioPlayerButton audioPlayerButton headerButton headerButtonRight hide"><span class="material-icons music_note"></span></button>';
|
||||
html += '<button is="paper-icon-button-light" class="headerCastButton castButton headerButton headerButtonRight hide"><span class="material-icons cast"></span></button>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="headerButton headerButtonRight headerSearchButton hide"><span class="material-icons search"></span></button>';
|
||||
|
@ -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();
|
||||
|
@ -84,9 +86,16 @@ 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');
|
||||
headerSyncButton.classList.add('hide');
|
||||
|
||||
if (headerSearchButton) {
|
||||
headerSearchButton.classList.add('hide');
|
||||
|
@ -147,6 +156,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
}
|
||||
|
||||
headerAudioPlayerButton.addEventListener('click', showAudioPlayer);
|
||||
headerSyncButton.addEventListener('click', onSyncButtonClicked);
|
||||
|
||||
if (layoutManager.mobile) {
|
||||
initHeadRoom(skinHeader);
|
||||
|
@ -177,6 +187,31 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
});
|
||||
}
|
||||
|
||||
function onSyncButtonClicked() {
|
||||
var btn = this;
|
||||
groupSelectionMenu.show(btn);
|
||||
}
|
||||
|
||||
function onSyncPlayEnabled(event, enabled) {
|
||||
var icon = headerSyncButton.querySelector('span');
|
||||
icon.classList.remove('sync', 'sync_disabled', 'sync_problem');
|
||||
if (enabled) {
|
||||
icon.classList.add('sync');
|
||||
} else {
|
||||
icon.classList.add('sync_disabled');
|
||||
}
|
||||
}
|
||||
|
||||
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.classList.add('sync');
|
||||
}
|
||||
}
|
||||
|
||||
function getItemHref(item, context) {
|
||||
return appRouter.getRouteUrl(item, {
|
||||
context: context
|
||||
|
@ -799,6 +834,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 +967,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
|
|||
updateUserInHeader();
|
||||
});
|
||||
events.on(playbackManager, 'playerchange', updateCastIcon);
|
||||
events.on(syncPlayManager, 'enabled', onSyncPlayEnabled);
|
||||
events.on(syncPlayManager, 'syncing', onSyncPlaySyncing);
|
||||
loadNavDrawer();
|
||||
return LibraryMenu;
|
||||
});
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
@ -819,6 +826,10 @@ 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('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);
|
||||
define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"ButtonAddScheduledTaskTrigger": "إضافة زناد",
|
||||
"ButtonAddServer": "إضافة خادم",
|
||||
"ButtonAddUser": "اضافة مستخدم",
|
||||
"ButtonArrowDown": "أدنى",
|
||||
"ButtonArrowDown": "أسفل",
|
||||
"ButtonArrowLeft": "يسار",
|
||||
"ButtonArrowRight": "يمين",
|
||||
"ButtonArrowUp": "أعلى",
|
||||
|
@ -234,7 +234,7 @@
|
|||
"HeaderPlayback": "تشغيل الوسائط",
|
||||
"HeaderPleaseSignIn": "الرجاء تسجيل الدخول",
|
||||
"HeaderPluginInstallation": "تثبيت الملحفات",
|
||||
"HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات:",
|
||||
"HeaderPreferredMetadataLanguage": "اللغة المفضلة لواصفات البيانات",
|
||||
"HeaderProfile": "الحساب",
|
||||
"HeaderProfileInformation": "معلومات العريضة",
|
||||
"HeaderProfileServerSettingsHelp": "هذه القيم ستتحكم في كيفية تقديم شكل خادم أمبي في الجهاز",
|
||||
|
@ -1045,5 +1045,112 @@
|
|||
"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": "عندما يتقدم رمز تحويل أو إعادة تحويل بعيدًا بما فيه الكفاية عن موضع التشغيل الحالي ، أوقف العملية مؤقتًا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون البحث كثيرًا. أوقف هذا إذا واجهت مشاكل في التشغيل.",
|
||||
"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": "لم يعجبنى"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"AlwaysPlaySubtitles": "Vždy zobrazovat",
|
||||
"AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.",
|
||||
"Anytime": "Kdykoliv",
|
||||
"AroundTime": "Okolo {0}",
|
||||
"AroundTime": "Okolo",
|
||||
"Art": "Umění",
|
||||
"Artists": "Umělci",
|
||||
"AsManyAsPossible": "Tolikrát jak je možné",
|
||||
|
@ -1353,7 +1353,7 @@
|
|||
"LabelLimit": "Limit:",
|
||||
"LabelMaxStreamingBitrate": "Maximální kvalita streamování:",
|
||||
"LabelMetadata": "Metadata:",
|
||||
"LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, zadání cesty ke sdílené složce umožnit aplikacím Jellyfin na jiných zařízeních přímý přístup k souborům s médii.",
|
||||
"LabelOptionalNetworkPathHelp": "Pokud je tato složka sdílena ve vaší síti, zadání cesty ke sdílené složce umožní aplikacím Jellyfin na jiných zařízeních přímý přístup k souborům s médii. Například {0} nebo {1}.",
|
||||
"LabelPersonRole": "Úloha:",
|
||||
"LabelPlaylist": "Playlist:",
|
||||
"LabelReasonForTranscoding": "Důvod pro překódování:",
|
||||
|
@ -1600,5 +1600,36 @@
|
|||
"LabelRequireHttpsHelp": "Server automaticky přesměruje všechny požadavky z HTTP na HTTPS. Pokud server nenaslouchá na portu HTTPS, tato funkce nemá žádný účinek.",
|
||||
"LabelRequireHttps": "Vyžadovat HTTPS",
|
||||
"TabDVR": "Nahrávání",
|
||||
"HeaderDVR": "Nahrávání"
|
||||
"HeaderDVR": "Nahrávání",
|
||||
"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 <b>{0}</b> se načítá…",
|
||||
"MessageSyncPlayUserLeft": "Uživatel <b>{0}</b> opustil skupinu.",
|
||||
"MessageSyncPlayUserJoined": "Uživatel <b>{0}</b> 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ě"
|
||||
}
|
||||
|
|
|
@ -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,9 +1535,39 @@
|
|||
"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",
|
||||
"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": "<b>{0}</b> ist am laden...",
|
||||
"MessageSyncPlayUserLeft": "<b>{0}</b> hat die Gruppe verlassen.",
|
||||
"MessageSyncPlayUserJoined": "<b>{0}</b> 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"
|
||||
}
|
||||
|
|
|
@ -1233,5 +1233,8 @@
|
|||
"AskAdminToCreateLibrary": "Ζητήστε από έναν διαχειριστή να δημιουργήσει μια βιβλιοθήκη.",
|
||||
"Artist": "Καλλιτέχνης",
|
||||
"AllowedRemoteAddressesHelp": "Λίστα διαχωρισμένων διευθύνσεων IP ή καταχωρίσεων IP / netmask για δίκτυα που θα επιτρέπεται η σύνδεση εξ αποστάσεως. Εάν αφεθεί κενό, όλες οι απομακρυσμένες διευθύνσεις θα επιτρέπονται.",
|
||||
"AllowFfmpegThrottlingHelp": "Όταν ένας διακωδικοποιητής ή remux φτάσει αρκετά μπροστά από την τρέχουσα θέση αναπαραγωγής, διακόψτε τη διαδικασία ώστε να καταναλώσει λιγότερους πόρους. Αυτό είναι πιο χρήσιμο όταν παρακολουθείτε χωρίς να αναζητάτε συχνά. Απενεργοποιήστε το εάν αντιμετωπίζετε προβλήματα αναπαραγωγής."
|
||||
"AllowFfmpegThrottlingHelp": "Όταν ένας διακωδικοποιητής ή remux φτάσει αρκετά μπροστά από την τρέχουσα θέση αναπαραγωγής, διακόψτε τη διαδικασία ώστε να καταναλώσει λιγότερους πόρους. Αυτό είναι πιο χρήσιμο όταν παρακολουθείτε χωρίς να αναζητάτε συχνά. Απενεργοποιήστε το εάν αντιμετωπίζετε προβλήματα αναπαραγωγής.",
|
||||
"ButtonTogglePlaylist": "Λίστα αναπαραγωγής",
|
||||
"ButtonToggleContextMenu": "Περισσότερα",
|
||||
"ButtonSplit": "Διαχωρισμός"
|
||||
}
|
||||
|
|
|
@ -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: <code>http://example.com/<b><baseurl></b></code>",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -495,6 +495,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",
|
||||
|
@ -646,7 +648,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",
|
||||
|
@ -766,7 +768,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:",
|
||||
|
@ -863,6 +865,18 @@
|
|||
"LabelSubtitlePlaybackMode": "Subtitle mode:",
|
||||
"LabelSubtitles": "Subtitles",
|
||||
"LabelSupportedMediaTypes": "Supported Media Types:",
|
||||
"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",
|
||||
"LabelTVHomeScreen": "TV mode home screen:",
|
||||
"LabelTag": "Tag:",
|
||||
"LabelTagline": "Tagline:",
|
||||
|
@ -991,7 +1005,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.",
|
||||
|
@ -1026,6 +1039,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": "<b>{0}</b> has joined the group.",
|
||||
"MessageSyncPlayUserLeft": "<b>{0}</b> has left the group.",
|
||||
"MessageSyncPlayGroupWait": "<b>{0}</b> 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.",
|
||||
|
@ -1373,6 +1401,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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1228,7 +1228,7 @@
|
|||
"Aired": "Emitido",
|
||||
"AnyLanguage": "Cualquier idioma",
|
||||
"Anytime": "En cualquier momento",
|
||||
"AroundTime": "Aproximadamente {0}",
|
||||
"AroundTime": "Aproximadamente",
|
||||
"Ascending": "Ascendente",
|
||||
"Audio": "Audio",
|
||||
"Auto": "Automático",
|
||||
|
@ -1520,6 +1520,9 @@
|
|||
"HeaderHttpsSettings": "Opciones HTTPS",
|
||||
"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"
|
||||
"LabelEnableHttpsHelp": "Permite que el servidor escuche en el puerto HTTPS configurado. También se debe configurar un certificado válido para que esto surta efecto.",
|
||||
"LabelEnableHttps": "Activar HTTPS",
|
||||
"TabDVR": "DVR",
|
||||
"SaveChanges": "Guardar cambios",
|
||||
"HeaderDVR": "DVR"
|
||||
}
|
||||
|
|
|
@ -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": "مورد علاقهها",
|
||||
|
|
|
@ -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: <code>http://example.com/<b><baseurl></b></code>",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -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": "उनके लिए खोज करके संग्रह में आइटम जोड़ें और उन्हें संग्रह में जोड़ने के लिए उनके राइट-क्लिक या टैप मेनू का उपयोग करें।"
|
||||
}
|
||||
|
|
|
@ -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:",
|
||||
|
@ -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": "<b>{0}</b> bufferel...",
|
||||
"MessageSyncPlayUserLeft": "<b>{0}</b> elhagyta a csoportot.",
|
||||
"MessageSyncPlayUserJoined": "<b>{0}</b> 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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -282,8 +282,266 @@
|
|||
"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": "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}",
|
||||
"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",
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"AlwaysPlaySubtitlesHelp": "I sottotitoli corrispondenti alla lingua preferita saranno caricati a prescindere dalla lingua dell'audio.",
|
||||
"AnyLanguage": "Qualsiasi lingua",
|
||||
"Anytime": "In qualsiasi momento",
|
||||
"AroundTime": "Circa {0}",
|
||||
"AroundTime": "Circa",
|
||||
"Artists": "Artisti",
|
||||
"AsManyAsPossible": "Tutto il possibile",
|
||||
"Ascending": "Crescente",
|
||||
|
@ -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:",
|
||||
|
@ -1516,5 +1516,9 @@
|
|||
"LabelEnableHttps": "Abilita HTTPS",
|
||||
"HeaderServerAddressSettings": "Configurazione Indirizzo Server",
|
||||
"HeaderRemoteAccessSettings": "Configurazione Access Remoto",
|
||||
"HeaderHttpsSettings": "Configurazione HTTPS"
|
||||
"HeaderHttpsSettings": "Configurazione HTTPS",
|
||||
"TabDVR": "DVR",
|
||||
"SaveChanges": "Salva modifiche",
|
||||
"HeaderDVR": "DVR",
|
||||
"LabelNightly": "Nightly"
|
||||
}
|
||||
|
|
|
@ -1 +1,23 @@
|
|||
{}
|
||||
{
|
||||
"TabLogs": "Crow's Nest",
|
||||
"HeaderAdmin": "Cap'n",
|
||||
"WelcomeToProject": "Ahoy, matey! This be Jellyfin!",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -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)",
|
||||
|
@ -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:",
|
||||
|
@ -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:<code>http://exemplo.com/<b><baseurl></b></code>",
|
||||
"LabelBaseUrl": "URL Base:",
|
||||
"LabelBitrate": "Bitrate:",
|
||||
"LabelAudioSampleRate": "Taxa de amostragem do áudio:",
|
||||
|
@ -1507,5 +1507,48 @@
|
|||
"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 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",
|
||||
"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": "<b>{0}</b> está carregando...",
|
||||
"MessageSyncPlayUserLeft": "<b>{0}</b> deixou o grupo.",
|
||||
"MessageSyncPlayUserJoined": "<b>{0}</b> 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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@
|
|||
"Anytime": "Oricând",
|
||||
"Art": "Artă",
|
||||
"AlwaysPlaySubtitlesHelp": "Subtitrările care se potrivesc cu preferințele limbii utilizate vor fi încărcate indiferent de limba audio.",
|
||||
"AroundTime": "Împrejur {0}",
|
||||
"AroundTime": "Împrejur",
|
||||
"AsManyAsPossible": "Cât mai mulți cu putință",
|
||||
"Ascending": "Ascendent",
|
||||
"AspectRatio": "Raportul aspectului",
|
||||
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"AlwaysPlaySubtitlesHelp": "Субтитры, соответствующие настройке языка, будут загружаться независимо от языка аудио.",
|
||||
"AnyLanguage": "Любой язык",
|
||||
"Anytime": "В любое время",
|
||||
"AroundTime": "Около {0}",
|
||||
"AroundTime": "Около",
|
||||
"Art": "Виньетка",
|
||||
"Artists": "Исполнители",
|
||||
"AsManyAsPossible": "Как можно больше",
|
||||
|
@ -710,7 +710,7 @@
|
|||
"LabelNumberOfGuideDays": "Число дней для загрузки данных телегида:",
|
||||
"LabelNumberOfGuideDaysHelp": "Больше дней загрузки данных телегида обеспечивает возможность заблаговременно назначать расписание и просматривать больше перечней, однако это займёт больше времени для загрузки. При значении «Авто» выбор определяется числом каналов.",
|
||||
"LabelOptionalNetworkPath": "(Необязательно) Общедоступная сетевая папка:",
|
||||
"LabelOptionalNetworkPathHelp": "Если данная папка общедоступна в своей сети, предоставление пути к сетевой папке может позволить Jellyfin-приложениям на других устройствах получить прямой доступ к медиафайлам.",
|
||||
"LabelOptionalNetworkPathHelp": "Если данная папка является общей в сети, указание пути к сетевой папке может позволить Jellyfin-приложениям на других устройствах иметь прямой доступ к медиафайлам. Например, {0} или {1}.",
|
||||
"LabelOriginalAspectRatio": "Исходное соотношение сторон:",
|
||||
"LabelOriginalTitle": "Оригинальное название:",
|
||||
"LabelOverview": "Обзор:",
|
||||
|
@ -1508,7 +1508,7 @@
|
|||
"MessageUnauthorizedUser": "В настоящее время у вас нет доступа к серверу. Пожалуйста, свяжитесь с администратором сервера для получения дополнительной информации.",
|
||||
"HeaderFavoritePlaylists": "Избранные плей-листы",
|
||||
"LabelRequireHttpsHelp": "Если этот флажок установлен, сервер будет автоматически перенаправлять все запросы через HTTP на HTTPS. Это не имеет никакого эффекта, если сервер не слушает HTTPS.",
|
||||
"LabelEnableHttpsHelp": "Позволяет серверу слушать сконфигурированный порт HTTPS. Действительный сертификат также должен быть сконфигурирован для того, чтобы это вступило в силу.",
|
||||
"LabelEnableHttpsHelp": "Позволяет серверу слушать сконфигурированный HTTPS-порт. Действительный сертификат также должен быть сконфигурирован для того, чтобы это вступило в силу.",
|
||||
"ApiKeysCaption": "Список действующих текущих API-ключей",
|
||||
"TabDVR": "DVR",
|
||||
"SaveChanges": "Сохранить изменения",
|
||||
|
|
|
@ -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.",
|
||||
|
@ -1520,5 +1520,35 @@
|
|||
"HeaderServerAddressSettings": "Nastavenie adresy servera",
|
||||
"HeaderRemoteAccessSettings": "Nastavenie vzdialeného prístupu",
|
||||
"HeaderHttpsSettings": "Nastavenia HTTPS",
|
||||
"HeaderDVR": "DVR"
|
||||
"HeaderDVR": "DVR",
|
||||
"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 <b>{0}</b> sa načítava...",
|
||||
"MessageSyncPlayUserLeft": "Používateľ <b>{0}</b> opustil skupinu.",
|
||||
"MessageSyncPlayUserJoined": "Používateľ <b>{0}</b> 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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
|
@ -706,5 +706,83 @@
|
|||
"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 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.",
|
||||
"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",
|
||||
"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ụ: <code>http://example.com/<b><baseurl></b></code>",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -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": "客户端搜寻时间间隔(秒)",
|
||||
|
@ -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添加自定义子目录,例如:<code>http://example.com/<b><baseurl></b></code>。",
|
||||
"MusicLibraryHelp": "重播 {0}音乐命名指南{1}。",
|
||||
"HeaderFavoritePeople": "最喜欢的人物",
|
||||
"OptionRandom": "随机",
|
||||
|
@ -1510,5 +1510,19 @@
|
|||
"New": "新的",
|
||||
"HeaderFavoritePlaylists": "收藏的播放列表",
|
||||
"ButtonTogglePlaylist": "播放列表",
|
||||
"ButtonToggleContextMenu": "更多"
|
||||
"ButtonToggleContextMenu": "更多",
|
||||
"HeaderServerAddressSettings": "服务器地址设置",
|
||||
"HeaderRemoteAccessSettings": "远程访问设置",
|
||||
"HeaderHttpsSettings": "HTTPS 设置",
|
||||
"ApiKeysCaption": "当前启用的 API 密钥",
|
||||
"TabDVR": "DVR",
|
||||
"SaveChanges": "保存更改",
|
||||
"LabelRequireHttpsHelp": "开启后服务器将自动将所有 HTTP 请求重定向到 HTTPS。如果服务器没有启用 HTTPS 则不生效。",
|
||||
"LabelRequireHttps": "强制 HTTPS",
|
||||
"LabelStable": "稳定版",
|
||||
"LabelEnableHttpsHelp": "开启服务器对所配置HTTPS 端口的监听。必须配置有效的证书才会生效。",
|
||||
"LabelEnableHttps": "启用 HTTPS",
|
||||
"LabelChromecastVersion": "Chromecast版本",
|
||||
"HeaderDVR": "DVR",
|
||||
"LabelNightly": "Nightly"
|
||||
}
|
||||
|
|
|
@ -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": "目前限制訪問。 請稍後再試。"
|
||||
}
|
||||
|
|
|
@ -293,7 +293,17 @@ html {
|
|||
|
||||
.emby-checkbox:checked + span + .checkboxOutline,
|
||||
.itemProgressBarForeground {
|
||||
background-color: #00a4dc;
|
||||
background: linear-gradient(90deg, rgba(0, 210, 201, 1) 0%, rgba(13, 194, 98, 1) 28%, rgba(0, 75, 185, 1) 100%);
|
||||
}
|
||||
|
||||
.itemProgressBar {
|
||||
background: rgba(230, 230, 230, 0.8);
|
||||
height: 0.18rem;
|
||||
}
|
||||
|
||||
.innerCardFooter {
|
||||
border-radius: 0.14rem;
|
||||
margin: 0.4rem 0.5rem 0.4rem 0.5rem;
|
||||
}
|
||||
|
||||
.emby-checkbox:focus:not(:checked) + span + .checkboxOutline {
|
||||
|
@ -445,6 +455,12 @@ html {
|
|||
border-color: #00a4dc !important;
|
||||
}
|
||||
|
||||
.cardContent-button,
|
||||
.itemDetailImage,
|
||||
.cardOverlayContainer {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.metadataSidebarIcon {
|
||||
color: #00a4dc;
|
||||
}
|
||||
|
|
|
@ -104,6 +104,16 @@
|
|||
<div class="fieldDescription">${LabelUserRemoteClientBitrateLimitHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<div class="selectContainer fldSelectSyncPlayAccess">
|
||||
<select class="selectSyncPlayAccess" is="emby-select" id="selectSyncPlayAccess" label="${LabelSyncPlayAccess}">
|
||||
<option value="CreateAndJoinGroups">${LabelSyncPlayAccessCreateAndJoinGroups}</option>
|
||||
<option value="JoinGroups">${LabelSyncPlayAccessJoinGroups}</option>
|
||||
<option value="None">${LabelSyncPlayAccessNone}</option>
|
||||
</select>
|
||||
<div class="fieldDescription">${SyncPlayAccessHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<h2 class="checkboxListLabel" style="margin-bottom:1em;">${HeaderAllowMediaDeletionFrom}</h2>
|
||||
<div class="checkboxList paperList checkboxList-paperList">
|
||||
|
|
|
@ -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: {
|
||||
|
@ -47,6 +47,10 @@ module.exports = merge(common, {
|
|||
use: [
|
||||
'file-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(mp3)$/i,
|
||||
use: ['file-loader']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
@ -40,6 +40,10 @@ module.exports = merge(common, {
|
|||
use: [
|
||||
'file-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(mp3)$/i,
|
||||
use: ['file-loader']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
62
yarn.lock
62
yarn.lock
|
@ -3169,10 +3169,10 @@ dashdash@^1.12.0:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
date-fns@^2.13.0:
|
||||
version "2.13.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.13.0.tgz#d7b8a0a2d392e8d88a8024d0a46b980bbfdbd708"
|
||||
integrity sha512-xm0c61mevGF7f0XpCGtDTGpzEFC/1fpLXHbmFpxZZQJuvByIK2ozm6cSYuU+nxFYOPh2EuCfzUwlTEFwKG+h5w==
|
||||
date-fns@^2.14.0:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba"
|
||||
integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==
|
||||
|
||||
dateformat@^2.0.0:
|
||||
version "2.2.0"
|
||||
|
@ -3523,12 +3523,12 @@ dom-serializer@0:
|
|||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom7@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/dom7/-/dom7-2.1.3.tgz#a736f9c3bfbc4ca039a81cd095f97d1d7f3de19c"
|
||||
integrity sha512-QTxHHDox+M6ZFz1zHPAHZKI3JOHY5iY4i9BK2uctlggxKQwRhO3q3HHFq1BKsT25Bm/ySSj70K6Wk/G4bs9rMQ==
|
||||
dom7@^2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/dom7/-/dom7-2.1.5.tgz#a79411017800b31d8400070cdaebbfc92c1f6377"
|
||||
integrity sha512-xnhwVgyOh3eD++/XGtH+5qBwYTgCm0aW91GFgPJ3XG+jlsRLyJivnbP0QmUBFhI+Oaz9FV0s7cxgXHezwOEBYA==
|
||||
dependencies:
|
||||
ssr-window "^1.0.1"
|
||||
ssr-window "^2.0.0"
|
||||
|
||||
domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
|
@ -5244,10 +5244,10 @@ gulp-babel@^8.0.0:
|
|||
through2 "^2.0.0"
|
||||
vinyl-sourcemaps-apply "^0.2.0"
|
||||
|
||||
gulp-cli@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc"
|
||||
integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==
|
||||
gulp-cli@^2.2.0, gulp-cli@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.1.tgz#376e427661b7996430a89d71c15df75defa3360a"
|
||||
integrity sha512-yEMxrXqY8mJFlaauFQxNrCpzWJThu0sH1sqlToaTOT063Hub9s/Nt2C+GSLe6feQ/IMWrHvGOOsyES7CQc9O+A==
|
||||
dependencies:
|
||||
ansi-colors "^1.0.1"
|
||||
archy "^1.0.0"
|
||||
|
@ -6575,10 +6575,10 @@ isurl@^1.0.0-alpha5:
|
|||
has-to-string-tag-x "^1.2.0"
|
||||
is-object "^1.0.1"
|
||||
|
||||
jellyfin-apiclient@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jellyfin-apiclient/-/jellyfin-apiclient-1.1.1.tgz#9b9e6e2f827d221ef000eb3dac09dda7623a6a06"
|
||||
integrity sha512-ga+LAVxRu9LvnNI1gx53iZTvN/4VnwAdUOUX5xfq+3KJXeg8rWcPg4sdArR7efZeN9Cs60diKwDw8Y+RlnYmHA==
|
||||
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-noto@https://github.com/jellyfin/jellyfin-noto":
|
||||
version "1.0.3"
|
||||
|
@ -10997,10 +10997,10 @@ sshpk@^1.7.0:
|
|||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssr-window@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssr-window/-/ssr-window-1.0.1.tgz#30752a6a4666e7767f0b7e6aa6fc2fdbd0d9b369"
|
||||
integrity sha512-dgFqB+f00LJTEgb6UXhx0h+SrG50LJvti2yMKMqAgzfUmUXZrLSv2fjULF7AWGwK25EXu8+smLR3jYsJQChPsg==
|
||||
ssr-window@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ssr-window/-/ssr-window-2.0.0.tgz#98c301aef99523317f8d69618f0010791096efc4"
|
||||
integrity sha512-NXzN+/HPObKAx191H3zKlYomE5WrVIkoCB5IaSdvKokxTpjBdWfr0RaP+1Z5KOfDT0ZVz+2tdtiBkhsEQ9p+0A==
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
|
@ -11394,10 +11394,10 @@ stylelint-order@^4.0.0:
|
|||
postcss "^7.0.26"
|
||||
postcss-sorting "^5.0.1"
|
||||
|
||||
stylelint@^13.4.0:
|
||||
version "13.4.0"
|
||||
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.4.0.tgz#15071c4cc15138365acf2396395c17823cf652b9"
|
||||
integrity sha512-VOAOkTil5GmUfEJH+O6LdsggoUt692QTSu+YfLhVP5sFTVCVp0+PS2oCjjG8ZdUAP9aNNNYxTP6GWVbB1tl2bg==
|
||||
stylelint@^13.5.0:
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.5.0.tgz#9edbf90c8c02c47fd0c4818376e3799145f22cab"
|
||||
integrity sha512-+Jy7ieKAWKTf2tmcAE7jgScxH39Urb87i0bjK/enScFaGWWaFn4kAPwepGOSk2b7CLUDVt/O6kwA0x0p/V7moQ==
|
||||
dependencies:
|
||||
"@stylelint/postcss-css-in-js" "^0.37.1"
|
||||
"@stylelint/postcss-markdown" "^0.36.1"
|
||||
|
@ -11573,13 +11573,13 @@ svgo@^1.0.0, svgo@^1.3.2:
|
|||
unquote "~1.1.1"
|
||||
util.promisify "~1.0.0"
|
||||
|
||||
swiper@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/swiper/-/swiper-5.4.0.tgz#fa7222bf39e068404362f6c8e11964cfbe41ab0e"
|
||||
integrity sha512-2L2JX3BsTUjyZVFcwGI0eYg5G7w5EF/Pb4ogqwHlqGk3GbfI84N+p3khpaVsBvZK1tIkjyf9OwR0uD+BC38Dng==
|
||||
swiper@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/swiper/-/swiper-5.4.1.tgz#6731e000e97f8b6560c11b141ebaf559063af565"
|
||||
integrity sha512-l2EiWe7uOXB2EBMVLtJqn51FW22wF9e24WETT+S+tuFNvSDq1gadc/hyGGsAMqFGKJKIO6q6cqk7ToVaOI+onw==
|
||||
dependencies:
|
||||
dom7 "^2.1.3"
|
||||
ssr-window "^1.0.1"
|
||||
dom7 "^2.1.5"
|
||||
ssr-window "^2.0.0"
|
||||
|
||||
symbol-observable@1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue