';
};
+ nowPlayingPositionSlider.getMarkerInfo = function () {
+ const markers = [];
+
+ const item = currentItem;
+
+ // use markers based on chapters
+ if (item?.Chapters?.length) {
+ item.Chapters.forEach(currentChapter => {
+ markers.push({
+ className: 'chapterMarker',
+ name: currentChapter.Name,
+ progress: currentChapter.StartPositionTicks / item.RunTimeTicks
+ });
+ });
+ }
+
+ return markers;
+ };
+
view.querySelector('.btnPreviousTrack').addEventListener('click', function () {
playbackManager.previousTrack(currentPlayer);
});
diff --git a/src/controllers/session/forgotPassword/index.js b/src/controllers/session/forgotPassword/index.js
index 208e54d06..87a0498bf 100644
--- a/src/controllers/session/forgotPassword/index.js
+++ b/src/controllers/session/forgotPassword/index.js
@@ -35,7 +35,6 @@ import Dashboard from '../../../utils/dashboard';
Dashboard.navigate('forgotpasswordpin.html');
}
});
- return;
}
}
diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js
index 7dd916f6d..671ddd12b 100644
--- a/src/controllers/shows/tvrecommended.js
+++ b/src/controllers/shows/tvrecommended.js
@@ -333,10 +333,9 @@ import autoFocuser from '../../components/autoFocuser';
}
function onInputCommand(e) {
- switch (e.detail.command) {
- case 'search':
- e.preventDefault();
- Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId);
+ if (e.detail.command === 'search') {
+ e.preventDefault();
+ Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId);
}
}
diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js
index f9ea23f2e..f62bcbf34 100644
--- a/src/elements/emby-input/emby-input.js
+++ b/src/elements/emby-input/emby-input.js
@@ -85,13 +85,9 @@ import 'webcomponents.js/webcomponents-lite';
passive: true
});
- if (browser.orsay) {
- if (this === document.activeElement) {
- //Make sure the IME pops up if this is the first/default element on the page
- if (document.attachIME) {
- document.attachIME(this);
- }
- }
+ //Make sure the IME pops up if this is the first/default element on the page
+ if (browser.orsay && this === document.activeElement && document.attachIME) {
+ document.attachIME(this);
}
};
diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js
index 37edb8224..b6d0f147b 100644
--- a/src/elements/emby-itemscontainer/emby-itemscontainer.js
+++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js
@@ -21,10 +21,8 @@ import Sortable from 'sortablejs';
const itemsContainer = this;
const multiSelect = itemsContainer.multiSelect;
- if (multiSelect) {
- if (multiSelect.onContainerClick.call(itemsContainer, e) === false) {
- return;
- }
+ if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) {
+ return;
}
itemShortcuts.onClick.call(itemsContainer, e);
@@ -155,9 +153,9 @@ import Sortable from 'sortablejs';
const eventsToMonitor = getEventsToMonitor(itemsContainer);
// TODO: Check user data change reason?
- if (eventsToMonitor.indexOf('markfavorite') !== -1) {
- itemsContainer.notifyRefreshNeeded();
- } else if (eventsToMonitor.indexOf('markplayed') !== -1) {
+ if (eventsToMonitor.indexOf('markfavorite') !== -1
+ || eventsToMonitor.indexOf('markplayed') !== -1
+ ) {
itemsContainer.notifyRefreshNeeded();
}
}
@@ -192,7 +190,6 @@ import Sortable from 'sortablejs';
const itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) {
itemsContainer.notifyRefreshNeeded();
- return;
}
}
@@ -259,11 +256,9 @@ import Sortable from 'sortablejs';
itemsContainer.notifyRefreshNeeded(true);
return;
}
- } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') {
- if (eventsToMonitor.indexOf('audioplayback') !== -1) {
- itemsContainer.notifyRefreshNeeded(true);
- return;
- }
+ } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) {
+ itemsContainer.notifyRefreshNeeded(true);
+ return;
}
}
@@ -298,10 +293,8 @@ import Sortable from 'sortablejs';
}
}
- if (layoutManager.desktop || layoutManager.mobile) {
- if (this.getAttribute('data-multiselect') !== 'false') {
- this.enableMultiSelect(true);
- }
+ if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') {
+ this.enableMultiSelect(true);
}
if (layoutManager.tv) {
diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js
index dea35f36f..91233cef4 100644
--- a/src/elements/emby-ratingbutton/emby-ratingbutton.js
+++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js
@@ -63,18 +63,6 @@ import ServerConnections from '../../components/ServerConnections';
}
button.classList.add('ratingbutton-withrating');
- } else if (likes) {
- if (icon) {
- icon.classList.add('favorite');
- icon.classList.remove('ratingbutton-icon-withrating');
- }
- button.classList.remove('ratingbutton-withrating');
- } else if (likes === false) {
- if (icon) {
- icon.classList.add('favorite');
- icon.classList.remove('ratingbutton-icon-withrating');
- }
- button.classList.remove('ratingbutton-withrating');
} else {
if (icon) {
icon.classList.add('favorite');
diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js
index e06968782..b57604282 100644
--- a/src/elements/emby-select/emby-select.js
+++ b/src/elements/emby-select/emby-select.js
@@ -23,11 +23,7 @@ import 'webcomponents.js/webcomponents-lite';
return true;
}
- if (layoutManager.tv) {
- return false;
- }
-
- return true;
+ return !layoutManager.tv;
}
function triggerChange(select) {
diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js
index f8c492cef..d49b1b8ff 100644
--- a/src/elements/emby-slider/emby-slider.js
+++ b/src/elements/emby-slider/emby-slider.js
@@ -105,6 +105,13 @@ import globalize from '../../scripts/globalize';
fraction *= 100;
backgroundLower.style.width = fraction + '%';
}
+
+ if (range.markerContainerElement) {
+ if (!range.triedAddingMarkers) {
+ addMarkers(range);
+ }
+ updateMarkers(range, value);
+ }
});
}
@@ -136,6 +143,70 @@ import globalize from '../../scripts/globalize';
});
}
+ function setMarker(range, valueMarker, marker, valueProgress) {
+ requestAnimationFrame(function () {
+ const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect();
+ const markerRect = marker.getBoundingClientRect();
+
+ if (!bubbleTrackRect.width || !markerRect.width) {
+ // width is not set, most probably because the OSD is currently hidden
+ return;
+ }
+
+ let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2;
+ markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2);
+
+ marker.style.left = markerPos + 'px';
+
+ if (valueProgress >= valueMarker) {
+ marker.classList.remove('unwatched');
+ marker.classList.add('watched');
+ } else {
+ marker.classList.add('unwatched');
+ marker.classList.remove('watched');
+ }
+ });
+ }
+
+ function updateMarkers(range, currentValue) {
+ if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) {
+ for (let i = 0, length = range.markerElements.length; i < length; i++) {
+ if (range.markerInfo.length > i) {
+ setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue);
+ }
+ }
+ }
+ }
+
+ function addMarkers(range) {
+ range.markerInfo = [];
+ if (range.getMarkerInfo) {
+ range.markerInfo = range.getMarkerInfo();
+ }
+
+ function getMarkerHtml(markerInfo) {
+ let markerTypeSpecificClasses = '';
+
+ if (markerInfo.className === 'chapterMarker') {
+ markerTypeSpecificClasses = markerInfo.className;
+
+ if (typeof markerInfo.name === 'string' && markerInfo.name.length) {
+ // limit the class length in case the name contains half a novel
+ markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`;
+ }
+ }
+
+ return ``;
+ }
+
+ range.markerInfo.forEach(info => {
+ range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info));
+ });
+
+ range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker');
+ range.triedAddingMarkers = true;
+ }
+
EmbySliderPrototype.attachedCallback = function () {
if (this.getAttribute('data-embyslider') === 'true') {
return;
@@ -193,7 +264,9 @@ import globalize from '../../scripts/globalize';
this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper');
const sliderBubble = containerElement.querySelector('.sliderBubble');
- let hasHideClass = sliderBubble.classList.contains('hide');
+ let hasHideBubbleClass = sliderBubble.classList.contains('hide');
+
+ this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer');
dom.addEventListener(this, 'input', function () {
this.dragging = true;
@@ -205,9 +278,9 @@ import globalize from '../../scripts/globalize';
const bubbleValue = mapValueToFraction(this, this.value) * 100;
updateBubble(this, bubbleValue, sliderBubble);
- if (hasHideClass) {
+ if (hasHideBubbleClass) {
sliderBubble.classList.remove('hide');
- hasHideClass = false;
+ hasHideBubbleClass = false;
}
}, {
passive: true
@@ -221,7 +294,7 @@ import globalize from '../../scripts/globalize';
}
sliderBubble.classList.add('hide');
- hasHideClass = true;
+ hasHideBubbleClass = true;
}, {
passive: true
});
@@ -233,9 +306,9 @@ import globalize from '../../scripts/globalize';
updateBubble(this, bubbleValue, sliderBubble);
- if (hasHideClass) {
+ if (hasHideBubbleClass) {
sliderBubble.classList.remove('hide');
- hasHideClass = false;
+ hasHideBubbleClass = false;
}
}
}, {
@@ -245,7 +318,7 @@ import globalize from '../../scripts/globalize';
/* eslint-disable-next-line compat/compat */
dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () {
sliderBubble.classList.add('hide');
- hasHideClass = true;
+ hasHideBubbleClass = true;
}, {
passive: true
});
@@ -452,10 +525,8 @@ import globalize from '../../scripts/globalize';
}
for (const range of ranges) {
- if (position != null) {
- if (position >= range.end) {
- continue;
- }
+ if (position != null && position >= range.end) {
+ continue;
}
setRange(elem, range.start, range.end);
diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss
index 9d663f10a..f40e6329d 100644
--- a/src/elements/emby-slider/emby-slider.scss
+++ b/src/elements/emby-slider/emby-slider.scss
@@ -266,3 +266,25 @@
display: block;
margin-bottom: 0.25em;
}
+
+.sliderMarkerContainer {
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 0.54em; /* half of slider thumb size */
+}
+
+.sliderMarker {
+ position: absolute;
+ width: 2px;
+ height: 0.5em;
+ transform: translate3d(0, 25%, 0);
+}
+
+.sliderMarker.unwatched {
+ background-color: rgba(255, 255, 255, 0.3);
+}
+
+.sliderMarker.watched {
+ background-color: #00a4dc;
+}
diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js
index ce1fcee05..723cb7e8f 100644
--- a/src/elements/emby-tabs/emby-tabs.js
+++ b/src/elements/emby-tabs/emby-tabs.js
@@ -272,10 +272,8 @@ import '../../assets/css/scrollstyles.scss';
let sibling = elem[method];
while (sibling) {
- if (sibling.classList.contains(buttonClass)) {
- if (!sibling.classList.contains('hide')) {
- return sibling;
- }
+ if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) {
+ return sibling;
}
sibling = sibling[method];
diff --git a/src/index.jsx b/src/index.jsx
index 7ca4b92a2..c15297288 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -35,18 +35,13 @@ import './legacy/domParserTextHtml';
import './legacy/focusPreventScroll';
import './legacy/htmlMediaElement';
import './legacy/vendorStyles';
-import SyncPlay from './components/syncPlay/core';
-import { playbackManager } from './components/playback/playbackmanager';
-import SyncPlayNoActivePlayer from './components/syncPlay/ui/players/NoActivePlayer';
-import SyncPlayHtmlVideoPlayer from './components/syncPlay/ui/players/HtmlVideoPlayer';
-import SyncPlayHtmlAudioPlayer from './components/syncPlay/ui/players/HtmlAudioPlayer';
import { currentSettings } from './scripts/settings/userSettings';
import taskButton from './scripts/taskbutton';
import { HistoryRouter } from './components/HistoryRouter.tsx';
import AppRoutes from './routes/index.tsx';
function loadCoreDictionary() {
- const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
+ const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
const translations = languages.map(function (language) {
return {
lang: language,
@@ -84,10 +79,10 @@ function init() {
}
function onGlobalizeInit() {
- if (window.appMode === 'android') {
- if (window.location.href.toString().toLowerCase().indexOf('start=backgroundsync') !== -1) {
- return onAppReady();
- }
+ if (window.appMode === 'android'
+ && window.location.href.toString().toLowerCase().indexOf('start=backgroundsync') !== -1
+ ) {
+ return onAppReady();
}
document.title = globalize.translateHtml(document.title, 'core');
@@ -102,10 +97,7 @@ function onGlobalizeInit() {
import('./assets/css/librarybrowser.scss');
- loadPlugins().then(function () {
- initSyncPlay();
- onAppReady();
- });
+ loadPlugins().then(onAppReady);
}
function loadPlugins() {
@@ -137,27 +129,6 @@ function loadPlugins() {
});
}
-function initSyncPlay() {
- // Register player wrappers.
- SyncPlay.PlayerFactory.setDefaultWrapper(SyncPlayNoActivePlayer);
- SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlVideoPlayer);
- SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlAudioPlayer);
-
- // Listen for player changes.
- Events.on(playbackManager, 'playerchange', (event, newPlayer, newTarget, oldPlayer) => {
- SyncPlay.Manager.onPlayerChange(newPlayer, newTarget, oldPlayer);
- });
-
- // Start SyncPlay.
- const apiClient = ServerConnections.currentApiClient();
- if (apiClient) SyncPlay.Manager.init(apiClient);
-
- // FIXME: Multiple apiClients?
- Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient));
- Events.on(ServerConnections, 'localusersignedin', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
- Events.on(ServerConnections, 'localusersignedout', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
-}
-
async function onAppReady() {
console.debug('begin onAppReady');
diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js
index 64306c2db..3f409568f 100644
--- a/src/libraries/navdrawer/navdrawer.js
+++ b/src/libraries/navdrawer/navdrawer.js
@@ -303,34 +303,32 @@ class NavDrawer {
setEdgeSwipeEnabled(enabled) {
const options = this.options;
- if (!options.disableEdgeSwipe) {
- if (browser.touch) {
- if (enabled) {
- if (!this._edgeSwipeEnabled) {
- this._edgeSwipeEnabled = true;
- dom.addEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, {
- passive: true
- });
- dom.addEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, {
- passive: true
- });
- dom.addEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, {
- passive: true
- });
- }
- } else {
- if (this._edgeSwipeEnabled) {
- this._edgeSwipeEnabled = false;
- dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, {
- passive: true
- });
- dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, {
- passive: true
- });
- dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, {
- passive: true
- });
- }
+ if (!options.disableEdgeSwipe && browser.touch) {
+ if (enabled) {
+ if (!this._edgeSwipeEnabled) {
+ this._edgeSwipeEnabled = true;
+ dom.addEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, {
+ passive: true
+ });
+ dom.addEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, {
+ passive: true
+ });
+ dom.addEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, {
+ passive: true
+ });
+ }
+ } else {
+ if (this._edgeSwipeEnabled) {
+ this._edgeSwipeEnabled = false;
+ dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, {
+ passive: true
+ });
+ dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, {
+ passive: true
+ });
+ dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, {
+ passive: true
+ });
}
}
}
diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js
index 2acc4bdc0..385c4e091 100644
--- a/src/libraries/scroller.js
+++ b/src/libraries/scroller.js
@@ -291,10 +291,8 @@ const scrollerFactory = function (frame, options) {
const now = new Date().getTime();
- if (o.autoImmediate) {
- if (!immediate && (now - (lastAnimate || 0)) <= 50) {
- immediate = true;
- }
+ if (o.autoImmediate && !immediate && (now - (lastAnimate || 0)) <= 50) {
+ immediate = true;
}
if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) {
@@ -482,7 +480,8 @@ const scrollerFactory = function (frame, options) {
*/
function dragHandler(event) {
dragging.released = event.type === 'mouseup' || event.type === 'touchend';
- const pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event;
+ const eventName = dragging.released ? 'changedTouches' : 'touches';
+ const pointer = dragging.touch ? event[eventName][0] : event;
dragging.pathX = pointer.pageX - dragging.initX;
dragging.pathY = pointer.pageY - dragging.initY;
dragging.path = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2));
@@ -808,15 +807,13 @@ const scrollerFactory = function (frame, options) {
passive: true
});
}
- } else if (o.horizontal) {
+ } else if (o.horizontal && o.mouseWheel) {
// Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively
- if (o.mouseWheel) {
- // Scrolling navigation
- dom.addEventListener(scrollSource, wheelEvent, scrollHandler, {
- passive: true
- });
- }
+ // Scrolling navigation
+ dom.addEventListener(scrollSource, wheelEvent, scrollHandler, {
+ passive: true
+ });
}
dom.addEventListener(frame, 'click', onFrameClick, {
diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js
index a4f49b8b6..d0fcffa48 100644
--- a/src/plugins/bookPlayer/plugin.js
+++ b/src/plugins/bookPlayer/plugin.js
@@ -339,11 +339,7 @@ export class BookPlayer {
}
canPlayItem(item) {
- if (item.Path && item.Path.endsWith('epub')) {
- return true;
- }
-
- return false;
+ return item.Path && item.Path.endsWith('epub');
}
}
diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js
index 947426ec2..aeced81b8 100644
--- a/src/plugins/chromecastPlayer/plugin.js
+++ b/src/plugins/chromecastPlayer/plugin.js
@@ -452,11 +452,9 @@ function normalizeImages(state) {
if (state && state.NowPlayingItem) {
const item = state.NowPlayingItem;
- if (!item.ImageTags || !item.ImageTags.Primary) {
- if (item.PrimaryImageTag) {
- item.ImageTags = item.ImageTags || {};
- item.ImageTags.Primary = item.PrimaryImageTag;
- }
+ if ((!item.ImageTags || !item.ImageTags.Primary) && item.PrimaryImageTag) {
+ item.ImageTags = item.ImageTags || {};
+ item.ImageTags.Primary = item.PrimaryImageTag;
}
if (item.BackdropImageTag && item.BackdropItemId === item.Id) {
item.BackdropImageTags = [item.BackdropImageTag];
diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js
index 57772d8de..d209069ad 100644
--- a/src/plugins/comicsPlayer/plugin.js
+++ b/src/plugins/comicsPlayer/plugin.js
@@ -172,10 +172,8 @@ export class ComicsPlayer {
onWindowKeyUp(e) {
const key = keyboardnavigation.getKeyName(e);
- switch (key) {
- case 'Escape':
- this.stop();
- break;
+ if (key === 'Escape') {
+ this.stop();
}
}
@@ -358,11 +356,7 @@ export class ComicsPlayer {
}
canPlayItem(item) {
- if (item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr'))) {
- return true;
- }
-
- return false;
+ return item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr'));
}
}
diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js
index 440bb4c3d..1873970e1 100644
--- a/src/plugins/htmlAudioPlayer/plugin.js
+++ b/src/plugins/htmlAudioPlayer/plugin.js
@@ -41,13 +41,9 @@ function cancelFadeTimeout() {
}
function supportsFade() {
- if (browser.tv) {
- // Not working on tizen.
- // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive
- return false;
- }
-
- return true;
+ // Not working on tizen.
+ // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive
+ return !browser.tv;
}
function requireHlsPlayer(callback) {
@@ -417,10 +413,7 @@ class HtmlAudioPlayer {
// This is a retry after error
resume() {
- const mediaElement = this._mediaElement;
- if (mediaElement) {
- mediaElement.play();
- }
+ this.unpause();
}
unpause() {
diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js
index f1c83961e..99859d6fd 100644
--- a/src/plugins/htmlVideoPlayer/plugin.js
+++ b/src/plugins/htmlVideoPlayer/plugin.js
@@ -67,16 +67,12 @@ function tryRemoveElement(elem) {
}
function enableNativeTrackSupport(currentSrc, track) {
- if (track) {
- if (track.DeliveryMethod === 'Embed') {
- return true;
- }
+ if (track?.DeliveryMethod === 'Embed') {
+ return true;
}
- if (browser.firefox) {
- if ((currentSrc || '').toLowerCase().includes('.m3u8')) {
- return false;
- }
+ if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) {
+ return false;
}
if (browser.ps4) {
@@ -92,11 +88,9 @@ function tryRemoveElement(elem) {
return false;
}
- if (browser.iOS) {
+ if (browser.iOS && (browser.iosVersion || 10) < 10) {
// works in the browser but not the native app
- if ((browser.iosVersion || 10) < 10) {
- return false;
- }
+ return false;
}
if (track) {
@@ -279,10 +273,6 @@ function tryRemoveElement(elem) {
* @type {any | undefined}
*/
#lastProfile;
- /**
- * @type {MutationObserver | IntersectionObserver | undefined} (Unclear observer typing)
- */
- #resizeObserver;
constructor() {
if (browser.edgeUwp) {
@@ -969,11 +959,6 @@ function tryRemoveElement(elem) {
* @private
*/
destroyCustomTrack(videoElement) {
- if (this.#resizeObserver) {
- this.#resizeObserver.disconnect();
- this.#resizeObserver = null;
- }
-
if (this.#videoSubtitlesElem) {
const subtitlesContainer = this.#videoSubtitlesElem.parentNode;
if (subtitlesContainer) {
@@ -1497,14 +1482,14 @@ function tryRemoveElement(elem) {
if (
// Check non-standard Safari PiP support
typeof video.webkitSupportsPresentationMode === 'function' && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === 'function'
+ // Check non-standard Windows PiP support
+ || (window.Windows
+ && Windows.UI.ViewManagement.ApplicationView.getForCurrentView()
+ .isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay))
// Check standard PiP support
|| document.pictureInPictureEnabled
) {
list.push('PictureInPicture');
- } else if (window.Windows) {
- if (Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) {
- list.push('PictureInPicture');
- }
}
if (browser.safari || browser.iOS || browser.iPad) {
@@ -1565,13 +1550,7 @@ function tryRemoveElement(elem) {
}
const video = this.#mediaElement;
- if (video) {
- if (video.audioTracks) {
- return true;
- }
- }
-
- return false;
+ return !!video?.audioTracks;
}
static onPictureInPictureError(err) {
@@ -1703,10 +1682,7 @@ function tryRemoveElement(elem) {
// This is a retry after error
resume() {
- const mediaElement = this.#mediaElement;
- if (mediaElement) {
- mediaElement.play();
- }
+ this.unpause();
}
unpause() {
diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js
index 5bdd5e0bd..e1f59735d 100644
--- a/src/plugins/pdfPlayer/plugin.js
+++ b/src/plugins/pdfPlayer/plugin.js
@@ -304,11 +304,7 @@ export class PdfPlayer {
}
canPlayItem(item) {
- if (item.Path && item.Path.endsWith('pdf')) {
- return true;
- }
-
- return false;
+ return item.Path && item.Path.endsWith('pdf');
}
}
diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js
index 6997dc173..50c6dae8a 100644
--- a/src/plugins/sessionPlayer/plugin.js
+++ b/src/plugins/sessionPlayer/plugin.js
@@ -159,11 +159,9 @@ function normalizeImages(state, apiClient) {
if (state && state.NowPlayingItem) {
const item = state.NowPlayingItem;
- if (!item.ImageTags || !item.ImageTags.Primary) {
- if (item.PrimaryImageTag) {
- item.ImageTags = item.ImageTags || {};
- item.ImageTags.Primary = item.PrimaryImageTag;
- }
+ if (!item.ImageTags || !item.ImageTags.Primary && item.PrimaryImageTag) {
+ item.ImageTags = item.ImageTags || {};
+ item.ImageTags.Primary = item.PrimaryImageTag;
}
if (item.BackdropImageTag && item.BackdropItemId === item.Id) {
item.BackdropImageTags = [item.BackdropImageTag];
diff --git a/src/components/syncPlay/core/Controller.js b/src/plugins/syncPlay/core/Controller.js
similarity index 100%
rename from src/components/syncPlay/core/Controller.js
rename to src/plugins/syncPlay/core/Controller.js
diff --git a/src/components/syncPlay/core/Helper.js b/src/plugins/syncPlay/core/Helper.js
similarity index 100%
rename from src/components/syncPlay/core/Helper.js
rename to src/plugins/syncPlay/core/Helper.js
diff --git a/src/components/syncPlay/core/Manager.js b/src/plugins/syncPlay/core/Manager.js
similarity index 99%
rename from src/components/syncPlay/core/Manager.js
rename to src/plugins/syncPlay/core/Manager.js
index 16640c1df..c41fe14fb 100644
--- a/src/components/syncPlay/core/Manager.js
+++ b/src/plugins/syncPlay/core/Manager.js
@@ -9,7 +9,7 @@ import TimeSyncCore from './timeSync/TimeSyncCore';
import PlaybackCore from './PlaybackCore';
import QueueCore from './QueueCore';
import Controller from './Controller';
-import toast from '../../toast/toast';
+import toast from '../../../components/toast/toast';
import globalize from '../../../scripts/globalize';
/**
diff --git a/src/components/syncPlay/core/PlaybackCore.js b/src/plugins/syncPlay/core/PlaybackCore.js
similarity index 100%
rename from src/components/syncPlay/core/PlaybackCore.js
rename to src/plugins/syncPlay/core/PlaybackCore.js
diff --git a/src/components/syncPlay/core/QueueCore.js b/src/plugins/syncPlay/core/QueueCore.js
similarity index 99%
rename from src/components/syncPlay/core/QueueCore.js
rename to src/plugins/syncPlay/core/QueueCore.js
index 81feac0ac..f1a435106 100644
--- a/src/components/syncPlay/core/QueueCore.js
+++ b/src/plugins/syncPlay/core/QueueCore.js
@@ -4,7 +4,7 @@
*/
import globalize from '../../../scripts/globalize';
-import toast from '../../toast/toast';
+import toast from '../../../components/toast/toast';
import * as Helper from './Helper';
/**
@@ -193,7 +193,6 @@ class QueueCore {
}
this.manager.haltGroupPlayback(apiClient);
- return;
});
}
diff --git a/src/components/syncPlay/core/Settings.js b/src/plugins/syncPlay/core/Settings.js
similarity index 100%
rename from src/components/syncPlay/core/Settings.js
rename to src/plugins/syncPlay/core/Settings.js
diff --git a/src/components/syncPlay/core/index.js b/src/plugins/syncPlay/core/index.js
similarity index 100%
rename from src/components/syncPlay/core/index.js
rename to src/plugins/syncPlay/core/index.js
diff --git a/src/components/syncPlay/core/players/GenericPlayer.js b/src/plugins/syncPlay/core/players/GenericPlayer.js
similarity index 100%
rename from src/components/syncPlay/core/players/GenericPlayer.js
rename to src/plugins/syncPlay/core/players/GenericPlayer.js
diff --git a/src/components/syncPlay/core/players/PlayerFactory.js b/src/plugins/syncPlay/core/players/PlayerFactory.js
similarity index 94%
rename from src/components/syncPlay/core/players/PlayerFactory.js
rename to src/plugins/syncPlay/core/players/PlayerFactory.js
index 1de9aa512..ab4170c9e 100644
--- a/src/components/syncPlay/core/players/PlayerFactory.js
+++ b/src/plugins/syncPlay/core/players/PlayerFactory.js
@@ -16,7 +16,7 @@ class PlayerFactory {
/**
* Registers a wrapper to the list of players that can be managed.
- * @param {GenericPlayer} wrapperClass The wrapper to register.
+ * @param {typeof GenericPlayer} wrapperClass The wrapper to register.
*/
registerWrapper(wrapperClass) {
console.debug('SyncPlay WrapperFactory registerWrapper:', wrapperClass.type);
@@ -25,7 +25,7 @@ class PlayerFactory {
/**
* Sets the default player wrapper.
- * @param {GenericPlayer} wrapperClass The wrapper.
+ * @param {typeof GenericPlayer} wrapperClass The wrapper.
*/
setDefaultWrapper(wrapperClass) {
console.debug('SyncPlay WrapperFactory setDefaultWrapper:', wrapperClass.type);
diff --git a/src/components/syncPlay/core/timeSync/TimeSync.js b/src/plugins/syncPlay/core/timeSync/TimeSync.js
similarity index 100%
rename from src/components/syncPlay/core/timeSync/TimeSync.js
rename to src/plugins/syncPlay/core/timeSync/TimeSync.js
diff --git a/src/components/syncPlay/core/timeSync/TimeSyncCore.js b/src/plugins/syncPlay/core/timeSync/TimeSyncCore.js
similarity index 100%
rename from src/components/syncPlay/core/timeSync/TimeSyncCore.js
rename to src/plugins/syncPlay/core/timeSync/TimeSyncCore.js
diff --git a/src/components/syncPlay/core/timeSync/TimeSyncServer.js b/src/plugins/syncPlay/core/timeSync/TimeSyncServer.js
similarity index 93%
rename from src/components/syncPlay/core/timeSync/TimeSyncServer.js
rename to src/plugins/syncPlay/core/timeSync/TimeSyncServer.js
index 734763c07..4cb9fdd30 100644
--- a/src/components/syncPlay/core/timeSync/TimeSyncServer.js
+++ b/src/plugins/syncPlay/core/timeSync/TimeSyncServer.js
@@ -9,10 +9,6 @@ import TimeSync from './TimeSync';
* Class that manages time syncing with server.
*/
class TimeSyncServer extends TimeSync {
- constructor(syncPlayManager) {
- super(syncPlayManager);
- }
-
/**
* Makes a ping request to the server.
*/
diff --git a/src/plugins/syncPlay/plugin.ts b/src/plugins/syncPlay/plugin.ts
new file mode 100644
index 000000000..e1b7c29cd
--- /dev/null
+++ b/src/plugins/syncPlay/plugin.ts
@@ -0,0 +1,49 @@
+import { Events } from 'jellyfin-apiclient';
+
+import { playbackManager } from '../../components/playback/playbackmanager';
+import ServerConnections from '../../components/ServerConnections';
+import SyncPlay from './core';
+import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
+import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer';
+import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer';
+
+class SyncPlayPlugin {
+ name: string;
+ id: string;
+ type: string;
+ priority: number;
+
+ constructor() {
+ this.name = 'SyncPlay Plugin';
+ this.id = 'syncplay';
+ // NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but
+ // SyncPlay needs refactored so it does not have an independent playback manager.
+ this.type = 'syncplay';
+ this.priority = 1;
+
+ this.init();
+ }
+
+ init() {
+ // Register player wrappers.
+ SyncPlay.PlayerFactory.setDefaultWrapper(SyncPlayNoActivePlayer);
+ SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlVideoPlayer);
+ SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlAudioPlayer);
+
+ // Listen for player changes.
+ Events.on(playbackManager, 'playerchange', (_, newPlayer) => {
+ SyncPlay.Manager.onPlayerChange(newPlayer);
+ });
+
+ // Start SyncPlay.
+ const apiClient = ServerConnections.currentApiClient();
+ if (apiClient) SyncPlay.Manager.init(apiClient);
+
+ // FIXME: Multiple apiClients?
+ Events.on(ServerConnections, 'apiclientcreated', (_, newApiClient) => SyncPlay.Manager.init(newApiClient));
+ Events.on(ServerConnections, 'localusersignedin', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
+ Events.on(ServerConnections, 'localusersignedout', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient()));
+ }
+}
+
+export default SyncPlayPlugin;
diff --git a/src/components/syncPlay/ui/groupSelectionMenu.js b/src/plugins/syncPlay/ui/groupSelectionMenu.js
similarity index 96%
rename from src/components/syncPlay/ui/groupSelectionMenu.js
rename to src/plugins/syncPlay/ui/groupSelectionMenu.js
index 807899773..106ee8e42 100644
--- a/src/components/syncPlay/ui/groupSelectionMenu.js
+++ b/src/plugins/syncPlay/ui/groupSelectionMenu.js
@@ -1,12 +1,12 @@
import { Events } from 'jellyfin-apiclient';
import SyncPlay from '../core';
import SyncPlaySettingsEditor from './settings/SettingsEditor';
-import loading from '../../loading/loading';
-import toast from '../../toast/toast';
-import actionsheet from '../../actionSheet/actionSheet';
+import loading from '../../../components/loading/loading';
+import toast from '../../../components/toast/toast';
+import actionsheet from '../../../components/actionSheet/actionSheet';
import globalize from '../../../scripts/globalize';
import playbackPermissionManager from './playbackPermissionManager';
-import ServerConnections from '../../ServerConnections';
+import ServerConnections from '../../../components/ServerConnections';
import './groupSelectionMenu.scss';
/**
diff --git a/src/components/syncPlay/ui/groupSelectionMenu.scss b/src/plugins/syncPlay/ui/groupSelectionMenu.scss
similarity index 100%
rename from src/components/syncPlay/ui/groupSelectionMenu.scss
rename to src/plugins/syncPlay/ui/groupSelectionMenu.scss
diff --git a/src/components/syncPlay/ui/playbackPermissionManager.js b/src/plugins/syncPlay/ui/playbackPermissionManager.js
similarity index 96%
rename from src/components/syncPlay/ui/playbackPermissionManager.js
rename to src/plugins/syncPlay/ui/playbackPermissionManager.js
index 2a70d7370..816b57bca 100644
--- a/src/components/syncPlay/ui/playbackPermissionManager.js
+++ b/src/plugins/syncPlay/ui/playbackPermissionManager.js
@@ -1,4 +1,4 @@
-import { appHost } from '../../apphost';
+import { appHost } from '../../../components/apphost';
/**
* Creates an audio element that plays a silent sound.
diff --git a/src/components/syncPlay/ui/players/HtmlAudioPlayer.js b/src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js
similarity index 79%
rename from src/components/syncPlay/ui/players/HtmlAudioPlayer.js
rename to src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js
index 89929eb68..c0b5a3bf6 100644
--- a/src/components/syncPlay/ui/players/HtmlAudioPlayer.js
+++ b/src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js
@@ -10,10 +10,6 @@ import HtmlVideoPlayer from './HtmlVideoPlayer';
*/
class HtmlAudioPlayer extends HtmlVideoPlayer {
static type = 'htmlaudioplayer';
-
- constructor(player, syncPlayManager) {
- super(player, syncPlayManager);
- }
}
export default HtmlAudioPlayer;
diff --git a/src/components/syncPlay/ui/players/HtmlVideoPlayer.js b/src/plugins/syncPlay/ui/players/HtmlVideoPlayer.js
similarity index 100%
rename from src/components/syncPlay/ui/players/HtmlVideoPlayer.js
rename to src/plugins/syncPlay/ui/players/HtmlVideoPlayer.js
diff --git a/src/components/syncPlay/ui/players/NoActivePlayer.js b/src/plugins/syncPlay/ui/players/NoActivePlayer.js
similarity index 99%
rename from src/components/syncPlay/ui/players/NoActivePlayer.js
rename to src/plugins/syncPlay/ui/players/NoActivePlayer.js
index 699d31517..c5f528938 100644
--- a/src/components/syncPlay/ui/players/NoActivePlayer.js
+++ b/src/plugins/syncPlay/ui/players/NoActivePlayer.js
@@ -3,7 +3,7 @@
* @module components/syncPlay/ui/players/NoActivePlayer
*/
-import { playbackManager } from '../../../playback/playbackmanager';
+import { playbackManager } from '../../../../components/playback/playbackmanager';
import SyncPlay from '../../core';
import QueueManager from './QueueManager';
diff --git a/src/components/syncPlay/ui/players/QueueManager.js b/src/plugins/syncPlay/ui/players/QueueManager.js
similarity index 100%
rename from src/components/syncPlay/ui/players/QueueManager.js
rename to src/plugins/syncPlay/ui/players/QueueManager.js
diff --git a/src/components/syncPlay/ui/settings/SettingsEditor.js b/src/plugins/syncPlay/ui/settings/SettingsEditor.js
similarity index 93%
rename from src/components/syncPlay/ui/settings/SettingsEditor.js
rename to src/plugins/syncPlay/ui/settings/SettingsEditor.js
index 3f9072514..ede7e267a 100644
--- a/src/components/syncPlay/ui/settings/SettingsEditor.js
+++ b/src/plugins/syncPlay/ui/settings/SettingsEditor.js
@@ -6,10 +6,10 @@
import { Events } from 'jellyfin-apiclient';
import SyncPlay from '../../core';
import { setSetting } from '../../core/Settings';
-import dialogHelper from '../../../dialogHelper/dialogHelper';
-import layoutManager from '../../../layoutManager';
-import loading from '../../../loading/loading';
-import toast from '../../../toast/toast';
+import dialogHelper from '../../../../components/dialogHelper/dialogHelper';
+import layoutManager from '../../../../components/layoutManager';
+import loading from '../../../../components/loading/loading';
+import toast from '../../../../components/toast/toast';
import globalize from '../../../../scripts/globalize';
import 'material-design-icons-iconfont';
@@ -18,8 +18,8 @@ import '../../../../elements/emby-select/emby-select';
import '../../../../elements/emby-button/emby-button';
import '../../../../elements/emby-button/paper-icon-button-light';
import '../../../../elements/emby-checkbox/emby-checkbox';
-import '../../../listview/listview.scss';
-import '../../../formdialog.scss';
+import '../../../../components/listview/listview.scss';
+import '../../../../components/formdialog.scss';
function centerFocus(elem, horiz, on) {
import('../../../../scripts/scrollHelper').then((scrollHelper) => {
diff --git a/src/components/syncPlay/ui/settings/editor.html b/src/plugins/syncPlay/ui/settings/editor.html
similarity index 100%
rename from src/components/syncPlay/ui/settings/editor.html
rename to src/plugins/syncPlay/ui/settings/editor.html
diff --git a/src/plugins/youtubePlayer/plugin.js b/src/plugins/youtubePlayer/plugin.js
index 96d23c2f3..788dbfcfb 100644
--- a/src/plugins/youtubePlayer/plugin.js
+++ b/src/plugins/youtubePlayer/plugin.js
@@ -333,10 +333,8 @@ class YoutubePlayer {
setVolume(val) {
const currentYoutubePlayer = this.currentYoutubePlayer;
- if (currentYoutubePlayer) {
- if (val != null) {
- currentYoutubePlayer.setVolume(val);
- }
+ if (currentYoutubePlayer && val != null) {
+ currentYoutubePlayer.setVolume(val);
}
}
getVolume() {
diff --git a/src/scripts/browser.js b/src/scripts/browser.js
index b340a403f..fe0601fc9 100644
--- a/src/scripts/browser.js
+++ b/src/scripts/browser.js
@@ -19,25 +19,14 @@ function isTv() {
return true;
}
- if (isWeb0s()) {
- return true;
- }
-
- return false;
+ return isWeb0s();
}
function isWeb0s() {
const userAgent = navigator.userAgent.toLowerCase();
- if (userAgent.indexOf('netcast') !== -1) {
- return true;
- }
-
- if (userAgent.indexOf('web0s') !== -1) {
- return true;
- }
-
- return false;
+ return userAgent.indexOf('netcast') !== -1
+ || userAgent.indexOf('web0s') !== -1;
}
function isMobile(userAgent) {
@@ -84,11 +73,7 @@ function hasKeyboard(browser) {
return true;
}
- if (browser.tv) {
- return true;
- }
-
- return false;
+ return !!browser.tv;
}
function iOSversion() {
@@ -324,11 +309,9 @@ if (browser.mobile || browser.tv) {
browser.slow = true;
}
-if (typeof document !== 'undefined') {
- /* eslint-disable-next-line compat/compat */
- if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
- browser.touch = true;
- }
+/* eslint-disable-next-line compat/compat */
+if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
+ browser.touch = true;
}
browser.keyboard = hasKeyboard(browser);
diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js
index 5c3b0b8ca..f68faf4e4 100644
--- a/src/scripts/browserDeviceProfile.js
+++ b/src/scripts/browserDeviceProfile.js
@@ -53,12 +53,8 @@ import browser from './browser';
}
const media = document.createElement('video');
- if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
- media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) {
- return true;
- }
-
- return false;
+ return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
+ media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, ''));
}
function canPlayHlsWithMSE() {
@@ -110,7 +106,7 @@ import browser from './browser';
function canPlayAudioFormat(format) {
let typeString;
- if (format === 'flac') {
+ if (format === 'flac' || format === 'asf') {
if (browser.tizen || browser.web0s || browser.edgeUwp) {
return true;
}
@@ -118,10 +114,6 @@ import browser from './browser';
if (browser.tizen || browser.edgeUwp) {
return true;
}
- } else if (format === 'asf') {
- if (browser.tizen || browser.web0s || browser.edgeUwp) {
- return true;
- }
} else if (format === 'opus') {
if (browser.web0s) {
// canPlayType lies about OPUS support
@@ -163,17 +155,11 @@ import browser from './browser';
return true;
}
- if (browser.edgeUwp) {
- return true;
- }
-
- return false;
+ return !!browser.edgeUwp;
}
function testCanPlayAv1(videoTestElement) {
- if (browser.tizenVersion >= 5.5) {
- return true;
- } else if (browser.web0sVersion >= 5) {
+ if (browser.tizenVersion >= 5.5 || browser.web0sVersion >= 5) {
return true;
}
@@ -199,6 +185,7 @@ import browser from './browser';
switch (container) {
case 'asf':
+ case 'wmv':
supported = browser.tizen || browser.web0s || browser.edgeUwp;
videoAudioCodecs = [];
break;
@@ -241,10 +228,6 @@ import browser from './browser';
videoCodecs.push('mpeg2video');
}
break;
- case 'wmv':
- supported = browser.tizen || browser.web0s || browser.edgeUwp;
- videoAudioCodecs = [];
- break;
case 'ts':
supported = testCanPlayTs();
videoCodecs.push('h264');
@@ -822,12 +805,12 @@ import browser from './browser';
maxH264Level = 52;
}
- if (browser.tizen ||
- videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {
+ if ((browser.tizen ||
+ videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, ''))
// These tests are passing in safari, but playback is failing
- if (!browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile) {
- h264Profiles += '|high 10';
- }
+ && !browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile
+ ) {
+ h264Profiles += '|high 10';
}
let maxHevcLevel = 120;
diff --git a/src/scripts/dfnshelper.js b/src/scripts/dfnshelper.js
index e184f1035..8e3a2516e 100644
--- a/src/scripts/dfnshelper.js
+++ b/src/scripts/dfnshelper.js
@@ -1,4 +1,4 @@
-import { af, arDZ, be, bg, bn, ca, cs, cy, da, de, el, enGB, enUS, eo, es, et, faIR, fi, fr, frCA, gl, he, hi, hr, hu, id, is, it, ja, kk, ko, lt, lv, ms, nb,
+import { af, arDZ, be, bg, bn, ca, cs, cy, da, de, el, enGB, enUS, eo, es, et, eu, faIR, fi, fr, frCA, gl, he, hi, hr, hu, id, is, it, ja, kk, ko, lt, lv, ms, nb,
nl, nn, pl, ptBR, pt, ro, ru, sk, sl, sv, ta, th, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
import globalize from './globalize';
@@ -22,6 +22,7 @@ const dateLocales = (locale) => ({
'es-do': es,
'es-mx': es,
'et': et,
+ 'eu': eu,
'fa': faIR,
'fi': fi,
'fr': fr,
@@ -67,9 +68,14 @@ export function getLocale() {
return dateLocales(globalize.getCurrentLocale()) || dateLocales(globalize.getCurrentLocale().replace(/-.*/, '')) || enUS;
}
-export const localeWithSuffix = { addSuffix: true, locale: getLocale() };
+export function getLocaleWithSuffix() {
+ return {
+ addSuffix: true,
+ locale: getLocale()
+ };
+}
export default {
getLocale: getLocale,
- localeWithSuffix: localeWithSuffix
+ getLocaleWithSuffix
};
diff --git a/src/scripts/dom.js b/src/scripts/dom.js
index 8cc3e7d03..e336088c8 100644
--- a/src/scripts/dom.js
+++ b/src/scripts/dom.js
@@ -183,9 +183,9 @@
width = height * (16.0 / 9.0);
}
- return standardWidths.sort(function (a, b) {
- return Math.abs(width - a) - Math.abs(width - b);
- })[0];
+ standardWidths.sort((a, b) => Math.abs(width - a) - Math.abs(width - b));
+
+ return standardWidths[0];
}
/**
diff --git a/src/scripts/editorsidebar.js b/src/scripts/editorsidebar.js
index ca0db0503..10a474798 100644
--- a/src/scripts/editorsidebar.js
+++ b/src/scripts/editorsidebar.js
@@ -215,19 +215,7 @@ import { getParameterByName } from '../utils/url.ts';
}
}
- function onNodeOpen(event, data) {
- const page = $(this).parents('.page')[0];
- const node = data.node;
- if (node.children) {
- loadNodesToLoad(page, node);
- }
- if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) {
- node.li_attr.loadedFromServer = true;
- $.jstree.reference('.libraryTree', page).load_node(node.id, loadNodeCallback);
- }
- }
-
- function onNodeLoad(event, data) {
+ function onNodeOpen(_, data) {
const page = $(this).parents('.page')[0];
const node = data.node;
if (node.children) {
@@ -254,7 +242,13 @@ import { getParameterByName } from '../utils/url.ts';
variant: 'large'
}
}
- }).off('select_node.jstree', onNodeSelect).on('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).on('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad).on('load_node.jstree', onNodeLoad);
+ })
+ .off('select_node.jstree', onNodeSelect)
+ .on('select_node.jstree', onNodeSelect)
+ .off('open_node.jstree', onNodeOpen)
+ .on('open_node.jstree', onNodeOpen)
+ .off('load_node.jstree', onNodeOpen)
+ .on('load_node.jstree', onNodeOpen);
}
function loadNodesToLoad(page, node) {
@@ -327,7 +321,10 @@ import { getParameterByName } from '../utils/url.ts';
});
}).on('pagebeforehide', '.metadataEditorPage', function () {
const page = this;
- $('.libraryTree', page).off('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad);
+ $('.libraryTree', page)
+ .off('select_node.jstree', onNodeSelect)
+ .off('open_node.jstree', onNodeOpen)
+ .off('load_node.jstree', onNodeOpen);
});
let itemId;
window.MetadataEditor = {
diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js
index d7916f1fe..40e33a7ca 100644
--- a/src/scripts/gamepadtokey.js
+++ b/src/scripts/gamepadtokey.js
@@ -168,12 +168,7 @@ function throttle(key) {
const time = times[key] || 0;
const now = new Date().getTime();
- if ((now - time) >= 200) {
- //times[key] = now;
- return true;
- }
-
- return false;
+ return (now - time) >= 200;
}
function resetThrottle(key) {
@@ -187,11 +182,7 @@ function allowInput() {
return false;
}
- if (appHost.getWindowState() === 'Minimized') {
- return false;
- }
-
- return true;
+ return appHost.getWindowState() !== 'Minimized';
}
function raiseEvent(name, key, keyCode) {
diff --git a/src/scripts/imagehelper.js b/src/scripts/imagehelper.js
index 4fd2c0a0c..d481f7e4a 100644
--- a/src/scripts/imagehelper.js
+++ b/src/scripts/imagehelper.js
@@ -1,57 +1,63 @@
+const BASE_DEVICE_IMAGE_URL = 'assets/img/devices/';
+
+function getWebDeviceIcon(browser) {
+ switch (browser) {
+ case 'Opera':
+ case 'Opera TV':
+ case 'Opera Android':
+ return BASE_DEVICE_IMAGE_URL + 'opera.svg';
+ case 'Chrome':
+ case 'Chrome Android':
+ return BASE_DEVICE_IMAGE_URL + 'chrome.svg';
+ case 'Firefox':
+ case 'Firefox Android':
+ return BASE_DEVICE_IMAGE_URL + 'firefox.svg';
+ case 'Safari':
+ case 'Safari iPad':
+ case 'Safari iPhone':
+ return BASE_DEVICE_IMAGE_URL + 'safari.svg';
+ case 'Edge Chromium':
+ case 'Edge Chromium Android':
+ case 'Edge Chromium iPad':
+ case 'Edge Chromium iPhone':
+ return BASE_DEVICE_IMAGE_URL + 'edgechromium.svg';
+ case 'Edge':
+ return BASE_DEVICE_IMAGE_URL + 'edge.svg';
+ case 'Internet Explorer':
+ return BASE_DEVICE_IMAGE_URL + 'msie.svg';
+ default:
+ return BASE_DEVICE_IMAGE_URL + 'html5.svg';
+ }
+}
/* eslint-disable indent */
export function getDeviceIcon(device) {
- const baseUrl = 'assets/img/devices/';
switch (device.AppName || device.Client) {
case 'Samsung Smart TV':
- return baseUrl + 'samsung.svg';
+ return BASE_DEVICE_IMAGE_URL + 'samsung.svg';
case 'Xbox One':
- return baseUrl + 'xbox.svg';
+ return BASE_DEVICE_IMAGE_URL + 'xbox.svg';
case 'Sony PS4':
- return baseUrl + 'playstation.svg';
+ return BASE_DEVICE_IMAGE_URL + 'playstation.svg';
case 'Kodi':
case 'Kodi JellyCon':
- return baseUrl + 'kodi.svg';
+ return BASE_DEVICE_IMAGE_URL + 'kodi.svg';
case 'Jellyfin Android':
case 'AndroidTV':
case 'Android TV':
- return baseUrl + 'android.svg';
+ return BASE_DEVICE_IMAGE_URL + 'android.svg';
case 'Jellyfin Mobile (iOS)':
case 'Jellyfin Mobile (iPadOS)':
case 'Jellyfin iOS':
case 'Infuse':
- return baseUrl + 'apple.svg';
+ return BASE_DEVICE_IMAGE_URL + 'apple.svg';
+ case 'Home Assistant':
+ return BASE_DEVICE_IMAGE_URL + 'home-assistant.svg';
case 'Jellyfin Web':
- switch (device.Name || device.DeviceName) {
- case 'Opera':
- case 'Opera TV':
- case 'Opera Android':
- return baseUrl + 'opera.svg';
- case 'Chrome':
- case 'Chrome Android':
- return baseUrl + 'chrome.svg';
- case 'Firefox':
- case 'Firefox Android':
- return baseUrl + 'firefox.svg';
- case 'Safari':
- case 'Safari iPad':
- case 'Safari iPhone':
- return baseUrl + 'safari.svg';
- case 'Edge Chromium':
- case 'Edge Chromium Android':
- case 'Edge Chromium iPad':
- case 'Edge Chromium iPhone':
- return baseUrl + 'edgechromium.svg';
- case 'Edge':
- return baseUrl + 'edge.svg';
- case 'Internet Explorer':
- return baseUrl + 'msie.svg';
- default:
- return baseUrl + 'html5.svg';
- }
+ return getWebDeviceIcon(device.Name || device.DeviceName);
default:
- return baseUrl + 'other.svg';
+ return BASE_DEVICE_IMAGE_URL + 'other.svg';
}
}
diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js
index 87ee2a7d4..eaec77740 100644
--- a/src/scripts/libraryBrowser.js
+++ b/src/scripts/libraryBrowser.js
@@ -68,10 +68,8 @@ export function showLayoutMenu (button, currentLayout, views) {
cancelable: false
}));
- if (!dispatchEvent) {
- if (window.$) {
- $(button).trigger('layoutchange', [id]);
- }
+ if (!dispatchEvent && window.$) {
+ $(button).trigger('layoutchange', [id]);
}
}
});
@@ -117,7 +115,8 @@ export function getQueryPagingHtml (options) {
html += '
';
}
- return html += '';
+ html += '';
+ return html;
}
export function showSortMenu (options) {
diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js
index 81f819f80..ce1918e19 100644
--- a/src/scripts/libraryMenu.js
+++ b/src/scripts/libraryMenu.js
@@ -9,7 +9,8 @@ import viewManager from '../components/viewManager/viewManager';
import { appRouter } from '../components/appRouter';
import { appHost } from '../components/apphost';
import { playbackManager } from '../components/playback/playbackmanager';
-import groupSelectionMenu from '../components/syncPlay/ui/groupSelectionMenu';
+import { pluginManager } from '../components/pluginManager';
+import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu';
import browser from './browser';
import globalize from './globalize';
import imageHelper from './imagehelper';
@@ -154,8 +155,14 @@ import '../assets/css/flexstyles.scss';
const policy = user.Policy ? user.Policy : user.localUser.Policy;
- const apiClient = getCurrentApiClient();
- if (headerSyncButton && policy?.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) {
+ if (
+ // Button is present
+ headerSyncButton
+ // SyncPlay plugin is loaded
+ && pluginManager.plugins.filter(plugin => plugin.id === 'syncplay').length > 0
+ // SyncPlay enabled for user
+ && policy?.SyncPlayAccess !== 'None'
+ ) {
headerSyncButton.classList.remove('hide');
}
} else {
diff --git a/src/scripts/mouseManager.js b/src/scripts/mouseManager.js
index 1ad3389c0..b6ba2e3ca 100644
--- a/src/scripts/mouseManager.js
+++ b/src/scripts/mouseManager.js
@@ -88,12 +88,10 @@ import dom from '../scripts/dom';
function onPointerEnter(e) {
const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
- if (pointerType === 'mouse') {
- if (!isMouseIdle) {
- const parent = focusManager.focusableParent(e.target);
- if (parent) {
- focusManager.focus(parent);
- }
+ if (pointerType === 'mouse' && !isMouseIdle) {
+ const parent = focusManager.focusableParent(e.target);
+ if (parent) {
+ focusManager.focus(parent);
}
}
}
@@ -107,11 +105,7 @@ import dom from '../scripts/dom';
return false;
}
- if (browser.tv) {
- return true;
- }
-
- return false;
+ return !!browser.tv;
}
function onMouseInterval() {
diff --git a/src/scripts/multiDownload.js b/src/scripts/multiDownload.js
index 54921bf4e..ec3db5201 100644
--- a/src/scripts/multiDownload.js
+++ b/src/scripts/multiDownload.js
@@ -56,7 +56,8 @@ export default function (urls) {
urls.forEach(function (url) {
// the download init has to be sequential for firefox if the urls are not on the same domain
if (browser.firefox && !sameDomain(url)) {
- return setTimeout(download.bind(null, url), 100 * ++delay);
+ setTimeout(download.bind(null, url), 100 * ++delay);
+ return;
}
download(url);
diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js
index 73a10bcbd..30d2171ae 100644
--- a/src/scripts/serverNotifications.js
+++ b/src/scripts/serverNotifications.js
@@ -1,5 +1,5 @@
import { playbackManager } from '../components/playback/playbackmanager';
-import SyncPlay from '../components/syncPlay/core';
+import SyncPlay from '../plugins/syncPlay/core';
import { Events } from 'jellyfin-apiclient';
import inputManager from '../scripts/inputManager';
import focusManager from '../components/focusManager';
@@ -123,16 +123,11 @@ function processGeneralCommand(cmd, apiClient) {
displayMessage(cmd);
break;
case 'ToggleOsd':
- // todo
- break;
case 'ToggleContextMenu':
- // todo
- break;
case 'SendKey':
// todo
break;
case 'SendString':
- // todo
focusManager.sendText(cmd.Arguments.String);
break;
default:
diff --git a/src/scripts/shell.js b/src/scripts/shell.js
index aba8698cd..b21d658f9 100644
--- a/src/scripts/shell.js
+++ b/src/scripts/shell.js
@@ -43,7 +43,9 @@ export default {
*/
downloadFiles(items) {
if (window.NativeShell?.downloadFile) {
- items.forEach(item => window.NativeShell.downloadFile(item));
+ items.forEach(item => {
+ window.NativeShell.downloadFile(item);
+ });
return true;
}
return false;
diff --git a/src/strings/ar.json b/src/strings/ar.json
index 22f643e25..3a3ffe10a 100644
--- a/src/strings/ar.json
+++ b/src/strings/ar.json
@@ -1672,5 +1672,9 @@
"RememberSubtitleSelections": "تعيين مسار الترجمة على أساس البند السابق",
"RememberAudioSelectionsHelp": "حاول ضبط المسار الصوتي على أقرب تطابق للفيديو الأخير.",
"RememberAudioSelections": "تعيين مسار الصوت على أساس البند السابق",
- "LabelMaxVideoResolution": "الحد الأقصى المسموح به لقرار تحويل ترميز الفيديو"
+ "LabelMaxVideoResolution": "الحد الأقصى المسموح به لقرار تحويل ترميز الفيديو",
+ "OptionDateShowAdded": "تاريخ اضافة العرض",
+ "OptionDateEpisodeAdded": "تاريخ اضافة الحلقة",
+ "IgnoreDtsHelp": "قد يؤدي تعطيل هذا الخيار إلى حل بعض المشكلات ، على سبيل المثال فقدان الصوت على القنوات التي تحتوي على دفق صوت وفيديو منفصل.",
+ "IgnoreDts": "تجاهل DTS (الطابع الزمني لفك التشفير)"
}
diff --git a/src/strings/ca.json b/src/strings/ca.json
index a5f85cae8..c8db8f23e 100644
--- a/src/strings/ca.json
+++ b/src/strings/ca.json
@@ -969,7 +969,7 @@
"NoNewDevicesFound": "No es van trobar nous dispositius. Per afegir un nou sintonitzador, tancar aquest diàleg i introduïu la informació de el dispositiu de forma manual.",
"NoCreatedLibraries": "Sembla que no s'ha creat cap biblioteca encara. {0} Li agradaria crear una ara? {1}",
"No": "No",
- "NextUp": "Fins a la pròxima",
+ "NextUp": "A continuació",
"NextTrack": "Passar a la següent",
"Next": "Pròxim",
"News": "Notícies",
@@ -1644,5 +1644,8 @@
"ButtonClose": "Tancar",
"ButtonBackspace": "Retrocés",
"Arranger": "Arranjador",
- "AddToFavorites": "Afegir a preferits"
+ "AddToFavorites": "Afegir a preferits",
+ "EnableSplashScreen": "Habilita la pantalla de benvinguda",
+ "ScreenResolution": "Resolució de pantalla",
+ "Bold": "Negreta"
}
diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json
index a05d5e3d7..5b721ee88 100644
--- a/src/strings/en-gb.json
+++ b/src/strings/en-gb.json
@@ -1675,5 +1675,9 @@
"RememberSubtitleSelections": "Set subtitle track based on previous item",
"RememberAudioSelectionsHelp": "Try to set the audio track to the closest match to the last video.",
"RememberAudioSelections": "Set audio track based on previous item",
- "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution"
+ "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution",
+ "OptionDateShowAdded": "Date Show Added",
+ "OptionDateEpisodeAdded": "Date Episode Added",
+ "IgnoreDtsHelp": "Disabling this option may resolve some issues, e.g. missing audio on channels with separate audio and video streams.",
+ "IgnoreDts": "Ignore DTS (decoding timestamp)"
}
diff --git a/src/strings/fr.json b/src/strings/fr.json
index 85fe0a38c..41fdac284 100644
--- a/src/strings/fr.json
+++ b/src/strings/fr.json
@@ -1676,5 +1676,8 @@
"RememberSubtitleSelections": "Définir la piste de sous-titre en fonction de l'élément précédent",
"RememberAudioSelectionsHelp": "Choisir la piste audio la plus proche de la dernière vidéo.",
"RememberAudioSelections": "Définir la piste audio en fonction de l'élément précédent",
- "IgnoreDtsHelp": "Désactiver cette option pourrait résoudre certains problèmes, comme par exemple l'audio manquant sur des pistes ayant des flux audios et vidéos séparés."
+ "IgnoreDtsHelp": "La désactivation de cette option peut résoudre certains problèmes, par ex. audio manquant sur les canaux avec des flux audio et vidéo séparés.",
+ "OptionDateShowAdded": "Date d'ajout de la série",
+ "OptionDateEpisodeAdded": "Date d'ajout de l'épisode",
+ "IgnoreDts": "Ignorer DTS (horodatage de décodage)"
}
diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json
index 770239a50..e0c72aa6d 100644
--- a/src/strings/pt-pt.json
+++ b/src/strings/pt-pt.json
@@ -1413,7 +1413,7 @@
"SmallCaps": "Maiúsculas reduzidas",
"LabelTonemappingRange": "Alcance do Tone Mapping :",
"TonemappingAlgorithmHelp": "O Tone Mapping pode ser afinado. Se não estiver familiarizado com estas opções, mantenha-as nas opções padrão. O valor recomendado é 'BT.2390'.",
- "AllowTonemappingHelp": "O processo de Tone Mapping transforma o Dynamic Range de um vídeo HDR em SDR, mantendo os detalhes e as cores, sendo estes bastante importantes para preservar a cena original. Atualmente funciona apenas com vídeos HDR10 ou HLG. Esta opção requer os executáveis CUDA ou OpenCL correspondentes.",
+ "AllowTonemappingHelp": "O processo de Tone Mapping transforma o Dynamic Range de um vídeo HDR em SDR, mantendo os detalhes e as cores, sendo estes bastante importantes para preservar a cena original. Atualmente funciona apenas com vídeos HDR10 ou HLG. Esta opção requer os executáveis CUDA ou OpenCL correspondentes.",
"LabelMaxMuxingQueueSizeHelp": "Número máximo de pacotes armazenados em buffer enquanto aguardam que todas as transmissões iniciem. Tente aumentá-lo caso se depare com o seguinte erro nos registos do FFmpeg \"Too many packets buffered for output stream\". O valor recomendado é 2048.",
"DeleteAll": "Apagar tudo",
"DeleteDevicesConfirmation": "Tem a certeza que deseja remover todos os dispositivos? Todas as outras sessões serão terminadas. Os dispositivos reaparecerão da próxima vez que um utilizador iniciar sessão.",
diff --git a/src/strings/pt.json b/src/strings/pt.json
index 89c160448..ed7298ea2 100644
--- a/src/strings/pt.json
+++ b/src/strings/pt.json
@@ -934,7 +934,7 @@
"AllLibraries": "Todas as bibliotecas",
"AllLanguages": "Todos os idiomas",
"AllEpisodes": "Todos os episódios",
- "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)",
+ "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VobSub, PGS, SUB, IDX, etc.)",
"AllChannels": "Todos os canais",
"All": "Todos",
"Alerts": "Alertas",
diff --git a/src/strings/sk.json b/src/strings/sk.json
index 649edb3e4..9fee841ca 100644
--- a/src/strings/sk.json
+++ b/src/strings/sk.json
@@ -1675,5 +1675,9 @@
"RememberSubtitleSelections": "Nastaviť titulky podľa predchádzajúcej položky",
"RememberAudioSelectionsHelp": "Pokúsi sa nastaviť zvukovú stopu tak, aby sa čo najviac zhodovala s posledným videom.",
"RememberAudioSelections": "Nastaviť zvukovú stopu podľa predchádzajúcej položky",
- "LabelMaxVideoResolution": "Maximálne dovolené rozlíšenie videa pre prekódovanie"
+ "LabelMaxVideoResolution": "Maximálne dovolené rozlíšenie videa pre prekódovanie",
+ "OptionDateShowAdded": "Dátum pridania seriálu",
+ "OptionDateEpisodeAdded": "Dátum pridania epizódy",
+ "IgnoreDtsHelp": "Vypnutím sa môžu vyriešiť niektoré problémy, napr. chýbajúci zvuk pri kanáloch so samostatnými zvukovými a video streamami.",
+ "IgnoreDts": "Ignorovať DTS (dekódovacia časová pečiatka)"
}
diff --git a/src/strings/ta.json b/src/strings/ta.json
index 1432ff278..20e085348 100644
--- a/src/strings/ta.json
+++ b/src/strings/ta.json
@@ -429,9 +429,9 @@
"HeaderXmlSettings": "XML அமைப்புகள்",
"HeaderXmlDocumentAttributes": "XML ஆவண பண்புக்கூறுகள்",
"HeaderXmlDocumentAttribute": "XML ஆவணப் பண்புக்கூறு",
- "HeaderVideos": "வீடியோக்கள்",
- "HeaderVideoTypes": "வீடியோ வகைகள்",
- "HeaderVideoType": "வீடியோ வகை",
+ "HeaderVideos": "காணொளிகள்",
+ "HeaderVideoTypes": "காணொளி வகைகள்",
+ "HeaderVideoType": "காணொளி வகை",
"HeaderVideoQuality": "வீடியோ தரம்",
"HeaderUsers": "பயனர்கள்",
"HeaderUser": "பயனர்",
@@ -604,7 +604,7 @@
"LabelMinScreenshotDownloadWidth": "குறைந்தபட்ச ஸ்கிரீன்ஷாட் பதிவிறக்க அகலம்:",
"LabelMinResumePercentageHelp": "இந்த நேரத்திற்கு முன் நிறுத்தப்பட்டால் தலைப்புகள் காட்டப்படாது என்று கருதப்படுகிறது.",
"LabelMinResumePercentage": "குறைந்தபட்ச மறுதொடக்கம் சதவீதம்:",
- "LabelMinResumeDurationHelp": "பின்னணி இருப்பிடத்தைச் சேமிக்கும் மற்றும் மீண்டும் தொடங்க அனுமதிக்கும் வினாடிகளில் மிகக் குறுகிய வீடியோ நீளம்.",
+ "LabelMinResumeDurationHelp": "பின்னணி இருப்பிடத்தைச் சேமிக்கும் மற்றும் மீண்டும் தொடங்க அனுமதிக்கும் வினாடிகளில் மிகக் குறுகிய காணொளி நீளம்.",
"LabelMinResumeDuration": "குறைந்தபட்ச மறுதொடக்கம் காலம்:",
"LabelMinBackdropDownloadWidth": "குறைந்தபட்ச பின்னணி பதிவிறக்க அகலம்:",
"LabelMethod": "முறை:",
@@ -670,7 +670,7 @@
"LabelHardwareAccelerationType": "வன்பொருள் முடுக்கம்:",
"LabelEncoderPreset": "குறியீட்டு முன்னமைவு:",
"LabelH264Crf": "H.264 குறியாக்கம் CRF:",
- "LabelGroupMoviesIntoCollectionsHelp": "திரைப்படங்களின் பட்டியல் காட்சியைத் தேர்வுசெய்தால், பெட்டித் தொகுப்புகள் குழுவாக்கப்பட்ட திரைப்படங்களுடன் உருப்படிகளாகக் காட்டப்படும்.",
+ "LabelGroupMoviesIntoCollectionsHelp": "திரைப்படப் பட்டியல்களைக் காண்பிக்கும் போது தொகுப்பில் உள்ள திரைப்படங்கள் ஒரு குழுவாகக் காட்டப்படும்.",
"LabelGroupMoviesIntoCollections": "திரைப்படங்களை தொகுப்பாக குழு செய்யவும்",
"LabelServerNameHelp": "சேவையகத்தை அடையாளம் காண இந்த பெயர் பயன்படுத்தப்படும் மற்றும் சேவையகத்தின் ஹோஸ்ட்பெயருக்கு இயல்புநிலையாக இருக்கும்.",
"LabelFriendlyName": "நட்பு பெயர்:",
@@ -681,7 +681,7 @@
"LabelFinish": "முடி",
"LabelFileOrUrl": "கோப்பு அல்லது URL:",
"LabelFailed": "தோல்வி",
- "LabelExtractChaptersDuringLibraryScanHelp": "நூலக ஸ்கேன் போது வீடியோக்கள் இறக்குமதி செய்யப்படும்போது அத்தியாய படங்களை உருவாக்கவும். இல்லையெனில், வழக்கமான பட ஸ்கேன் வேகமாக முடிக்க அனுமதிக்கும் அத்தியாயப் படங்கள் திட்டமிடப்பட்ட பணியின் போது அவை பிரித்தெடுக்கப்படும்.",
+ "LabelExtractChaptersDuringLibraryScanHelp": "நூலக ஸ்கேன் போது காணொளிகள் இறக்குமதி செய்யப்படும்போது அத்தியாய படங்களை உருவாக்கவும். இல்லையெனில், வழக்கமான பட ஸ்கேன் வேகமாக முடிக்க அனுமதிக்கும் அத்தியாயப் படங்கள் திட்டமிடப்பட்ட பணியின் போது அவை பிரித்தெடுக்கப்படும்.",
"LabelExtractChaptersDuringLibraryScan": "நூலக ஸ்கேன் போது அத்தியாய படங்களை பிரித்தெடுக்கவும்",
"LabelBaseUrlHelp": "சேவையக URL இல் தனிப்பயன் துணை அடைவைச் சேர்க்கவும். உதாரணத்திற்கு: http://example.com/<baseurl>",
"LabelBaseUrl": "அடிப்படை URL:",
@@ -807,7 +807,7 @@
"LabelSonyAggregationFlags": "Sony திரட்டல் கொடிகள்:",
"LabelSkipIfGraphicalSubsPresentHelp": "வசன வரிகள் உரை பதிப்புகளை வைத்திருப்பது மிகவும் திறமையான விநியோகத்திற்கு வழிவகுக்கும் மற்றும் வீடியோ டிரான்ஸ்கோடிங்கின் சாத்தியத்தை குறைக்கும்.",
"LabelSkipIfGraphicalSubsPresent": "வீடியோவில் ஏற்கனவே உட்பொதிக்கப்பட்ட வசன வரிகள் இருந்தால் தவிர்க்கவும்",
- "LabelSkipIfAudioTrackPresentHelp": "ஆடியோ மொழியைப் பொருட்படுத்தாமல் எல்லா வீடியோக்களுக்கும் வசன வரிகள் இருப்பதை உறுதிப்படுத்த இதைத் தேர்வுநீக்கவும்.",
+ "LabelSkipIfAudioTrackPresentHelp": "ஆடியோ மொழியைப் பொருட்படுத்தாமல் எல்லா காணொளிகளுக்கும் வசன வரிகள் இருப்பதை உறுதிப்படுத்த இதைத் தேர்வுநீக்கவும்.",
"LabelSkipIfAudioTrackPresent": "இயல்புநிலை ஆடியோ டிராக் பதிவிறக்க மொழியுடன் பொருந்தினால் தவிர்க்கவும்",
"LabelSkipForwardLength": "முன்னோக்கி நீளத்தைத் தவிர்:",
"LabelSkipBackLength": "பின் நீளத்தைத் தவிர்:",
@@ -833,7 +833,7 @@
"LabelRuntimeMinutes": "இயக்க நேரம்:",
"LabelRequireHttpsHelp": "சரிபார்க்கப்பட்டால், சேவையகம் தானாகவே HTTP வழியாக அனைத்து கோரிக்கைகளையும் HTTPS க்கு திருப்பிவிடும். HTTPS இல் சேவையகம் கேட்கவில்லை என்றால் இது எந்த விளைவையும் ஏற்படுத்தாது.",
"LabelRequireHttps": "HTTPS தேவை",
- "LabelRemoteClientBitrateLimitHelp": "நெட்வொர்க் சாதனங்களுக்கு வெளியே ஒரு விருப்பமான ஸ்ட்ரீம் பிட்ரேட் வரம்பு. உங்கள் இணைய இணைப்பு கையாளக்கூடியதை விட சாதனங்களை அதிக பிட்ரேட்டைக் கோருவதைத் தடுக்க இது பயனுள்ளதாக இருக்கும். பறக்கும்போது வீடியோக்களை குறைந்த பிட்ரேட்டுக்கு டிரான்ஸ்கோட் செய்வதற்காக இது உங்கள் சேவையகத்தில் CPU சுமை அதிகரிக்கும்.",
+ "LabelRemoteClientBitrateLimitHelp": "நெட்வொர்க் சாதனங்களுக்கு வெளியே ஒரு விருப்பமான ஸ்ட்ரீம் பிட்ரேட் வரம்பு. உங்கள் இணைய இணைப்பு கையாளக்கூடியதை விட சாதனங்களை அதிக பிட்ரேட்டைக் கோருவதைத் தடுக்க இது பயனுள்ளதாக இருக்கும். பறக்கும்போது காணொளிகளை குறைந்த பிட்ரேட்டுக்கு டிரான்ஸ்கோட் செய்வதற்காக இது உங்கள் சேவையகத்தில் CPU சுமை அதிகரிக்கும்.",
"LabelRemoteClientBitrateLimit": "இணைய ஸ்ட்ரீமிங் பிட்ரேட் வரம்பு (Mbps):",
"LabelReleaseDate": "வெளிவரும் தேதி:",
"LabelRefreshMode": "புதுப்பிப்பு பயன்முறை:",
@@ -848,7 +848,7 @@
"LabelProtocolInfoHelp": "சாதனத்திலிருந்து GetProtocolInfo கோரிக்கைகளுக்கு பதிலளிக்கும் போது பயன்படுத்தப்படும் மதிப்பு.",
"LabelProtocolInfo": "நெறிமுறை தகவல்:",
"LabelProtocol": "நெறிமுறை:",
- "LabelProfileVideoCodecs": "வீடியோ கோடெக்குகள்:",
+ "LabelProfileVideoCodecs": "காணொளி கோடெக்குகள்:",
"LabelProfileContainersHelp": "கமாவால் பிரிக்கப்பட்டது. எல்லா கொள்கலன்களுக்கும் விண்ணப்பிக்க இதை காலியாக விடலாம்.",
"LabelProfileContainer": "கொள்கலன்:",
"LabelProfileCodecsHelp": "கமாவால் பிரிக்கப்பட்டது. எல்லா கோடெக்குகளுக்கும் விண்ணப்பிக்க இதை காலியாக விடலாம்.",
@@ -1665,5 +1665,28 @@
"EnableRewatchingNextUp": "அடுத்ததில் மீண்டும் பார்ப்பதை இயக்கவும்",
"Digital": "டிஜிட்டல்",
"EnableEnhancedNvdecDecoderHelp": "சோதனை NVDEC செயல்படுத்தல், டிகோடிங் பிழைகளை நீங்கள் சந்திக்கும் வரை இந்த விருப்பத்தை இயக்க வேண்டாம்.",
- "HomeVideosPhotos": "முகப்பு வீடியோக்கள் மற்றும் புகைப்படங்கள்"
+ "HomeVideosPhotos": "முகப்பு காணொளிகள் மற்றும் புகைப்படங்கள்",
+ "LabelMaxVideoResolution": "அனுமதிக்கப்பட்ட அதிகபட்ச காணொளி டிரான்ஸ்கோடிங் தெளிவுத்திறன்",
+ "IgnoreDtsHelp": "இந்த விருப்பத்தை முடக்குவது சில சிக்கல்களை தீர்க்கலாம், எ.கா. தனி ஆடியோ மற்றும் காணொளி ஸ்ட்ரீம்கள் கொண்ட சேனல்களில் ஆடியோ இருக்காது.",
+ "MediaInfoDvProfile": "DV விவரம்",
+ "MediaInfoDvLevel": "DV நிலை",
+ "MediaInfoDvVersionMinor": "சிறிய DV பதிப்பு",
+ "MediaInfoDvVersionMajor": "முக்கிய DV பதிப்பு",
+ "MediaInfoDoViTitle": "DV தலைப்பு",
+ "MediaInfoVideoRangeType": "வீடியோ வரம்பு வகை",
+ "LabelVideoRangeType": "வீடியோ வரம்பு வகை:",
+ "VideoRangeTypeNotSupported": "வீடியோவின் வரம்பு வகை ஆதரிக்கப்படவில்லை",
+ "LabelVppTonemappingContrastHelp": "VPP tone மேப்பிங்கில் கான்ட்ராஸ்ட் ஆதாயத்தைப் பயன்படுத்தவும். பரிந்துரைக்கப்பட்ட மற்றும் இயல்புநிலை மதிப்புகள் 1.2 மற்றும் 1 ஆகும்.",
+ "LabelVppTonemappingBrightnessHelp": "VPP tone மேப்பிங்கில் பிரகாச ஆதாயத்தைப் பயன்படுத்தவும். பரிந்துரைக்கப்பட்ட மற்றும் இயல்புநிலை மதிப்புகள் இரண்டும் 0 ஆகும்.",
+ "EnableSplashScreen": "ஸ்பிளாஸ் திரையை இயக்கவும்",
+ "ScreenResolution": "திரை அளவு",
+ "RememberSubtitleSelectionsHelp": "சப்டைட்டில் டிராக்கை கடைசி வீடியோவுக்கு மிக நெருக்கமான பொருத்தமாக அமைக்க முயற்சிக்கவும்.",
+ "RememberSubtitleSelections": "முந்தைய உருப்படியின் அடிப்படையில் வசன வரிகளை அமைக்கவும்",
+ "RememberAudioSelectionsHelp": "கடைசி வீடியோவுக்கு மிக நெருக்கமான பொருத்தத்திற்கு ஆடியோ டிராக்கை அமைக்க முயற்சிக்கவும்.",
+ "RememberAudioSelections": "முந்தைய உருப்படியின் அடிப்படையில் ஆடியோ டிராக்கை அமைக்கவும்",
+ "OptionDateShowAdded": "நிகழ்ச்சி சேர்க்கப்பட்ட தேதி",
+ "OptionDateEpisodeAdded": "அத்தியாயம் சேர்க்கப்பட்ட தேதி",
+ "Bold": "தடி",
+ "LabelTextWeight": "உரையின் எடை:",
+ "IgnoreDts": "DTS புறக்கணிக்கவும்"
}
diff --git a/src/strings/tr.json b/src/strings/tr.json
index 1afaa9734..7b1262c88 100644
--- a/src/strings/tr.json
+++ b/src/strings/tr.json
@@ -914,7 +914,7 @@
"QuickConnectActivationSuccessful": "Başarıyla etkinleştirildi",
"QuickConnect": "Hızlı Bağlantı",
"LabelQuickConnectCode": "Hızlı Bağlantı kodu:",
- "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Bu ayarlar, bu cihaz tarafından başlatılan herhangi bir Chromecast oynatma için de geçerlidir.",
+ "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Bu ayarlar, bu cihaz tarafından başlatılan herhangi bir ChromeCast oynatma için de geçerlidir.",
"StopPlayback": "Oynatmayı durdur",
"SyncPlayAccessHelp": "SyncPlay özelliği, oynatmayı diğer cihazlarla senkronize etmeyi sağlar. Bu kullanıcının SyncPlay sahip olduğu erişim düzeyini seçin.",
"TitlePlayback": "Oynatma",
@@ -1670,5 +1670,8 @@
"RememberSubtitleSelectionsHelp": "Altyazı dilini, son videoya en yakın eşleşmeye göre ayarlamayı dene.",
"RememberAudioSelectionsHelp": "Ses dilini, son videoya en yakın eşleşmeye göre ayarlamayı dene.",
"RememberAudioSelections": "Ses dilini önceki öğeye göre ayarla",
- "RememberSubtitleSelections": "Altyazı dilini önceki öğeye göre ayarla"
+ "RememberSubtitleSelections": "Altyazı dilini önceki öğeye göre ayarla",
+ "TabContainers": "Barındırıcılar",
+ "OptionDateShowAdded": "Dizi Eklenme Tarihi",
+ "OptionDateEpisodeAdded": "Bölüm Eklenme Tarihi"
}
diff --git a/src/strings/ug.json b/src/strings/ug.json
index d9bab4f51..c2126a764 100644
--- a/src/strings/ug.json
+++ b/src/strings/ug.json
@@ -2,7 +2,7 @@
"Album": "البم",
"Actor": "ئاكتيور",
"Absolute": "مۇتلەق",
- "ButtonCast": "ميڊيا کي ڊوائس ۾ ڪاسٽ ڪريو",
+ "ButtonCast": "ئۈسكۈنىگە يوللاش",
"Channels": "قانال",
"Books": "كىتاب",
"Artists": "سەنئەتكار",
@@ -13,5 +13,17 @@
"Favorites": "ساقلىغۇچ",
"AddedOnValue": "{0} ى قوشۇلدى",
"Add": "قوشۇش",
- "AccessRestrictedTryAgainLater": "نۆۋەتتە زىيارىتىڭىز چەكلىمىگە ئۇچرىدى. سەل تۇرۇپ قايتا سىناڭ."
+ "AccessRestrictedTryAgainLater": "نۆۋەتتە زىيارىتىڭىز چەكلىمىگە ئۇچرىدى. سەل تۇرۇپ قايتا سىناڭ.",
+ "ValueSpecialEpisodeName": "خاسلىق - {0}",
+ "Sync": "ماس قەدەمدەش",
+ "Songs": "ناخشىلار",
+ "Shows": "پروگراممىلار",
+ "Playlists": "قويۇش تىزىملىكى",
+ "Photos": "رەسىملەر",
+ "MusicVideos": "سىنلىق مۇزىكا",
+ "Movies": "فىلىملەر",
+ "HeaderContinueWatching": "داۋاملىق كۆرۈش",
+ "HeaderAlbumArtists": "پىلاستىنكا سەنئەتكارلىرى",
+ "Genres": "ئۇسلۇبلار",
+ "Small": "كىچىك"
}
diff --git a/src/strings/vi.json b/src/strings/vi.json
index 4b3e69f6a..ae96ca1c4 100644
--- a/src/strings/vi.json
+++ b/src/strings/vi.json
@@ -1668,5 +1668,8 @@
"Trailer": "",
"TypeOptionPluralVideo": "Videos",
"ButtonSpace": "Cách",
- "ButtonBackspace": "Xóa"
+ "ButtonBackspace": "Xóa",
+ "OptionDateEpisodeAdded": "Ngày Thêm Tập",
+ "IgnoreDtsHelp": "Việc tắt tùy chọn này có thể giải quyết một số vấn đề, ví dụ: thiếu âm thanh trên các kênh có luồng âm thanh và video riêng biệt.",
+ "IgnoreDts": "Bỏ qua DTS (dấu thời gian giải mã)"
}
diff --git a/webpack.common.js b/webpack.common.js
index fddcfe66a..b7c3ad6f4 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -121,7 +121,7 @@ const config = {
},
{
test: /\.(js|jsx)$/,
- exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|react-router|screenfull)/,
+ exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|@remix-run[\\/]router|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|react-router|screenfull)/,
use: [{
loader: 'babel-loader',
options: {