From f3b84cadadc75ba2a519e3ff97c8964393dd2363 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Mon, 10 Apr 2023 12:06:00 +0300 Subject: [PATCH 01/18] enable airplay for audioplayer --- src/components/nowPlayingBar/nowPlayingBar.js | 16 +++++++++ src/plugins/htmlAudioPlayer/plugin.js | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 2bf29a2a9c02..b25097e325e2 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -32,6 +32,7 @@ import { appRouter } from '../appRouter'; let volumeSliderContainer; let playPauseButtons; let positionSlider; + let toggleAirPlayButton; let toggleRepeatButton; let toggleRepeatButtonIcon; @@ -80,6 +81,8 @@ import { appRouter } from '../appRouter'; html += ''; html += ''; + html += ''; + html += ''; html += ''; @@ -190,6 +193,13 @@ import { appRouter } from '../appRouter'; } }); + toggleAirPlayButton = elem.querySelector('.btnAirPlay'); + toggleAirPlayButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleAirPlay(currentPlayer); + } + }); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { if (currentPlayer) { playbackManager.toggleQueueShuffleMode(); @@ -326,6 +336,12 @@ import { appRouter } from '../appRouter'; toggleRepeatButton.classList.remove('hide'); } + if (supportedCommands.indexOf('AirPlay') === -1) { + toggleAirPlayButton.classList.add('hide'); + } else { + toggleAirPlayButton.classList.remove('hide'); + } + updateRepeatModeDisplay(playbackManager.getRepeatMode()); onQueueShuffleModeChange(); diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 585d935f05cc..30e1cb488057 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -347,6 +347,10 @@ class HtmlAudioPlayer { return getDefaultProfile(); } + toggleAirPlay() { + return this.setAirPlayEnabled(!this.isAirPlayEnabled()); + } + // Save this for when playback stops, because querying the time at that point might return 0 currentTime(val) { const mediaElement = this._mediaElement; @@ -488,6 +492,33 @@ class HtmlAudioPlayer { return false; } + isAirPlayEnabled() { + if (document.AirPlayEnabled) { + return !!document.AirplayElement; + } + return false; + } + + setAirPlayEnabled(isEnabled) { + const mediaElement = this._mediaElement; + + if (document.AirPlayEnabled) { + if (mediaElement) { + if (isEnabled) { + mediaElement.requestAirPlay().catch(function(err) { + console.error('Error requesting AirPlay', err); + }); + } else { + document.exitAirPLay().catch(function(err) { + console.error('Error exiting AirPlay', err); + }); + } + } + } else { + mediaElement.webkitShowPlaybackTargetPicker(); + } + } + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); @@ -507,6 +538,10 @@ function getSupportedFeatures() { list.push('PlaybackRate'); } + if (browser.safari || browser.iOS || browser.iPad) { + list.push('AirPlay'); + } + return list; } From 14fca37e783a15f8f5f924d01efd0abc635ab827 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Tue, 18 Apr 2023 20:17:25 +0300 Subject: [PATCH 02/18] Convert if/else add/remove statements to toggle --- src/components/nowPlayingBar/nowPlayingBar.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index b25097e325e2..8ec7103fe364 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -336,11 +336,8 @@ import { appRouter } from '../appRouter'; toggleRepeatButton.classList.remove('hide'); } - if (supportedCommands.indexOf('AirPlay') === -1) { - toggleAirPlayButton.classList.add('hide'); - } else { - toggleAirPlayButton.classList.remove('hide'); - } + const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1; + toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton); updateRepeatModeDisplay(playbackManager.getRepeatMode()); onQueueShuffleModeChange(); From 90fc207c9eecb6a9f6e7e8224bdaadd93f1def7b Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Thu, 20 Apr 2023 22:26:01 +0300 Subject: [PATCH 03/18] fix eslint newline required at end of file --- src/components/nowPlayingBar/nowPlayingBar.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index b2c892e85ca1..ba14fc41d83b 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -793,4 +793,5 @@ document.addEventListener('viewbeforeshow', function (e) { hideNowPlayingBar(); } } -}); \ No newline at end of file +}); + From 70f1557086aee41f26f9f96549e4a7b21527a473 Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Thu, 11 May 2023 22:27:57 +0300 Subject: [PATCH 04/18] Add AirPlay for html audioplayer only if is safari desktop --- src/plugins/htmlAudioPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index fb6afc72e7c0..66b95ecc1d00 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -538,7 +538,7 @@ function getSupportedFeatures() { list.push('PlaybackRate'); } - if (browser.safari || browser.iOS || browser.iPad) { + if (browser.safari) { list.push('AirPlay'); } From 23158a352c4bb7d2050003bf712a32c584a3191b Mon Sep 17 00:00:00 2001 From: Georgi Stamatov Date: Sat, 17 Jun 2023 13:18:13 +0300 Subject: [PATCH 05/18] update airplay conditions for htmlaudioplayer --- src/plugins/htmlAudioPlayer/plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 66b95ecc1d00..653796055c54 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -502,8 +502,8 @@ class HtmlAudioPlayer { setAirPlayEnabled(isEnabled) { const mediaElement = this._mediaElement; - if (document.AirPlayEnabled) { - if (mediaElement) { + if (mediaElement) { + if (document.AirPlayEnabled) { if (isEnabled) { mediaElement.requestAirPlay().catch(function(err) { console.error('Error requesting AirPlay', err); @@ -513,9 +513,9 @@ class HtmlAudioPlayer { console.error('Error exiting AirPlay', err); }); } + } else { + mediaElement.webkitShowPlaybackTargetPicker(); } - } else { - mediaElement.webkitShowPlaybackTargetPicker(); } } From 1825b821af94db2f8ba6b5179b9be4b7691f9876 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Fri, 14 Jul 2023 21:32:34 +0300 Subject: [PATCH 06/18] cleanup duplicate userSettings from libraryBrowser --- src/controllers/movies/moviecollections.js | 8 ++-- src/controllers/movies/moviegenres.js | 10 ++-- src/controllers/movies/movietrailers.js | 6 +-- src/controllers/music/musicalbums.js | 15 ++---- src/controllers/music/musicartists.js | 56 ++++++++++------------ src/controllers/music/musicgenres.js | 12 ++--- src/controllers/music/musicplaylists.js | 10 ++-- src/controllers/music/songs.js | 36 +++++++------- src/controllers/shows/episodes.js | 34 ++++++------- src/controllers/shows/tvgenres.js | 10 ++-- src/controllers/shows/tvshows.js | 44 ++++++++--------- src/controllers/shows/tvstudios.js | 7 ++- src/scripts/libraryBrowser.js | 43 ----------------- src/scripts/playlists.js | 32 ++++++------- src/scripts/settings/userSettings.js | 20 ++++++++ 15 files changed, 147 insertions(+), 196 deletions(-) diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index 6e6af9dc98c6..da9477c888a6 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -24,7 +24,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -32,7 +32,7 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -183,7 +183,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -234,7 +234,7 @@ export default function (view, params, tabContent) { btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(tabElement); diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index 52cb97bb79a3..3d866c6e9acb 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -1,7 +1,7 @@ import escapeHtml from 'escape-html'; import layoutManager from '../../components/layoutManager'; import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; import globalize from '../../scripts/globalize'; @@ -22,10 +22,10 @@ export default function (view, params, tabContent) { Recursive: true, EnableTotalRecordCount: false }, - view: 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -181,7 +181,7 @@ export default function (view, params, tabContent) { elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); }); } @@ -203,7 +203,7 @@ export default function (view, params, tabContent) { this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 81f9f8b14931..8e9f7c4acccc 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -27,14 +27,14 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { pageData.query['Limit'] = userSettings.libraryPageSize(); } - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -172,7 +172,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; }); diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 6c0c45a0f0d8..f878b7e65c5a 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -42,7 +42,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -50,7 +50,7 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -61,11 +61,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - if (!savedQueryKey) { - savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); - } - - return savedQueryKey; + return `${params.topParentId}-musicalbums`; } const onViewStyleChange = () => { @@ -174,7 +170,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -184,7 +180,6 @@ export default function (view, params, tabContent) { }); }; - let savedQueryKey; let pageData; let isLoading = false; @@ -280,7 +275,7 @@ export default function (view, params, tabContent) { btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(); diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 4126086adebe..2d89fd80782c 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -10,8 +10,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -31,26 +31,22 @@ export default function (view, params, tabContent) { pageData = data[key] = { query: queryValues, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - const getSavedQueryKey = (context) => { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); - } - - return context.savedQueryKey; - }; + function getSavedQueryKey() { + return `${params.topParentId}-${this.mode}`; + } const onViewStyleChange = () => { const viewStyle = this.getCurrentViewStyle(); @@ -67,10 +63,10 @@ export default function (view, params, tabContent) { itemsContainer.innerHTML = ''; }; - const reloadItems = (page) => { + const reloadItems = () => { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); const promise = this.mode == 'albumartists' ? ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : ApiClient.getArtists(ApiClient.getCurrentUserId(), query); @@ -83,7 +79,7 @@ export default function (view, params, tabContent) { if (userSettings.libraryPageSize() > 0) { query.StartIndex += query.Limit; } - reloadItems(tabContent); + reloadItems(); } function onPreviousPageClick() { @@ -94,7 +90,7 @@ export default function (view, params, tabContent) { if (userSettings.libraryPageSize() > 0) { query.StartIndex = Math.max(0, query.StartIndex - query.Limit); } - reloadItems(tabContent); + reloadItems(); } window.scrollTo(0, 0); @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -172,20 +168,20 @@ export default function (view, params, tabContent) { this.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: this.mode, serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); + getQuery().StartIndex = 0; + reloadItems(); }); filterDialog.show(); }); }; this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; const initPage = (tabElement) => { @@ -194,7 +190,7 @@ export default function (view, params, tabContent) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { const newValue = e.detail.value; - const query = getQuery(tabElement); + const query = getQuery(); if (newValue === '#') { query.NameLessThan = 'A'; delete query.NameStartsWith; @@ -203,7 +199,7 @@ export default function (view, params, tabContent) { delete query.NameLessThan; } query.StartIndex = 0; - reloadItems(tabElement); + reloadItems(); }); this.alphaPicker = new AlphaPicker({ element: alphaPickerElement, @@ -223,11 +219,11 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; onViewStyleChange(); - reloadItems(tabElement); + reloadItems(); }); }; @@ -235,8 +231,8 @@ export default function (view, params, tabContent) { onViewStyleChange(); this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); + reloadItems(); + this.alphaPicker?.updateControls(getQuery()); }; } diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index 9c5bc9dcb2c7..3b7952957081 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -1,4 +1,4 @@ -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; @@ -17,10 +17,10 @@ export default function (view, params, tabContent) { Fields: 'PrimaryImageAspectRatio,ItemCounts', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -31,7 +31,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); + return `${params.topParentId}-genres`; } function getPromise() { @@ -87,7 +87,7 @@ export default function (view, params, tabContent) { const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); import('../../components/autoFocuser').then(({ default: autoFocuser }) => { @@ -113,7 +113,7 @@ export default function (view, params, tabContent) { this.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index ab3cf8f1d248..3bf23b385781 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -1,4 +1,4 @@ -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; @@ -18,10 +18,10 @@ export default function (view, params, tabContent) { Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -32,7 +32,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); + return `${params.topParentId}-musicplaylists`; } function getPromise() { @@ -58,7 +58,7 @@ export default function (view, params, tabContent) { const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); import('../../components/autoFocuser').then(({ default: autoFocuser }) => { diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 5e5337d03260..9f405ed7b1eb 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -12,8 +12,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -35,28 +35,24 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('songs'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-songs`; } function reloadItems(page) { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -117,7 +113,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); tabContent.querySelector('.btnShuffle').classList.toggle('hide', result.TotalRecordCount < 1); @@ -137,13 +133,13 @@ export default function (view, params, tabContent) { self.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'songs', serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); + getQuery().StartIndex = 0; + reloadItems(); }); filterDialog.show(); }); @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { } self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; function initPage(tabElement) { @@ -197,10 +193,10 @@ export default function (view, params, tabContent) { id: 'Random,SortName' }], callback: function () { - getQuery(tabElement).StartIndex = 0; - reloadItems(tabElement); + getQuery().StartIndex = 0; + reloadItems(); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index f2f6c605d71c..ea67583fed54 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -11,8 +11,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -28,7 +28,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -36,22 +36,18 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-episodes`; } function onViewStyleChange() { @@ -72,7 +68,7 @@ export default function (view, params, tabContent) { function reloadItems(page) { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -156,7 +152,7 @@ export default function (view, params, tabContent) { itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -173,7 +169,7 @@ export default function (view, params, tabContent) { self.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'episodes', serverId: ApiClient.serverId() }); @@ -185,7 +181,7 @@ export default function (view, params, tabContent) { }; self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; function initPage(tabElement) { @@ -222,7 +218,7 @@ export default function (view, params, tabContent) { callback: function () { reloadItems(tabElement); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); @@ -232,8 +228,8 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); onViewStyleChange(); reloadItems(tabElement); }); diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index b9e02039eb92..061089af86c2 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -1,7 +1,7 @@ import escapeHtml from 'escape-html'; import layoutManager from '../../components/layoutManager'; import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; +import * as userSettings from '../../scripts/settings/userSettings'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; import globalize from '../../scripts/globalize'; @@ -25,7 +25,7 @@ export default function (view, params, tabContent) { view: 'Poster' }; pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; @@ -36,7 +36,7 @@ export default function (view, params, tabContent) { } function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('seriesgenres'); + return `${params.topParentId}-seriesgenres`; } function getPromise() { @@ -176,7 +176,7 @@ export default function (view, params, tabContent) { elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); }); } @@ -199,7 +199,7 @@ export default function (view, params, tabContent) { self.setCurrentViewStyle = function (viewStyle) { getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); fullyReload(); }; diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 3dea00f1b88c..f6751f38f3ba 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -11,8 +11,8 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -27,7 +27,7 @@ export default function (view, params, tabContent) { EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -35,22 +35,18 @@ export default function (view, params, tabContent) { } pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${params.topParentId}-series`; } const onViewStyleChange = () => { @@ -71,7 +67,7 @@ export default function (view, params, tabContent) { const reloadItems = (page) => { loading.show(); isLoading = true; - const query = getQuery(page); + const query = getQuery(); ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { function onNextPageClick() { if (isLoading) { @@ -185,7 +181,7 @@ export default function (view, params, tabContent) { const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); loading.hide(); isLoading = false; @@ -201,12 +197,12 @@ export default function (view, params, tabContent) { this.showFilterMenu = function () { import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { const filterDialog = new FilterDialog({ - query: getQuery(tabContent), + query: getQuery(), mode: 'series', serverId: ApiClient.serverId() }); Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; + getQuery().StartIndex = 0; reloadItems(tabContent); }); filterDialog.show(); @@ -214,7 +210,7 @@ export default function (view, params, tabContent) { }; this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; + return getPageData().view; }; const initPage = (tabElement) => { @@ -223,7 +219,7 @@ export default function (view, params, tabContent) { alphaPickerElement.addEventListener('alphavaluechanged', function (e) { const newValue = e.detail.value; - const query = getQuery(tabElement); + const query = getQuery(); if (newValue === '#') { query.NameLessThan = 'A'; delete query.NameStartsWith; @@ -274,10 +270,10 @@ export default function (view, params, tabContent) { id: 'PremiereDate,SortName' }], callback: function () { - getQuery(tabElement).StartIndex = 0; + getQuery().StartIndex = 0; reloadItems(tabElement); }, - query: getQuery(tabElement), + query: getQuery(), button: e.target }); }); @@ -287,9 +283,9 @@ export default function (view, params, tabContent) { }); btnSelectView.addEventListener('layoutchange', function (e) { const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; + getPageData().view = viewStyle; + userSettings.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; onViewStyleChange(); reloadItems(tabElement); }); @@ -300,7 +296,7 @@ export default function (view, params, tabContent) { this.renderTab = () => { reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); + this.alphaPicker?.updateControls(getQuery()); }; } diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index 26ed743f733f..3f75cd27fddc 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -1,9 +1,8 @@ import loading from '../../components/loading/loading'; -import libraryBrowser from '../../scripts/libraryBrowser'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; function getQuery(params) { - const key = getSavedQueryKey(); + const key = getSavedQueryKey(params); let pageData = data[key]; if (!pageData) { @@ -23,8 +22,8 @@ function getQuery(params) { return pageData.query; } -function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('studios'); +function getSavedQueryKey(params) { + return `${params.topParentId}-studios`; } function getPromise(context, params) { diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index ce4f6410a1dc..abf14886880c 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -1,43 +1,5 @@ -import * as userSettings from './settings/userSettings'; import globalize from './globalize'; -export function getSavedQueryKey(modifier) { - return window.location.href.split('#')[0] + (modifier || ''); -} - -export function loadSavedQueryValues(key, query) { - let values = userSettings.get(key); - - if (values) { - values = JSON.parse(values); - return Object.assign(query, values); - } - - return query; -} - -export function saveQueryValues(key, query) { - const values = {}; - - if (query.SortBy) { - values.SortBy = query.SortBy; - } - - if (query.SortOrder) { - values.SortOrder = query.SortOrder; - } - - userSettings.set(key, JSON.stringify(values)); -} - -export function saveViewSetting (key, value) { - userSettings.set(key + '-_view', value); -} - -export function getSavedView (key) { - return userSettings.get(key + '-_view'); -} - export function showLayoutMenu (button, currentLayout, views) { let dispatchEvent = true; @@ -204,11 +166,6 @@ export function showSortMenu (options) { } const libraryBrowser = { - getSavedQueryKey, - loadSavedQueryValues, - saveQueryValues, - saveViewSetting, - getSavedView, showLayoutMenu, getQueryPagingHtml, showSortMenu diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index aba0c389ae77..b8b7380b5794 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -9,8 +9,8 @@ import '../elements/emby-itemscontainer/emby-itemscontainer'; import Dashboard from '../utils/dashboard'; export default function (view) { - function getPageData(context) { - const key = getSavedQueryKey(context); + function getPageData() { + const key = getSavedQueryKey(); let pageData = data[key]; if (!pageData) { @@ -23,7 +23,7 @@ export default function (view) { Fields: 'PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete', StartIndex: 0 }, - view: libraryBrowser.getSavedView(key) || 'Poster' + view: userSettings.getSavedView(key) || 'Poster' }; if (userSettings.libraryPageSize() > 0) { @@ -31,22 +31,18 @@ export default function (view) { } pageData.query.ParentId = libraryMenu.getTopParentId(); - libraryBrowser.loadSavedQueryValues(key, pageData.query); + userSettings.loadQuerySettings(key, pageData.query); } return pageData; } - function getQuery(context) { - return getPageData(context).query; + function getQuery() { + return getPageData().query; } - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(); - } - - return context.savedQueryKey; + function getSavedQueryKey() { + return `${libraryMenu.getTopParentId()}-playlists`; } function showLoadingMessage() { @@ -58,7 +54,7 @@ export default function (view) { } function onViewStyleChange() { - const viewStyle = getPageData(view).view; + const viewStyle = getPageData().view; const itemsContainer = view.querySelector('.itemsContainer'); if (viewStyle == 'List') { @@ -74,7 +70,7 @@ export default function (view) { function reloadItems() { showLoadingMessage(); - const query = getQuery(view); + const query = getQuery(); const promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query); // TODO: promise2 is unused, check if necessary. const promise2 = Dashboard.getCurrentUser(); @@ -83,7 +79,7 @@ export default function (view) { // TODO: Is the scroll necessary? window.scrollTo(0, 0); let html = ''; - const viewStyle = getPageData(view).view; + const viewStyle = getPageData().view; view.querySelector('.listTopPaging').innerHTML = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, @@ -172,14 +168,14 @@ export default function (view) { if (btnChangeLayout) { btnChangeLayout.addEventListener('layoutchange', function (e) { const layout = e.detail.viewStyle; - getPageData(view).view = layout; - libraryBrowser.saveViewSetting(getSavedQueryKey(view), layout); + getPageData().view = layout; + userSettings.saveViewSetting(getSavedQueryKey(), layout); onViewStyleChange(); reloadItems(); }); } - libraryBrowser.saveQueryValues(getSavedQueryKey(view), query); + userSettings.saveQuerySettings(getSavedQueryKey(), query); hideLoadingMessage(); }); } diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 086ae747dc2e..bfe060868d54 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -533,6 +533,24 @@ export class UserSettings { return this.set(key, JSON.stringify(values)); } + /** + * Get view layout setting. + * @param {string} key - View Setting key. + * @return {string} View Setting value. + */ + getSavedView(key) { + return this.get(key + '-_view'); + } + + /** + * Set view layout setting. + * @param {string} key - View Setting key. + * @param {string} value - View Setting value. + */ + saveViewSetting(key, value) { + return this.set(key + '-_view', value); + } + /** * Get subtitle appearance settings. * @param {string|undefined} key - Settings key. @@ -638,3 +656,5 @@ export const setFilter = currentSettings.setFilter.bind(currentSettings); export const getFilter = currentSettings.getFilter.bind(currentSettings); export const customCss = currentSettings.customCss.bind(currentSettings); export const disableCustomCss = currentSettings.disableCustomCss.bind(currentSettings); +export const getSavedView = currentSettings.getSavedView.bind(currentSettings); +export const saveViewSetting = currentSettings.saveViewSetting.bind(currentSettings); From f47bd0633d159ef86dd80c83127a2ff0720a9735 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Sat, 8 Jul 2023 20:04:51 +0300 Subject: [PATCH 07/18] Add Sort Menu setting components --- .../components/library/SortButton.tsx | 185 ++++++++++++++++++ src/types/libraryTab.ts | 5 +- 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/apps/experimental/components/library/SortButton.tsx diff --git a/src/apps/experimental/components/library/SortButton.tsx b/src/apps/experimental/components/library/SortButton.tsx new file mode 100644 index 000000000000..7deeae349b09 --- /dev/null +++ b/src/apps/experimental/components/library/SortButton.tsx @@ -0,0 +1,185 @@ +import React, { FC, useCallback } from 'react'; +import IconButton from '@mui/material/IconButton'; +import MenuItem from '@mui/material/MenuItem'; +import Popover from '@mui/material/Popover'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; +import Box from '@mui/material/Box'; +import InputLabel from '@mui/material/InputLabel'; +import FormControl from '@mui/material/FormControl'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import SortByAlphaIcon from '@mui/icons-material/SortByAlpha'; + +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client'; + +const sortMenuOptions = [ + { label: 'Name', value: ItemSortBy.SortName }, + { label: 'OptionRandom', value: ItemSortBy.Random }, + { label: 'OptionImdbRating', value: ItemSortBy.CommunityRating }, + { label: 'OptionCriticRating', value: ItemSortBy.CriticRating }, + { label: 'OptionDateAdded', value: ItemSortBy.DateCreated }, + { label: 'OptionDatePlayed', value: ItemSortBy.DatePlayed }, + { label: 'OptionParentalRating', value: ItemSortBy.OfficialRating }, + { label: 'OptionPlayCount', value: ItemSortBy.PlayCount }, + { label: 'OptionReleaseDate', value: ItemSortBy.PremiereDate }, + { label: 'Runtime', value: ItemSortBy.Runtime } +]; + +const sortOrderMenuOptions = [ + { label: 'Ascending', value: SortOrder.Ascending }, + { label: 'Descending', value: SortOrder.Descending } +]; + +interface SortButtonProps { + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch< + React.SetStateAction + >; +} + +const SortButton: FC = ({ + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const id = open ? 'sort-popover' : undefined; + + const handleClick = useCallback((event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const onSelectChange = useCallback( + (event: SelectChangeEvent) => { + const name = event.target.name; + + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + [name]: event.target.value + })); + }, + [setLibraryViewSettings] + ); + + const getVisibleSortMenu = () => { + const visibleSortMenu: ItemSortBy[] = [ItemSortBy.SortName, ItemSortBy.Random, ItemSortBy.DateCreated]; + + if ( + viewType !== LibraryTab.Photos + && viewType !== LibraryTab.Videos + && viewType !== LibraryTab.Books + ) { + visibleSortMenu.push(ItemSortBy.CommunityRating); + visibleSortMenu.push(ItemSortBy.CriticRating); + visibleSortMenu.push(ItemSortBy.DatePlayed); + visibleSortMenu.push(ItemSortBy.OfficialRating); + visibleSortMenu.push(ItemSortBy.PlayCount); + visibleSortMenu.push(ItemSortBy.PremiereDate); + visibleSortMenu.push(ItemSortBy.Runtime); + } + + return visibleSortMenu; + }; + + return ( + + + + + + + + + + {globalize.translate('LabelSortBy')} + + + + + + + + + + {globalize.translate('LabelSortOrder')} + + + + + + + ); +}; + +export default SortButton; diff --git a/src/types/libraryTab.ts b/src/types/libraryTab.ts index 8ccf93663ed1..1484ed9646d7 100644 --- a/src/types/libraryTab.ts +++ b/src/types/libraryTab.ts @@ -19,5 +19,8 @@ export enum LibraryTab { Songs = 'songs', Suggestions = 'suggestions', Trailers = 'trailers', - Upcoming = 'upcoming' + Upcoming = 'upcoming', + Photos = 'photos', + Videos = 'videos', + Books = 'books', } From 28ab879f3c99f8c7831c05309199ef2740c66d4a Mon Sep 17 00:00:00 2001 From: Marcus Nilsson Date: Fri, 18 Aug 2023 19:41:12 +0200 Subject: [PATCH 08/18] Change Subtitle Sync slider to go from -300 to 300 This patch changes the subtitle sync from using a procentage to a 'slider value' that ranges from -300 to 300. The reasons for this is that WebOS doesn't jump in 0.1 increments but instead jumps 1.0 increments in the slider, which results in subtitle sync jumping 0.6s per increment. Using a value from -300 to 300 makes LG WebOS jump 0.1s instead. --- src/components/subtitlesync/subtitlesync.js | 34 ++++++++++--------- .../subtitlesync/subtitlesync.template.html | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index dcdfe45874cd..ba8984486be9 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -54,7 +54,7 @@ function init(instance) { playbackManager.setSubtitleOffset(inputOffset, player); // synchronize with slider value subtitleSyncSlider.updateOffset( - getPercentageFromOffset(inputOffset)); + getSliderValueFromOffset(inputOffset)); } else { this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's'; } @@ -79,17 +79,17 @@ function init(instance) { } }; - subtitleSyncSlider.updateOffset = function (percent) { - // default value is 0s = 50% - this.value = percent === undefined ? 50 : percent; + subtitleSyncSlider.updateOffset = function (sliderValue) { + // default value is 0s = 0ms + this.value = sliderValue === undefined ? 0 : sliderValue; }; subtitleSyncSlider.addEventListener('change', function () { // set new offset - playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + playbackManager.setSubtitleOffset(getOffsetFromSliderValue(this.value), player); // synchronize with textField value subtitleSyncTextField.updateOffset( - getOffsetFromPercentage(this.value)); + getOffsetFromSliderValue(this.value)); }); subtitleSyncSlider.getBubbleHtml = function (value) { @@ -108,20 +108,22 @@ function init(instance) { } function getOffsetFromPercentage(value) { - // convert percent to fraction + // convert percentage to fraction let offset = (value - 50) / 50; // multiply by offset min/max range value (-x to +x) : offset *= 30; return offset.toFixed(1); } -function getPercentageFromOffset(value) { - // divide by offset min/max range value (-x to +x) : - let percentValue = value / 30; - // convert fraction to percent - percentValue *= 50; - percentValue += 50; - return Math.min(100, Math.max(0, percentValue.toFixed(1))); +function getOffsetFromSliderValue(value) { + // convert slider value to offset + const offset = value / 10; + return offset.toFixed(1); +} + +function getSliderValueFromOffset(value) { + const sliderValue = value * 10; + return Math.min(300, Math.max(-300, sliderValue.toFixed(1))); } class SubtitleSync { @@ -155,8 +157,8 @@ class SubtitleSync { if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) { // if no subtitle offset is defined or element has focus (offset being defined) if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) { - // set default offset to '0' = 50% - subtitleSyncSlider.value = '50'; + // set default offset to '0' = 0ms + subtitleSyncSlider.value = '0'; subtitleSyncTextField.textContent = '0s'; playbackManager.setSubtitleOffset(0, player); } diff --git a/src/components/subtitlesync/subtitlesync.template.html b/src/components/subtitlesync/subtitlesync.template.html index fcd86dbc323e..a055d24fcb71 100644 --- a/src/components/subtitlesync/subtitlesync.template.html +++ b/src/components/subtitlesync/subtitlesync.template.html @@ -3,7 +3,7 @@
0s
- +
From 5cff1d9eac9b2d70fa9b6cde2123da5b6fbb0052 Mon Sep 17 00:00:00 2001 From: Csaba Date: Sat, 2 Sep 2023 19:02:52 +0000 Subject: [PATCH 09/18] Translated using Weblate (Hungarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hu/ --- src/strings/hu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/hu.json b/src/strings/hu.json index 0c8c2a4f7cee..ce412a519037 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -1761,5 +1761,7 @@ "LogLevel.None": "Egyik sem", "MessageRepositoryInstallDisclaimer": "FIGYELMEZTETÉS: Harmadik féltől származó beépülő modulok tárolójának telepítése kockázatokkal jár. Instabil vagy rosszindulatú kódot tartalmazhat, és bármikor megváltozhat. Csak olyan szerzők tárolóit telepítse, akikben megbízik.", "TonemappingModeHelp": "Válassza ki a tónus leképezési módot. Ha kiégett fénypontokat tapasztal, próbáljon átváltani RGB módra.", - "Unknown": "Ismeretlen" + "Unknown": "Ismeretlen", + "LabelBackdropScreensaverInterval": "Háttér Képernyővédő intervallum", + "LabelBackdropScreensaverIntervalHelp": "A különböző hátterek közötti idő másodpercben a háttér képernyővédő használatakor." } From 1e817d37d20473bf693866ba60dbe233d181c677 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sun, 3 Sep 2023 05:22:37 +0000 Subject: [PATCH 10/18] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fi/ --- src/strings/fi.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strings/fi.json b/src/strings/fi.json index 9aab413ec3d5..3d2b0a7e18d9 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1021,7 +1021,7 @@ "ErrorGettingTvLineups": "Ladattaessa televisiokokoonpanoja tapahtui virhe. Varmista, että tiedot ovat oikein ja yritä uudelleen.", "EnableDetailsBannerHelp": "Näyttää julistekuvan yksityiskohdat -sivun ylälaidassa.", "EnableDetailsBanner": "Yksityiskohtien julistekuva", - "ButtonSyncPlay": "Synkronoitu toisto", + "ButtonSyncPlay": "SyncPlay", "HeaderAccessSchedule": "Käyttöaikataulu", "HeaderAccessScheduleHelp": "Rajoita käyttö tiettyihin aikoihin luomalla käyttöaikataulu.", "HardwareAccelerationWarning": "Laitteistokiihdytyksen käyttöönotto voi joissain ympäristöissä aiheuttaa epävakautta. Varmista, että käyttöjärjestelmän ja näytönohjaimen ajurit ovat ajan tasalla. Mikäli huomaat videotoistossa ongelmia käyttöönoton jälkeen, palauta \"Ei mitään\" -asetus.", @@ -1062,7 +1062,7 @@ "LabelDownMixAudioScale": "Äänen tehostus alasmiksatessa", "LabelDateAddedBehavior": "Uudelle sisällölle käytettävä päiväys", "LabelBlastMessageInterval": "Hereiläolo-viestin väli", - "LabelBindToLocalNetworkAddress": "Kiinnitä verkko-osoitteeseen", + "LabelBindToLocalNetworkAddress": "Sido lähiverkon osoitteeseen", "LabelAirsBeforeSeason": "Lähetetään ennen kautta", "LabelAirsBeforeEpisode": "Lähetetään ennen jaksoa", "LabelAirsAfterSeason": "Lähetetään kauden jälkeen", @@ -1760,6 +1760,6 @@ "LabelThrottleDelaySecondsHelp": "Aika sekunneissa, jonka kuluttua transkooderi rauhoitetaan. Tämän on oltava riittävän suuri, jotta päätelaite kykenee ylläpitämään reilua puskuria. Toimii vain rauhoituksen ollessa käytössä.", "LabelSegmentKeepSeconds": "Osioiden säilytysaika", "LabelSegmentKeepSecondsHelp": "Aika sekunteina, jonka osiot säilytetään ennen päällekirjoitusta. Oltava \"Rahoita kun on kulunut\" -aikaa suurempi. Toimii vain osioiden poiston ollessa käytössä.", - "LabelBackdropScreensaverInterval": "Taustan näytönsäästäjän aikaväli", - "LabelBackdropScreensaverIntervalHelp": "Aikaväli sekuntteina eri taustojen välillä, kun taustan näytönsäästäjä on käytössä." + "LabelBackdropScreensaverInterval": "Taustanäytönsäästäjän ajoitus", + "LabelBackdropScreensaverIntervalHelp": "Aika sekuntteina, jonka kuluttua kuva vaihtuu taustanäytönsäästäjää käytettäessä." } From 7ec8815162d4c451253eea890132c42b9630c1e9 Mon Sep 17 00:00:00 2001 From: Kityn Date: Sun, 3 Sep 2023 21:44:47 +0000 Subject: [PATCH 11/18] Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pl/ --- src/strings/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/pl.json b/src/strings/pl.json index 8a8a206f0509..19e031e64588 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -311,7 +311,7 @@ "HeaderLibraryFolders": "Foldery biblioteki", "HeaderLibraryOrder": "Kolejność biblioteki", "HeaderLibrarySettings": "Ustawienia biblioteki", - "HeaderLiveTvTunerSetup": "Konfiguracja tunera telewizyjnego", + "HeaderLiveTvTunerSetup": "Konfiguracja tunera TV", "HeaderLoginFailure": "Niepowodzenie logowania", "HeaderMedia": "Multimedia", "HeaderMediaFolders": "Foldery mediów", From 70fd9788c0778b79bc21e3827eff05357c2c39aa Mon Sep 17 00:00:00 2001 From: jackqj Date: Mon, 4 Sep 2023 02:50:49 +0000 Subject: [PATCH 12/18] Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/zh_Hans/ --- src/strings/zh-cn.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index dd144aea0126..d95a1ec96202 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1761,5 +1761,7 @@ "LabelThrottleDelaySeconds": "向后缓存限制", "LabelSegmentKeepSeconds": "缓存片段时长", "LabelSegmentKeepSecondsHelp": "在覆盖之前应保留片段的时间(以秒为单位)。必须大于“向后缓存限制”。仅在启用片段删除时生效。", - "HeaderEpisodesStatus": "剧集状态" + "HeaderEpisodesStatus": "剧集状态", + "LabelBackdropScreensaverInterval": "屏幕保护程序间隔", + "LabelBackdropScreensaverIntervalHelp": "不同屏幕保护切换的时间间隔秒数。" } From a0b7c4a6547203d6d0ed16bbe32a3aae34f787b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Mon, 4 Sep 2023 05:55:14 +0000 Subject: [PATCH 13/18] Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/cs/ --- src/strings/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/cs.json b/src/strings/cs.json index ecf5bc0e6ac8..600ad2c4161a 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1120,7 +1120,7 @@ "LabelUserRemoteClientBitrateLimitHelp": "Přepíše výchozí globální hodnotu nastavenou v nastavení serveru, viz Nástěnka > Přehrávání > Streamování.", "LabelVideoCodec": "Video kodek", "LeaveBlankToNotSetAPassword": "Můžete ponechat prázdné pro nastavení bez hesla.", - "LiveTV": "Televize", + "LiveTV": "Živý přenos", "Logo": "Logo", "ManageLibrary": "Spravovat knihovnu", "MediaInfoDefault": "Výchozí", From 4290b84828d17154a817db992cf0687d318a2950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Beni=C4=87?= Date: Wed, 6 Sep 2023 20:14:44 +0000 Subject: [PATCH 14/18] Translated using Weblate (Croatian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/hr/ --- src/strings/hr.json | 47 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/strings/hr.json b/src/strings/hr.json index dd9a518e09ea..b1588a79db66 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -1370,5 +1370,50 @@ "HeaderRecordingMetadataSaving": "Snimanje metapodataka", "LabelChapterImageResolution": "Rezolucija", "AllowCollectionManagement": "Dozvoli ovom korisniku da upravlja kolekcijama", - "LabelDummyChapterDuration": "Interval" + "LabelDummyChapterDuration": "Interval", + "UnknownVideoStreamInfo": "Informacije o video zapisu su nepoznate", + "LabelTonemappingMode": "Način mapiranja tonova", + "MediaInfoDvProfile": "DV profil", + "SelectAll": "Odaberi Sve", + "LabelVppTonemappingContrast": "Pojačanje kontrasta u VPP mapiranju tonova", + "LabelVppTonemappingBrightnessHelp": "Postavi pojačanje svjetline u VPP mapiranju tonova. Preporučene i zadane vrijednosti su 16 i 0.", + "MediaInfoDvVersionMinor": "Minor DV verzija", + "MediaInfoVideoRangeType": "Vrsta raspona videa", + "LabelVideoRangeType": "Vrsta raspona videa", + "VideoRangeTypeNotSupported": "Vrsta raspona videa nije podržana", + "LabelHardwareEncodingOptions": "Opcije hardverskog kodiranja", + "VideoBitrateNotSupported": "Bitrate videa nije podržan", + "AudioIsExternal": "Audio zapis je vanjski", + "DeletedScene": "Obrisana Scena", + "BehindTheScenes": "Iza kulisa", + "ContainerBitrateExceedsLimit": "Bitrate videa premašuje ograničenje", + "LabelVppTonemappingContrastHelp": "Postavi pojačanje kontrasta u VPP mapiranju tonova. Preporučene i zadane vrijednosti su 1.", + "MediaInfoRpuPresentFlag": "Zastavica DV rpu predloška", + "Interview": "Intervju", + "TonemappingModeHelp": "Odaberite način mapiranja tonova. Ako primijetite preintenzivne svijetle dijelove, pokušajte prebaciti na RGB način.", + "MediaInfoBlPresentFlag": "Zastavica DV bl predloška", + "MediaInfoDvLevel": "DV razina", + "Unknown": "Nepoznato", + "UnknownAudioStreamInfo": "Informacije o audio zapisu su nepoznate", + "DirectPlayError": "Došlo je do greške prilikom pokretanja izravne reprodukcije", + "ThemeVideo": "", + "Scene": "Scena", + "Sample": "Uzorak", + "MediaInfoDvVersionMajor": "Major DV verzija", + "MediaInfoElPresentFlag": "Zastavica DV el predloška", + "EnableIntelLowPowerHevcHwEncoder": "Omogući Intel HEVC hardversko kodiranje s niskom potrošnjom", + "MediaInfoDvBlSignalCompatibilityId": "ID kompatibilnost DV bl signala", + "ThemeSong": "Glavna Pjesma", + "Select": "Odabir", + "Trailer": "Trailer", + "Clip": "Klip", + "Short": "Kratki film", + "Featurette": "Kratki prilog", + "EnableIntelLowPowerH264HwEncoder": "Omogući Intel H.264 hardversko kodiranje s niskom potrošnjom", + "LabelVppTonemappingBrightness": "Pojačanje svjetline u VPP mapiranju tonova", + "EnableEnhancedNvdecDecoderHelp": "Eksperimentalna NVDEC implementacija, ne omogućujte ovu opciju osim ako ne naiđete na greške pri dekodiranju.", + "MediaInfoDoViTitle": "DV naslov", + "IntelLowPowerEncHelp": "Kodiranje s niskom potrošnjom može zadržati nepotrebnu sinkronizaciju CPU-GPU. Na Linuxu moraju biti onemogućeni ako i915 HuC firmware nije konfiguriran", + "PreferSystemNativeHwDecoder": "Preferiraj izvorne DXVA ili VA-API hardverske dekodere operativnog sustava", + "EnableSplashScreen": "Omogući početni ekran" } From 215616f8a5a646044c186682ce478d58e8cbbb71 Mon Sep 17 00:00:00 2001 From: Bas Date: Thu, 7 Sep 2023 19:38:38 +0000 Subject: [PATCH 15/18] Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/nl/ --- src/strings/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/nl.json b/src/strings/nl.json index 7ba5c3c1387d..a69adfcf0bc7 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -474,7 +474,7 @@ "LabelEnableDlnaClientDiscoveryInterval": "Interval voor het zoeken naar clients", "LabelEnableDlnaClientDiscoveryIntervalHelp": "Bepaalt de duur in seconden tussen twee SSDP-zoekopdrachten.", "LabelEnableDlnaDebugLogging": "DLNA-foutopsporingslogboek inschakelen", - "LabelEnableDlnaDebugLoggingHelp": "Genereer grote logboekbestanden en is alleen bedoeld voor het troubleshooting doeleinden.", + "LabelEnableDlnaDebugLoggingHelp": "Genereert grote logboekbestanden en is alleen bedoeld voor troubleshooting-doeleinden.", "LabelEnableDlnaPlayTo": "'Play to' DLNA-functie inschakelen", "LabelEnableDlnaPlayToHelp": "Apparaten detecteren binnen uw netwerk en maak het mogelijk om ze op afstand te gebruiken.", "LabelEnableDlnaServer": "DLNA-server inschakelen", From ea9fc551065745ea01d2e59a96ec727d469c6f4d Mon Sep 17 00:00:00 2001 From: Rajmond Burgaj Date: Fri, 8 Sep 2023 11:10:53 +0000 Subject: [PATCH 16/18] Translated using Weblate (Albanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sq/ --- src/strings/sq.json | 52 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/strings/sq.json b/src/strings/sq.json index e2e5c11d39d6..9eda6faae130 100644 --- a/src/strings/sq.json +++ b/src/strings/sq.json @@ -354,5 +354,55 @@ "DatePlayed": "E luajtur me datën", "ButtonSpace": "Hapsirë", "Digital": "Dixhital", - "EveryHour": "Çdo orë" + "EveryHour": "Çdo orë", + "Experimental": "Eksperimentale", + "FastForward": "Shpejto", + "EnableNextVideoInfoOverlay": "Shfaq informacionin e videos së radhës gjatë luatjes", + "General": "Të përgjithshme", + "DropShadow": "Nënhije", + "EnableThemeVideosHelp": "Luani video tematike ne prapaskenë përgjatë navigimit të librarisë.", + "DisplayMissingEpisodesWithinSeasons": "Shfaq episodet që mungojnë brenda sezoneve", + "GroupBySeries": "Grupo sipas serive", + "EnableCinemaMode": "Formati Kinema", + "Features": "Veçoritë", + "DrmChannelsNotImported": "Kanalet me DRM nuk do të importohen.", + "ErrorStartHourGreaterThanEnd": "Koha e mbarimit duhet të jetë më e madhe se koha e fillimit.", + "EditMetadata": "Redaktoni të dhënat bazë", + "EnableNextVideoInfoOverlayHelp": "Në fund të videos, shfaq informacion përreth videos së radhës që shfaqet nga lista aktuale.", + "Filter": "Filtro", + "ErrorSavingTvProvider": "Ndodhi një problem gjatë ruajtjes së ofruesit televiziv. Ju lutem sigurohuni që të jetë i aksesueshëm dhe provoni përsëri.", + "ErrorAddingXmlTvFile": "Ndodhi një problem gjatë aksesimit të skedarit XMLTV. Sigurohuni që skedari në fjalë ekziston dhe provoni përsëri.", + "ErrorGettingTvLineups": "Ndodhi një problem gjatë shkarkimit të formacionit të TV-së. Ju lutem sigurohuni që informacioni juaj është korrekt dhe provoni përsëri.", + "ErrorPlayerNotFound": "Asnjë instrument nuk i gjend për median në fjalë.", + "FetchingData": "Duke marrë të dhëna të tjera", + "ErrorDefault": "Ndodhi një problem gjatë procesimit të kërkesës. Ju lutem provojeni përseri.", + "GroupVersions": "Versionet e grupimit", + "DownloadAll": "Shkarkoni të gjitha", + "CustomDlnaProfilesHelp": "Krijoni një profil të personalizuar për paisje të reja ose të anashkaloni profilin e sistemit.", + "FileReadError": "Ndodhi një problem gjatë leximit të skedarit.", + "EnableDisplayMirroring": "Pasqyrimi i pamjes", + "DisplayModeHelp": "Përzgjidhni stilin e faqosjes që dëshironi për ndërfaqe.", + "DefaultMetadataLangaugeDescription": "Këto janë vlerat e paracaktuara të cilat mund të ndryshohen në nivel librarie.", + "ConfirmDeleteItems": "Fshirja e ketyre gjërave nënkupton fshirjen nga sistemi dhe nga libraria juaj. Dëshironi të vazhdoni?", + "Filters": "Filtrat", + "ConfirmEndPlayerSession": "Dëshironi te fikni Jellyfin në {0}?", + "EnableHardwareEncoding": "Lejoni shifrimin me anë të paisjeve elektronike", + "EnableBlurHashHelp": "Imazhet që janë duke u ngarkuar do të shfaqen me një tiketë të veçantë.", + "Cursive": "Kursive", + "DirectPlayHelp": "Skedari është plotësisht e pajtueshme me përdoruesin dhe sesioni në fjalë është duke pranur skedarin siç është.", + "EnableRewatchingNextUpHelp": "Lejoni shfaqjen e episodeve të shikuara në seksionet e radhës.", + "GoogleCastUnsupported": "Google Cast nuk suportohet", + "AllowCollectionManagement": "Lejoni këtë përdorues për menaxhimin e koleksioneve", + "DisplayInMyMedia": "Shfaq në hyrje", + "DisplayMissingEpisodesWithinSeasonsHelp": "Duhet të jetë gjithashtu e lejueshme për libraritë TV në konfigurimin e serverit.", + "EnableAudioNormalization": "Normalizimi i audios", + "AllowSegmentDeletion": "Fshi pjesët", + "AllowSegmentDeletionHelp": "Fshi pjesët pasi të jenë dërguar tek përdoruesi. Kjo parandalon ruajtjen e të gjithë dosjes së transkoduar në disk. Punon vetëm kur \"Limitimi\" është i aktivizuar. Fikëni nëse keni ndonjë problem me shikimin.", + "LabelThrottleDelaySeconds": "Limito pas", + "LabelThrottleDelaySecondsHelp": "Koha në sekonda pas të ciles traskoduesi do të limitohet. Duhet të jetë majftueshëm e madhe që përdoruesi të ketë një hapësire të nevojshme. Punon vetëm kur \"Limitimi\" është i aktivizuar.", + "LabelSegmentKeepSeconds": "Koha e ruajtjes së pjesëve", + "LabelSegmentKeepSecondsHelp": "Koha në sekonda që pjesët duhen ruajtur para se të rishkruhen. Duhet të jetë më e madhe se fusha \"Limito pas\". Punon vetëm nëse fshirja e pjesëve është e aktivizuar.", + "DeleteDevicesConfirmation": "Dëshironi të fshini të gjitha paisjet? Të gjithe sesionet e tjera do te shkyçen. Paisjet do të rishfaqen herës tjetër kur një përdorues kyçet sërish.", + "EnablePhotosHelp": "Imazhet do të zbulohen dhe shfaqen përgjatë skedarëve të tjera mediatikë.", + "ErrorDeletingItem": "Ndodhi një problem gjatë fshirjes së artikullit në server. Ju lutem sigurohuni që Jellyfin kë të drejtë shkrimi në dosjen e medies dhe provoni përsëri." } From 83968b229608263a65967f8506454d557df96c38 Mon Sep 17 00:00:00 2001 From: Rajmond Burgaj Date: Fri, 8 Sep 2023 14:32:11 +0000 Subject: [PATCH 17/18] Translated using Weblate (Albanian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/sq/ --- src/strings/sq.json | 179 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/src/strings/sq.json b/src/strings/sq.json index 9eda6faae130..67f453646a36 100644 --- a/src/strings/sq.json +++ b/src/strings/sq.json @@ -404,5 +404,182 @@ "LabelSegmentKeepSecondsHelp": "Koha në sekonda që pjesët duhen ruajtur para se të rishkruhen. Duhet të jetë më e madhe se fusha \"Limito pas\". Punon vetëm nëse fshirja e pjesëve është e aktivizuar.", "DeleteDevicesConfirmation": "Dëshironi të fshini të gjitha paisjet? Të gjithe sesionet e tjera do te shkyçen. Paisjet do të rishfaqen herës tjetër kur një përdorues kyçet sërish.", "EnablePhotosHelp": "Imazhet do të zbulohen dhe shfaqen përgjatë skedarëve të tjera mediatikë.", - "ErrorDeletingItem": "Ndodhi një problem gjatë fshirjes së artikullit në server. Ju lutem sigurohuni që Jellyfin kë të drejtë shkrimi në dosjen e medies dhe provoni përsëri." + "ErrorDeletingItem": "Ndodhi një problem gjatë fshirjes së artikullit në server. Ju lutem sigurohuni që Jellyfin kë të drejtë shkrimi në dosjen e medies dhe provoni përsëri.", + "HeaderSelectMetadataPathHelp": "Shfletoni ose vendosni shtegun ku dëshironi të ruani të dhënat bazë. Dosja duhet të jetë e shkrueshme.", + "HeaderSyncPlaySettings": "Përcaktimet e SyncPlay", + "HeaderApp": "Aplikacion", + "HeaderPlayOn": "Luaj", + "LabelAutomaticallyAddToCollection": "Shto automatikisht ne koleksion", + "HeaderAudioBooks": "Libra dëgjimor", + "HeaderBranding": "Markë", + "HeaderCancelRecording": "Anulloni regjistrimin", + "HeaderNewApiKey": "Çelës i ri për API", + "HeaderPreferredMetadataLanguage": "Gjuha e preferuar e të dhënave baze", + "LabelArtistsHelp": "Ndani artistët shumës me një pikëpresje.", + "HeaderMyDevice": "Paisja ime", + "HeaderRemoveMediaFolder": "Hiqni dosjen e Medies", + "HeaderAppearsOn": "Shfaqet në", + "ItemCount": "{0} artikuj", + "HeaderSelectServerCachePath": "Zgjidhni shtegun e të dhënave të memorizuara në Server", + "HeaderProfileServerSettingsHelp": "Këto vlera kontrollojnë mënyren sesi serveri paraqitet kundrejt përdoruesve.", + "HeaderVideoQuality": "Kualiteti i videos", + "Items": "Artikuj", + "HeaderLatestRecordings": "Regjistrimet e shtuara së fundmi", + "HeaderIdentification": "Identifikim", + "HeaderDeleteDevices": "Fshini të gjitha paisjet", + "HeaderUploadSubtitle": "Ngarko titrat", + "HeaderPassword": "Fjalëkalim", + "HeaderPaths": "Vendndodhjet", + "HeaderYears": "Vitet", + "HeaderRevisionHistory": "Historia e rishikimeve", + "HeaderSelectCertificatePath": "Zgjidh shtegun e çertefikatës", + "Image": "Imazh", + "InstallingPackage": "Duke instaluar {0} (versioni {1})", + "HeaderRecordingPostProcessing": "Procesimi pas regjistrimit", + "HeaderMediaFolders": "Dosjet e medies", + "HeaderSeasons": "Sezone", + "HeaderNextEpisodePlayingInValue": "Episodi i ri luhet në {0}", + "HeaderMedia": "Media", + "HeaderFrequentlyPlayed": "E luajtur shpeshherë", + "Hide": "Fsheh", + "HeaderPleaseSignIn": "Ju lutem kyçuni", + "HeaderLatestEpisodes": "Episodet e shtuara së fundmi", + "LabelAppNameExample": "Shembull: Sickbeard, Sonarr", + "HeaderNewRepository": "Vendndodhje e re", + "HeaderMyMedia": "Media ime", + "HeaderConfirmPluginInstallation": "Konfirmoni instalimin e shtojcës", + "HeaderUploadImage": "Ngarko imazhin", + "HeaderUninstallPlugin": "Ç'instalo shtojcën", + "HeaderActiveRecordings": "Regjistrime aktive", + "HeaderRemoveMediaLocation": "Hiqni vendndodhjen e Medies", + "HeaderContinueListening": "Vazhdoni me dëgjimin", + "HeaderDownloadSync": "Shkarko & Sinkronizo", + "HeaderImageSettings": "Përcaktimet e imazhit", + "HeaderSyncPlayEnabled": "SyncPlay e aktivizuar", + "HeaderHttpsSettings": "Përcaktimet e HTTPS", + "HttpsRequiresCert": "Të aktivizosh lidhje të sigurtë, ju duhet të keni një çertefikate SSL të besueshme, siç është Let's Encrypt. Ju lutem ose kini një çertefikatë të tillë, ose çaktivizoni lidhjet e sigurta.", + "HeaderMusicQuality": "Kualiteti i muzikes", + "HeaderSeriesOptions": "Opsionet e koleksionit", + "HeaderSubtitleProfile": "Profili i titrave", + "HeaderOnNow": "Ndezur tani", + "HeaderVideos": "Videot", + "HeaderCancelSeries": "Anulloni serialin", + "HeaderLibraryOrder": "Renditja e librarisë", + "HeaderLibraryAccess": "Akses në librari", + "HeaderPlayAll": "Luaj të gjithën", + "HeaderUser": "Përdorues", + "HeaderStartNow": "Vazhdo tani", + "HeaderDeleteProvider": "Fshini ofruesin", + "HeaderSystemDlnaProfiles": "Profilet e sistemit", + "HeaderDeviceAccess": "Akses i paisjes", + "HeaderScenes": "Skena", + "HeaderOtherItems": "Artikujt e tjerë", + "HeaderDeleteItem": "Fshini artikullin", + "HeaderPlaybackError": "Gabim gjatë luajtjes", + "HeaderDeleteItems": "Fshini artikujt", + "HeaderLibraryFolders": "Dosjet e librarisë", + "HeaderConnectionFailure": "Dështim në lidhje", + "HeaderConnectToServer": "Lidhuni me serverin", + "HeaderLatestMovies": "Filmat e shtuar së fundmi", + "HeaderLibrarySettings": "Përcaktimet e librarise", + "HeaderDateIssued": "Data e lëshimit", + "HeaderLatestMedia": "Mediat e shtuara së fundmi", + "HeaderNetworking": "Protokolli i IP-së", + "Identify": "Identifikohu", + "HeaderSubtitleProfiles": "Profilet e titrave", + "HeaderSubtitleDownloads": "Shkarkimet i titrave", + "HeaderError": "Gabim", + "HeaderLatestMusic": "Muzika e shtuar së fundmi", + "HeaderLoginFailure": "Dështim në kyçje", + "Help": "Ndihmë", + "LabelAbortedByServerShutdown": "(Abortuar nga një fikje e serverit)", + "HeaderEnabledFields": "Fushat aktive", + "HeaderSelectTranscodingPathHelp": "Shfletoni ose vendosni shtegun për ruajtjen e skedarëve të transkoduar. Dosja duhet të jetë e shkrueshme.", + "HeaderStatus": "Status", + "HeaderPluginInstallation": "Instalimi i shtojcës", + "HeaderProfileInformation": "Informacionet e profilit", + "Images": "Imazhe", + "HeaderSendMessage": "Dërgoni mesazh", + "HeaderSortBy": "Rradhisni sipas", + "Home": "Hyrje", + "Label3DFormat": "Format 3D", + "HeaderConfirmRevokeApiKey": "Anulo çelësin e API", + "HeaderSubtitleAppearance": "Dukja e titrave", + "HeaderVideoTypes": "Tipet e videos", + "HeaderDeveloperInfo": "Informacione per zhvilluesit", + "HeaderEditImages": "Editoni imazhet", + "LabelAllowHWTranscoding": "Lejoni transkodimin me anë të paisjeve elektronike", + "HeaderSelectPath": "Zgjidhni shtegun", + "Horizontal": "Horizontale", + "HeaderRecordingOptions": "Opsionet e regjistrimit", + "HeaderTypeText": "Vendos tekstin", + "HeaderRecentlyPlayed": "E luajtur së fundmi", + "HeaderPasswordReset": "Rivendosja e fjalëkalimit", + "ItemDetails": "Detajet e artikullit", + "HomeVideosPhotos": "Videot dhe fotot e hyrjes", + "LabelAppName": "Emri i aplikacionit", + "HeaderCustomDlnaProfiles": "Profile të personalizuara", + "HeaderDefaultRecordingSettings": "Përcaktimet a paracaktuara të regjistrimit", + "HeaderDeleteDevice": "Fshini paisjen", + "HeaderDetectMyDevices": "Zbuloni paisjet e mia", + "HeaderEpisodesStatus": "Statusi i episodit", + "LabelAirsBeforeSeason": "Transmetohet para sezonit", + "LabelAirTime": "Koha e transmetimit", + "LabelAlbum": "Album", + "LabelArtists": "Artistët", + "HeaderSyncPlayTimeSyncSettings": "Sinkronizimi i kohës", + "IgnoreDtsHelp": "Ç'aktivizimi i këtij opsioni mund të zgjidhë disa probleme, p.sh mungesën e audios në kanale me rrymë të ndarë të audios nga videoja.", + "LabelAudioChannels": "Kanalet e Audios", + "LabelAuthProvider": "Ofruesi i vërtetësisë", + "HeaderEnabledFieldsHelp": "Ç'zgjidhni fushen që doni të kyçni dhe të parandaloni ndryshimin e të dhënave të saj.", + "HeaderThisUserIsCurrentlyDisabled": "Përdoruesi është aktualisht i çaktivizuar", + "LabelAccessEnd": "Koha e mbarimit", + "LabelAccessStart": "Koha e fillimit", + "LabelAirsAfterSeason": "Transmetohet pas sezonit", + "LabelAirsBeforeEpisode": "Transmetohet para episodit", + "HeaderDummyChapter": "Imazhet e kapitullit", + "HeaderForKids": "Për fëmijë", + "HeaderIdentifyItemHelp": "Vendosni një ose më shumë kritere kërkimi. Hiqni kriteret që të rrisni rezultatet e kërkimit.", + "HeaderImageOptions": "Opsionet e imazhit", + "HeaderMyMediaSmall": "Media ime (e vogel)", + "HeaderInstall": "Instalo", + "HeaderNavigation": "Navigimi", + "HeaderKeepRecording": "Vazhdo me regjistrimin", + "HeaderNewDevices": "Paisjet e reja", + "HeaderNextVideoPlayingInValue": "Videoja e re luhet në {0}", + "HeaderMoreLikeThis": "Më shumë si kjo", + "HeaderExternalIds": "ID-të e jashtme", + "HeaderFetchImages": "Ngarkoni imazhet", + "HeaderSpecialEpisodeInfo": "Informacion special mbi episodin", + "HeaderIdentificationCriteriaHelp": "Vendosni të pakten një kriter identifikimi.", + "HeaderPhotoAlbums": "Albumë fotosh", + "HeaderUsers": "Përdoruesit", + "HeaderVideoType": "Tipi i videos", + "Kids": "Fëmijë", + "HeaderPerformance": "Performancë", + "LabelAudioLanguagePreference": "Gjuha e preferuar audio", + "HeaderConfirmRepositoryInstallation": "Konfirmoni instalimin e vendndodhjes së shtojcave", + "HeaderContinueReading": "Vazhdoni me leximin", + "HeaderDevices": "Paisje", + "HeaderSeriesStatus": "Statusi i koleksionit", + "HeaderSetupLibrary": "Konfiguroni librarite Mediatike tuajat", + "HeaderSecondsValue": "{0} sekond(a)", + "HeaderSortOrder": "Rendi i radhitjes", + "HeaderSelectMetadataPath": "Zgjidh shtegun e të dhënave bazë", + "HeaderRunningTasks": "Detyra në ekzekutim", + "HeaderSelectServerCachePathHelp": "Shfletoni ose vendosni shtegun për ruajten e skedarëve të memorizuar në server. Dosja duhet të jetë e shkrueshme.", + "LabelAlbumArtists": "Artistet e albumit", + "HeaderRecordingMetadataSaving": "Të dhënat bazë të regjistrimit", + "HeaderBlockItemsWithNoRating": "Bllokoni gjërat pa, ose me vlerësime të panjohura", + "HeaderCastAndCrew": "Aktoret dhe grupi i punes", + "HeaderChannelAccess": "Akses në kanal", + "HeaderChapterImages": "Imazhet e kapitullit", + "HeaderCodecProfile": "Profili i Codec", + "HeaderConfirmProfileDeletion": "Konfirmoni fshirjen e profilit", + "HeaderAccessScheduleHelp": "Krijoni një orar aksesi në mënyrë që të limitoni aksesion në orë të caktuara.", + "HeaderAudioSettings": "Përcaktimet Audio", + "HeaderAutoDiscovery": "Zbulim ne rrjet", + "HeaderStopRecording": "Ndaloni regjistrimin", + "HeaderSubtitleProfilesHelp": "Profilet e titrave përshkruajnë formatin e titrave që suportohen nga paisja.", + "HeaderSyncPlaySelectGroup": "Bashkohuni një grupi" } From 0dd130c9bd12d41587d9326a6ad4e225c24b3128 Mon Sep 17 00:00:00 2001 From: Patrick Chek Date: Fri, 8 Sep 2023 18:53:41 +0000 Subject: [PATCH 18/18] Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/he/ --- src/strings/he.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/strings/he.json b/src/strings/he.json index 9825cc17e6e4..28ed1c274e0e 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -1176,5 +1176,10 @@ "LabelModelUrl": "כתובת דגם", "Restart": "הפעלה מחדש", "TabStreaming": "הזרמה", - "Metadata": "נתוני על" + "Metadata": "נתוני על", + "AllowSegmentDeletion": "מחק אותות", + "AllowSegmentDeletionHelp": "מחק אותות ישנים אחרי שהם נשלחו ללקוח. זה ימנע שמירת כל הקובץ המפוענח על הדיסק. עובד רק כאשר מצב חנק פעיל. כבה אפשרות זו אם הינך חווה בעיות בנגן.", + "LabelThrottleDelaySeconds": "חנוק אחרי", + "LabelSegmentKeepSeconds": "זמן שמירת אותות", + "LabelSegmentKeepSecondsHelp": "זמן בשניות שבהן הקטעים צריכים להישאר לפני שהם נדרסים. צריך להיות גדול מ\"חנוק אחרי\". עובד רק כאשר מחיקת אות מאופשרת." }