From 865e9323b260547a9943ba62cac6fb0ffe51e9ae Mon Sep 17 00:00:00 2001 From: rushmash Date: Sun, 19 Feb 2023 17:10:32 +0000 Subject: [PATCH 01/45] Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/ru/ --- src/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/ru.json b/src/strings/ru.json index 5f6dad04e0..90b9def5f2 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -1693,5 +1693,5 @@ "LabelChapterImageResolution": "Разрешение изображения:", "HeaderDummyChapter": "Изображения глав", "LabelDummyChapterCount": "Лимит:", - "HeaderRecordingMetadataSaving": "Метадата записей" + "HeaderRecordingMetadataSaving": "Метаданные записей" } From 89b82e6d5214d37d94deed23dbf2acc7c7f838fc Mon Sep 17 00:00:00 2001 From: Cyteon Date: Mon, 20 Feb 2023 13:56:41 +0000 Subject: [PATCH 02/45] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)=20Translation:=20Jellyfin/Jellyfin=20Web=20Tran?= =?UTF-8?q?slate-URL:=20https://translate.jellyfin.org/projects/jellyfin/j?= =?UTF-8?q?ellyfin-web/nb=5FNO/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/strings/nb.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/strings/nb.json b/src/strings/nb.json index 27185c3720..c67c6a61d0 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1525,7 +1525,7 @@ "VideoFramerateNotSupported": "Videoens bildefrekvens støttes ikke", "VideoBitDepthNotSupported": "Videoens bitdybde støttes ikke", "RefFramesNotSupported": "Referanse-bilder støttes ikke", - "EnableGamepadHelp": "Lytt til inndata fra tilkoblet kontroller.", + "EnableGamepadHelp": "Lytt til inndata fra tilkoblet kontroller. (Krever: \"TV\"-visningsmodus)", "LabelEnableGamepad": "Aktiver spillkontroller", "AudioBitDepthNotSupported": "Lydens bitdybde støttes ikke", "ThemeSong": "Tema-låt", @@ -1685,5 +1685,12 @@ "MessageNoItemsAvailable": "Ingen filer er tilgjengelige for øyeblikket.", "OptionDateShowAdded": "Dato serien ble lagt til", "Experimental": "Eksperimentell", - "DownloadAll": "Laste ned alt" + "DownloadAll": "Laste ned alt", + "LabelDummyChapterCountHelp": "Maksimalt antall kapittelbilder som vil bli ekstrahert for hver mediefil.", + "LabelStereoDownmixAlgorithm": "Stereo nedmiksingsalgoritme", + "HeaderDummyChapter": "Kapittel Bilder", + "LabelDummyChapterCount": "Grense:", + "LabelChapterImageResolution": "Oppløsning:", + "LabelDummyChapterDuration": "Intervall:", + "HeaderRecordingMetadataSaving": "Opptak metadata" } From 9686fb58f40554dca5d35914fe3c75405fa8ed93 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:25:36 +0000 Subject: [PATCH 03/45] Update dependency dompurify to v2.4.4 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a06ce5f18e..8e41559fff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "classnames": "2.3.2", "core-js": "3.27.2", "date-fns": "2.29.3", - "dompurify": "2.4.3", + "dompurify": "2.4.4", "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", @@ -5940,9 +5940,9 @@ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" }, "node_modules/dompurify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", - "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", + "integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" }, "node_modules/domutils": { "version": "1.7.0", @@ -23399,9 +23399,9 @@ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" }, "dompurify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", - "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", + "integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" }, "domutils": { "version": "1.7.0", diff --git a/package.json b/package.json index f664622998..c32a113e06 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "classnames": "2.3.2", "core-js": "3.27.2", "date-fns": "2.29.3", - "dompurify": "2.4.3", + "dompurify": "2.4.4", "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", From 288196827fd2c74a2c58e8757f2f15b4e9fe3a74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:39:36 +0000 Subject: [PATCH 04/45] Update dependency core-js to v3.28.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a06ce5f18e..f4fe09eb6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "blurhash": "2.0.4", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.27.2", + "core-js": "3.28.0", "date-fns": "2.29.3", "dompurify": "2.4.3", "epubjs": "0.4.2", @@ -5141,9 +5141,9 @@ } }, "node_modules/core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz", + "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22822,9 +22822,9 @@ } }, "core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==" + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz", + "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==" }, "core-js-compat": { "version": "3.25.3", diff --git a/package.json b/package.json index f664622998..fe76051456 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "blurhash": "2.0.4", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.27.2", + "core-js": "3.28.0", "date-fns": "2.29.3", "dompurify": "2.4.3", "epubjs": "0.4.2", From 2a4d34711690beb2c602d52969aa24ae059354ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20I=C3=B1aki=20Bilbao?= Date: Tue, 21 Feb 2023 03:31:34 +0000 Subject: [PATCH 05/45] Translated using Weblate (Spanish (Argentina)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/es_AR/ --- src/strings/es-ar.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index 5d4d6f500b..4962e4b8e0 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -1235,7 +1235,7 @@ "TitleHostingSettings": "Configuraciones de alojamiento", "TitleHardwareAcceleration": "Aceleración por hardware", "Thursday": "Jueves", - "Thumb": "Pulgar", + "Thumb": "Miniatura", "TheseSettingsAffectSubtitlesOnThisDevice": "Esta configuración afecta los subtítulos en este dispositivo", "ThemeVideos": "Videos temáticos", "ThemeSongs": "Canciones temáticas", @@ -1353,7 +1353,7 @@ "NextTrack": "Pasar al siguiente", "LabelUnstable": "Inestable", "Video": "Video", - "ThumbCard": "Tarjeta de pulgar", + "ThumbCard": "Tarjeta de miniatura", "Subtitle": "Subtítulo", "SpecialFeatures": "Características especiales", "SelectServer": "Seleccionar servidor", @@ -1517,7 +1517,7 @@ "MessageSent": "Mensaje enviado.", "LabelSlowResponseTime": "Tiempo en ms después de lo cual una respuesta es considerada lenta:", "LabelSlowResponseEnabled": "Log de alarma si la respuesta del servidor fue lenta", - "UseEpisodeImagesInNextUpHelp": "Las secciones Siguiente y Continuar viendo utilizaran imagenes del episodio como miniaturas en lugar de miniaturas del show.", + "UseEpisodeImagesInNextUpHelp": "Las secciones 'Siguiente' y 'Continuar viendo' utilizarán imágenes del episodio como miniaturas en lugar de miniaturas del show.", "UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"", "LabelAutomaticallyAddToCollection": "Agregar automáticamente a la colección", "HeaderSyncPlayTimeSyncSettings": "Sincronización de tiempo", From d69d4b22d9b72180fd8f27e6cbf384e2f4db032c Mon Sep 17 00:00:00 2001 From: lyaschuchenko Date: Tue, 21 Feb 2023 05:23:24 +0000 Subject: [PATCH 06/45] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index a529cdbb78..07b7b3c2a0 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -8,10 +8,10 @@ "DeathDateValue": "Помер: {0}", "Favorite": "Обране", "HeaderDeleteDevice": "Видаліть пристрій", - "HeaderLatestEpisodes": "Нещодавно переглянуті епізоди", - "HeaderLatestMedia": "Нещодавно переглянуті", - "HeaderLatestMovies": "Нещодавні фільми", - "HeaderLatestMusic": "Остання музика", + "HeaderLatestEpisodes": "Нещодавно додані серії", + "HeaderLatestMedia": "Нещодавно додані медіа", + "HeaderLatestMovies": "Нещодавно додані фільми", + "HeaderLatestMusic": "Нещодавно додана музика", "HeaderSeasons": "Сезони", "HeaderTracks": "Доріжки", "HeaderUsers": "Користувачі", @@ -274,7 +274,7 @@ "DrmChannelsNotImported": "Канали з DRM не імпортуватимуться.", "DisplayModeHelp": "Виберіть бажаний стиль макету інтерфейсу.", "DisplayMissingEpisodesWithinSeasonsHelp": "Також, це має бути включено для ТВ-медіатек у конфігурації сервера.", - "DisplayInOtherHomeScreenSections": "Показувати на головному екрані такі розділи як \"Останні медіа\" і \"Продовження перегляду\"", + "DisplayInOtherHomeScreenSections": "Відображення в розділах головного екрана, таких як «Нещодавно додані медіа» та «Продовжити перегляд»", "DeleteDevicesConfirmation": "Ви впевнені, що хочете видалити всі пристрої? Усі інші сеанси будуть завершені. Пристрої знову з’являться, після того як користувач увійде в систему.", "DeleteAll": "Видалити все", "Data": "Дані", @@ -583,7 +583,7 @@ "Identify": "Ідентифікувати", "Horizontal": "Горизонтально", "Home": "Головна", - "HideWatchedContentFromLatestMedia": "Приховати переглянуте з останніх медіа", + "HideWatchedContentFromLatestMedia": "Приховати переглянутий вміст із «Нещодавно доданих медіа»", "Hide": "Приховати", "Help": "Допомога", "HeaderYears": "Роки", @@ -723,7 +723,7 @@ "HeaderLibraryAccess": "Доступ до медіатеки", "HeaderLibraryFolders": "Папки медіатеки", "HeaderLibraryOrder": "Порядок медіатек", - "HeaderLatestRecordings": "Останні записи", + "HeaderLatestRecordings": "Нещодавно додані записи", "HeaderKodiMetadataHelp": "Щоб увімкнути або вимкнути метадані NFO, відкрийте меню редагування медіатеки та знайдіть розділ \"Збереження метаданих\".", "HeaderKeepSeries": "Зберегти серіал", "HeaderKeepRecording": "Продовжуйте записувати", @@ -909,7 +909,7 @@ "LibraryAccessHelp": "Виберіть медіатеки, якими хочете поділитися з цим користувачем. Адміністратори зможуть редагувати всі папки за допомогою менеджера метаданих.", "LeaveBlankToNotSetAPassword": "Ви можете залишити це поле порожнім, щоб не встановлювати пароль.", "LearnHowYouCanContribute": "Дізнайтеся, як ви можете зробити свій внесок.", - "LatestFromLibrary": "Нове в {0}", + "LatestFromLibrary": "Нещодавно додано в {0}", "LastSeen": "Востаннє був {0}", "Larger": "Більший", "LabelZipCode": "Індекс:", @@ -1049,7 +1049,7 @@ "LabelSelectFolderGroupsHelp": "Папки, для яких не встановлено прапорець, відображатимуться самі по собі у власному поданні.", "LabelSeasonNumber": "Номер сезону:", "LabelScreensaver": "Заставка:", - "LabelScheduledTaskLastRan": "Останній раз запускалося: {0}, час виконання: {1}.", + "LabelScheduledTaskLastRan": "Останній запуск: {0}, час виконання: {1}.", "LabelSaveLocalMetadata": "Збережіть ілюстрацію в медіа-папках", "LabelRuntimeMinutes": "Час виконання:", "LabelRequireHttps": "Вимагати HTTPS", @@ -1284,7 +1284,7 @@ "PackageInstallFailed": "Помилка встановлення {0} (версія {1}).", "PackageInstallCompleted": "Установлення {0} (версія {1}) завершено.", "PackageInstallCancelled": "Установлення {0} (версія {1}) скасовано.", - "OtherArtist": "Інший художник", + "OtherArtist": "Інший виконавець", "OriginalAirDateValue": "Початкова дата ефіру: {0}", "OptionWakeFromSleep": "Пробудити", "OptionUnairedEpisode": "Невипущені епізоди", @@ -1422,7 +1422,7 @@ "TabMyPlugins": "Мої плагіни", "TabMusic": "Музика", "TabLogs": "Журнали", - "TabLatest": "Останні", + "TabLatest": "Нещодавно додані", "TabDashboard": "Панель", "TabContainers": "Контейнери", "TabCodecs": "Кодеки", @@ -1518,7 +1518,7 @@ "SubtitleCodecNotSupported": "Кодек субтитрів не підтримується", "ContainerNotSupported": "Контейнер не підтримується", "AudioCodecNotSupported": "Аудіокодек не підтримується", - "EnableGamepadHelp": "Слухайте вхід з будь-яких підключених контролерів.", + "EnableGamepadHelp": "Очікуйте вхідних даних від будь-яких підключених контролерів. (Вимагається: режим відображення «TV»)", "LabelEnableGamepad": "Увімкнути геймпад", "Controls": "Елементи керування", "AllowVppTonemappingHelp": "Повне відображення тонів на основі драйверів Intel. Наразі працює лише на певному обладнанні з відео HDR10. Це має вищий пріоритет порівняно з іншою реалізацією OpenCL.", @@ -1559,8 +1559,8 @@ "XmlTvMovieCategoriesHelp": "Програми з цими категоріями відображатимуться як фільми. Розділіть множинні символом \"|\".", "XmlTvKidsCategoriesHelp": "Програми з цими категоріями відображатимуться як програми для дітей. Розділіть множинні символом \"|\".", "XmlDocumentAttributeListHelp": "Ці атрибути застосовуються до кореневого елемента кожної відповіді XML.", - "Writers": "Письменники", - "Writer": "Письменник", + "Writers": "Сценаристи", + "Writer": "Сценарист", "WizardCompleted": "Це все, що нам зараз потрібно. Jellyfin почав збирати інформацію про вашу медіатеку. Перегляньте деякі з наших програм, а потім натисніть Готово, щоб переглянути інформаційну панель.", "Whitelist": "Білий список", "WelcomeToProject": "Ласкаво просимо до Jellyfin!", @@ -1685,5 +1685,20 @@ "MessageNoFavoritesAvailable": "Зараз немає доступних улюблених.", "Experimental": "Експериментальний", "LabelStereoDownmixAlgorithm": "Stereo Downmix алгоритм", - "StereoDownmixAlgorithmHelp": "Алгоритм мікшування багатоканального аудіо у стерео." + "StereoDownmixAlgorithmHelp": "Алгоритм мікшування багатоканального аудіо у стерео.", + "LabelChapterImageResolutionHelp": "Роздільна здатність витягнутих зображень розділу.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Віддавайте перевагу вбудованим назвам, а не назвам файлів для додаткових функцій", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Додатки часто мають таке ж вбудоване ім’я, що й батьківське, позначте це, щоб усе одно використовувати для них вбудовані заголовки.", + "ResolutionMatchSource": "Джерело відповідності", + "SaveRecordingNFO": "Збережіть метадані EPG запису в NFO", + "SaveRecordingNFOHelp": "Зберігайте метадані від постачальника списків EPG разом із бічними носіями.", + "SaveRecordingImages": "Зберегти записані зображення EPG", + "SaveRecordingImagesHelp": "Зберігайте зображення з постачальника списків EPG разом із носієм.", + "HeaderDummyChapter": "Зображення розділів", + "LabelDummyChapterDuration": "Інтервал:", + "LabelDummyChapterDurationHelp": "Інтервал вилучення зображення розділу в секундах.", + "LabelDummyChapterCount": "Ліміт:", + "LabelDummyChapterCountHelp": "Максимальна кількість зображень розділів, які буде видобуто для кожного медіафайлу.", + "LabelChapterImageResolution": "Роздільна здатність:", + "HeaderRecordingMetadataSaving": "Метадані запису" } From 145aea184fb6746d551da90a88831e201330deb9 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 14:22:41 -0400 Subject: [PATCH 07/45] feat: add native secondary subtitle support --- src/components/playback/playbackmanager.js | 67 ++++++++++++++++++++ src/controllers/playback/video/index.js | 73 +++++++++++++++++++++- src/plugins/htmlVideoPlayer/plugin.js | 71 ++++++++++++++++----- src/strings/en-us.json | 1 + 4 files changed, 192 insertions(+), 20 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 0013eb6544..48e2913188 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -876,6 +876,17 @@ class PlaybackManager { }); }; + self.hasSecondarySubtitleSupport = function (player = self._currentPlayer) { + if (!player) return false; + return Boolean(player.supports('SecondarySubtitles')); + }; + + self.secondarySubtitleTracks = function (player = self._currentPlayer) { + const streams = self.subtitleTracks(player); + // Currently, only External subtitles are supported + return streams.filter((stream) => getDeliveryMethod(stream) === 'External'); + }; + function getCurrentSubtitleStream(player) { if (!player) { throw new Error('player cannot be null'); @@ -890,6 +901,20 @@ class PlaybackManager { return getSubtitleStream(player, index); } + function getCurrentSecondarySubtitleStream(player) { + if (!player) { + throw new Error('player cannot be null'); + } + + const index = getPlayerData(player).secondarySubtitleStreamIndex; + + if (index == null || index === -1) { + return null; + } + + return getSubtitleStream(player, index); + } + function getSubtitleStream(player, index) { return self.subtitleTracks(player).filter(function (s) { return s.Type === 'Subtitle' && s.Index === index; @@ -1522,9 +1547,51 @@ class PlaybackManager { player.setSubtitleStreamIndex(selectedTrackElementIndex); + // Also disable secondary subtitles when disabling the primary subtitles + if (selectedTrackElementIndex === -1) { + self.setSecondarySubtitleStreamIndex(selectedTrackElementIndex); + } + getPlayerData(player).subtitleStreamIndex = index; }; + self.setSecondarySubtitleStreamIndex = function (index, player) { + player = player || self._currentPlayer; + if (!self.hasSecondarySubtitleSupport(player)) return; + if (player && !enableLocalPlaylistManagement(player)) { + try { + return player.setSecondarySubtitleStreamIndex(index); + } catch (e) { + console.error(`AutoSet - Failed to set secondary track: ${e}`); + } + } + + const currentStream = getCurrentSecondarySubtitleStream(player); + + const newStream = getSubtitleStream(player, index); + + if (!currentStream && !newStream) { + return; + } + + const clearingStream = currentStream && !newStream; + const changingStream = currentStream && newStream; + const addingStream = !currentStream && newStream; + // Secondary subtitles are currently only handled client side + // Changes to the server code are required before we can handle other delivery methods + if (!clearingStream && (changingStream || addingStream) && getDeliveryMethod(newStream) !== 'External') { + return; + } + + getPlayerData(player).secondarySubtitleStreamIndex = index; + + try { + player.setSecondarySubtitleStreamIndex(index); + } catch (e) { + console.error(`AutoSet - Failed to set secondary track: ${e}`); + } + }; + self.supportSubtitleOffset = function (player) { player = player || self._currentPlayer; return player && 'setSubtitleOffset' in player; diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 8fc6976598..6ca0f59b4a 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -988,9 +988,57 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components }); } + function showSecondarySubtitlesMenu(actionsheet, positionTo) { + const player = currentPlayer; + if (!playbackManager.hasSecondarySubtitleSupport(player)) return; + let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player); + const streams = playbackManager.secondarySubtitleTracks(player); + + if (currentIndex == null) { + currentIndex = -1; + } + + streams.unshift({ + Index: -1, + DisplayTitle: globalize.translate('Off') + }); + + const menuItems = streams.map(function (stream) { + const opt = { + name: stream.DisplayTitle, + id: stream.Index + }; + + if (stream.Index === currentIndex) { + opt.selected = true; + } + + return opt; + }); + + actionsheet.show({ + title: globalize.translate('SecondarySubtitles'), + items: menuItems, + positionTo + }).then(function (id) { + if (id) { + const index = parseInt(id); + if (index !== currentIndex) { + playbackManager.setSecondarySubtitleStreamIndex(index, player); + } + } + }) + .finally(() => { + resetIdle(); + }); + + setTimeout(resetIdle, 0); + } + function showSubtitleTrackSelection() { const player = currentPlayer; const streams = playbackManager.subtitleTracks(player); + const secondaryStreams = playbackManager.secondarySubtitleTracks(player); let currentIndex = playbackManager.getSubtitleStreamIndex(player); if (currentIndex == null) { @@ -1013,18 +1061,37 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return opt; }); + + // Only show option if: player has support, has more than 1 subtitle track, has valid secondary tracks, primary subtitle is not off + if (playbackManager.hasSecondarySubtitleSupport(player) && streams.length > 1 && secondaryStreams.length > 0 && currentIndex !== -1) { + const secondarySubtitleMenuItem = { + name: globalize.translate('SecondarySubtitles'), + id: 'secondarysubtitle' + }; + menuItems.unshift(secondarySubtitleMenuItem); + } + const positionTo = this; import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => { actionsheet.show({ title: globalize.translate('Subtitles'), items: menuItems, + resolveOnClick: true, positionTo: positionTo }).then(function (id) { - const index = parseInt(id); + if (id === 'secondarysubtitle') { + try { + showSecondarySubtitlesMenu(actionsheet, positionTo); + } catch (e) { + console.error(e); + } + } else { + const index = parseInt(id); - if (index !== currentIndex) { - playbackManager.setSubtitleStreamIndex(index, player); + if (index !== currentIndex) { + playbackManager.setSubtitleStreamIndex(index, player); + } } toggleSubtitleSync(); diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index af136e9185..2222548a7c 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -178,7 +178,6 @@ function tryRemoveElement(elem) { * @type {boolean} */ isFetching = false; - /** * @type {HTMLDivElement | null | undefined} */ @@ -207,6 +206,10 @@ function tryRemoveElement(elem) { * @type {number | undefined} */ #customTrackIndex; + /** + * @type {number | undefined} + */ + #customSecondaryTrackIndex; /** * @type {boolean | undefined} */ @@ -270,6 +273,14 @@ function tryRemoveElement(elem) { * @type {any | undefined} */ _currentPlayOptions; + /** + * @type {number} + */ + _PRIMARY_TEXT_TRACK_INDEX = 0; + /** + * @type {number} + */ + _SECONDARY_TEXT_TRACK_INDEX = 1; /** * @type {any | undefined} */ @@ -490,6 +501,10 @@ function tryRemoveElement(elem) { this.setCurrentTrackElement(index); } + setSecondarySubtitleStreamIndex(index) { + this.setCurrentTrackElement(index, this._SECONDARY_TEXT_TRACK_INDEX); + } + resetSubtitleOffset() { this.#currentTrackOffset = 0; this.#showTrackOffset = false; @@ -514,7 +529,7 @@ function tryRemoveElement(elem) { const videoElement = this.#mediaElement; if (videoElement) { return Array.from(videoElement.textTracks) - .find(function (trackElement) { + .filter(function (trackElement) { // get showing .vtt textTack return trackElement.mode === 'showing'; }); @@ -591,6 +606,10 @@ function tryRemoveElement(elem) { return this.#currentTrackOffset; } + isSecondaryTrack(textTrackIndex) { + return textTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; + } + /** * @private */ @@ -956,7 +975,9 @@ function tryRemoveElement(elem) { /** * @private */ - destroyCustomTrack(videoElement) { + destroyCustomTrack(videoElement, targetTrackIndex) { + const destroySingleTrack = typeof targetTrackIndex === 'number'; + if (this.#videoSubtitlesElem) { const subtitlesContainer = this.#videoSubtitlesElem.parentNode; if (subtitlesContainer) { @@ -969,7 +990,11 @@ function tryRemoveElement(elem) { if (videoElement) { const allTracks = videoElement.textTracks || []; // get list of tracks - for (const track of allTracks) { + for (let index = 0; index < allTracks.length; index++) { + const track = allTracks[index]; + if (destroySingleTrack && targetTrackIndex !== index) { + continue; + } if (track.label.includes('manualTrack')) { track.mode = 'disabled'; } @@ -1029,23 +1054,34 @@ function tryRemoveElement(elem) { /** * @private */ - setTrackForDisplay(videoElement, track) { + setTrackForDisplay(videoElement, track, targetTextTrackIndex = this._PRIMARY_TEXT_TRACK_INDEX) { if (!track) { - this.destroyCustomTrack(videoElement); + // Destroy all tracks by passing undefined if there is no valid primary track + this.destroyCustomTrack(videoElement, this.isSecondaryTrack(targetTextTrackIndex) ? targetTextTrackIndex : undefined); return; } + let targetTrackIndex = this.#customTrackIndex; + if (this.isSecondaryTrack(targetTextTrackIndex)) { + targetTrackIndex = this.#customSecondaryTrackIndex; + } + // skip if already playing this track - if (this.#customTrackIndex === track.Index) { + if (targetTrackIndex === track.Index) { return; } this.resetSubtitleOffset(); const item = this._currentPlayOptions.item; - this.destroyCustomTrack(videoElement); - this.#customTrackIndex = track.Index; - this.renderTracksEvents(videoElement, track, item); + this.destroyCustomTrack(videoElement, targetTextTrackIndex); + + if (this.isSecondaryTrack(targetTextTrackIndex)) { + this.#customSecondaryTrackIndex = track.Index; + } else { + this.#customTrackIndex = track.Index; + } + this.renderTracksEvents(videoElement, track, item, targetTextTrackIndex); } /** @@ -1211,7 +1247,7 @@ function tryRemoveElement(elem) { /** * @private */ - renderTracksEvents(videoElement, track, item) { + renderTracksEvents(videoElement, track, item, targetTextTrackIndex = this._PRIMARY_TEXT_TRACK_INDEX) { if (!itemHelper.isLocalItem(item) || track.IsExternal) { const format = (track.Codec || '').toLowerCase(); if (format === 'ssa' || format === 'ass') { @@ -1220,15 +1256,15 @@ function tryRemoveElement(elem) { } if (this.requiresCustomSubtitlesElement()) { - this.renderSubtitlesWithCustomElement(videoElement, track, item); + this.renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex); return; } } let trackElement = null; - if (videoElement.textTracks && videoElement.textTracks.length > 0) { - trackElement = videoElement.textTracks[0]; - + const updatingTrack = videoElement.textTracks && videoElement.textTracks.length > (this.isSecondaryTrack(targetTextTrackIndex) ? 1 : 0); + if (updatingTrack) { + trackElement = videoElement.textTracks[targetTextTrackIndex]; // This throws an error in IE, but is fine in chrome // In IE it's not necessary anyway because changing the src seems to be enough try { @@ -1313,7 +1349,7 @@ function tryRemoveElement(elem) { /** * @private */ - setCurrentTrackElement(streamIndex) { + setCurrentTrackElement(streamIndex, targetTextTrackIndex) { console.debug(`setting new text track index to: ${streamIndex}`); const mediaStreamTextTracks = getMediaStreamTextTracks(this._currentPlayOptions.mediaSource); @@ -1322,7 +1358,7 @@ function tryRemoveElement(elem) { return t.Index === streamIndex; })[0]; - this.setTrackForDisplay(this.#mediaElement, track); + this.setTrackForDisplay(this.#mediaElement, track, targetTextTrackIndex); if (enableNativeTrackSupport(this.#currentSrc, track)) { if (streamIndex !== -1) { this.setCueAppearance(); @@ -1500,6 +1536,7 @@ function tryRemoveElement(elem) { list.push('SetBrightness'); list.push('SetAspectRatio'); + list.push('SecondarySubtitles'); return list; } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 50567aadad..5ca842e8fa 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1412,6 +1412,7 @@ "SearchForSubtitles": "Search for Subtitles", "SearchResults": "Search Results", "Season": "Season", + "SecondarySubtitles": "Secondary Subtitles", "SelectAdminUsername": "Please select a username for the admin account.", "SelectServer": "Select Server", "SendMessage": "Send message", From ab993886c12021e595a9febf9f26e639fc0a60e3 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 14:25:34 -0400 Subject: [PATCH 08/45] feat: add custom rendered secondary tracks --- src/plugins/htmlVideoPlayer/plugin.js | 111 ++++++++++++++++++------- src/plugins/htmlVideoPlayer/style.scss | 8 ++ 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 2222548a7c..3766624d68 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -222,10 +222,18 @@ function tryRemoveElement(elem) { * @type {HTMLElement | null | undefined} */ #videoSubtitlesElem; + /** + * @type {HTMLElement | null | undefined} + */ + #videoSecondarySubtitlesElem; /** * @type {any | null | undefined} */ #currentTrackEvents; + /** + * @type {any | null | undefined} + */ + #currentSecondaryTrackEvents; /** * @type {string[] | undefined} */ @@ -977,17 +985,30 @@ function tryRemoveElement(elem) { */ destroyCustomTrack(videoElement, targetTrackIndex) { const destroySingleTrack = typeof targetTrackIndex === 'number'; + const destroyPrimaryTrack = targetTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; + const destroySecondaryTrack = targetTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; - if (this.#videoSubtitlesElem) { - const subtitlesContainer = this.#videoSubtitlesElem.parentNode; - if (subtitlesContainer) { - tryRemoveElement(subtitlesContainer); + if (destroyPrimaryTrack) { + if (this.#videoSubtitlesElem) { + tryRemoveElement(this.#videoSubtitlesElem); + this.#videoSubtitlesElem = null; + } + } else if (destroySecondaryTrack) { + if (this.#videoSecondarySubtitlesElem) { + tryRemoveElement(this.#videoSecondarySubtitlesElem); + this.#videoSecondarySubtitlesElem = null; + } + } else { + if (this.#videoSubtitlesElem) { + const subtitlesContainer = this.#videoSubtitlesElem.parentNode; + if (subtitlesContainer) { + tryRemoveElement(subtitlesContainer); + } + this.#videoSubtitlesElem = null; + this.#videoSecondarySubtitlesElem = null; } - this.#videoSubtitlesElem = null; } - this.#currentTrackEvents = null; - if (videoElement) { const allTracks = videoElement.textTracks || []; // get list of tracks for (let index = 0; index < allTracks.length; index++) { @@ -1001,7 +1022,19 @@ function tryRemoveElement(elem) { } } - this.#customTrackIndex = -1; + if (destroyPrimaryTrack) { + this.#customTrackIndex = -1; + this.#currentTrackEvents = null; + } else if (destroySecondaryTrack) { + this.#customSecondaryTrackIndex = -1; + this.#currentSecondaryTrackEvents = null; + } else { + this.#customTrackIndex = -1; + this.#customSecondaryTrackIndex = -1; + this.#currentTrackEvents = null; + this.#currentSecondaryTrackEvents = null; + } + this.#currentClock = null; this._currentAspectRatio = null; @@ -1191,16 +1224,27 @@ function tryRemoveElement(elem) { /** * @private */ - renderSubtitlesWithCustomElement(videoElement, track, item) { + renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) { this.fetchSubtitles(track, item).then((data) => { if (!this.#videoSubtitlesElem) { - const subtitlesContainer = document.createElement('div'); - subtitlesContainer.classList.add('videoSubtitles'); - subtitlesContainer.innerHTML = '
'; - this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); - this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); - videoElement.parentNode.appendChild(subtitlesContainer); - this.#currentTrackEvents = data.TrackEvents; + if (!this.isSecondaryTrack(targetTextTrackIndex)) { + const subtitlesContainer = document.createElement('div'); + subtitlesContainer.classList.add('videoSubtitles'); + subtitlesContainer.innerHTML = '
'; + this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); + this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); + videoElement.parentNode.appendChild(subtitlesContainer); + this.#currentTrackEvents = data.TrackEvents; + } + } else if (this.isSecondaryTrack(targetTextTrackIndex)) { + const subtitlesContainer = document.querySelector('.videoSubtitles'); + if (!subtitlesContainer) return; + const secondarySubtitlesElement = document.createElement('div'); + secondarySubtitlesElement.classList.add('videoSecondarySubtitlesInner'); + subtitlesContainer.prepend(secondarySubtitlesElement); + this.#videoSecondarySubtitlesElem = secondarySubtitlesElement; + this.setSubtitleAppearance(subtitlesContainer, this.#videoSecondarySubtitlesElem); + this.#currentSecondaryTrackEvents = data.TrackEvents; } }); } @@ -1324,24 +1368,29 @@ function tryRemoveElement(elem) { return; } - const trackEvents = this.#currentTrackEvents; - const subtitleTextElement = this.#videoSubtitlesElem; + const allTrackEvents = [this.#currentTrackEvents, this.#currentSecondaryTrackEvents]; + const subtitleTextElements = [this.#videoSubtitlesElem, this.#videoSecondarySubtitlesElem]; - if (trackEvents && subtitleTextElement) { - const ticks = timeMs * 10000; - let selectedTrackEvent; - for (const trackEvent of trackEvents) { - if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) { - selectedTrackEvent = trackEvent; - break; + for (let i = 0; i < allTrackEvents.length; i++) { + const trackEvents = allTrackEvents[i]; + const subtitleTextElement = subtitleTextElements[i]; + + if (trackEvents && subtitleTextElement) { + const ticks = timeMs * 10000; + let selectedTrackEvent; + for (const trackEvent of trackEvents) { + if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) { + selectedTrackEvent = trackEvent; + break; + } } - } - if (selectedTrackEvent && selectedTrackEvent.Text) { - subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); - subtitleTextElement.classList.remove('hide'); - } else { - subtitleTextElement.classList.add('hide'); + if (selectedTrackEvent && selectedTrackEvent.Text) { + subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); + subtitleTextElement.classList.remove('hide'); + } else { + subtitleTextElement.classList.add('hide'); + } } } } diff --git a/src/plugins/htmlVideoPlayer/style.scss b/src/plugins/htmlVideoPlayer/style.scss index 390e25a994..36f86314b4 100644 --- a/src/plugins/htmlVideoPlayer/style.scss +++ b/src/plugins/htmlVideoPlayer/style.scss @@ -74,6 +74,14 @@ video[controls]::-webkit-media-controls { display: inline-block; } +.videoSecondarySubtitlesInner { + max-width: 70%; + background-color: rgba(0, 0, 0, 0.8); + margin: auto; + display: block; + margin-bottom: 0 !important; +} + @keyframes htmlvideoplayer-zoomin { from { transform: scale3d(0.2, 0.2, 0.2); From 256084ffb8a560998daa18b0e3a1c38e4cd07b37 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 14:29:21 -0400 Subject: [PATCH 09/45] fix: correctly apply offsets/hide existing for certain browsers --- src/plugins/htmlVideoPlayer/plugin.js | 73 +++++++++++++++++++++------ 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 3766624d68..5b9fb3d471 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -533,7 +533,7 @@ function tryRemoveElement(elem) { /** * @private */ - getTextTrack() { + getTextTracks() { const videoElement = this.#mediaElement; if (videoElement) { return Array.from(videoElement.textTracks) @@ -546,9 +546,6 @@ function tryRemoveElement(elem) { } } - /** - * @private - */ setSubtitleOffset(offset) { const offsetValue = parseFloat(offset); @@ -557,12 +554,13 @@ function tryRemoveElement(elem) { this.updateCurrentTrackOffset(offsetValue); this.#currentSubtitlesOctopus.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; } else { - const trackElement = this.getTextTrack(); + const trackElements = this.getTextTracks(); // if .vtt currently rendering - if (trackElement) { - this.setTextTrackSubtitleOffset(trackElement, offsetValue); - } else if (this.#currentTrackEvents) { - this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue); + if (trackElements.length > 0) { + trackElements.forEach((trackElement, index) => this.setTextTrackSubtitleOffset(trackElement, offsetValue, index)); + } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { + this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, this._PRIMARY_TEXT_TRACK_INDEX); + this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, this._SECONDARY_TEXT_TRACK_INDEX); } else { console.debug('No available track, cannot apply offset: ', offsetValue); } @@ -572,10 +570,21 @@ function tryRemoveElement(elem) { /** * @private */ - updateCurrentTrackOffset(offsetValue) { + updateCurrentTrackOffset(offsetValue, currentTrackIndex = 0) { + const skipRelativeOffset = currentTrackIndex !== this._PRIMARY_TEXT_TRACK_INDEX; let relativeOffset = offsetValue; const newTrackOffset = offsetValue; - if (this.#currentTrackOffset) { + if (this.#currentTrackOffset && !skipRelativeOffset) { + /** + * Only calculate the offset for the first track. + * The offset gets set after this method is first called. + * Subsequent method calls (when playing multiple tracks) + * will have the calculated relative offset cancel out + * and will be `0` + * @example + * first_call: (relativeOffset = -2) -= (this.#currentTrackOffset = -1) -> 1 + * second_call: (relativeOffset = -2) -= (this.#currentTrackOffset = -2) -> 0 + */ relativeOffset -= this.#currentTrackOffset; } this.#currentTrackOffset = newTrackOffset; @@ -585,10 +594,41 @@ function tryRemoveElement(elem) { /** * @private + * These browsers will not clear the existing active cue when setting an offset + * for native TextTracks. + * Any previous text tracks that are on the screen when the offset changes will + * remain next to the new tracks until they reach the new offset's instance of the track. */ - setTextTrackSubtitleOffset(currentTrack, offsetValue) { + requiresHidingActiveCuesOnOffsetChange() { + if (browser.firefox) { + return true; + } + return false; + } + + /** + * @private + */ + hideTextTrackActiveCues(currentTrack) { + if (currentTrack.activeCues) { + Array.from(currentTrack.activeCues).forEach((cue) => { + cue.text = ''; + }); + } + } + + /** + * @private + */ + setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { if (currentTrack.cues) { - offsetValue = this.updateCurrentTrackOffset(offsetValue); + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); + if (offsetValue === 0) { + return; + } + if (this.requiresHidingActiveCuesOnOffsetChange()) { + this.hideTextTrackActiveCues(currentTrack); + } Array.from(currentTrack.cues) .forEach(function (cue) { cue.startTime -= offsetValue; @@ -600,9 +640,12 @@ function tryRemoveElement(elem) { /** * @private */ - setTrackEventsSubtitleOffset(trackEvents, offsetValue) { + setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { if (Array.isArray(trackEvents)) { - offsetValue = this.updateCurrentTrackOffset(offsetValue) * 1e7; // ticks + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks + if (offsetValue === 0) { + return; + } trackEvents.forEach(function (trackEvent) { trackEvent.StartPositionTicks -= offsetValue; trackEvent.EndPositionTicks -= offsetValue; From 348de5ac7f1d5b7e5a735c10a31c89f115980465 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 14:30:51 -0400 Subject: [PATCH 10/45] feat: resume secondary track for current session --- src/components/playback/playbackmanager.js | 18 +++++++++++++++++ src/plugins/htmlVideoPlayer/plugin.js | 23 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 48e2913188..f0cf735522 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1488,6 +1488,24 @@ class PlaybackManager { return getPlayerData(player).subtitleStreamIndex; }; + self.getSecondarySubtitleStreamIndex = function (player) { + player = player || self._currentPlayer; + + try { + if (player && !enableLocalPlaylistManagement(player)) { + return player.getSecondarySubtitleStreamIndex(); + } + } catch (e) { + console.error(`Failed to get secondary stream index: ${e}`); + } + + if (!player) { + throw new Error('player cannot be null'); + } + + return getPlayerData(player).secondarySubtitleStreamIndex; + }; + function getDeliveryMethod(subtitleStream) { // This will be null for internal subs for local items if (subtitleStream.DeliveryMethod) { diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 5b9fb3d471..ac178d8511 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -186,6 +186,10 @@ function tryRemoveElement(elem) { * @type {number | undefined} */ #subtitleTrackIndexToSetOnPlaying; + /** + * @type {number | undefined} + */ + #secondarySubtitleTrackIndexToSetOnPlaying; /** * @type {number | null} */ @@ -474,6 +478,15 @@ function tryRemoveElement(elem) { this.#subtitleTrackIndexToSetOnPlaying = -1; } } + // Continue using the secondary track that has been set during this watch session + const currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(); + this.#secondarySubtitleTrackIndexToSetOnPlaying = currentSecondaryTrackIndex == null ? -1 : currentSecondaryTrackIndex; + if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { + const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; + if (!initialSecondarySubtitleStream || initialSecondarySubtitleStream.DeliveryMethod !== 'External') { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } + } this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; @@ -889,6 +902,16 @@ function tryRemoveElement(elem) { if (this.#audioTrackIndexToSetOnPlaying != null && this.canSetAudioStreamIndex()) { this.setAudioStreamIndex(this.#audioTrackIndexToSetOnPlaying); } + + if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { + /** + * Using a 0ms timeout to set the secondary subtitles because of some weird race condition when + * setting both primary and secondary tracks at the same time. + * The `TextTrack` content and cues will somehow get mixed up and each track will play a mix of both languages. + * Putting this in a timeout fixes it completely. + */ + setTimeout(() => this.setSecondarySubtitleStreamIndex(this.#secondarySubtitleTrackIndexToSetOnPlaying), 0); + } } /** From 529e70222a16aa5daac026e174755b82dc5e16ca Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 20:22:11 -0400 Subject: [PATCH 11/45] fix: update custom renderer logic/custom track spacing --- src/plugins/htmlVideoPlayer/plugin.js | 21 ++++++++++++--------- src/plugins/htmlVideoPlayer/style.scss | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index ac178d8511..1605bcfbbd 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1292,17 +1292,20 @@ function tryRemoveElement(elem) { */ renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) { this.fetchSubtitles(track, item).then((data) => { - if (!this.#videoSubtitlesElem) { - if (!this.isSecondaryTrack(targetTextTrackIndex)) { - const subtitlesContainer = document.createElement('div'); + if (!this.#videoSubtitlesElem && !this.isSecondaryTrack(targetTextTrackIndex)) { + let subtitlesContainer = document.querySelector('.videoSubtitles'); + if (!subtitlesContainer) { + subtitlesContainer = document.createElement('div'); subtitlesContainer.classList.add('videoSubtitles'); - subtitlesContainer.innerHTML = '
'; - this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); - this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); - videoElement.parentNode.appendChild(subtitlesContainer); - this.#currentTrackEvents = data.TrackEvents; } - } else if (this.isSecondaryTrack(targetTextTrackIndex)) { + const subtitlesElement = document.createElement('div'); + subtitlesElement.classList.add('videoSubtitlesInner'); + subtitlesContainer.appendChild(subtitlesElement); + this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); + this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); + videoElement.parentNode.appendChild(subtitlesContainer); + this.#currentTrackEvents = data.TrackEvents; + } else if (!this.#videoSecondarySubtitlesElem && this.isSecondaryTrack(targetTextTrackIndex)) { const subtitlesContainer = document.querySelector('.videoSubtitles'); if (!subtitlesContainer) return; const secondarySubtitlesElement = document.createElement('div'); diff --git a/src/plugins/htmlVideoPlayer/style.scss b/src/plugins/htmlVideoPlayer/style.scss index 36f86314b4..54137685b7 100644 --- a/src/plugins/htmlVideoPlayer/style.scss +++ b/src/plugins/htmlVideoPlayer/style.scss @@ -79,7 +79,8 @@ video[controls]::-webkit-media-controls { background-color: rgba(0, 0, 0, 0.8); margin: auto; display: block; - margin-bottom: 0 !important; + min-height: 0 !important; + margin-bottom: 0.5em !important; } @keyframes htmlvideoplayer-zoomin { From e8713002659e8acce61328eea1c114122a1b92b7 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 15 Sep 2022 21:36:25 -0400 Subject: [PATCH 12/45] chore: refactor destroyCustomTrack method --- src/plugins/htmlVideoPlayer/plugin.js | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 1605bcfbbd..274247ff91 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1049,8 +1049,7 @@ function tryRemoveElement(elem) { /** * @private */ - destroyCustomTrack(videoElement, targetTrackIndex) { - const destroySingleTrack = typeof targetTrackIndex === 'number'; + destroyCustomRenderedTrackElements(targetTrackIndex) { const destroyPrimaryTrack = targetTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; const destroySecondaryTrack = targetTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; @@ -1064,7 +1063,7 @@ function tryRemoveElement(elem) { tryRemoveElement(this.#videoSecondarySubtitlesElem); this.#videoSecondarySubtitlesElem = null; } - } else { + } else { // destroy all if (this.#videoSubtitlesElem) { const subtitlesContainer = this.#videoSubtitlesElem.parentNode; if (subtitlesContainer) { @@ -1074,11 +1073,18 @@ function tryRemoveElement(elem) { this.#videoSecondarySubtitlesElem = null; } } + } + /** + * @private + */ + destroyNativeTracks(videoElement, targetTrackIndex) { if (videoElement) { + const destroySingleTrack = typeof targetTrackIndex === 'number'; const allTracks = videoElement.textTracks || []; // get list of tracks for (let index = 0; index < allTracks.length; index++) { const track = allTracks[index]; + // Skip all other tracks if we are targeting just one if (destroySingleTrack && targetTrackIndex !== index) { continue; } @@ -1087,6 +1093,14 @@ function tryRemoveElement(elem) { } } } + } + + /** + * @private + */ + destroyStoredTrackInfo(targetTrackIndex) { + const destroyPrimaryTrack = targetTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; + const destroySecondaryTrack = targetTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; if (destroyPrimaryTrack) { this.#customTrackIndex = -1; @@ -1094,12 +1108,26 @@ function tryRemoveElement(elem) { } else if (destroySecondaryTrack) { this.#customSecondaryTrackIndex = -1; this.#currentSecondaryTrackEvents = null; - } else { + } else { // destroy all this.#customTrackIndex = -1; this.#customSecondaryTrackIndex = -1; this.#currentTrackEvents = null; this.#currentSecondaryTrackEvents = null; } + } + + /** + * @private + */ + destroyCustomTrack(videoElement, targetTrackIndex) { + if (this.#resizeObserver) { + this.#resizeObserver.disconnect(); + this.#resizeObserver = null; + } + + this.destroyCustomRenderedTrackElements(targetTrackIndex); + this.destroyNativeTracks(videoElement, targetTrackIndex); + this.destroyStoredTrackInfo(targetTrackIndex); this.#currentClock = null; this._currentAspectRatio = null; From e01124cbcaa14675b6129092c0c2ad5ce804380a Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Fri, 16 Sep 2022 13:16:15 -0400 Subject: [PATCH 13/45] chore: use specific method for checking track --- src/plugins/htmlVideoPlayer/plugin.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 274247ff91..74a824706e 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -670,6 +670,10 @@ function tryRemoveElement(elem) { return this.#currentTrackOffset; } + isPrimaryTrack(textTrackIndex) { + return textTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; + } + isSecondaryTrack(textTrackIndex) { return textTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; } @@ -1050,15 +1054,12 @@ function tryRemoveElement(elem) { * @private */ destroyCustomRenderedTrackElements(targetTrackIndex) { - const destroyPrimaryTrack = targetTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; - const destroySecondaryTrack = targetTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; - - if (destroyPrimaryTrack) { + if (this.isPrimaryTrack(targetTrackIndex)) { if (this.#videoSubtitlesElem) { tryRemoveElement(this.#videoSubtitlesElem); this.#videoSubtitlesElem = null; } - } else if (destroySecondaryTrack) { + } else if (this.isSecondaryTrack(targetTrackIndex)) { if (this.#videoSecondarySubtitlesElem) { tryRemoveElement(this.#videoSecondarySubtitlesElem); this.#videoSecondarySubtitlesElem = null; @@ -1099,13 +1100,10 @@ function tryRemoveElement(elem) { * @private */ destroyStoredTrackInfo(targetTrackIndex) { - const destroyPrimaryTrack = targetTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; - const destroySecondaryTrack = targetTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; - - if (destroyPrimaryTrack) { + if (this.isPrimaryTrack(targetTrackIndex)) { this.#customTrackIndex = -1; this.#currentTrackEvents = null; - } else if (destroySecondaryTrack) { + } else if (this.isSecondaryTrack(targetTrackIndex)) { this.#customSecondaryTrackIndex = -1; this.#currentSecondaryTrackEvents = null; } else { // destroy all From f33699ad8a341383b422fba8bca92343a5ef494d Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 9 Oct 2022 22:33:08 -0400 Subject: [PATCH 14/45] fix: only show secondary if primary sill valid, remove resolveOnClick --- src/controllers/playback/video/index.js | 18 +++++++++++++++--- src/plugins/htmlVideoPlayer/plugin.js | 24 +++++++++++++++++------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 6ca0f59b4a..dfbf714c7a 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1062,8 +1062,21 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return opt; }); - // Only show option if: player has support, has more than 1 subtitle track, has valid secondary tracks, primary subtitle is not off - if (playbackManager.hasSecondarySubtitleSupport(player) && streams.length > 1 && secondaryStreams.length > 0 && currentIndex !== -1) { + /** + * Only show option if: + * - player has support + * - has more than 1 subtitle track + * - has valid secondary tracks + * - primary subtitle is not off + * - primary subtitle is `External` + */ + if ( + playbackManager.hasSecondarySubtitleSupport(player) && + streams.length > 1 && + secondaryStreams.length > 0 && + currentIndex !== -1 && + playbackManager.isSubtitleStreamExternal(currentIndex, player) + ) { const secondarySubtitleMenuItem = { name: globalize.translate('SecondarySubtitles'), id: 'secondarysubtitle' @@ -1077,7 +1090,6 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components actionsheet.show({ title: globalize.translate('Subtitles'), items: menuItems, - resolveOnClick: true, positionTo: positionTo }).then(function (id) { if (id === 'secondarysubtitle') { diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 74a824706e..d00ff53d2c 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -471,15 +471,30 @@ function tryRemoveElement(elem) { destroyFlvPlayer(this); destroyCastPlayer(this); + let secondaryTrackValid = true; + this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { this.#subtitleTrackIndexToSetOnPlaying = -1; } + // secondary track should not be shown if primary track is no longer `External` or is not on + if (initialSubtitleStream && initialSubtitleStream.DeliveryMethod !== 'External') { + secondaryTrackValid = false; + } + } else { + secondaryTrackValid = false; } - // Continue using the secondary track that has been set during this watch session - const currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(); + + // Get the secondary track that has been set during this watch session + let currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(); + + if (!secondaryTrackValid) { + currentSecondaryTrackIndex = -1; + playbackManager.setSecondarySubtitleStreamIndex(currentSecondaryTrackIndex); + } + this.#secondarySubtitleTrackIndexToSetOnPlaying = currentSecondaryTrackIndex == null ? -1 : currentSecondaryTrackIndex; if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; @@ -1118,11 +1133,6 @@ function tryRemoveElement(elem) { * @private */ destroyCustomTrack(videoElement, targetTrackIndex) { - if (this.#resizeObserver) { - this.#resizeObserver.disconnect(); - this.#resizeObserver = null; - } - this.destroyCustomRenderedTrackElements(targetTrackIndex); this.destroyNativeTracks(videoElement, targetTrackIndex); this.destroyStoredTrackInfo(targetTrackIndex); From 9ddafb063b104728acda04bb1195bcf62f7cce7e Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Mon, 10 Oct 2022 21:26:15 -0400 Subject: [PATCH 15/45] fix: limit secondary to non-SSA/ASS subtitles --- src/components/playback/playbackmanager.js | 21 +++++++++++++++++---- src/controllers/playback/video/index.js | 4 ++-- src/plugins/htmlVideoPlayer/plugin.js | 5 +++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index f0cf735522..f6c366b7ae 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -876,15 +876,28 @@ class PlaybackManager { }); }; - self.hasSecondarySubtitleSupport = function (player = self._currentPlayer) { + self.playerHasSecondarySubtitleSupport = function (player = self._currentPlayer) { if (!player) return false; return Boolean(player.supports('SecondarySubtitles')); }; + /** + * Checks if: + * - the track can be used directly as a secondary subtitle + * - or if it can be paired with a secondary subtitle when used as a primary subtitle + */ + self.trackHasSecondarySubtitleSupport = function (track, player = self._currentPlayer) { + if (!player || !self.playerHasSecondarySubtitleSupport(player)) return false; + const format = (track.Codec || '').toLowerCase(); + // Currently, only non-SSA/non-ASS external subtitles are supported. + // Showing secondary subtitles does not work with any SSA/ASS subtitle combinations because + // of the complexity of how they are rendered and the risk of the subtitles overlapping + return format !== 'ssa' && format !== 'ass' && getDeliveryMethod(track) === 'External'; + }; + self.secondarySubtitleTracks = function (player = self._currentPlayer) { const streams = self.subtitleTracks(player); - // Currently, only External subtitles are supported - return streams.filter((stream) => getDeliveryMethod(stream) === 'External'); + return streams.filter((stream) => self.trackHasSecondarySubtitleSupport(stream, player)); }; function getCurrentSubtitleStream(player) { @@ -1575,7 +1588,7 @@ class PlaybackManager { self.setSecondarySubtitleStreamIndex = function (index, player) { player = player || self._currentPlayer; - if (!self.hasSecondarySubtitleSupport(player)) return; + if (!self.playerHasSecondarySubtitleSupport(player)) return; if (player && !enableLocalPlaylistManagement(player)) { try { return player.setSecondarySubtitleStreamIndex(index); diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index dfbf714c7a..84e7a8d719 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -990,7 +990,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components function showSecondarySubtitlesMenu(actionsheet, positionTo) { const player = currentPlayer; - if (!playbackManager.hasSecondarySubtitleSupport(player)) return; + if (!playbackManager.playerHasSecondarySubtitleSupport(player)) return; let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player); const streams = playbackManager.secondarySubtitleTracks(player); @@ -1071,7 +1071,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components * - primary subtitle is `External` */ if ( - playbackManager.hasSecondarySubtitleSupport(player) && + playbackManager.playerHasSecondarySubtitleSupport(player) && streams.length > 1 && secondaryStreams.length > 0 && currentIndex !== -1 && diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index d00ff53d2c..c532bc95a4 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -478,9 +478,10 @@ function tryRemoveElement(elem) { const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { this.#subtitleTrackIndexToSetOnPlaying = -1; + secondaryTrackValid = false; } - // secondary track should not be shown if primary track is no longer `External` or is not on - if (initialSubtitleStream && initialSubtitleStream.DeliveryMethod !== 'External') { + // secondary track should not be shown if primary track is no longer a valid pair + if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream)) { secondaryTrackValid = false; } } else { From 8fd9d83d8ecf9e61d5ab82b9abdc80726abe0eaf Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Wed, 19 Oct 2022 20:24:55 -0400 Subject: [PATCH 16/45] chore: refactor check, move consts --- src/plugins/htmlVideoPlayer/plugin.js | 32 ++++++++++----------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index c532bc95a4..7df882fceb 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -155,6 +155,9 @@ function tryRemoveElement(elem) { return profileBuilder({}); } + const PRIMARY_TEXT_TRACK_INDEX = 0; + const SECONDARY_TEXT_TRACK_INDEX = 1; + export class HtmlVideoPlayer { /** * @type {string} @@ -285,14 +288,6 @@ function tryRemoveElement(elem) { * @type {any | undefined} */ _currentPlayOptions; - /** - * @type {number} - */ - _PRIMARY_TEXT_TRACK_INDEX = 0; - /** - * @type {number} - */ - _SECONDARY_TEXT_TRACK_INDEX = 1; /** * @type {any | undefined} */ @@ -539,7 +534,7 @@ function tryRemoveElement(elem) { } setSecondarySubtitleStreamIndex(index) { - this.setCurrentTrackElement(index, this._SECONDARY_TEXT_TRACK_INDEX); + this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX); } resetSubtitleOffset() { @@ -588,8 +583,8 @@ function tryRemoveElement(elem) { if (trackElements.length > 0) { trackElements.forEach((trackElement, index) => this.setTextTrackSubtitleOffset(trackElement, offsetValue, index)); } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { - this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, this._PRIMARY_TEXT_TRACK_INDEX); - this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, this._SECONDARY_TEXT_TRACK_INDEX); + this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); + this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); } else { console.debug('No available track, cannot apply offset: ', offsetValue); } @@ -600,7 +595,7 @@ function tryRemoveElement(elem) { * @private */ updateCurrentTrackOffset(offsetValue, currentTrackIndex = 0) { - const skipRelativeOffset = currentTrackIndex !== this._PRIMARY_TEXT_TRACK_INDEX; + const skipRelativeOffset = currentTrackIndex !== PRIMARY_TEXT_TRACK_INDEX; let relativeOffset = offsetValue; const newTrackOffset = offsetValue; if (this.#currentTrackOffset && !skipRelativeOffset) { @@ -629,10 +624,7 @@ function tryRemoveElement(elem) { * remain next to the new tracks until they reach the new offset's instance of the track. */ requiresHidingActiveCuesOnOffsetChange() { - if (browser.firefox) { - return true; - } - return false; + return !!browser.firefox; } /** @@ -687,11 +679,11 @@ function tryRemoveElement(elem) { } isPrimaryTrack(textTrackIndex) { - return textTrackIndex === this._PRIMARY_TEXT_TRACK_INDEX; + return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX; } isSecondaryTrack(textTrackIndex) { - return textTrackIndex === this._SECONDARY_TEXT_TRACK_INDEX; + return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX; } /** @@ -1190,7 +1182,7 @@ function tryRemoveElement(elem) { /** * @private */ - setTrackForDisplay(videoElement, track, targetTextTrackIndex = this._PRIMARY_TEXT_TRACK_INDEX) { + setTrackForDisplay(videoElement, track, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { if (!track) { // Destroy all tracks by passing undefined if there is no valid primary track this.destroyCustomTrack(videoElement, this.isSecondaryTrack(targetTextTrackIndex) ? targetTextTrackIndex : undefined); @@ -1397,7 +1389,7 @@ function tryRemoveElement(elem) { /** * @private */ - renderTracksEvents(videoElement, track, item, targetTextTrackIndex = this._PRIMARY_TEXT_TRACK_INDEX) { + renderTracksEvents(videoElement, track, item, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { if (!itemHelper.isLocalItem(item) || track.IsExternal) { const format = (track.Codec || '').toLowerCase(); if (format === 'ssa' || format === 'ass') { From 49bae6b67c9c6010611a0d595a156cd80eaa8dd2 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Wed, 19 Oct 2022 21:20:18 -0400 Subject: [PATCH 17/45] fix: failed lint check --- src/plugins/htmlVideoPlayer/plugin.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 7df882fceb..b8903ee326 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -581,7 +581,9 @@ function tryRemoveElement(elem) { const trackElements = this.getTextTracks(); // if .vtt currently rendering if (trackElements.length > 0) { - trackElements.forEach((trackElement, index) => this.setTextTrackSubtitleOffset(trackElement, offsetValue, index)); + trackElements.forEach(function (trackElement, index) { + this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); + }); } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); From abc663f6f6ecca6c7c4babe35b057e3c938f884e Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 13 Nov 2022 20:22:55 -0500 Subject: [PATCH 18/45] fix: update ssa/ass checks, custom track location, offsets --- src/components/playback/playbackmanager.js | 12 ++--- src/controllers/playback/video/index.js | 34 +++++++------ src/plugins/htmlVideoPlayer/plugin.js | 58 +++++++++++++++------- src/plugins/htmlVideoPlayer/style.scss | 1 + 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index f6c366b7ae..9104eceb78 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -887,7 +887,7 @@ class PlaybackManager { * - or if it can be paired with a secondary subtitle when used as a primary subtitle */ self.trackHasSecondarySubtitleSupport = function (track, player = self._currentPlayer) { - if (!player || !self.playerHasSecondarySubtitleSupport(player)) return false; + if (!player) return false; const format = (track.Codec || '').toLowerCase(); // Currently, only non-SSA/non-ASS external subtitles are supported. // Showing secondary subtitles does not work with any SSA/ASS subtitle combinations because @@ -1578,8 +1578,9 @@ class PlaybackManager { player.setSubtitleStreamIndex(selectedTrackElementIndex); - // Also disable secondary subtitles when disabling the primary subtitles - if (selectedTrackElementIndex === -1) { + // Also disable secondary subtitles when disabling the primary + // subtitles, or if it doesn't support a secondary pair + if (selectedTrackElementIndex === -1 || !self.trackHasSecondarySubtitleSupport(newStream)) { self.setSecondarySubtitleStreamIndex(selectedTrackElementIndex); } @@ -1605,12 +1606,9 @@ class PlaybackManager { return; } - const clearingStream = currentStream && !newStream; - const changingStream = currentStream && newStream; - const addingStream = !currentStream && newStream; // Secondary subtitles are currently only handled client side // Changes to the server code are required before we can handle other delivery methods - if (!clearingStream && (changingStream || addingStream) && getDeliveryMethod(newStream) !== 'External') { + if (newStream && getDeliveryMethod(newStream) !== 'External') { return; } diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 84e7a8d719..ccbc295987 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1035,10 +1035,26 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components setTimeout(resetIdle, 0); } + /** + * Only show option if: + * - player has support + * - has more than 1 subtitle track + * - has valid secondary tracks + * - primary subtitle is not off + * - primary subtitle has support (index + 1 because `'Off'` is index 0 in `streams` array) + */ + function currentTrackCanHaveSecondarySubtitle(player, streams, currentIndex) { + const secondaryStreams = playbackManager.secondarySubtitleTracks(player); + return playbackManager.playerHasSecondarySubtitleSupport(player) && + streams.length > 1 && + secondaryStreams.length > 0 && + currentIndex !== -1 && + playbackManager.trackHasSecondarySubtitleSupport(streams[currentIndex + 1], player); + } + function showSubtitleTrackSelection() { const player = currentPlayer; const streams = playbackManager.subtitleTracks(player); - const secondaryStreams = playbackManager.secondarySubtitleTracks(player); let currentIndex = playbackManager.getSubtitleStreamIndex(player); if (currentIndex == null) { @@ -1062,21 +1078,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return opt; }); - /** - * Only show option if: - * - player has support - * - has more than 1 subtitle track - * - has valid secondary tracks - * - primary subtitle is not off - * - primary subtitle is `External` - */ - if ( - playbackManager.playerHasSecondarySubtitleSupport(player) && - streams.length > 1 && - secondaryStreams.length > 0 && - currentIndex !== -1 && - playbackManager.isSubtitleStreamExternal(currentIndex, player) - ) { + if (currentTrackCanHaveSecondarySubtitle(player, streams, currentIndex)) { const secondarySubtitleMenuItem = { name: globalize.translate('SecondarySubtitles'), id: 'secondarysubtitle' diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index b8903ee326..a40b99a273 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -225,6 +225,14 @@ function tryRemoveElement(elem) { * @type {number | undefined} */ #currentTrackOffset; + /** + * @type {HTMLElement | null | undefined} + */ + #secondaryTrackOffset; + /** + * @type {number | null | undefined} + */ + #subtitleVerticalPosition; /** * @type {HTMLElement | null | undefined} */ @@ -539,6 +547,7 @@ function tryRemoveElement(elem) { resetSubtitleOffset() { this.#currentTrackOffset = 0; + this.#secondaryTrackOffset = 0; this.#showTrackOffset = false; } @@ -581,7 +590,7 @@ function tryRemoveElement(elem) { const trackElements = this.getTextTracks(); // if .vtt currently rendering if (trackElements.length > 0) { - trackElements.forEach(function (trackElement, index) { + trackElements.forEach((trackElement, index) => { this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); }); } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { @@ -596,24 +605,25 @@ function tryRemoveElement(elem) { /** * @private */ - updateCurrentTrackOffset(offsetValue, currentTrackIndex = 0) { - const skipRelativeOffset = currentTrackIndex !== PRIMARY_TEXT_TRACK_INDEX; + updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { + let offsetToCompare = this.#currentTrackOffset; + if (this.isSecondaryTrack(currentTrackIndex)) { + offsetToCompare = this.#secondaryTrackOffset; + } + let relativeOffset = offsetValue; const newTrackOffset = offsetValue; - if (this.#currentTrackOffset && !skipRelativeOffset) { - /** - * Only calculate the offset for the first track. - * The offset gets set after this method is first called. - * Subsequent method calls (when playing multiple tracks) - * will have the calculated relative offset cancel out - * and will be `0` - * @example - * first_call: (relativeOffset = -2) -= (this.#currentTrackOffset = -1) -> 1 - * second_call: (relativeOffset = -2) -= (this.#currentTrackOffset = -2) -> 0 - */ - relativeOffset -= this.#currentTrackOffset; + + if (offsetToCompare) { + relativeOffset -= offsetToCompare; } - this.#currentTrackOffset = newTrackOffset; + + if (this.isSecondaryTrack(currentTrackIndex)) { + this.#secondaryTrackOffset = newTrackOffset; + } else { + this.#currentTrackOffset = newTrackOffset; + } + // relative to currentTrackOffset return relativeOffset; } @@ -1132,6 +1142,7 @@ function tryRemoveElement(elem) { this.destroyNativeTracks(videoElement, targetTrackIndex); this.destroyStoredTrackInfo(targetTrackIndex); + this.#subtitleVerticalPosition = null; this.#currentClock = null; this._currentAspectRatio = null; @@ -1322,6 +1333,14 @@ function tryRemoveElement(elem) { * @private */ renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) { + if (this.#subtitleVerticalPosition == null) { + import('../../scripts/settings/userSettings').then((userSettings) => { + const subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); + this.#subtitleVerticalPosition = subtitleAppearance.verticalPosition; + this.#subtitleVerticalPosition = parseInt(subtitleAppearance.verticalPosition, 10); + }); + } + this.fetchSubtitles(track, item).then((data) => { if (!this.#videoSubtitlesElem && !this.isSecondaryTrack(targetTextTrackIndex)) { let subtitlesContainer = document.querySelector('.videoSubtitles'); @@ -1341,7 +1360,12 @@ function tryRemoveElement(elem) { if (!subtitlesContainer) return; const secondarySubtitlesElement = document.createElement('div'); secondarySubtitlesElement.classList.add('videoSecondarySubtitlesInner'); - subtitlesContainer.prepend(secondarySubtitlesElement); + // determine the order of the subtitles + if (this.#subtitleVerticalPosition < 0) { + subtitlesContainer.prepend(secondarySubtitlesElement); + } else { + subtitlesContainer.appendChild(secondarySubtitlesElement); + } this.#videoSecondarySubtitlesElem = secondarySubtitlesElement; this.setSubtitleAppearance(subtitlesContainer, this.#videoSecondarySubtitlesElem); this.#currentSecondaryTrackEvents = data.TrackEvents; diff --git a/src/plugins/htmlVideoPlayer/style.scss b/src/plugins/htmlVideoPlayer/style.scss index 54137685b7..21b2047392 100644 --- a/src/plugins/htmlVideoPlayer/style.scss +++ b/src/plugins/htmlVideoPlayer/style.scss @@ -80,6 +80,7 @@ video[controls]::-webkit-media-controls { margin: auto; display: block; min-height: 0 !important; + margin-top: 0.5em !important; margin-bottom: 0.5em !important; } From f3865f0dac1063e0accc519fa003a525b1945cd1 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 13 Nov 2022 20:40:02 -0500 Subject: [PATCH 19/45] chore: move logic from method to const --- src/controllers/playback/video/index.js | 34 ++++++++++++------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index ccbc295987..1f1006ddbd 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1035,26 +1035,10 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components setTimeout(resetIdle, 0); } - /** - * Only show option if: - * - player has support - * - has more than 1 subtitle track - * - has valid secondary tracks - * - primary subtitle is not off - * - primary subtitle has support (index + 1 because `'Off'` is index 0 in `streams` array) - */ - function currentTrackCanHaveSecondarySubtitle(player, streams, currentIndex) { - const secondaryStreams = playbackManager.secondarySubtitleTracks(player); - return playbackManager.playerHasSecondarySubtitleSupport(player) && - streams.length > 1 && - secondaryStreams.length > 0 && - currentIndex !== -1 && - playbackManager.trackHasSecondarySubtitleSupport(streams[currentIndex + 1], player); - } - function showSubtitleTrackSelection() { const player = currentPlayer; const streams = playbackManager.subtitleTracks(player); + const secondaryStreams = playbackManager.secondarySubtitleTracks(player); let currentIndex = playbackManager.getSubtitleStreamIndex(player); if (currentIndex == null) { @@ -1078,7 +1062,21 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return opt; }); - if (currentTrackCanHaveSecondarySubtitle(player, streams, currentIndex)) { + /** + * Only show option if: + * - player has support + * - has more than 1 subtitle track + * - has valid secondary tracks + * - primary subtitle is not off + * - primary subtitle has support (index + 1 because `'Off'` is index 0 in `streams` array) + */ + const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) && + streams.length > 1 && + secondaryStreams.length > 0 && + currentIndex !== -1 && + playbackManager.trackHasSecondarySubtitleSupport(streams[currentIndex + 1], player); + + if (currentTrackCanAddSecondarySubtitle) { const secondarySubtitleMenuItem = { name: globalize.translate('SecondarySubtitles'), id: 'secondarysubtitle' From b1e397c4bcadd3514543f32eda25413b52e0c6dc Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Mon, 14 Nov 2022 22:29:30 -0500 Subject: [PATCH 20/45] fix: use correct stream, code safety, race conditions, update css --- src/components/playback/playbackmanager.js | 16 ++++++------- src/controllers/playback/video/index.js | 4 ++-- src/plugins/htmlVideoPlayer/plugin.js | 27 ++++++++-------------- src/plugins/htmlVideoPlayer/style.scss | 7 +++--- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 9104eceb78..493df16e14 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -911,7 +911,7 @@ class PlaybackManager { return null; } - return getSubtitleStream(player, index); + return self.getSubtitleStream(player, index); } function getCurrentSecondarySubtitleStream(player) { @@ -925,14 +925,14 @@ class PlaybackManager { return null; } - return getSubtitleStream(player, index); + return self.getSubtitleStream(player, index); } - function getSubtitleStream(player, index) { + self.getSubtitleStream = function (player, index) { return self.subtitleTracks(player).filter(function (s) { return s.Type === 'Subtitle' && s.Index === index; })[0]; - } + }; self.getPlaylist = function (player) { player = player || self._currentPlayer; @@ -1536,7 +1536,7 @@ class PlaybackManager { const currentStream = getCurrentSubtitleStream(player); - const newStream = getSubtitleStream(player, index); + const newStream = self.getSubtitleStream(player, index); if (!currentStream && !newStream) { return; @@ -1581,7 +1581,7 @@ class PlaybackManager { // Also disable secondary subtitles when disabling the primary // subtitles, or if it doesn't support a secondary pair if (selectedTrackElementIndex === -1 || !self.trackHasSecondarySubtitleSupport(newStream)) { - self.setSecondarySubtitleStreamIndex(selectedTrackElementIndex); + self.setSecondarySubtitleStreamIndex(-1); } getPlayerData(player).subtitleStreamIndex = index; @@ -1600,7 +1600,7 @@ class PlaybackManager { const currentStream = getCurrentSecondarySubtitleStream(player); - const newStream = getSubtitleStream(player, index); + const newStream = self.getSubtitleStream(player, index); if (!currentStream && !newStream) { return; @@ -1644,7 +1644,7 @@ class PlaybackManager { }; self.isSubtitleStreamExternal = function (index, player) { - const stream = getSubtitleStream(player, index); + const stream = self.getSubtitleStream(player, index); return stream ? getDeliveryMethod(stream) === 'External' : false; }; diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 1f1006ddbd..74ba461e84 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1068,13 +1068,13 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components * - has more than 1 subtitle track * - has valid secondary tracks * - primary subtitle is not off - * - primary subtitle has support (index + 1 because `'Off'` is index 0 in `streams` array) + * - primary subtitle has support */ const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) && streams.length > 1 && secondaryStreams.length > 0 && currentIndex !== -1 && - playbackManager.trackHasSecondarySubtitleSupport(streams[currentIndex + 1], player); + playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player); if (currentTrackCanAddSecondarySubtitle) { const secondarySubtitleMenuItem = { diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index a40b99a273..bfa74fb2c2 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -229,10 +229,6 @@ function tryRemoveElement(elem) { * @type {HTMLElement | null | undefined} */ #secondaryTrackOffset; - /** - * @type {number | null | undefined} - */ - #subtitleVerticalPosition; /** * @type {HTMLElement | null | undefined} */ @@ -589,7 +585,7 @@ function tryRemoveElement(elem) { } else { const trackElements = this.getTextTracks(); // if .vtt currently rendering - if (trackElements.length > 0) { + if (trackElements?.length > 0) { trackElements.forEach((trackElement, index) => { this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); }); @@ -1142,7 +1138,6 @@ function tryRemoveElement(elem) { this.destroyNativeTracks(videoElement, targetTrackIndex); this.destroyStoredTrackInfo(targetTrackIndex); - this.#subtitleVerticalPosition = null; this.#currentClock = null; this._currentAspectRatio = null; @@ -1333,15 +1328,11 @@ function tryRemoveElement(elem) { * @private */ renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) { - if (this.#subtitleVerticalPosition == null) { - import('../../scripts/settings/userSettings').then((userSettings) => { - const subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); - this.#subtitleVerticalPosition = subtitleAppearance.verticalPosition; - this.#subtitleVerticalPosition = parseInt(subtitleAppearance.verticalPosition, 10); - }); - } + Promise.all([import('../../scripts/settings/userSettings'), this.fetchSubtitles(track, item)]).then((results) => { + const [userSettings, subtitleData] = results; + const subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); + const subtitleVerticalPosition = parseInt(subtitleAppearance.verticalPosition, 10); - this.fetchSubtitles(track, item).then((data) => { if (!this.#videoSubtitlesElem && !this.isSecondaryTrack(targetTextTrackIndex)) { let subtitlesContainer = document.querySelector('.videoSubtitles'); if (!subtitlesContainer) { @@ -1354,21 +1345,21 @@ function tryRemoveElement(elem) { this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); videoElement.parentNode.appendChild(subtitlesContainer); - this.#currentTrackEvents = data.TrackEvents; + this.#currentTrackEvents = subtitleData.TrackEvents; } else if (!this.#videoSecondarySubtitlesElem && this.isSecondaryTrack(targetTextTrackIndex)) { const subtitlesContainer = document.querySelector('.videoSubtitles'); if (!subtitlesContainer) return; const secondarySubtitlesElement = document.createElement('div'); secondarySubtitlesElement.classList.add('videoSecondarySubtitlesInner'); // determine the order of the subtitles - if (this.#subtitleVerticalPosition < 0) { - subtitlesContainer.prepend(secondarySubtitlesElement); + if (subtitleVerticalPosition < 0) { + subtitlesContainer.insertBefore(secondarySubtitlesElement, subtitlesContainer.firstChild); } else { subtitlesContainer.appendChild(secondarySubtitlesElement); } this.#videoSecondarySubtitlesElem = secondarySubtitlesElement; this.setSubtitleAppearance(subtitlesContainer, this.#videoSecondarySubtitlesElem); - this.#currentSecondaryTrackEvents = data.TrackEvents; + this.#currentSecondaryTrackEvents = subtitleData.TrackEvents; } }); } diff --git a/src/plugins/htmlVideoPlayer/style.scss b/src/plugins/htmlVideoPlayer/style.scss index 21b2047392..0026146081 100644 --- a/src/plugins/htmlVideoPlayer/style.scss +++ b/src/plugins/htmlVideoPlayer/style.scss @@ -65,20 +65,19 @@ video[controls]::-webkit-media-controls { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); + display: flex; + flex-direction: column; + align-items: center; } .videoSubtitlesInner { max-width: 70%; background-color: rgba(0, 0, 0, 0.8); - margin: auto; - display: inline-block; } .videoSecondarySubtitlesInner { max-width: 70%; background-color: rgba(0, 0, 0, 0.8); - margin: auto; - display: block; min-height: 0 !important; margin-top: 0.5em !important; margin-bottom: 0.5em !important; From a50fae3b7d264d5774444829169b1676cfdb74e6 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Thu, 17 Nov 2022 17:21:12 -0500 Subject: [PATCH 21/45] chore: simplify/refactor to reuse existing method --- src/components/playback/playbackmanager.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 493df16e14..f19d1f0fa4 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -900,26 +900,12 @@ class PlaybackManager { return streams.filter((stream) => self.trackHasSecondarySubtitleSupport(stream, player)); }; - function getCurrentSubtitleStream(player) { + function getCurrentSubtitleStream(player, isSecondaryStream = false) { if (!player) { throw new Error('player cannot be null'); } - const index = getPlayerData(player).subtitleStreamIndex; - - if (index == null || index === -1) { - return null; - } - - return self.getSubtitleStream(player, index); - } - - function getCurrentSecondarySubtitleStream(player) { - if (!player) { - throw new Error('player cannot be null'); - } - - const index = getPlayerData(player).secondarySubtitleStreamIndex; + const index = isSecondaryStream ? getPlayerData(player).secondarySubtitleStreamIndex : getPlayerData(player).subtitleStreamIndex; if (index == null || index === -1) { return null; @@ -1598,7 +1584,7 @@ class PlaybackManager { } } - const currentStream = getCurrentSecondarySubtitleStream(player); + const currentStream = getCurrentSubtitleStream(player, true); const newStream = self.getSubtitleStream(player, index); From 69265e2118625803602b37201ad30207e0defee4 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Wed, 8 Feb 2023 17:34:27 -0500 Subject: [PATCH 22/45] Update src/components/playback/playbackmanager.js Update error logging Co-authored-by: Bill Thornton --- src/components/playback/playbackmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index f19d1f0fa4..20e466b83d 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1495,7 +1495,7 @@ class PlaybackManager { return player.getSecondarySubtitleStreamIndex(); } } catch (e) { - console.error(`Failed to get secondary stream index: ${e}`); + console.error('[playbackmanager] Failed to get secondary stream index:', e); } if (!player) { From c68d01a0f0b6888c735ba796a2919fff626e0859 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:38:03 -0500 Subject: [PATCH 23/45] Update error logging in src/components/playback/playbackmanager.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/playback/playbackmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 20e466b83d..b25854146e 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1580,7 +1580,7 @@ class PlaybackManager { try { return player.setSecondarySubtitleStreamIndex(index); } catch (e) { - console.error(`AutoSet - Failed to set secondary track: ${e}`); + console.error('[playbackmanager] AutoSet - Failed to set secondary track:', e); } } From e5c05b4ebcb10cb7e313c37043b35587cc7ad004 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:38:28 -0500 Subject: [PATCH 24/45] Update error logging in src/components/playback/playbackmanager.js src/components/playback/playbackmanager.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/playback/playbackmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index b25854146e..0633aceb9d 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1603,7 +1603,7 @@ class PlaybackManager { try { player.setSecondarySubtitleStreamIndex(index); } catch (e) { - console.error(`AutoSet - Failed to set secondary track: ${e}`); + console.error('[playbackmanager] AutoSet - Failed to set secondary track:', e); } }; From 5a217ca0849f7479e8efab62533c2c3114040bf2 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:39:52 -0500 Subject: [PATCH 25/45] set secondary sub index after player succeeds Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/playback/playbackmanager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 0633aceb9d..e2c2fefa04 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1598,10 +1598,9 @@ class PlaybackManager { return; } - getPlayerData(player).secondarySubtitleStreamIndex = index; - try { player.setSecondarySubtitleStreamIndex(index); + getPlayerData(player).secondarySubtitleStreamIndex = index; } catch (e) { console.error('[playbackmanager] AutoSet - Failed to set secondary track:', e); } From 2a1ff26ad9a64eda2f6e6b7912f0432dbaaafade Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:40:28 -0500 Subject: [PATCH 26/45] clean up code Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/playback/playbackmanager.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index e2c2fefa04..cf26ee7d67 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1490,18 +1490,18 @@ class PlaybackManager { self.getSecondarySubtitleStreamIndex = function (player) { player = player || self._currentPlayer; + if (!player) { + throw new Error('player cannot be null'); + } + try { - if (player && !enableLocalPlaylistManagement(player)) { + if (!enableLocalPlaylistManagement(player)) { return player.getSecondarySubtitleStreamIndex(); } } catch (e) { console.error('[playbackmanager] Failed to get secondary stream index:', e); } - if (!player) { - throw new Error('player cannot be null'); - } - return getPlayerData(player).secondarySubtitleStreamIndex; }; From c3b50c6d7307f75738033165c896a457c365437f Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:41:50 -0500 Subject: [PATCH 27/45] fix code styling Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/controllers/playback/video/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 74ba461e84..19c1492d0e 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1070,11 +1070,11 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components * - primary subtitle is not off * - primary subtitle has support */ - const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) && - streams.length > 1 && - secondaryStreams.length > 0 && - currentIndex !== -1 && - playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player); + const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player) + && streams.length > 1 + && secondaryStreams.length > 0 + && currentIndex !== -1 + && playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player); if (currentTrackCanAddSecondarySubtitle) { const secondarySubtitleMenuItem = { From 6c0a1733612f597088a55bfe023559dde1997a5c Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:15:12 -0500 Subject: [PATCH 28/45] use whole subtitles element Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/plugins/htmlVideoPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index bfa74fb2c2..ca07c2d7a5 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1342,7 +1342,7 @@ function tryRemoveElement(elem) { const subtitlesElement = document.createElement('div'); subtitlesElement.classList.add('videoSubtitlesInner'); subtitlesContainer.appendChild(subtitlesElement); - this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner'); + this.#videoSubtitlesElem = subtitlesElement; this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem); videoElement.parentNode.appendChild(subtitlesContainer); this.#currentTrackEvents = subtitleData.TrackEvents; From c7f31c0fac2f4060fc2f12dd0076e922657ac4c7 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Fri, 10 Feb 2023 13:52:53 -0500 Subject: [PATCH 29/45] fix: pass player to playbackManager --- src/plugins/htmlVideoPlayer/plugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index ca07c2d7a5..700eedd141 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -480,7 +480,7 @@ function tryRemoveElement(elem) { secondaryTrackValid = false; } // secondary track should not be shown if primary track is no longer a valid pair - if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream)) { + if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) { secondaryTrackValid = false; } } else { @@ -488,11 +488,11 @@ function tryRemoveElement(elem) { } // Get the secondary track that has been set during this watch session - let currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(); + let currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(this); if (!secondaryTrackValid) { currentSecondaryTrackIndex = -1; - playbackManager.setSecondarySubtitleStreamIndex(currentSecondaryTrackIndex); + playbackManager.setSecondarySubtitleStreamIndex(currentSecondaryTrackIndex, this); } this.#secondarySubtitleTrackIndexToSetOnPlaying = currentSecondaryTrackIndex == null ? -1 : currentSecondaryTrackIndex; From fe970743f1b37293cea2aa17a2e4b9a6f62613c1 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Tue, 14 Feb 2023 19:11:16 -0500 Subject: [PATCH 30/45] chore: remove fix for stuck track cues --- src/plugins/htmlVideoPlayer/plugin.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 700eedd141..b45bc4d845 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -624,28 +624,6 @@ function tryRemoveElement(elem) { return relativeOffset; } - /** - * @private - * These browsers will not clear the existing active cue when setting an offset - * for native TextTracks. - * Any previous text tracks that are on the screen when the offset changes will - * remain next to the new tracks until they reach the new offset's instance of the track. - */ - requiresHidingActiveCuesOnOffsetChange() { - return !!browser.firefox; - } - - /** - * @private - */ - hideTextTrackActiveCues(currentTrack) { - if (currentTrack.activeCues) { - Array.from(currentTrack.activeCues).forEach((cue) => { - cue.text = ''; - }); - } - } - /** * @private */ @@ -655,9 +633,6 @@ function tryRemoveElement(elem) { if (offsetValue === 0) { return; } - if (this.requiresHidingActiveCuesOnOffsetChange()) { - this.hideTextTrackActiveCues(currentTrack); - } Array.from(currentTrack.cues) .forEach(function (cue) { cue.startTime -= offsetValue; From fb68bb1419044d0c7b67c4a585b9b90502e00174 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Wed, 15 Feb 2023 02:10:00 -0500 Subject: [PATCH 31/45] fix: define options before setting secondary track --- src/plugins/htmlVideoPlayer/plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index b45bc4d845..646c902a5d 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -487,6 +487,10 @@ function tryRemoveElement(elem) { secondaryTrackValid = false; } + this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; + + this._currentPlayOptions = options; + // Get the secondary track that has been set during this watch session let currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(this); @@ -503,10 +507,6 @@ function tryRemoveElement(elem) { } } - this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - - this._currentPlayOptions = options; - const crossOrigin = getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; From c74c0cddde3d9a0a8779bfd7241f1a68eec9d557 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Wed, 15 Feb 2023 02:10:55 -0500 Subject: [PATCH 32/45] chore: clear and set secondary track for continued plays --- src/components/playback/playbackmanager.js | 46 ++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index cf26ee7d67..7463b4b614 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -422,7 +422,8 @@ function getPlaybackInfo(player, enableDirectPlay, enableDirectStream, allowVideoStreamCopy, - allowAudioStreamCopy) { + allowAudioStreamCopy, + secondarySubtitleStreamIndex) { if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) { return Promise.resolve({ MediaSources: [ @@ -462,6 +463,9 @@ function getPlaybackInfo(player, if (subtitleStreamIndex != null) { query.SubtitleStreamIndex = subtitleStreamIndex; } + if (secondarySubtitleStreamIndex != null) { + query.SecondarySubtitleStreamIndex = secondarySubtitleStreamIndex; + } if (enableDirectPlay != null) { query.EnableDirectPlay = enableDirectPlay; } @@ -1720,6 +1724,7 @@ class PlaybackManager { }).then(function (deviceProfile) { const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; + const secondarySubtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).secondarySubtitleStreamIndex : params.secondarySubtitleStreamIndex; let currentMediaSource = self.currentMediaSource(player); const apiClient = ServerConnections.getApiClient(currentItem.ServerId); @@ -1746,6 +1751,7 @@ class PlaybackManager { } getPlayerData(player).subtitleStreamIndex = subtitleStreamIndex; + getPlayerData(player).secondarySubtitleStreamIndex = secondarySubtitleStreamIndex; getPlayerData(player).audioStreamIndex = audioStreamIndex; getPlayerData(player).maxStreamingBitrate = maxBitrate; @@ -2031,6 +2037,7 @@ class PlaybackManager { state.PlayState.PlaybackRate = self.getPlaybackRate(player); state.PlayState.SubtitleStreamIndex = self.getSubtitleStreamIndex(player); + state.PlayState.SecondarySubtitleStreamIndex = self.getSecondarySubtitleStreamIndex(player); state.PlayState.AudioStreamIndex = self.getAudioStreamIndex(player); state.PlayState.BufferedRanges = self.getBufferedRanges(player); @@ -2311,11 +2318,16 @@ class PlaybackManager { }); } - function rankStreamType(prevIndex, prevSource, mediaSource, streamType) { + function rankStreamType(prevIndex, prevSource, mediaSource, streamType, isSecondarySubtitle) { if (prevIndex == -1) { console.debug(`AutoSet ${streamType} - No Stream Set`); - if (streamType == 'Subtitle') - mediaSource.DefaultSubtitleStreamIndex = -1; + if (streamType == 'Subtitle') { + if (isSecondarySubtitle) { + mediaSource.DefaultSecondarySubtitleStreamIndex = -1; + } else { + mediaSource.DefaultSubtitleStreamIndex = -1; + } + } return; } @@ -2373,8 +2385,13 @@ class PlaybackManager { if (bestStreamIndex != null) { console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`); - if (streamType == 'Subtitle') - mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; + if (streamType == 'Subtitle') { + if (isSecondarySubtitle) { + mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex; + } else { + mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; + } + } if (streamType == 'Audio') mediaSource.DefaultAudioStreamIndex = bestStreamIndex; } else { @@ -2382,7 +2399,7 @@ class PlaybackManager { } } - function autoSetNextTracks(prevSource, mediaSource, audio, subtitle) { + function autoSetNextTracks(prevSource, mediaSource, audio, subtitle, secondarySubtitle) { try { if (!prevSource) return; @@ -2398,6 +2415,10 @@ class PlaybackManager { if (subtitle && typeof prevSource.DefaultSubtitleStreamIndex == 'number') { rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle'); } + + if (secondarySubtitle && typeof prevSource.DefaultSecondarySubtitleStreamIndex == 'number') { + rankStreamType(prevSource.DefaultSecondarySubtitleStreamIndex, prevSource, mediaSource, 'Subtitle', true); + } } catch (e) { console.error(`AutoSet - Caught unexpected error: ${e}`); } @@ -2463,14 +2484,14 @@ class PlaybackManager { return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => { const user = await apiClient.getCurrentUser(); - autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections); + const playerData = getPlayerData(player); + + autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections, playerData.secondarySubtitleStreamIndex); const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); streamInfo.fullscreen = playOptions.fullscreen; - const playerData = getPlayerData(player); - playerData.isChangingStream = false; playerData.maxStreamingBitrate = maxBitrate; playerData.streamInfo = streamInfo; @@ -2832,7 +2853,8 @@ class PlaybackManager { return { ...prevSource, DefaultAudioStreamIndex: prevPlayerData.audioStreamIndex, - DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex + DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex, + DefaultSecondarySubtitleStreamIndex: prevPlayerData.secondarySubtitleStreamIndex }; } @@ -2991,9 +3013,11 @@ class PlaybackManager { if (mediaSource) { playerData.audioStreamIndex = mediaSource.DefaultAudioStreamIndex; playerData.subtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex; + playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; } else { playerData.audioStreamIndex = null; playerData.subtitleStreamIndex = null; + playerData.secondarySubtitleStreamIndex = null; } self._playNextAfterEnded = true; From 89ec4cf9cf2b7ffd612b609087bc4c4c0e86d7da Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Fri, 17 Feb 2023 17:45:43 -0500 Subject: [PATCH 33/45] chore: implement feedback --- src/components/playback/playbackmanager.js | 12 ++++++------ src/plugins/htmlVideoPlayer/plugin.js | 22 +++++++++------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 7463b4b614..fe50820b50 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1724,7 +1724,7 @@ class PlaybackManager { }).then(function (deviceProfile) { const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex; const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex; - const secondarySubtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).secondarySubtitleStreamIndex : params.secondarySubtitleStreamIndex; + const secondarySubtitleStreamIndex = params.SecondarySubtitleStreamIndex == null ? getPlayerData(player).secondarySubtitleStreamIndex : params.SecondarySubtitleStreamIndex; let currentMediaSource = self.currentMediaSource(player); const apiClient = ServerConnections.getApiClient(currentItem.ServerId); @@ -2399,7 +2399,7 @@ class PlaybackManager { } } - function autoSetNextTracks(prevSource, mediaSource, audio, subtitle, secondarySubtitle) { + function autoSetNextTracks(prevSource, mediaSource, audio, subtitle) { try { if (!prevSource) return; @@ -2416,7 +2416,7 @@ class PlaybackManager { rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle'); } - if (secondarySubtitle && typeof prevSource.DefaultSecondarySubtitleStreamIndex == 'number') { + if (subtitle && typeof prevSource.DefaultSecondarySubtitleStreamIndex == 'number') { rankStreamType(prevSource.DefaultSecondarySubtitleStreamIndex, prevSource, mediaSource, 'Subtitle', true); } } catch (e) { @@ -2484,14 +2484,14 @@ class PlaybackManager { return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => { const user = await apiClient.getCurrentUser(); - const playerData = getPlayerData(player); - - autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections, playerData.secondarySubtitleStreamIndex); + autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections); const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); streamInfo.fullscreen = playOptions.fullscreen; + const playerData = getPlayerData(player); + playerData.isChangingStream = false; playerData.maxStreamingBitrate = maxBitrate; playerData.streamInfo = streamInfo; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 646c902a5d..4a9d1a7bf9 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -491,20 +491,16 @@ function tryRemoveElement(elem) { this._currentPlayOptions = options; - // Get the secondary track that has been set during this watch session - let currentSecondaryTrackIndex = playbackManager.getSecondarySubtitleStreamIndex(this); - - if (!secondaryTrackValid) { - currentSecondaryTrackIndex = -1; - playbackManager.setSecondarySubtitleStreamIndex(currentSecondaryTrackIndex, this); - } - - this.#secondarySubtitleTrackIndexToSetOnPlaying = currentSecondaryTrackIndex == null ? -1 : currentSecondaryTrackIndex; - if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { - const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; - if (!initialSecondarySubtitleStream || initialSecondarySubtitleStream.DeliveryMethod !== 'External') { - this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + if (secondaryTrackValid) { + this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; + if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { + const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; + if (!initialSecondarySubtitleStream || initialSecondarySubtitleStream.DeliveryMethod !== 'External') { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } } + } else { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; } const crossOrigin = getCrossOriginValue(options.mediaSource); From 95a705a893814bd06cc01a71cb179bf6aad6af0c Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Sun, 19 Feb 2023 11:58:36 -0500 Subject: [PATCH 34/45] Update src/components/playback/playbackmanager.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/components/playback/playbackmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index fe50820b50..587705b1f8 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1598,7 +1598,7 @@ class PlaybackManager { // Secondary subtitles are currently only handled client side // Changes to the server code are required before we can handle other delivery methods - if (newStream && getDeliveryMethod(newStream) !== 'External') { + if (newStream && !self.trackHasSecondarySubtitleSupport(newStream, player)) { return; } From 4ca927991f04ed2028c89fdbf336ea83226947b8 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel <30599893+is343@users.noreply.github.com> Date: Sun, 19 Feb 2023 11:58:52 -0500 Subject: [PATCH 35/45] Update src/plugins/htmlVideoPlayer/plugin.js Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com> --- src/plugins/htmlVideoPlayer/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 4a9d1a7bf9..f2a2b8d30b 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -495,7 +495,7 @@ function tryRemoveElement(elem) { this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; - if (!initialSecondarySubtitleStream || initialSecondarySubtitleStream.DeliveryMethod !== 'External') { + if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) { this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; } } From 3c0ace33865b0852769e31ef224548e87fa9c46d Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 12:50:12 -0500 Subject: [PATCH 36/45] fix: ensure secondary index is valid --- src/components/playback/playbackmanager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 587705b1f8..6d95f4b9a2 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2387,7 +2387,11 @@ class PlaybackManager { console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`); if (streamType == 'Subtitle') { if (isSecondarySubtitle) { - mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex; + if (self.trackHasSecondarySubtitleSupport(mediaSource.MediaStreams[bestStreamIndex])) { + mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex; + } else { + mediaSource.DefaultSecondarySubtitleStreamIndex = -1; + } } else { mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; } From 50eb5f277d4fae10423354b1b572138e009bcfa1 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 13:09:37 -0500 Subject: [PATCH 37/45] fix: check if primary supports secondary track --- src/components/playback/playbackmanager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 6d95f4b9a2..16e617f36e 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3017,7 +3017,11 @@ class PlaybackManager { if (mediaSource) { playerData.audioStreamIndex = mediaSource.DefaultAudioStreamIndex; playerData.subtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex; - playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; + if (self.trackHasSecondarySubtitleSupport(mediaSource.MediaStreams[mediaSource.DefaultSubtitleStreamIndex])) { + playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; + } else { + playerData.secondarySubtitleStreamIndex = -1; + } } else { playerData.audioStreamIndex = null; playerData.subtitleStreamIndex = null; From 3d3a0c43b3e4e4e851a564b9e9e4a762856ed6d6 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 15:40:36 -0500 Subject: [PATCH 38/45] chore: ensure check has valid track --- src/components/playback/playbackmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 16e617f36e..e3a73b7bf3 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -891,7 +891,7 @@ class PlaybackManager { * - or if it can be paired with a secondary subtitle when used as a primary subtitle */ self.trackHasSecondarySubtitleSupport = function (track, player = self._currentPlayer) { - if (!player) return false; + if (!player || !track) return false; const format = (track.Codec || '').toLowerCase(); // Currently, only non-SSA/non-ASS external subtitles are supported. // Showing secondary subtitles does not work with any SSA/ASS subtitle combinations because From 698abb929b74946150d54d409164fcc8d918b984 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 16:40:12 -0500 Subject: [PATCH 39/45] Revert "fix: check if primary supports secondary track" This reverts commit e3e0348daeded50ed641c8def9a2619aff4f49bf. --- src/components/playback/playbackmanager.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index e3a73b7bf3..84def75483 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3017,11 +3017,7 @@ class PlaybackManager { if (mediaSource) { playerData.audioStreamIndex = mediaSource.DefaultAudioStreamIndex; playerData.subtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex; - if (self.trackHasSecondarySubtitleSupport(mediaSource.MediaStreams[mediaSource.DefaultSubtitleStreamIndex])) { - playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; - } else { - playerData.secondarySubtitleStreamIndex = -1; - } + playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; } else { playerData.audioStreamIndex = null; playerData.subtitleStreamIndex = null; From ab75013d60f88f95bac5607d31d17c0e23ea545b Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 16:40:47 -0500 Subject: [PATCH 40/45] Revert "fix: ensure secondary index is valid" This reverts commit 01dfad4996d2bdc96a8506b6d0c4542bfd15bc3b. --- src/components/playback/playbackmanager.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 84def75483..34e83eb683 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2387,11 +2387,7 @@ class PlaybackManager { console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`); if (streamType == 'Subtitle') { if (isSecondarySubtitle) { - if (self.trackHasSecondarySubtitleSupport(mediaSource.MediaStreams[bestStreamIndex])) { - mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex; - } else { - mediaSource.DefaultSecondarySubtitleStreamIndex = -1; - } + mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex; } else { mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; } From 0ebf6c68995298fe66a7d0fbff24608e4a3fa478 Mon Sep 17 00:00:00 2001 From: Ivan Schurawel Date: Sun, 19 Feb 2023 16:53:52 -0500 Subject: [PATCH 41/45] chore: simplify valid secondary track checks --- src/components/playback/playbackmanager.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 34e83eb683..2b8bd21280 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2486,6 +2486,19 @@ class PlaybackManager { const user = await apiClient.getCurrentUser(); autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections); + if (mediaSource.DefaultSubtitleStreamIndex == null || mediaSource.DefaultSubtitleStreamIndex < 0) { + mediaSource.DefaultSubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex; + mediaSource.DefaultSecondarySubtitleStreamIndex = -1; + } + + const subtitleTrack1 = mediaSource.MediaStreams[mediaSource.DefaultSubtitleStreamIndex]; + const subtitleTrack2 = mediaSource.MediaStreams[mediaSource.DefaultSecondarySubtitleStreamIndex]; + + if (!self.trackHasSecondarySubtitleSupport(subtitleTrack1, player) + || !self.trackHasSecondarySubtitleSupport(subtitleTrack2, player)) { + mediaSource.DefaultSecondarySubtitleStreamIndex = -1; + } + const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); streamInfo.fullscreen = playOptions.fullscreen; From 4f24ccdbbcf82889425cecf2129c77c8831aa2e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Feb 2023 01:58:22 +0000 Subject: [PATCH 42/45] Update React --- package-lock.json | 64 +++++++++++++++++++++++------------------------ package.json | 4 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index a06ce5f18e..9a8999a872 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "pdfjs-dist": "2.16.105", "react": "17.0.2", "react-dom": "17.0.2", - "react-router-dom": "6.8.0", + "react-router-dom": "6.8.1", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", "sortablejs": "1.15.0", @@ -67,7 +67,7 @@ "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", - "@types/react-dom": "17.0.18", + "@types/react-dom": "17.0.19", "@typescript-eslint/eslint-plugin": "5.51.0", "@typescript-eslint/parser": "5.51.0", "@uupaa/dynamic-import-polyfill": "1.0.2", @@ -2742,9 +2742,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", - "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", + "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==", "engines": { "node": ">=14" } @@ -3069,9 +3069,9 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz", - "integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", "dev": true, "dependencies": { "@types/react": "^17" @@ -13097,11 +13097,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-router": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", - "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", + "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", "dependencies": { - "@remix-run/router": "1.3.1" + "@remix-run/router": "1.3.2" }, "engines": { "node": ">=14" @@ -13111,12 +13111,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", - "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", + "integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", "dependencies": { - "@remix-run/router": "1.3.1", - "react-router": "6.8.0" + "@remix-run/router": "1.3.2", + "react-router": "6.8.1" }, "engines": { "node": ">=14" @@ -20956,9 +20956,9 @@ } }, "@remix-run/router": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", - "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", + "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==" }, "@rollup/plugin-babel": { "version": "5.3.1", @@ -21251,9 +21251,9 @@ } }, "@types/react-dom": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz", - "integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz", + "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==", "dev": true, "requires": { "@types/react": "^17" @@ -28632,20 +28632,20 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-router": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", - "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", + "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", "requires": { - "@remix-run/router": "1.3.1" + "@remix-run/router": "1.3.2" } }, "react-router-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", - "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", + "integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", "requires": { - "@remix-run/router": "1.3.1", - "react-router": "6.8.0" + "@remix-run/router": "1.3.2", + "react-router": "6.8.1" } }, "read-file-stdin": { diff --git a/package.json b/package.json index f664622998..8f607916e0 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", - "@types/react-dom": "17.0.18", + "@types/react-dom": "17.0.19", "@typescript-eslint/eslint-plugin": "5.51.0", "@typescript-eslint/parser": "5.51.0", "@uupaa/dynamic-import-polyfill": "1.0.2", @@ -102,7 +102,7 @@ "pdfjs-dist": "2.16.105", "react": "17.0.2", "react-dom": "17.0.2", - "react-router-dom": "6.8.0", + "react-router-dom": "6.8.1", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", "sortablejs": "1.15.0", From 009589b6c276bcf95d4fd0b2cbb7c0e08d0867ba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Feb 2023 02:04:40 +0000 Subject: [PATCH 43/45] Update dependency sass to v1.58.1 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a8999a872..ceb26bdb05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "postcss-loader": "7.0.2", "postcss-preset-env": "8.0.1", "postcss-scss": "4.0.6", - "sass": "1.58.0", + "sass": "1.58.1", "sass-loader": "13.2.0", "source-map-loader": "4.0.1", "style-loader": "3.3.1", @@ -13672,9 +13672,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", - "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz", + "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29068,9 +29068,9 @@ "dev": true }, "sass": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", - "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz", + "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/package.json b/package.json index 8f607916e0..5799b9d5bd 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "postcss-loader": "7.0.2", "postcss-preset-env": "8.0.1", "postcss-scss": "4.0.6", - "sass": "1.58.0", + "sass": "1.58.1", "sass-loader": "13.2.0", "source-map-loader": "4.0.1", "style-loader": "3.3.1", From ed5fd855d712f2c3fc69893b9939ac3d5ca66857 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Feb 2023 02:05:24 +0000 Subject: [PATCH 44/45] Update Linters --- package-lock.json | 180 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a8999a872..1976317dd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,8 +68,8 @@ "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.51.0", - "@typescript-eslint/parser": "5.51.0", + "@typescript-eslint/eslint-plugin": "5.52.0", + "@typescript-eslint/parser": "5.52.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -106,7 +106,7 @@ "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.2", - "stylelint-scss": "4.3.0", + "stylelint-scss": "4.4.0", "ts-loader": "9.4.2", "typescript": "4.9.5", "webpack": "5.75.0", @@ -3175,14 +3175,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3224,14 +3224,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" }, "engines": { @@ -3251,13 +3251,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3268,13 +3268,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3295,9 +3295,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3308,13 +3308,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3379,16 +3379,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -3420,12 +3420,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -17059,9 +17059,9 @@ } }, "node_modules/stylelint-scss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", - "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.4.0.tgz", + "integrity": "sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw==", "dev": true, "dependencies": { "lodash": "^4.17.21", @@ -17071,7 +17071,7 @@ "postcss-value-parser": "^4.1.0" }, "peerDependencies": { - "stylelint": "^14.5.1" + "stylelint": "^14.5.1 || ^15.0.0" } }, "node_modules/stylelint/node_modules/array-union": { @@ -21356,14 +21356,14 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -21385,53 +21385,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" } }, "@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21471,16 +21471,16 @@ } }, "@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -21498,12 +21498,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -31858,9 +31858,9 @@ } }, "stylelint-scss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", - "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.4.0.tgz", + "integrity": "sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw==", "dev": true, "requires": { "lodash": "^4.17.21", diff --git a/package.json b/package.json index 8f607916e0..b5b208c8b0 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.51.0", - "@typescript-eslint/parser": "5.51.0", + "@typescript-eslint/eslint-plugin": "5.52.0", + "@typescript-eslint/parser": "5.52.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -57,7 +57,7 @@ "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.2", - "stylelint-scss": "4.3.0", + "stylelint-scss": "4.4.0", "ts-loader": "9.4.2", "typescript": "4.9.5", "webpack": "5.75.0", From 4b6f072bef1633300cca2c393e8b3c09a2fca8e1 Mon Sep 17 00:00:00 2001 From: lyaschuchenko Date: Wed, 22 Feb 2023 07:41:54 +0000 Subject: [PATCH 45/45] Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/uk/ --- src/strings/uk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/uk.json b/src/strings/uk.json index 07b7b3c2a0..2168357faf 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -1329,7 +1329,7 @@ "OptionEnableM2tsMode": "Увімкнути режим M2TS", "OptionEnableForAllTuners": "Увімкнути для всіх пристроїв тюнера", "OptionEnableExternalContentInSuggestionsHelp": "Дозволити включати інтернет-трейлери та телепрограми в прямому ефірі до запропонованого вмісту.", - "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "OptionCaptionInfoExSamsung": "Функція CaptionInfoEx (Samsung)", "OptionEnableExternalContentInSuggestions": "Увімкнути зовнішній вміст у пропозиціях", "OptionEnableAccessToAllLibraries": "Увімкнути доступ до всіх медіатек", "OptionEnableAccessToAllChannels": "Увімкнути доступ до всіх каналів",