mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge branch 'development' into fix/copy-download-and-copy-streaming-urls
This commit is contained in:
commit
370443609b
19 changed files with 352 additions and 161 deletions
|
|
@ -17,9 +17,9 @@
|
||||||
"@babel/runtime": "7.26.0",
|
"@babel/runtime": "7.26.0",
|
||||||
"@sentry/browser": "8.42.0",
|
"@sentry/browser": "8.42.0",
|
||||||
"@stremio/stremio-colors": "5.2.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-icons": "5.8.0",
|
||||||
"@stremio/stremio-video": "0.0.64",
|
"@stremio/stremio-video": "0.0.70",
|
||||||
"a-color-picker": "1.2.1",
|
"a-color-picker": "1.2.1",
|
||||||
"bowser": "2.11.0",
|
"bowser": "2.11.0",
|
||||||
"buffer": "6.0.3",
|
"buffer": "6.0.3",
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"react-i18next": "^15.1.3",
|
"react-i18next": "^15.1.3",
|
||||||
"react-is": "18.3.1",
|
"react-is": "18.3.1",
|
||||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
"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",
|
"url": "0.11.4",
|
||||||
"use-long-press": "^3.2.0"
|
"use-long-press": "^3.2.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ importers:
|
||||||
specifier: 5.2.0
|
specifier: 5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
'@stremio/stremio-core-web':
|
'@stremio/stremio-core-web':
|
||||||
specifier: 0.51.1
|
specifier: 0.52.0
|
||||||
version: 0.51.1
|
version: 0.52.0
|
||||||
'@stremio/stremio-icons':
|
'@stremio/stremio-icons':
|
||||||
specifier: 5.8.0
|
specifier: 5.8.0
|
||||||
version: 5.8.0
|
version: 5.8.0
|
||||||
'@stremio/stremio-video':
|
'@stremio/stremio-video':
|
||||||
specifier: 0.0.64
|
specifier: 0.0.70
|
||||||
version: 0.0.64
|
version: 0.0.70
|
||||||
a-color-picker:
|
a-color-picker:
|
||||||
specifier: 1.2.1
|
specifier: 1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
|
|
@ -90,8 +90,8 @@ importers:
|
||||||
specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
specifier: github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
||||||
version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
version: https://codeload.github.com/Stremio/spatial-navigation/tar.gz/64871b1422466f5f45d24ebc8bbd315b2ebab6a6
|
||||||
stremio-translations:
|
stremio-translations:
|
||||||
specifier: github:Stremio/stremio-translations#0e7fbd8522148f5727ac6adee3b2eb96132c10ac
|
specifier: github:Stremio/stremio-translations#7c0c337f32163aa13158bb90cd6133da43feafef
|
||||||
version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac
|
version: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef
|
||||||
url:
|
url:
|
||||||
specifier: 0.11.4
|
specifier: 0.11.4
|
||||||
version: 0.11.4
|
version: 0.11.4
|
||||||
|
|
@ -1120,14 +1120,14 @@ packages:
|
||||||
'@stremio/stremio-colors@5.2.0':
|
'@stremio/stremio-colors@5.2.0':
|
||||||
resolution: {integrity: sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==}
|
resolution: {integrity: sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==}
|
||||||
|
|
||||||
'@stremio/stremio-core-web@0.51.1':
|
'@stremio/stremio-core-web@0.52.0':
|
||||||
resolution: {integrity: sha512-BD8i6zkDdMPeCyH50Bb7SB8r4nYx4eJwz4kLEJEl0PFjdr0gOmwHtEIgNa89ShJLNXUjPnpv4sVSNxFRG8fb5Q==}
|
resolution: {integrity: sha512-zT0P8JspGZ1oI9/11f3RIt7XG9b/1fOZE+xSnP+oAyhRmzzkqrnPUJkHdJdgoVD9XELDFAS2awNfl5/eRdh5kA==}
|
||||||
|
|
||||||
'@stremio/stremio-icons@5.8.0':
|
'@stremio/stremio-icons@5.8.0':
|
||||||
resolution: {integrity: sha512-IVUvQbIWfA4YEHCTed7v/sdQJCJ+OOCf84LTWpkE2W6GLQ+15WHcMEJrVkE1X3ekYJnGg3GjT0KLO6tKSU0P4w==}
|
resolution: {integrity: sha512-IVUvQbIWfA4YEHCTed7v/sdQJCJ+OOCf84LTWpkE2W6GLQ+15WHcMEJrVkE1X3ekYJnGg3GjT0KLO6tKSU0P4w==}
|
||||||
|
|
||||||
'@stremio/stremio-video@0.0.64':
|
'@stremio/stremio-video@0.0.70':
|
||||||
resolution: {integrity: sha512-29w/lwU8BB6ai8LUyCnpRc2F9kPf7cpys40NCobt70MqBP/UqvYISsrnD/ijoBwvtpKdZ6ptv5h9BbDj6rrerw==}
|
resolution: {integrity: sha512-a0flQYAUdrZNMm7mmts2vpZOqN1nus7Hs9Mjl4mrN5rtduD0ojUyhD5J4lPcCpZ7WB0YdEUOGLXR19qHpgoKmg==}
|
||||||
|
|
||||||
'@stylistic/eslint-plugin-jsx@4.4.1':
|
'@stylistic/eslint-plugin-jsx@4.4.1':
|
||||||
resolution: {integrity: sha512-83SInq4u7z71vWwGG+6ViOtlOmZ6tSrDkMPhrvdBBTGMLA0gs22WSdhQ4vZP3oJ5Xg4ythvqeUiFSedvVxzhyA==}
|
resolution: {integrity: sha512-83SInq4u7z71vWwGG+6ViOtlOmZ6tSrDkMPhrvdBBTGMLA0gs22WSdhQ4vZP3oJ5Xg4ythvqeUiFSedvVxzhyA==}
|
||||||
|
|
@ -3738,9 +3738,6 @@ packages:
|
||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
|
||||||
punycode@1.3.2:
|
|
||||||
resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
|
|
||||||
|
|
||||||
punycode@1.4.1:
|
punycode@1.4.1:
|
||||||
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
|
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
|
||||||
|
|
||||||
|
|
@ -3759,11 +3756,6 @@ packages:
|
||||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
engines: {node: '>=0.6'}
|
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:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
|
@ -4141,9 +4133,9 @@ packages:
|
||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac:
|
stremio-translations@https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef:
|
||||||
resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/0e7fbd8522148f5727ac6adee3b2eb96132c10ac}
|
resolution: {tarball: https://codeload.github.com/Stremio/stremio-translations/tar.gz/7c0c337f32163aa13158bb90cd6133da43feafef}
|
||||||
version: 1.44.14
|
version: 1.45.0
|
||||||
|
|
||||||
string-length@4.0.2:
|
string-length@4.0.2:
|
||||||
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
|
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
|
||||||
|
|
@ -4420,9 +4412,6 @@ packages:
|
||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
url@0.11.0:
|
|
||||||
resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==}
|
|
||||||
|
|
||||||
url@0.11.4:
|
url@0.11.4:
|
||||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -5881,13 +5870,13 @@ snapshots:
|
||||||
|
|
||||||
'@stremio/stremio-colors@5.2.0': {}
|
'@stremio/stremio-colors@5.2.0': {}
|
||||||
|
|
||||||
'@stremio/stremio-core-web@0.51.1':
|
'@stremio/stremio-core-web@0.52.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.24.1
|
'@babel/runtime': 7.24.1
|
||||||
|
|
||||||
'@stremio/stremio-icons@5.8.0': {}
|
'@stremio/stremio-icons@5.8.0': {}
|
||||||
|
|
||||||
'@stremio/stremio-video@0.0.64':
|
'@stremio/stremio-video@0.0.70':
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer: 6.0.3
|
buffer: 6.0.3
|
||||||
color: 4.2.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
|
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
|
lodash.clonedeep: 4.5.0
|
||||||
magnet-uri: 6.2.0
|
magnet-uri: 6.2.0
|
||||||
url: 0.11.0
|
url: 0.11.4
|
||||||
video-name-parser: 1.4.6
|
video-name-parser: 1.4.6
|
||||||
vtt.js: https://codeload.github.com/jaruba/vtt.js/tar.gz/84d33d157848407d790d78423dacc41a096294f0
|
vtt.js: https://codeload.github.com/jaruba/vtt.js/tar.gz/84d33d157848407d790d78423dacc41a096294f0
|
||||||
|
|
||||||
|
|
@ -8941,8 +8930,6 @@ snapshots:
|
||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
punycode@1.3.2: {}
|
|
||||||
|
|
||||||
punycode@1.4.1: {}
|
punycode@1.4.1: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
@ -8957,8 +8944,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
querystring@0.2.0: {}
|
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
randombytes@2.1.0:
|
randombytes@2.1.0:
|
||||||
|
|
@ -9393,7 +9378,7 @@ snapshots:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.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:
|
string-length@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -9688,11 +9673,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
url@0.11.0:
|
|
||||||
dependencies:
|
|
||||||
punycode: 1.3.2
|
|
||||||
querystring: 0.2.0
|
|
||||||
|
|
||||||
url@0.11.4:
|
url@0.11.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode: 1.4.1
|
punycode: 1.4.1
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
"name": "العربية",
|
"name": "العربية",
|
||||||
"codes": ["ar-AR", "ara"]
|
"codes": ["ar-AR", "ara"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Беларуская",
|
||||||
|
"codes": ["be-BY", "bel"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "български език",
|
"name": "български език",
|
||||||
"codes": ["bg-BG", "bul"]
|
"codes": ["bg-BG", "bul"]
|
||||||
|
|
@ -13,7 +17,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "català",
|
"name": "català",
|
||||||
"codes": ["ca-CA", "cat"]
|
"codes": ["ca-ES", "cat"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "čeština",
|
"name": "čeština",
|
||||||
|
|
@ -43,6 +47,10 @@
|
||||||
"name": "español",
|
"name": "español",
|
||||||
"codes": ["es-ES", "spa"]
|
"codes": ["es-ES", "spa"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Eesti",
|
||||||
|
"codes": ["et-EE", "est"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "euskara",
|
"name": "euskara",
|
||||||
"codes": ["eu-ES", "eus"]
|
"codes": ["eu-ES", "eus"]
|
||||||
|
|
@ -111,6 +119,10 @@
|
||||||
"name": "Norsk nynorsk",
|
"name": "Norsk nynorsk",
|
||||||
"codes": ["nn-NO", "nno"]
|
"codes": ["nn-NO", "nno"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ਪੰਜਾਬੀ",
|
||||||
|
"codes": ["pa-IN", "pan"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "język polski",
|
"name": "język polski",
|
||||||
"codes": ["pl-PL", "pol"]
|
"codes": ["pl-PL", "pol"]
|
||||||
|
|
@ -151,6 +163,10 @@
|
||||||
"name": "తెలుగు",
|
"name": "తెలుగు",
|
||||||
"codes": ["te-IN", "tel"]
|
"codes": ["te-IN", "tel"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "தமிழ்",
|
||||||
|
"codes": ["tl-TM", "tam"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Türkçe",
|
"name": "Türkçe",
|
||||||
"codes": ["tr-TR", "tur"]
|
"codes": ["tr-TR", "tur"]
|
||||||
|
|
@ -159,6 +175,10 @@
|
||||||
"name": "українська мова",
|
"name": "українська мова",
|
||||||
"codes": ["uk-UA", "ukr"]
|
"codes": ["uk-UA", "ukr"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "اُرْدُو",
|
||||||
|
"codes": ["ur-PK", "urd"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Tiếng Việt",
|
"name": "Tiếng Việt",
|
||||||
"codes": ["vi-VN", "vie"]
|
"codes": ["vi-VN", "vie"]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-height: 25rem;
|
||||||
width: 16rem;
|
width: 16rem;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ const styles = require('./styles');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
const { default: Indicator } = require('./Indicator/Indicator');
|
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 Player = ({ urlParams, queryParams }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const services = useServices();
|
const services = useServices();
|
||||||
|
|
@ -37,8 +40,8 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
return queryParams.has('forceTranscoding');
|
return queryParams.has('forceTranscoding');
|
||||||
}, [queryParams]);
|
}, [queryParams]);
|
||||||
const profile = useProfile();
|
const profile = useProfile();
|
||||||
const [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
|
const [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
|
||||||
const [settings, updateSettings] = useSettings();
|
const [settings] = useSettings();
|
||||||
const streamingServer = useStreamingServer();
|
const streamingServer = useStreamingServer();
|
||||||
const statistics = useStatistics(player, streamingServer);
|
const statistics = useStatistics(player, streamingServer);
|
||||||
const video = useVideo();
|
const video = useVideo();
|
||||||
|
|
@ -93,17 +96,12 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const isNavigating = React.useRef(false);
|
const isNavigating = React.useRef(false);
|
||||||
|
|
||||||
const onImplementationChanged = React.useCallback(() => {
|
const onImplementationChanged = React.useCallback(() => {
|
||||||
video.setProp('subtitlesSize', settings.subtitlesSize);
|
video.setSubtitlesSize(settings.subtitlesSize);
|
||||||
video.setProp('subtitlesOffset', settings.subtitlesOffset);
|
video.setSubtitlesOffset(settings.subtitlesOffset);
|
||||||
video.setProp('subtitlesTextColor', settings.subtitlesTextColor);
|
video.setSubtitlesTextColor(settings.subtitlesTextColor);
|
||||||
video.setProp('subtitlesBackgroundColor', settings.subtitlesBackgroundColor);
|
video.setSubtitlesBackgroundColor(settings.subtitlesBackgroundColor);
|
||||||
video.setProp('subtitlesOutlineColor', settings.subtitlesOutlineColor);
|
video.setSubtitlesOutlineColor(settings.subtitlesOutlineColor);
|
||||||
video.setProp('extraSubtitlesSize', settings.subtitlesSize);
|
}, [settings]);
|
||||||
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]);
|
|
||||||
|
|
||||||
const handleNextVideoNavigation = React.useCallback((deepLinks, bingeWatching, ended) => {
|
const handleNextVideoNavigation = React.useCallback((deepLinks, bingeWatching, ended) => {
|
||||||
if (ended) {
|
if (ended) {
|
||||||
|
|
@ -190,53 +188,71 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPlayRequested = React.useCallback(() => {
|
const onPlayRequested = React.useCallback(() => {
|
||||||
video.setProp('paused', false);
|
video.setPaused(false);
|
||||||
setSeeking(false);
|
setSeeking(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []);
|
const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []);
|
||||||
|
|
||||||
const onPauseRequested = React.useCallback(() => {
|
const onPauseRequested = React.useCallback(() => {
|
||||||
video.setProp('paused', true);
|
video.setPaused(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPauseRequestedDebounced = React.useCallback(debounce(onPauseRequested, 200), []);
|
const onPauseRequestedDebounced = React.useCallback(debounce(onPauseRequested, 200), []);
|
||||||
const onMuteRequested = React.useCallback(() => {
|
const onMuteRequested = React.useCallback(() => {
|
||||||
video.setProp('muted', true);
|
video.setMuted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onUnmuteRequested = React.useCallback(() => {
|
const onUnmuteRequested = React.useCallback(() => {
|
||||||
video.setProp('muted', false);
|
video.setMuted(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onVolumeChangeRequested = React.useCallback((volume) => {
|
const onVolumeChangeRequested = React.useCallback((volume) => {
|
||||||
video.setProp('volume', volume);
|
video.setVolume(volume);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSeekRequested = React.useCallback((time) => {
|
const onSeekRequested = React.useCallback((time) => {
|
||||||
video.setProp('time', time);
|
video.setTime(time);
|
||||||
seek(time, video.state.duration, video.state.manifest?.name);
|
seek(time, video.state.duration, video.state.manifest?.name);
|
||||||
}, [video.state.duration, video.state.manifest]);
|
}, [video.state.duration, video.state.manifest]);
|
||||||
|
|
||||||
const onPlaybackSpeedChanged = React.useCallback((rate) => {
|
const onPlaybackSpeedChanged = React.useCallback((rate) => {
|
||||||
video.setProp('playbackSpeed', rate);
|
video.setPlaybackSpeed(rate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubtitlesTrackSelected = React.useCallback((id) => {
|
const onSubtitlesTrackSelected = React.useCallback((id) => {
|
||||||
video.setSubtitlesTrack(id);
|
video.setSubtitlesTrack(id);
|
||||||
}, []);
|
streamStateChanged({
|
||||||
|
subtitleTrack: {
|
||||||
|
id,
|
||||||
|
embedded: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onExtraSubtitlesTrackSelected = React.useCallback((id) => {
|
const onExtraSubtitlesTrackSelected = React.useCallback((id) => {
|
||||||
video.setExtraSubtitlesTrack(id);
|
video.setExtraSubtitlesTrack(id);
|
||||||
}, []);
|
streamStateChanged({
|
||||||
|
subtitleTrack: {
|
||||||
|
id,
|
||||||
|
embedded: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onAudioTrackSelected = React.useCallback((id) => {
|
const onAudioTrackSelected = React.useCallback((id) => {
|
||||||
video.setProp('selectedAudioTrackId', id);
|
video.setAudioTrack(id);
|
||||||
}, []);
|
streamStateChanged({
|
||||||
|
audioTrack: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onExtraSubtitlesDelayChanged = React.useCallback((delay) => {
|
const onExtraSubtitlesDelayChanged = React.useCallback((delay) => {
|
||||||
video.setProp('extraSubtitlesDelay', delay);
|
video.setSubtitlesDelay(delay);
|
||||||
}, []);
|
streamStateChanged({ subtitleDelay: delay });
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onIncreaseSubtitlesDelay = React.useCallback(() => {
|
const onIncreaseSubtitlesDelay = React.useCallback(() => {
|
||||||
const delay = video.state.extraSubtitlesDelay + 250;
|
const delay = video.state.extraSubtitlesDelay + 250;
|
||||||
|
|
@ -249,8 +265,9 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]);
|
}, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]);
|
||||||
|
|
||||||
const onSubtitlesSizeChanged = React.useCallback((size) => {
|
const onSubtitlesSizeChanged = React.useCallback((size) => {
|
||||||
updateSettings({ subtitlesSize: size });
|
video.setSubtitlesSize(size);
|
||||||
}, [updateSettings]);
|
streamStateChanged({ subtitleSize: size });
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onUpdateSubtitlesSize = React.useCallback((delta) => {
|
const onUpdateSubtitlesSize = React.useCallback((delta) => {
|
||||||
const sizeIndex = CONSTANTS.SUBTITLES_SIZES.indexOf(video.state.subtitlesSize);
|
const sizeIndex = CONSTANTS.SUBTITLES_SIZES.indexOf(video.state.subtitlesSize);
|
||||||
|
|
@ -259,8 +276,9 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}, [video.state.subtitlesSize, onSubtitlesSizeChanged]);
|
}, [video.state.subtitlesSize, onSubtitlesSizeChanged]);
|
||||||
|
|
||||||
const onSubtitlesOffsetChanged = React.useCallback((offset) => {
|
const onSubtitlesOffsetChanged = React.useCallback((offset) => {
|
||||||
updateSettings({ subtitlesOffset: offset });
|
video.setSubtitlesOffset(offset);
|
||||||
}, [updateSettings]);
|
streamStateChanged({ subtitleOffset: offset });
|
||||||
|
}, [streamStateChanged]);
|
||||||
|
|
||||||
const onDismissNextVideoPopup = React.useCallback(() => {
|
const onDismissNextVideoPopup = React.useCallback(() => {
|
||||||
closeNextVideoPopup();
|
closeNextVideoPopup();
|
||||||
|
|
@ -361,6 +379,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
forceTranscoding: forceTranscoding || casting,
|
forceTranscoding: forceTranscoding || casting,
|
||||||
maxAudioChannels: settings.surroundSound ? 32 : 2,
|
maxAudioChannels: settings.surroundSound ? 32 : 2,
|
||||||
hardwareDecoding: settings.hardwareDecoding,
|
hardwareDecoding: settings.hardwareDecoding,
|
||||||
|
assSubtitlesStyling: settings.assSubtitlesStyling,
|
||||||
videoMode: settings.videoMode,
|
videoMode: settings.videoMode,
|
||||||
platform: platform.name,
|
platform: platform.name,
|
||||||
streamingServerURL: streamingServer.baseUrl ?
|
streamingServerURL: streamingServer.baseUrl ?
|
||||||
|
|
@ -387,31 +406,6 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
}, [player.subtitles, video.state.stream]);
|
}, [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(() => {
|
React.useEffect(() => {
|
||||||
!seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name);
|
!seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name);
|
||||||
}, [video.state.time, video.state.duration, video.state.manifest, seeking]);
|
}, [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]);
|
}, [player.nextVideo, video.state.time, video.state.duration]);
|
||||||
|
|
||||||
|
// Auto subtitles track selection
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!defaultSubtitlesSelected.current) {
|
if (!defaultSubtitlesSelected.current) {
|
||||||
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
|
||||||
|
|
||||||
if (settings.subtitlesLanguage === null) {
|
if (settings.subtitlesLanguage === null) {
|
||||||
onSubtitlesTrackSelected(null);
|
video.setSubtitlesTrack(null);
|
||||||
onExtraSubtitlesTrackSelected(null);
|
video.setExtraSubtitlesTrack(null);
|
||||||
defaultSubtitlesSelected.current = true;
|
defaultSubtitlesSelected.current = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subtitlesTrack = findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage);
|
const savedTrackId = player.streamState?.subtitleTrack?.id;
|
||||||
const extraSubtitlesTrack = findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage);
|
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) {
|
if (subtitlesTrack && subtitlesTrack.id) {
|
||||||
onSubtitlesTrackSelected(subtitlesTrack.id);
|
video.setSubtitlesTrack(subtitlesTrack.id);
|
||||||
defaultSubtitlesSelected.current = true;
|
defaultSubtitlesSelected.current = true;
|
||||||
} else if (extraSubtitlesTrack && extraSubtitlesTrack.id) {
|
} else if (extraSubtitlesTrack && extraSubtitlesTrack.id) {
|
||||||
onExtraSubtitlesTrackSelected(extraSubtitlesTrack.id);
|
video.setExtraSubtitlesTrack(extraSubtitlesTrack.id);
|
||||||
defaultSubtitlesSelected.current = true;
|
defaultSubtitlesSelected.current = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks]);
|
}, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks, player.streamState]);
|
||||||
|
|
||||||
|
// Auto audio track selection
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!defaultAudioTrackSelected.current) {
|
if (!defaultAudioTrackSelected.current) {
|
||||||
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
const savedTrackId = player.streamState?.audioTrack?.id;
|
||||||
const audioTrack = findTrackByLang(video.state.audioTracks, settings.audioLanguage);
|
const audioTrack = savedTrackId ?
|
||||||
|
findTrackById(video.state.audioTracks, savedTrackId) :
|
||||||
|
findTrackByLang(video.state.audioTracks, settings.audioLanguage);
|
||||||
|
|
||||||
if (audioTrack && audioTrack.id) {
|
if (audioTrack && audioTrack.id) {
|
||||||
onAudioTrackSelected(audioTrack.id);
|
video.setAudioTrack(audioTrack.id);
|
||||||
defaultAudioTrackSelected.current = true;
|
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(() => {
|
React.useEffect(() => {
|
||||||
defaultSubtitlesSelected.current = false;
|
defaultSubtitlesSelected.current = false;
|
||||||
|
|
|
||||||
|
|
@ -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 { useTranslation } from 'react-i18next';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from '@stremio/stremio-icons/react';
|
import Icon from '@stremio/stremio-icons/react';
|
||||||
|
|
@ -37,6 +37,18 @@ const Stepper = ({ className, label, value, unit, step, min, max, disabled, onCh
|
||||||
timeout.cancel();
|
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) => {
|
const updateValue = useCallback((delta: number) => {
|
||||||
onChange(clamp(localValue.current + delta, min, max));
|
onChange(clamp(localValue.current + delta, min, max));
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
@ -72,7 +84,7 @@ const Stepper = ({ className, label, value, unit, step, min, max, disabled, onCh
|
||||||
</div>
|
</div>
|
||||||
<div className={styles['content']}>
|
<div className={styles['content']}>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(styles['button'], { 'disabled': disabled })}
|
className={classNames(styles['button'], { 'disabled': decreaseDisabled })}
|
||||||
onMouseDown={onDecrementMouseDown}
|
onMouseDown={onDecrementMouseDown}
|
||||||
onMouseUp={onDecrementMouseUp}
|
onMouseUp={onDecrementMouseUp}
|
||||||
onMouseLeave={cancel}
|
onMouseLeave={cancel}
|
||||||
|
|
@ -80,10 +92,10 @@ const Stepper = ({ className, label, value, unit, step, min, max, disabled, onCh
|
||||||
<Icon className={styles['icon']} name={'remove'} />
|
<Icon className={styles['icon']} name={'remove'} />
|
||||||
</Button>
|
</Button>
|
||||||
<div className={styles['value']}>
|
<div className={styles['value']}>
|
||||||
{ disabled ? '--' : `${value}${unit}` }
|
{ valueLabel }
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(styles['button'], { 'disabled': disabled })}
|
className={classNames(styles['button'], { 'disabled': increaseDisabled })}
|
||||||
onMouseDown={onIncrementMouseDown}
|
onMouseDown={onIncrementMouseDown}
|
||||||
onMouseUp={onIncrementMouseUp}
|
onMouseUp={onIncrementMouseUp}
|
||||||
onMouseLeave={cancel}
|
onMouseLeave={cancel}
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,9 @@ const usePlayer = (urlParams) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [urlParams]);
|
}, [urlParams]);
|
||||||
|
|
||||||
|
const player = useModelState({ model: 'player', action, map });
|
||||||
|
|
||||||
const videoParamsChanged = React.useCallback((videoParams) => {
|
const videoParamsChanged = React.useCallback((videoParams) => {
|
||||||
core.transport.dispatch({
|
core.transport.dispatch({
|
||||||
action: 'Player',
|
action: 'Player',
|
||||||
|
|
@ -153,8 +156,22 @@ const usePlayer = (urlParams) => {
|
||||||
}, 'player');
|
}, 'player');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const player = useModelState({ model: 'player', action, map });
|
const streamStateChanged = React.useCallback((partialStreamState) => {
|
||||||
return [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo];
|
return core.transport.dispatch({
|
||||||
|
action: 'Player',
|
||||||
|
args: {
|
||||||
|
action: 'StreamStateChanged',
|
||||||
|
args: {
|
||||||
|
state: {
|
||||||
|
...player.streamState,
|
||||||
|
...partialStreamState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, 'player');
|
||||||
|
}, [player.streamState]);
|
||||||
|
|
||||||
|
return [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = usePlayer;
|
module.exports = usePlayer;
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,30 @@ const useVideo = () => {
|
||||||
dispatch({ type: 'setProp', propName: name, propValue: value });
|
dispatch({ type: 'setProp', propName: name, propValue: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setPaused = (state) => {
|
||||||
|
setProp('paused', state);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setVolume = (volume) => {
|
||||||
|
setProp('volume', volume);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMuted = (state) => {
|
||||||
|
setProp('muted', state);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTime = (time) => {
|
||||||
|
setProp('time', time);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPlaybackSpeed = (rate) => {
|
||||||
|
setProp('playbackSpeed', rate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAudioTrack = (id) => {
|
||||||
|
setProp('selectedAudioTrackId', id);
|
||||||
|
};
|
||||||
|
|
||||||
const setSubtitlesTrack = (id) => {
|
const setSubtitlesTrack = (id) => {
|
||||||
setProp('selectedSubtitlesTrackId', id);
|
setProp('selectedSubtitlesTrackId', id);
|
||||||
setProp('selectedExtraSubtitlesTrackId', null);
|
setProp('selectedExtraSubtitlesTrackId', null);
|
||||||
|
|
@ -104,6 +128,35 @@ const useVideo = () => {
|
||||||
setProp('selectedExtraSubtitlesTrackId', id);
|
setProp('selectedExtraSubtitlesTrackId', id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setSubtitlesDelay = (delay) => {
|
||||||
|
setProp('extraSubtitlesDelay', delay);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSubtitlesSize = (size) => {
|
||||||
|
setProp('subtitlesSize', size);
|
||||||
|
setProp('extraSubtitlesSize', size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSubtitlesOffset = (offset) => {
|
||||||
|
setProp('subtitlesOffset', offset);
|
||||||
|
setProp('extraSubtitlesOffset', offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSubtitlesTextColor = (color) => {
|
||||||
|
setProp('subtitlesTextColor', color);
|
||||||
|
setProp('extraSubtitlesTextColor', color);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSubtitlesBackgroundColor = (color) => {
|
||||||
|
setProp('subtitlesBackgroundColor', color);
|
||||||
|
setProp('extraSubtitlesBackgroundColor', color);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSubtitlesOutlineColor = (color) => {
|
||||||
|
setProp('subtitlesOutlineColor', color);
|
||||||
|
setProp('extraSubtitlesOutlineColor', color);
|
||||||
|
};
|
||||||
|
|
||||||
const onError = (error) => {
|
const onError = (error) => {
|
||||||
events.emit('error', error);
|
events.emit('error', error);
|
||||||
};
|
};
|
||||||
|
|
@ -171,8 +224,19 @@ const useVideo = () => {
|
||||||
unload,
|
unload,
|
||||||
addExtraSubtitlesTracks,
|
addExtraSubtitlesTracks,
|
||||||
addLocalSubtitles,
|
addLocalSubtitles,
|
||||||
setProp,
|
setPaused,
|
||||||
|
setVolume,
|
||||||
|
setMuted,
|
||||||
|
setTime,
|
||||||
|
setPlaybackSpeed,
|
||||||
|
setAudioTrack,
|
||||||
setSubtitlesTrack,
|
setSubtitlesTrack,
|
||||||
|
setSubtitlesDelay,
|
||||||
|
setSubtitlesSize,
|
||||||
|
setSubtitlesOffset,
|
||||||
|
setSubtitlesTextColor,
|
||||||
|
setSubtitlesBackgroundColor,
|
||||||
|
setSubtitlesOutlineColor,
|
||||||
setExtraSubtitlesTrack,
|
setExtraSubtitlesTrack,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, MultiselectMenu, Toggle } from 'stremio/components';
|
import { Button } from 'stremio/components';
|
||||||
import { useServices } from 'stremio/services';
|
import { useServices } from 'stremio/services';
|
||||||
import { usePlatform, useToast } from 'stremio/common';
|
import { usePlatform, useToast } from 'stremio/common';
|
||||||
import { Section, Option, Link } from '../components';
|
import { Section, Option, Link } from '../components';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import useDataExport from './useDataExport';
|
import useDataExport from './useDataExport';
|
||||||
import styles from './General.less';
|
import styles from './General.less';
|
||||||
import useGeneralOptions from './useGeneralOptions';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
|
|
@ -15,18 +14,11 @@ type Props = {
|
||||||
|
|
||||||
const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { core, shell } = useServices();
|
const { core } = useServices();
|
||||||
const platform = usePlatform();
|
const platform = usePlatform();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [dataExport, loadDataExport] = useDataExport();
|
const [dataExport, loadDataExport] = useDataExport();
|
||||||
|
|
||||||
const {
|
|
||||||
interfaceLanguageSelect,
|
|
||||||
quitOnCloseToggle,
|
|
||||||
escExitFullscreenToggle,
|
|
||||||
hideSpoilersToggle,
|
|
||||||
} = useGeneralOptions(profile);
|
|
||||||
|
|
||||||
const [traktAuthStarted, setTraktAuthStarted] = useState(false);
|
const [traktAuthStarted, setTraktAuthStarted] = useState(false);
|
||||||
|
|
||||||
const isTraktAuthenticated = useMemo(() => {
|
const isTraktAuthenticated = useMemo(() => {
|
||||||
|
|
@ -143,39 +135,6 @@ const General = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
</Button>
|
</Button>
|
||||||
</Option>
|
</Option>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section>
|
|
||||||
<Option label={'SETTINGS_UI_LANGUAGE'}>
|
|
||||||
<MultiselectMenu
|
|
||||||
className={'multiselect'}
|
|
||||||
{...interfaceLanguageSelect}
|
|
||||||
/>
|
|
||||||
</Option>
|
|
||||||
{
|
|
||||||
shell.active &&
|
|
||||||
<Option label={'SETTINGS_QUIT_ON_CLOSE'}>
|
|
||||||
<Toggle
|
|
||||||
tabIndex={-1}
|
|
||||||
{...quitOnCloseToggle}
|
|
||||||
/>
|
|
||||||
</Option>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
shell.active &&
|
|
||||||
<Option label={'SETTINGS_FULLSCREEN_EXIT'}>
|
|
||||||
<Toggle
|
|
||||||
tabIndex={-1}
|
|
||||||
{...escExitFullscreenToggle}
|
|
||||||
/>
|
|
||||||
</Option>
|
|
||||||
}
|
|
||||||
<Option label={'SETTINGS_BLUR_UNWATCHED_IMAGE'}>
|
|
||||||
<Toggle
|
|
||||||
tabIndex={-1}
|
|
||||||
{...hideSpoilersToggle}
|
|
||||||
/>
|
|
||||||
</Option>
|
|
||||||
</Section>
|
|
||||||
</>;
|
</>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
57
src/routes/Settings/Interface/Interface.tsx
Normal file
57
src/routes/Settings/Interface/Interface.tsx
Normal file
|
|
@ -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<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
|
const { shell } = useServices();
|
||||||
|
|
||||||
|
const {
|
||||||
|
interfaceLanguageSelect,
|
||||||
|
quitOnCloseToggle,
|
||||||
|
escExitFullscreenToggle,
|
||||||
|
hideSpoilersToggle,
|
||||||
|
} = useInterfaceOptions(profile);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section ref={ref} label={'INTERFACE'}>
|
||||||
|
<Option label={'SETTINGS_UI_LANGUAGE'}>
|
||||||
|
<MultiselectMenu
|
||||||
|
className={'multiselect'}
|
||||||
|
{...interfaceLanguageSelect}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
shell.active &&
|
||||||
|
<Option label={'SETTINGS_QUIT_ON_CLOSE'}>
|
||||||
|
<Toggle
|
||||||
|
tabIndex={-1}
|
||||||
|
{...quitOnCloseToggle}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
shell.active &&
|
||||||
|
<Option label={'SETTINGS_FULLSCREEN_EXIT'}>
|
||||||
|
<Toggle
|
||||||
|
tabIndex={-1}
|
||||||
|
{...escExitFullscreenToggle}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
}
|
||||||
|
<Option label={'SETTINGS_BLUR_UNWATCHED_IMAGE'}>
|
||||||
|
<Toggle
|
||||||
|
tabIndex={-1}
|
||||||
|
{...hideSpoilersToggle}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Interface;
|
||||||
2
src/routes/Settings/Interface/index.ts
Normal file
2
src/routes/Settings/Interface/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import Interface from './Interface';
|
||||||
|
export default Interface;
|
||||||
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||||
import { interfaceLanguages, useLanguageSorting } from 'stremio/common';
|
import { interfaceLanguages, useLanguageSorting } from 'stremio/common';
|
||||||
import { useServices } from 'stremio/services';
|
import { useServices } from 'stremio/services';
|
||||||
|
|
||||||
const useGeneralOptions = (profile: Profile) => {
|
const useInterfaceOptions = (profile: Profile) => {
|
||||||
const { core } = useServices();
|
const { core } = useServices();
|
||||||
|
|
||||||
const interfaceLanguageOptions = useMemo(() =>
|
const interfaceLanguageOptions = useMemo(() =>
|
||||||
|
|
@ -89,4 +89,4 @@ const useGeneralOptions = (profile: Profile) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useGeneralOptions;
|
export default useInterfaceOptions;
|
||||||
|
|
@ -26,6 +26,9 @@ const Menu = ({ selected, streamingServer, onSelect }: Props) => {
|
||||||
<Button className={classNames(styles['button'], { [styles['selected']]: selected === SECTIONS.GENERAL })} title={t('SETTINGS_NAV_GENERAL')} data-section={SECTIONS.GENERAL} onClick={onSelect}>
|
<Button className={classNames(styles['button'], { [styles['selected']]: selected === SECTIONS.GENERAL })} title={t('SETTINGS_NAV_GENERAL')} data-section={SECTIONS.GENERAL} onClick={onSelect}>
|
||||||
{ t('SETTINGS_NAV_GENERAL') }
|
{ t('SETTINGS_NAV_GENERAL') }
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className={classNames(styles['button'], { [styles['selected']]: selected === SECTIONS.INTERFACE })} title={t('INTERFACE')} data-section={SECTIONS.INTERFACE} onClick={onSelect}>
|
||||||
|
{ t('INTERFACE') }
|
||||||
|
</Button>
|
||||||
<Button className={classNames(styles['button'], { [styles['selected']]: selected === SECTIONS.PLAYER })} title={t('SETTINGS_NAV_PLAYER')} data-section={SECTIONS.PLAYER} onClick={onSelect}>
|
<Button className={classNames(styles['button'], { [styles['selected']]: selected === SECTIONS.PLAYER })} title={t('SETTINGS_NAV_PLAYER')} data-section={SECTIONS.PLAYER} onClick={onSelect}>
|
||||||
{ t('SETTINGS_NAV_PLAYER') }
|
{ t('SETTINGS_NAV_PLAYER') }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const Player = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
subtitlesTextColorInput,
|
subtitlesTextColorInput,
|
||||||
subtitlesBackgroundColorInput,
|
subtitlesBackgroundColorInput,
|
||||||
subtitlesOutlineColorInput,
|
subtitlesOutlineColorInput,
|
||||||
|
assSubtitlesStylingToggle,
|
||||||
audioLanguageSelect,
|
audioLanguageSelect,
|
||||||
surroundSoundToggle,
|
surroundSoundToggle,
|
||||||
seekTimeDurationSelect,
|
seekTimeDurationSelect,
|
||||||
|
|
@ -149,6 +150,15 @@ const Player = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||||
/>
|
/>
|
||||||
</Option>
|
</Option>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
shell.active &&
|
||||||
|
<Option label={'SETTINGS_ASS_SUBTITLES_STYLING'}>
|
||||||
|
<Toggle
|
||||||
|
tabIndex={-1}
|
||||||
|
{...assSubtitlesStylingToggle}
|
||||||
|
/>
|
||||||
|
</Option>
|
||||||
|
}
|
||||||
</Category>
|
</Category>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,22 @@ const usePlayerOptions = (profile: Profile) => {
|
||||||
}
|
}
|
||||||
}), [profile.settings]);
|
}), [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(() => ({
|
const subtitlesOutlineColorInput = useMemo(() => ({
|
||||||
value: profile.settings.subtitlesOutlineColor,
|
value: profile.settings.subtitlesOutlineColor,
|
||||||
onChange: (value: string) => {
|
onChange: (value: string) => {
|
||||||
|
|
@ -341,6 +357,7 @@ const usePlayerOptions = (profile: Profile) => {
|
||||||
subtitlesTextColorInput,
|
subtitlesTextColorInput,
|
||||||
subtitlesBackgroundColorInput,
|
subtitlesBackgroundColorInput,
|
||||||
subtitlesOutlineColorInput,
|
subtitlesOutlineColorInput,
|
||||||
|
assSubtitlesStylingToggle,
|
||||||
audioLanguageSelect,
|
audioLanguageSelect,
|
||||||
surroundSoundToggle,
|
surroundSoundToggle,
|
||||||
seekTimeDurationSelect,
|
seekTimeDurationSelect,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { MainNavBars } from 'stremio/components';
|
||||||
import { SECTIONS } from './constants';
|
import { SECTIONS } from './constants';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import General from './General';
|
import General from './General';
|
||||||
|
import Interface from './Interface';
|
||||||
import Player from './Player';
|
import Player from './Player';
|
||||||
import Streaming from './Streaming';
|
import Streaming from './Streaming';
|
||||||
import Shortcuts from './Shortcuts';
|
import Shortcuts from './Shortcuts';
|
||||||
|
|
@ -23,12 +24,14 @@ const Settings = () => {
|
||||||
|
|
||||||
const sectionsContainerRef = useRef<HTMLDivElement>(null);
|
const sectionsContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const generalSectionRef = useRef<HTMLDivElement>(null);
|
const generalSectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
const interfaceSectionRef = useRef<HTMLDivElement>(null);
|
||||||
const playerSectionRef = useRef<HTMLDivElement>(null);
|
const playerSectionRef = useRef<HTMLDivElement>(null);
|
||||||
const streamingServerSectionRef = useRef<HTMLDivElement>(null);
|
const streamingServerSectionRef = useRef<HTMLDivElement>(null);
|
||||||
const shortcutsSectionRef = useRef<HTMLDivElement>(null);
|
const shortcutsSectionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const sections = useMemo(() => ([
|
const sections = useMemo(() => ([
|
||||||
{ ref: generalSectionRef, id: SECTIONS.GENERAL },
|
{ ref: generalSectionRef, id: SECTIONS.GENERAL },
|
||||||
|
{ ref: interfaceSectionRef, id: SECTIONS.INTERFACE },
|
||||||
{ ref: playerSectionRef, id: SECTIONS.PLAYER },
|
{ ref: playerSectionRef, id: SECTIONS.PLAYER },
|
||||||
{ ref: streamingServerSectionRef, id: SECTIONS.STREAMING },
|
{ ref: streamingServerSectionRef, id: SECTIONS.STREAMING },
|
||||||
{ ref: shortcutsSectionRef, id: SECTIONS.SHORTCUTS },
|
{ ref: shortcutsSectionRef, id: SECTIONS.SHORTCUTS },
|
||||||
|
|
@ -82,6 +85,10 @@ const Settings = () => {
|
||||||
ref={generalSectionRef}
|
ref={generalSectionRef}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
/>
|
/>
|
||||||
|
<Interface
|
||||||
|
ref={interfaceSectionRef}
|
||||||
|
profile={profile}
|
||||||
|
/>
|
||||||
<Player
|
<Player
|
||||||
ref={playerSectionRef}
|
ref={playerSectionRef}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const SECTIONS = {
|
const SECTIONS = {
|
||||||
GENERAL: 'general',
|
GENERAL: 'general',
|
||||||
PLAYER: 'player',
|
PLAYER: 'player',
|
||||||
|
INTERFACE: 'interface',
|
||||||
STREAMING: 'streaming',
|
STREAMING: 'streaming',
|
||||||
SHORTCUTS: 'shortcuts',
|
SHORTCUTS: 'shortcuts',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
1
src/types/models/Ctx.d.ts
vendored
1
src/types/models/Ctx.d.ts
vendored
|
|
@ -42,6 +42,7 @@ type Settings = {
|
||||||
subtitlesOutlineColor: string,
|
subtitlesOutlineColor: string,
|
||||||
subtitlesSize: number,
|
subtitlesSize: number,
|
||||||
subtitlesTextColor: string,
|
subtitlesTextColor: string,
|
||||||
|
assSubtitlesStyling: boolean,
|
||||||
surroundSound: boolean,
|
surroundSound: boolean,
|
||||||
pauseOnMinimize: boolean,
|
pauseOnMinimize: boolean,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
18
src/types/models/Player.d.ts
vendored
18
src/types/models/Player.d.ts
vendored
|
|
@ -30,6 +30,23 @@ type SeriesInfo = {
|
||||||
season: number,
|
season: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SubtitlesTrackState = {
|
||||||
|
id: string,
|
||||||
|
embedded: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
type AudioTrackState = {
|
||||||
|
id: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type StreamState = {
|
||||||
|
subtitleTrack?: SubtitlesTrackState,
|
||||||
|
subtitleDelay?: number,
|
||||||
|
subtitleSize?: number,
|
||||||
|
subtitleOffset?: number,
|
||||||
|
audioTrack?: AudioTrackState,
|
||||||
|
};
|
||||||
|
|
||||||
type Player = {
|
type Player = {
|
||||||
addon: Addon | null,
|
addon: Addon | null,
|
||||||
libraryItem: LibraryItemPlayer | null,
|
libraryItem: LibraryItemPlayer | null,
|
||||||
|
|
@ -42,6 +59,7 @@ type Player = {
|
||||||
subtitlesPath: ResourceRequestPath,
|
subtitlesPath: ResourceRequestPath,
|
||||||
} | null,
|
} | null,
|
||||||
seriesInfo: SeriesInfo | null,
|
seriesInfo: SeriesInfo | null,
|
||||||
|
streamState: StreamState | null,
|
||||||
subtitles: Subtitle[],
|
subtitles: Subtitle[],
|
||||||
title: string | null,
|
title: string | null,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue