diff --git a/package.json b/package.json index 8b6efe5cc..0c9ca54ec 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ "@babel/runtime": "7.26.0", "@sentry/browser": "8.42.0", "@stremio/stremio-colors": "5.2.0", - "@stremio/stremio-core-web": "0.51.1", + "@stremio/stremio-core-web": "0.52.0", "@stremio/stremio-icons": "5.8.0", - "@stremio/stremio-video": "0.0.64", + "@stremio/stremio-video": "0.0.70", "a-color-picker": "1.2.1", "bowser": "2.11.0", "buffer": "6.0.3", @@ -41,7 +41,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#0e7fbd8522148f5727ac6adee3b2eb96132c10ac", + "stremio-translations": "github:Stremio/stremio-translations#7c0c337f32163aa13158bb90cd6133da43feafef", "url": "0.11.4", "use-long-press": "^3.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48afa6562..435a809e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 5.2.0 version: 5.2.0 '@stremio/stremio-core-web': - specifier: 0.51.1 - version: 0.51.1 + specifier: 0.52.0 + version: 0.52.0 '@stremio/stremio-icons': specifier: 5.8.0 version: 5.8.0 '@stremio/stremio-video': - specifier: 0.0.64 - version: 0.0.64 + specifier: 0.0.70 + version: 0.0.70 a-color-picker: specifier: 1.2.1 version: 1.2.1 @@ -90,8 +90,8 @@ importers: specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6 version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6 stremio-translations: - specifier: github:Stremio/stremio-translations#0e7fbd8522148f5727ac6adee3b2eb96132c10ac - version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac + specifier: github:Stremio/stremio-translations#7c0c337f32163aa13158bb90cd6133da43feafef + version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef url: specifier: 0.11.4 version: 0.11.4 @@ -1120,14 +1120,14 @@ packages: '@stremio/stremio-colors@5.2.0': resolution: {integrity: sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==} - '@stremio/stremio-core-web@0.51.1': - resolution: {integrity: sha512-BD8i6zkDdMPeCyH50Bb7SB8r4nYx4eJwz4kLEJEl0PFjdr0gOmwHtEIgNa89ShJLNXUjPnpv4sVSNxFRG8fb5Q==} + '@stremio/stremio-core-web@0.52.0': + resolution: {integrity: sha512-zT0P8JspGZ1oI9/11f3RIt7XG9b/1fOZE+xSnP+oAyhRmzzkqrnPUJkHdJdgoVD9XELDFAS2awNfl5/eRdh5kA==} '@stremio/stremio-icons@5.8.0': resolution: {integrity: sha512-IVUvQbIWfA4YEHCTed7v/sdQJCJ+OOCf84LTWpkE2W6GLQ+15WHcMEJrVkE1X3ekYJnGg3GjT0KLO6tKSU0P4w==} - '@stremio/stremio-video@0.0.64': - resolution: {integrity: sha512-29w/lwU8BB6ai8LUyCnpRc2F9kPf7cpys40NCobt70MqBP/UqvYISsrnD/ijoBwvtpKdZ6ptv5h9BbDj6rrerw==} + '@stremio/stremio-video@0.0.70': + resolution: {integrity: sha512-a0flQYAUdrZNMm7mmts2vpZOqN1nus7Hs9Mjl4mrN5rtduD0ojUyhD5J4lPcCpZ7WB0YdEUOGLXR19qHpgoKmg==} '@stylistic/eslint-plugin-jsx@4.4.1': resolution: {integrity: sha512-83SInq4u7z71vWwGG+6ViOtlOmZ6tSrDkMPhrvdBBTGMLA0gs22WSdhQ4vZP3oJ5Xg4ythvqeUiFSedvVxzhyA==} @@ -3738,9 +3738,6 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - punycode@1.3.2: - resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} - punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -3759,11 +3756,6 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} - querystring@0.2.0: - resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} - engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4141,9 +4133,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac: - resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac} - version: 1.44.14 + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef: + resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef} + version: 1.45.0 string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} @@ -4420,9 +4412,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url@0.11.0: - resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} - url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -5881,13 +5870,13 @@ snapshots: '@stremio/stremio-colors@5.2.0': {} - '@stremio/stremio-core-web@0.51.1': + '@stremio/stremio-core-web@0.52.0': dependencies: '@babel/runtime': 7.24.1 '@stremio/stremio-icons@5.8.0': {} - '@stremio/stremio-video@0.0.64': + '@stremio/stremio-video@0.0.70': dependencies: buffer: 6.0.3 color: 4.2.3 @@ -5897,7 +5886,7 @@ snapshots: hls.js: https://github.com/Stremio/hls.js/releases/download/v1.5.4-patch2/hls.js-1.5.4-patch2.tgz lodash.clonedeep: 4.5.0 magnet-uri: 6.2.0 - url: 0.11.0 + url: 0.11.4 video-name-parser: 1.4.6 vtt.js: https://codeload.github.com/jaruba/vtt.js/tar.gz/84d33d157848407d790d78423dacc41a096294f0 @@ -8941,8 +8930,6 @@ snapshots: prr@1.0.1: optional: true - punycode@1.3.2: {} - punycode@1.4.1: {} punycode@2.3.1: {} @@ -8957,8 +8944,6 @@ snapshots: dependencies: side-channel: 1.1.0 - querystring@0.2.0: {} - queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -9393,7 +9378,7 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac: {} + stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef: {} string-length@4.0.2: dependencies: @@ -9688,11 +9673,6 @@ snapshots: dependencies: punycode: 2.3.1 - url@0.11.0: - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - url@0.11.4: dependencies: punycode: 1.4.1 diff --git a/src/common/interfaceLanguages.json b/src/common/interfaceLanguages.json index ce3504691..91b87d5af 100644 --- a/src/common/interfaceLanguages.json +++ b/src/common/interfaceLanguages.json @@ -3,6 +3,10 @@ "name": "العربية", "codes": ["ar-AR", "ara"] }, + { + "name": "Беларуская", + "codes": ["be-BY", "bel"] + }, { "name": "български език", "codes": ["bg-BG", "bul"] @@ -13,7 +17,7 @@ }, { "name": "català", - "codes": ["ca-CA", "cat"] + "codes": ["ca-ES", "cat"] }, { "name": "čeština", @@ -43,6 +47,10 @@ "name": "español", "codes": ["es-ES", "spa"] }, + { + "name": "Eesti", + "codes": ["et-EE", "est"] + }, { "name": "euskara", "codes": ["eu-ES", "eus"] @@ -111,6 +119,10 @@ "name": "Norsk nynorsk", "codes": ["nn-NO", "nno"] }, + { + "name": "ਪੰਜਾਬੀ", + "codes": ["pa-IN", "pan"] + }, { "name": "język polski", "codes": ["pl-PL", "pol"] @@ -151,6 +163,10 @@ "name": "తెలుగు", "codes": ["te-IN", "tel"] }, + { + "name": "தமிழ்", + "codes": ["tl-TM", "tam"] + }, { "name": "Türkçe", "codes": ["tr-TR", "tur"] @@ -159,6 +175,10 @@ "name": "українська мова", "codes": ["uk-UA", "ukr"] }, + { + "name": "اُرْدُو", + "codes": ["ur-PK", "urd"] + }, { "name": "Tiếng Việt", "codes": ["vi-VN", "vie"] diff --git a/src/routes/Player/AudioMenu/AudioMenu.less b/src/routes/Player/AudioMenu/AudioMenu.less index 344ec13e7..ae3e8acb1 100644 --- a/src/routes/Player/AudioMenu/AudioMenu.less +++ b/src/routes/Player/AudioMenu/AudioMenu.less @@ -7,6 +7,7 @@ align-self: stretch; display: flex; flex-direction: column; + max-height: 25rem; width: 16rem; .header { diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 3ae2f066c..196bb813b 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -29,6 +29,9 @@ const styles = require('./styles'); const Video = require('./Video'); const { default: Indicator } = require('./Indicator/Indicator'); +const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); +const findTrackById = (tracks, id) => tracks.find((track) => track.id === id); + const Player = ({ urlParams, queryParams }) => { const { t } = useTranslation(); const services = useServices(); @@ -37,8 +40,8 @@ const Player = ({ urlParams, queryParams }) => { return queryParams.has('forceTranscoding'); }, [queryParams]); const profile = useProfile(); - const [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams); - const [settings, updateSettings] = useSettings(); + const [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams); + const [settings] = useSettings(); const streamingServer = useStreamingServer(); const statistics = useStatistics(player, streamingServer); const video = useVideo(); @@ -93,17 +96,12 @@ const Player = ({ urlParams, queryParams }) => { const isNavigating = React.useRef(false); const onImplementationChanged = React.useCallback(() => { - video.setProp('subtitlesSize', settings.subtitlesSize); - video.setProp('subtitlesOffset', settings.subtitlesOffset); - video.setProp('subtitlesTextColor', settings.subtitlesTextColor); - video.setProp('subtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('subtitlesOutlineColor', settings.subtitlesOutlineColor); - video.setProp('extraSubtitlesSize', settings.subtitlesSize); - video.setProp('extraSubtitlesOffset', settings.subtitlesOffset); - video.setProp('extraSubtitlesTextColor', settings.subtitlesTextColor); - video.setProp('extraSubtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); - }, [settings.subtitlesSize, settings.subtitlesOffset, settings.subtitlesTextColor, settings.subtitlesBackgroundColor, settings.subtitlesOutlineColor]); + video.setSubtitlesSize(settings.subtitlesSize); + video.setSubtitlesOffset(settings.subtitlesOffset); + video.setSubtitlesTextColor(settings.subtitlesTextColor); + video.setSubtitlesBackgroundColor(settings.subtitlesBackgroundColor); + video.setSubtitlesOutlineColor(settings.subtitlesOutlineColor); + }, [settings]); const handleNextVideoNavigation = React.useCallback((deepLinks, bingeWatching, ended) => { if (ended) { @@ -190,53 +188,71 @@ const Player = ({ urlParams, queryParams }) => { }, []); const onPlayRequested = React.useCallback(() => { - video.setProp('paused', false); + video.setPaused(false); setSeeking(false); }, []); const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []); const onPauseRequested = React.useCallback(() => { - video.setProp('paused', true); + video.setPaused(true); }, []); const onPauseRequestedDebounced = React.useCallback(debounce(onPauseRequested, 200), []); const onMuteRequested = React.useCallback(() => { - video.setProp('muted', true); + video.setMuted(true); }, []); const onUnmuteRequested = React.useCallback(() => { - video.setProp('muted', false); + video.setMuted(false); }, []); const onVolumeChangeRequested = React.useCallback((volume) => { - video.setProp('volume', volume); + video.setVolume(volume); }, []); const onSeekRequested = React.useCallback((time) => { - video.setProp('time', time); + video.setTime(time); seek(time, video.state.duration, video.state.manifest?.name); }, [video.state.duration, video.state.manifest]); const onPlaybackSpeedChanged = React.useCallback((rate) => { - video.setProp('playbackSpeed', rate); + video.setPlaybackSpeed(rate); }, []); const onSubtitlesTrackSelected = React.useCallback((id) => { video.setSubtitlesTrack(id); - }, []); + streamStateChanged({ + subtitleTrack: { + id, + embedded: true, + }, + }); + }, [streamStateChanged]); const onExtraSubtitlesTrackSelected = React.useCallback((id) => { video.setExtraSubtitlesTrack(id); - }, []); + streamStateChanged({ + subtitleTrack: { + id, + embedded: false, + }, + }); + }, [streamStateChanged]); const onAudioTrackSelected = React.useCallback((id) => { - video.setProp('selectedAudioTrackId', id); - }, []); + video.setAudioTrack(id); + streamStateChanged({ + audioTrack: { + id, + }, + }); + }, [streamStateChanged]); const onExtraSubtitlesDelayChanged = React.useCallback((delay) => { - video.setProp('extraSubtitlesDelay', delay); - }, []); + video.setSubtitlesDelay(delay); + streamStateChanged({ subtitleDelay: delay }); + }, [streamStateChanged]); const onIncreaseSubtitlesDelay = React.useCallback(() => { const delay = video.state.extraSubtitlesDelay + 250; @@ -249,8 +265,9 @@ const Player = ({ urlParams, queryParams }) => { }, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]); const onSubtitlesSizeChanged = React.useCallback((size) => { - updateSettings({ subtitlesSize: size }); - }, [updateSettings]); + video.setSubtitlesSize(size); + streamStateChanged({ subtitleSize: size }); + }, [streamStateChanged]); const onUpdateSubtitlesSize = React.useCallback((delta) => { const sizeIndex = CONSTANTS.SUBTITLES_SIZES.indexOf(video.state.subtitlesSize); @@ -259,8 +276,9 @@ const Player = ({ urlParams, queryParams }) => { }, [video.state.subtitlesSize, onSubtitlesSizeChanged]); const onSubtitlesOffsetChanged = React.useCallback((offset) => { - updateSettings({ subtitlesOffset: offset }); - }, [updateSettings]); + video.setSubtitlesOffset(offset); + streamStateChanged({ subtitleOffset: offset }); + }, [streamStateChanged]); const onDismissNextVideoPopup = React.useCallback(() => { closeNextVideoPopup(); @@ -361,6 +379,7 @@ const Player = ({ urlParams, queryParams }) => { forceTranscoding: forceTranscoding || casting, maxAudioChannels: settings.surroundSound ? 32 : 2, hardwareDecoding: settings.hardwareDecoding, + assSubtitlesStyling: settings.assSubtitlesStyling, videoMode: settings.videoMode, platform: platform.name, streamingServerURL: streamingServer.baseUrl ? @@ -387,31 +406,6 @@ const Player = ({ urlParams, queryParams }) => { } }, [player.subtitles, video.state.stream]); - React.useEffect(() => { - video.setProp('subtitlesSize', settings.subtitlesSize); - video.setProp('extraSubtitlesSize', settings.subtitlesSize); - }, [settings.subtitlesSize]); - - React.useEffect(() => { - video.setProp('subtitlesOffset', settings.subtitlesOffset); - video.setProp('extraSubtitlesOffset', settings.subtitlesOffset); - }, [settings.subtitlesOffset]); - - React.useEffect(() => { - video.setProp('subtitlesTextColor', settings.subtitlesTextColor); - video.setProp('extraSubtitlesTextColor', settings.subtitlesTextColor); - }, [settings.subtitlesTextColor]); - - React.useEffect(() => { - video.setProp('subtitlesBackgroundColor', settings.subtitlesBackgroundColor); - video.setProp('extraSubtitlesBackgroundColor', settings.subtitlesBackgroundColor); - }, [settings.subtitlesBackgroundColor]); - - React.useEffect(() => { - video.setProp('subtitlesOutlineColor', settings.subtitlesOutlineColor); - video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); - }, [settings.subtitlesOutlineColor]); - React.useEffect(() => { !seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name); }, [video.state.time, video.state.duration, video.state.manifest, seeking]); @@ -444,41 +438,69 @@ const Player = ({ urlParams, queryParams }) => { } }, [player.nextVideo, video.state.time, video.state.duration]); + // Auto subtitles track selection React.useEffect(() => { if (!defaultSubtitlesSelected.current) { - const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); - if (settings.subtitlesLanguage === null) { - onSubtitlesTrackSelected(null); - onExtraSubtitlesTrackSelected(null); + video.setSubtitlesTrack(null); + video.setExtraSubtitlesTrack(null); defaultSubtitlesSelected.current = true; return; } - const subtitlesTrack = findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage); - const extraSubtitlesTrack = findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage); + const savedTrackId = player.streamState?.subtitleTrack?.id; + const subtitlesTrack = savedTrackId ? + findTrackById(video.state.subtitlesTracks, savedTrackId) : + findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage); + + const extraSubtitlesTrack = savedTrackId ? + findTrackById(video.state.extraSubtitlesTracks, savedTrackId) : + findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage); if (subtitlesTrack && subtitlesTrack.id) { - onSubtitlesTrackSelected(subtitlesTrack.id); + video.setSubtitlesTrack(subtitlesTrack.id); defaultSubtitlesSelected.current = true; } else if (extraSubtitlesTrack && extraSubtitlesTrack.id) { - onExtraSubtitlesTrackSelected(extraSubtitlesTrack.id); + video.setExtraSubtitlesTrack(extraSubtitlesTrack.id); defaultSubtitlesSelected.current = true; } } - }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks]); + }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks, player.streamState]); + // Auto audio track selection React.useEffect(() => { if (!defaultAudioTrackSelected.current) { - const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); - const audioTrack = findTrackByLang(video.state.audioTracks, settings.audioLanguage); + const savedTrackId = player.streamState?.audioTrack?.id; + const audioTrack = savedTrackId ? + findTrackById(video.state.audioTracks, savedTrackId) : + findTrackByLang(video.state.audioTracks, settings.audioLanguage); if (audioTrack && audioTrack.id) { - onAudioTrackSelected(audioTrack.id); + video.setAudioTrack(audioTrack.id); defaultAudioTrackSelected.current = true; } } - }, [video.state.audioTracks]); + }, [video.state.audioTracks, player.streamState]); + + // Saved subtitles settings + React.useEffect(() => { + if (video.state.stream !== null) { + const delay = player.streamState?.subtitleDelay; + if (typeof delay === 'number') { + video.setSubtitlesDelay(delay); + } + + const size = player.streamState?.subtitleSize; + if (typeof size === 'number') { + video.setSubtitlesSize(size); + } + + const offset = player.streamState?.subtitleOffset; + if (typeof offset === 'number') { + video.setSubtitlesOffset(offset); + } + } + }, [video.state.stream, player.streamState]); React.useEffect(() => { defaultSubtitlesSelected.current = false; diff --git a/src/routes/Player/SubtitlesMenu/Stepper/Stepper.tsx b/src/routes/Player/SubtitlesMenu/Stepper/Stepper.tsx index 0d402a455..8f9e19d8a 100644 --- a/src/routes/Player/SubtitlesMenu/Stepper/Stepper.tsx +++ b/src/routes/Player/SubtitlesMenu/Stepper/Stepper.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; import Icon from '@stremio/stremio-icons/react'; @@ -37,6 +37,18 @@ const Stepper = ({ className, label, value, unit, step, min, max, disabled, onCh timeout.cancel(); }; + const decreaseDisabled = useMemo(() => { + return disabled || typeof value !== 'number' || (typeof min === 'number' && value <= min); + }, [disabled, min, value]); + + const increaseDisabled = useMemo(() => { + return disabled || typeof value !== 'number' || (typeof max === 'number' && value >= max); + }, [disabled, max, value]); + + const valueLabel = useMemo(() => { + return (disabled || typeof value !== 'number') ? '--' : `${value}${unit}`; + }, [disabled, value, unit]); + const updateValue = useCallback((delta: number) => { onChange(clamp(localValue.current + delta, min, max)); }, [onChange]); @@ -72,7 +84,7 @@ const Stepper = ({ className, label, value, unit, step, min, max, disabled, onCh
- { disabled ? '--' : `${value}${unit}` } + { valueLabel }
- -
- - { - shell.active && - - } - { - shell.active && - - } - -
; }); diff --git a/src/routes/Settings/Interface/Interface.tsx b/src/routes/Settings/Interface/Interface.tsx new file mode 100644 index 000000000..a4b429a56 --- /dev/null +++ b/src/routes/Settings/Interface/Interface.tsx @@ -0,0 +1,57 @@ +import React, { forwardRef } from 'react'; +import { useServices } from 'stremio/services'; +import { MultiselectMenu, Toggle } from 'stremio/components'; +import { Section, Option } from '../components'; +import useInterfaceOptions from './useInterfaceOptions'; + +type Props = { + profile: Profile, +}; + +const Interface = forwardRef(({ profile }: Props, ref) => { + const { shell } = useServices(); + + const { + interfaceLanguageSelect, + quitOnCloseToggle, + escExitFullscreenToggle, + hideSpoilersToggle, + } = useInterfaceOptions(profile); + + return ( +
+ + { + shell.active && + + } + { + shell.active && + + } + +
+ ); +}); + +export default Interface; diff --git a/src/routes/Settings/Interface/index.ts b/src/routes/Settings/Interface/index.ts new file mode 100644 index 000000000..480fa5ff4 --- /dev/null +++ b/src/routes/Settings/Interface/index.ts @@ -0,0 +1,2 @@ +import Interface from './Interface'; +export default Interface; diff --git a/src/routes/Settings/General/useGeneralOptions.ts b/src/routes/Settings/Interface/useInterfaceOptions.ts similarity index 96% rename from src/routes/Settings/General/useGeneralOptions.ts rename to src/routes/Settings/Interface/useInterfaceOptions.ts index a4ab84c5e..67780b7e1 100644 --- a/src/routes/Settings/General/useGeneralOptions.ts +++ b/src/routes/Settings/Interface/useInterfaceOptions.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { interfaceLanguages, useLanguageSorting } from 'stremio/common'; import { useServices } from 'stremio/services'; -const useGeneralOptions = (profile: Profile) => { +const useInterfaceOptions = (profile: Profile) => { const { core } = useServices(); const interfaceLanguageOptions = useMemo(() => @@ -89,4 +89,4 @@ const useGeneralOptions = (profile: Profile) => { }; }; -export default useGeneralOptions; +export default useInterfaceOptions; diff --git a/src/routes/Settings/Menu/Menu.tsx b/src/routes/Settings/Menu/Menu.tsx index ceafee94b..33cf41dc0 100644 --- a/src/routes/Settings/Menu/Menu.tsx +++ b/src/routes/Settings/Menu/Menu.tsx @@ -26,6 +26,9 @@ const Menu = ({ selected, streamingServer, onSelect }: Props) => { + diff --git a/src/routes/Settings/Player/Player.tsx b/src/routes/Settings/Player/Player.tsx index 6c493d479..213757cc3 100644 --- a/src/routes/Settings/Player/Player.tsx +++ b/src/routes/Settings/Player/Player.tsx @@ -19,6 +19,7 @@ const Player = forwardRef(({ profile }: Props, ref) => { subtitlesTextColorInput, subtitlesBackgroundColorInput, subtitlesOutlineColorInput, + assSubtitlesStylingToggle, audioLanguageSelect, surroundSoundToggle, seekTimeDurationSelect, @@ -149,6 +150,15 @@ const Player = forwardRef(({ profile }: Props, ref) => { /> } + { + shell.active && + + } ); diff --git a/src/routes/Settings/Player/usePlayerOptions.ts b/src/routes/Settings/Player/usePlayerOptions.ts index f913697b6..27081817b 100644 --- a/src/routes/Settings/Player/usePlayerOptions.ts +++ b/src/routes/Settings/Player/usePlayerOptions.ts @@ -92,6 +92,22 @@ const usePlayerOptions = (profile: Profile) => { } }), [profile.settings]); + const assSubtitlesStylingToggle = useMemo(() => ({ + checked: profile.settings.assSubtitlesStyling, + onClick: () => { + core.transport.dispatch({ + action: 'Ctx', + args: { + action: 'UpdateSettings', + args: { + ...profile.settings, + assSubtitlesStyling: !profile.settings.assSubtitlesStyling + } + } + }); + } + }), [profile.settings]); + const subtitlesOutlineColorInput = useMemo(() => ({ value: profile.settings.subtitlesOutlineColor, onChange: (value: string) => { @@ -341,6 +357,7 @@ const usePlayerOptions = (profile: Profile) => { subtitlesTextColorInput, subtitlesBackgroundColorInput, subtitlesOutlineColorInput, + assSubtitlesStylingToggle, audioLanguageSelect, surroundSoundToggle, seekTimeDurationSelect, diff --git a/src/routes/Settings/Settings.tsx b/src/routes/Settings/Settings.tsx index 2db68da2f..727da9e82 100644 --- a/src/routes/Settings/Settings.tsx +++ b/src/routes/Settings/Settings.tsx @@ -9,6 +9,7 @@ import { MainNavBars } from 'stremio/components'; import { SECTIONS } from './constants'; import Menu from './Menu'; import General from './General'; +import Interface from './Interface'; import Player from './Player'; import Streaming from './Streaming'; import Shortcuts from './Shortcuts'; @@ -23,12 +24,14 @@ const Settings = () => { const sectionsContainerRef = useRef(null); const generalSectionRef = useRef(null); + const interfaceSectionRef = useRef(null); const playerSectionRef = useRef(null); const streamingServerSectionRef = useRef(null); const shortcutsSectionRef = useRef(null); const sections = useMemo(() => ([ { ref: generalSectionRef, id: SECTIONS.GENERAL }, + { ref: interfaceSectionRef, id: SECTIONS.INTERFACE }, { ref: playerSectionRef, id: SECTIONS.PLAYER }, { ref: streamingServerSectionRef, id: SECTIONS.STREAMING }, { ref: shortcutsSectionRef, id: SECTIONS.SHORTCUTS }, @@ -82,6 +85,10 @@ const Settings = () => { ref={generalSectionRef} profile={profile} /> +