mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-11 08:10:40 +00:00
Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/addons-one-click-actions
This commit is contained in:
commit
c1820a324b
37 changed files with 835 additions and 281 deletions
75
package-lock.json
generated
75
package-lock.json
generated
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "stremio",
|
"name": "stremio",
|
||||||
"version": "5.0.0-beta.14",
|
"version": "5.0.0-beta.15",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "stremio",
|
"name": "stremio",
|
||||||
"version": "5.0.0-beta.14",
|
"version": "5.0.0-beta.15",
|
||||||
"license": "gpl-2.0",
|
"license": "gpl-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@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.48.4",
|
"@stremio/stremio-core-web": "0.48.4",
|
||||||
"@stremio/stremio-icons": "5.4.0",
|
"@stremio/stremio-icons": "5.4.1",
|
||||||
"@stremio/stremio-video": "0.0.46",
|
"@stremio/stremio-video": "0.0.48",
|
||||||
"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",
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
"filter-invalid-dom-props": "3.0.1",
|
"filter-invalid-dom-props": "3.0.1",
|
||||||
"hat": "^0.0.3",
|
"hat": "^0.0.3",
|
||||||
"i18next": "^24.0.5",
|
"i18next": "^24.0.5",
|
||||||
"langs": "^2.0.0",
|
"langs": "github:Stremio/nodejs-langs",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
"lodash.intersection": "4.4.0",
|
"lodash.intersection": "4.4.0",
|
||||||
"lodash.isequal": "4.5.0",
|
"lodash.isequal": "4.5.0",
|
||||||
|
|
@ -36,7 +36,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#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
|
"stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
|
||||||
"url": "0.11.4",
|
"url": "0.11.4",
|
||||||
"use-long-press": "^3.2.0"
|
"use-long-press": "^3.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -67,6 +67,7 @@
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"readdirp": "4.0.2",
|
"readdirp": "4.0.2",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
|
"thread-loader": "^4.0.4",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.17.0",
|
"typescript-eslint": "^8.17.0",
|
||||||
|
|
@ -1869,7 +1870,6 @@
|
||||||
"version": "7.26.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
|
||||||
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1880,8 +1880,7 @@
|
||||||
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
||||||
"version": "0.14.1",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.25.9",
|
"version": "7.25.9",
|
||||||
|
|
@ -3261,7 +3260,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz",
|
||||||
"integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==",
|
"integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "8.42.0"
|
"@sentry/core": "8.42.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3273,7 +3271,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz",
|
||||||
"integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==",
|
"integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "8.42.0"
|
"@sentry/core": "8.42.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3285,7 +3282,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz",
|
||||||
"integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==",
|
"integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "8.42.0",
|
"@sentry-internal/browser-utils": "8.42.0",
|
||||||
"@sentry/core": "8.42.0"
|
"@sentry/core": "8.42.0"
|
||||||
|
|
@ -3298,7 +3294,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz",
|
||||||
"integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==",
|
"integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/replay": "8.42.0",
|
"@sentry-internal/replay": "8.42.0",
|
||||||
"@sentry/core": "8.42.0"
|
"@sentry/core": "8.42.0"
|
||||||
|
|
@ -3311,7 +3306,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz",
|
||||||
"integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==",
|
"integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "8.42.0",
|
"@sentry-internal/browser-utils": "8.42.0",
|
||||||
"@sentry-internal/feedback": "8.42.0",
|
"@sentry-internal/feedback": "8.42.0",
|
||||||
|
|
@ -3327,7 +3321,6 @@
|
||||||
"version": "8.42.0",
|
"version": "8.42.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz",
|
||||||
"integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==",
|
"integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.18"
|
"node": ">=14.18"
|
||||||
}
|
}
|
||||||
|
|
@ -3375,8 +3368,7 @@
|
||||||
"node_modules/@stremio/stremio-colors": {
|
"node_modules/@stremio/stremio-colors": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-colors/-/stremio-colors-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@stremio/stremio-colors/-/stremio-colors-5.2.0.tgz",
|
||||||
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg==",
|
"integrity": "sha512-dYlPgu9W/H7c9s1zmW5tiDnRenaUa4Hg1QCyOg1lhOcgSfM/bVTi5nnqX+IfvGTTUNA0zgzh8hI3o3miwnZxTg=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@stremio/stremio-core-web": {
|
"node_modules/@stremio/stremio-core-web": {
|
||||||
"version": "0.48.4",
|
"version": "0.48.4",
|
||||||
|
|
@ -3406,9 +3398,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@stremio/stremio-icons": {
|
"node_modules/@stremio/stremio-icons": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@stremio/stremio-icons/-/stremio-icons-5.4.1.tgz",
|
||||||
"integrity": "sha512-rRWNER+wLgMjxd6sKT0MMq4lzXDOobY3GNdT3NDeeymBtB/CD0YmYqQuUOyYDjEZ1btIbNaniUOBoPW9d3ZQ8A==",
|
"integrity": "sha512-7g4JP7tPRT1UDZxbuH/Urq7fc6te3joy8qyx/NGWIW7wO169TTISO7ZWdejzESvUVgZ/7i6rzkRmXZ3wefWcBg==",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
|
|
@ -3417,9 +3409,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@stremio/stremio-video": {
|
"node_modules/@stremio/stremio-video": {
|
||||||
"version": "0.0.46",
|
"version": "0.0.48",
|
||||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.46.tgz",
|
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.48.tgz",
|
||||||
"integrity": "sha512-U15CGB6CrUZKq3IKcEouAEH2RQoLy2+BI/hDStEYEACxlRlFaavKPI2opl37muh9TY089RnZVBYAM3yDidBZdg==",
|
"integrity": "sha512-6ALGXCZC4NPsfhPcrwFWQzvH6UMMRsgSkHetnOhv9WmZ5ubiyUdbBzj9atGiGuuQz8pRcze66ztrub+dsaQbpw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "6.0.3",
|
"buffer": "6.0.3",
|
||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
|
|
@ -10162,6 +10155,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-parse-better-errors": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/json-parse-even-better-errors": {
|
"node_modules/json-parse-even-better-errors": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
|
|
@ -10264,6 +10264,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/langs": {
|
"node_modules/langs": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/Stremio/nodejs-langs.git#584beab4032469f6b76be1d119a0ab25f31f4ab6",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/launch-editor": {
|
"node_modules/launch-editor": {
|
||||||
|
|
@ -13373,8 +13374,9 @@
|
||||||
},
|
},
|
||||||
"node_modules/stremio-translations": {
|
"node_modules/stremio-translations": {
|
||||||
"version": "1.44.9",
|
"version": "1.44.9",
|
||||||
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
|
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a0f50634202f748a57907b645d2cd92fbaa479dd",
|
||||||
"integrity": "sha512-SzaIGUMqQuMAq58sI9L/RKSs5O4eF8VKPMqnWFddBSg/tZOU9xuNYqjRPKT07cp8MRfzzGQmCKMByozTYfjdIA=="
|
"integrity": "sha512-JJpd1JJet3T6/VTNdZ2NZ7uvHJ4zkuyqo5BnTcDGqLVNO/OpicGqKhZjE4WGSgmuhsfPBU8T0ICCfzKu2xpvKg==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
|
@ -13939,6 +13941,29 @@
|
||||||
"node": ">=0.2.6"
|
"node": ">=0.2.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/thread-loader": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-tXagu6Hivd03wB2tiS1bqvw345sc7mKei32EgpYpq31ZLes9FN0mEK2nKzXLRFgwt3PsBB0E/MZDp159rDoqwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"json-parse-better-errors": "^1.0.2",
|
||||||
|
"loader-runner": "^4.1.0",
|
||||||
|
"neo-async": "^2.6.2",
|
||||||
|
"schema-utils": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"webpack": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thunky": {
|
"node_modules/thunky": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
||||||
|
|
|
||||||
11
package.json
Executable file → Normal file
11
package.json
Executable file → Normal file
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "stremio",
|
"name": "stremio",
|
||||||
"displayName": "Stremio",
|
"displayName": "Stremio",
|
||||||
"version": "5.0.0-beta.14",
|
"version": "5.0.0-beta.15",
|
||||||
"author": "Smart Code OOD",
|
"author": "Smart Code OOD",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "gpl-2.0",
|
"license": "gpl-2.0",
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
"@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.48.4",
|
"@stremio/stremio-core-web": "0.48.4",
|
||||||
"@stremio/stremio-icons": "5.4.0",
|
"@stremio/stremio-icons": "5.4.1",
|
||||||
"@stremio/stremio-video": "0.0.46",
|
"@stremio/stremio-video": "0.0.48",
|
||||||
"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",
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"filter-invalid-dom-props": "3.0.1",
|
"filter-invalid-dom-props": "3.0.1",
|
||||||
"hat": "^0.0.3",
|
"hat": "^0.0.3",
|
||||||
"i18next": "^24.0.5",
|
"i18next": "^24.0.5",
|
||||||
"langs": "^2.0.0",
|
"langs": "github:Stremio/nodejs-langs",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
"lodash.intersection": "4.4.0",
|
"lodash.intersection": "4.4.0",
|
||||||
"lodash.isequal": "4.5.0",
|
"lodash.isequal": "4.5.0",
|
||||||
|
|
@ -40,7 +40,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#f666d9a97cafa5aa150878b5c51a2896b5f4f1b2",
|
"stremio-translations": "github:Stremio/stremio-translations#a0f50634202f748a57907b645d2cd92fbaa479dd",
|
||||||
"url": "0.11.4",
|
"url": "0.11.4",
|
||||||
"use-long-press": "^3.2.0"
|
"use-long-press": "^3.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -71,6 +71,7 @@
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"readdirp": "4.0.2",
|
"readdirp": "4.0.2",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
|
"thread-loader": "^4.0.4",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.17.0",
|
"typescript-eslint": "^8.17.0",
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: landscape) and (max-height: @xsmall) {
|
@media (orientation: landscape) and (max-height: @minimum) {
|
||||||
.event-modal {
|
.event-modal {
|
||||||
.modal-dialog-container {
|
.modal-dialog-container {
|
||||||
.modal-dialog-content {
|
.modal-dialog-content {
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@
|
||||||
object-position: center;
|
object-position: center;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
overflow-clip-margin: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-icon {
|
.placeholder-icon {
|
||||||
|
|
|
||||||
60
src/common/Transition/Transition.tsx
Normal file
60
src/common/Transition/Transition.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { cloneElement, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: JSX.Element,
|
||||||
|
when: boolean,
|
||||||
|
name: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Transition = ({ children, when, name }: Props) => {
|
||||||
|
const [element, setElement] = useState<HTMLElement | null>(null);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
const [state, setState] = useState('enter');
|
||||||
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
|
const callbackRef = useCallback((element: HTMLElement | null) => {
|
||||||
|
setElement(element);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const className = useMemo(() => {
|
||||||
|
const animationClass = `${name}-${state}`;
|
||||||
|
const activeClass = active ? `${name}-active` : null;
|
||||||
|
|
||||||
|
return children && classNames(
|
||||||
|
children.props.className,
|
||||||
|
animationClass,
|
||||||
|
activeClass,
|
||||||
|
);
|
||||||
|
}, [name, state, active, children]);
|
||||||
|
|
||||||
|
const onTransitionEnd = useCallback(() => {
|
||||||
|
state === 'exit' && setMounted(false);
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState(when ? 'enter' : 'exit');
|
||||||
|
when && setMounted(true);
|
||||||
|
}, [when]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setActive(!!element);
|
||||||
|
});
|
||||||
|
}, [element]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
element?.addEventListener('transitionend', onTransitionEnd);
|
||||||
|
return () => element?.removeEventListener('transitionend', onTransitionEnd);
|
||||||
|
}, [element, onTransitionEnd]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
mounted && cloneElement(children, {
|
||||||
|
ref: callbackRef,
|
||||||
|
className,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transition;
|
||||||
2
src/common/Transition/index.ts
Normal file
2
src/common/Transition/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import Transition from './Transition';
|
||||||
|
export default Transition;
|
||||||
|
|
@ -38,4 +38,17 @@
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(0%);
|
transform: translateY(0%);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-enter {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-active {
|
||||||
|
transform: translateX(0%);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.32, 0, 0.67, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-exit {
|
||||||
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ const Slider = require('./Slider');
|
||||||
const { default: TextInput } = require('./TextInput');
|
const { default: TextInput } = require('./TextInput');
|
||||||
const { ToastProvider, useToast } = require('./Toast');
|
const { ToastProvider, useToast } = require('./Toast');
|
||||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||||
|
const { default: Transition } = require('./Transition');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||||
const CONSTANTS = require('./CONSTANTS');
|
const CONSTANTS = require('./CONSTANTS');
|
||||||
|
|
@ -36,6 +37,7 @@ const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
|
||||||
const getVisibleChildrenRange = require('./getVisibleChildrenRange');
|
const getVisibleChildrenRange = require('./getVisibleChildrenRange');
|
||||||
const interfaceLanguages = require('./interfaceLanguages.json');
|
const interfaceLanguages = require('./interfaceLanguages.json');
|
||||||
const languageNames = require('./languageNames.json');
|
const languageNames = require('./languageNames.json');
|
||||||
|
const languages = require('./languages');
|
||||||
const routesRegexp = require('./routesRegexp');
|
const routesRegexp = require('./routesRegexp');
|
||||||
const useAnimationFrame = require('./useAnimationFrame');
|
const useAnimationFrame = require('./useAnimationFrame');
|
||||||
const useBinaryState = require('./useBinaryState');
|
const useBinaryState = require('./useBinaryState');
|
||||||
|
|
@ -84,6 +86,7 @@ module.exports = {
|
||||||
useToast,
|
useToast,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Transition,
|
||||||
Video,
|
Video,
|
||||||
comparatorWithPriorities,
|
comparatorWithPriorities,
|
||||||
CONSTANTS,
|
CONSTANTS,
|
||||||
|
|
@ -92,6 +95,7 @@ module.exports = {
|
||||||
getVisibleChildrenRange,
|
getVisibleChildrenRange,
|
||||||
interfaceLanguages,
|
interfaceLanguages,
|
||||||
languageNames,
|
languageNames,
|
||||||
|
languages,
|
||||||
routesRegexp,
|
routesRegexp,
|
||||||
useAnimationFrame,
|
useAnimationFrame,
|
||||||
useBinaryState,
|
useBinaryState,
|
||||||
|
|
|
||||||
25
src/common/languages.ts
Normal file
25
src/common/languages.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import langs from 'langs';
|
||||||
|
|
||||||
|
const all = langs.all().map((lang) => ({
|
||||||
|
...lang,
|
||||||
|
code: lang['2'],
|
||||||
|
label: lang.local,
|
||||||
|
alpha2: lang['1'],
|
||||||
|
alpha3: [lang['2'], lang['2B'], lang['2T'], lang['3']],
|
||||||
|
locale: lang['locale'],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const find = (code: string) => {
|
||||||
|
return all.find(({ alpha2, alpha3, locale }) => [alpha2, ...alpha3, locale].includes(code));
|
||||||
|
};
|
||||||
|
|
||||||
|
const label = (code: string) => {
|
||||||
|
const language = find(code);
|
||||||
|
return language?.label ?? code;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
all,
|
||||||
|
find,
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
|
@ -9,16 +9,20 @@ const { Button, Image, Multiselect } = require('stremio/common');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const Stream = require('./Stream');
|
const Stream = require('./Stream');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
const { usePlatform } = require('stremio/common');
|
||||||
|
|
||||||
const ALL_ADDONS_KEY = 'ALL';
|
const ALL_ADDONS_KEY = 'ALL';
|
||||||
|
|
||||||
const StreamsList = ({ className, video, ...props }) => {
|
const StreamsList = ({ className, video, ...props }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { core } = useServices();
|
const { core } = useServices();
|
||||||
|
const platform = usePlatform();
|
||||||
|
const streamsContainerRef = React.useRef(null);
|
||||||
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
|
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
|
||||||
const onAddonSelected = React.useCallback((event) => {
|
const onAddonSelected = React.useCallback((event) => {
|
||||||
|
streamsContainerRef.current.scrollTo({ top: 0, left: 0, behavior: platform.name === 'ios' ? 'smooth' : 'instant' });
|
||||||
setSelectedAddon(event.value);
|
setSelectedAddon(event.value);
|
||||||
}, []);
|
}, [platform]);
|
||||||
const backButtonOnClick = React.useCallback(() => {
|
const backButtonOnClick = React.useCallback(() => {
|
||||||
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
|
if (video.deepLinks && typeof video.deepLinks.metaDetailsVideos === 'string') {
|
||||||
window.location.replace(video.deepLinks.metaDetailsVideos + (
|
window.location.replace(video.deepLinks.metaDetailsVideos + (
|
||||||
|
|
@ -142,7 +146,7 @@ const StreamsList = ({ className, video, ...props }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
<div className={styles['streams-container']}>
|
<div className={styles['streams-container']} ref={streamsContainerRef}>
|
||||||
{filteredStreams.map((stream, index) => (
|
{filteredStreams.map((stream, index) => (
|
||||||
<Stream
|
<Stream
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@
|
||||||
background-color: var(--overlay-color);
|
background-color: var(--overlay-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: var(--primary-foreground-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&>:first-child {
|
&>:first-child {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
src/routes/Player/AudioMenu/AudioMenu.less
Normal file
78
src/routes/Player/AudioMenu/AudioMenu.less
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
.audio-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
flex: none;
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 16rem;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex: none;
|
||||||
|
align-self: stretch;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
|
||||||
|
.option {
|
||||||
|
flex: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
height: 4rem;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
&:global(.selected), &:hover {
|
||||||
|
background-color: var(--overlay-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.lang, .label {
|
||||||
|
flex: auto;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-placeholder-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex: none;
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: var(--secondary-accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/routes/Player/AudioMenu/AudioMenu.tsx
Normal file
66
src/routes/Player/AudioMenu/AudioMenu.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React, { MouseEvent, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Button, languages } from 'stremio/common';
|
||||||
|
import styles from './AudioMenu.less';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className: string,
|
||||||
|
selectedAudioTrackId: string | null,
|
||||||
|
audioTracks: AudioTrack[],
|
||||||
|
onAudioTrackSelected: (id: string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AudioMenu = ({ className, selectedAudioTrackId, audioTracks, onAudioTrackSelected }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onAudioTrackClick = useCallback(({ currentTarget }: MouseEvent) => {
|
||||||
|
const id = currentTarget.getAttribute('data-id')!;
|
||||||
|
onAudioTrackSelected && onAudioTrackSelected(id);
|
||||||
|
}, [onAudioTrackSelected]);
|
||||||
|
|
||||||
|
const onMouseDown = (event: MouseEvent) => {
|
||||||
|
// @ts-expect-error: Property 'audioMenuClosePrevented' does not exist on type 'MouseEvent'.
|
||||||
|
event.nativeEvent.audioMenuClosePrevented = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, styles['audio-menu'])} onMouseDown={onMouseDown}>
|
||||||
|
<div className={styles['container']}>
|
||||||
|
<div className={styles['header']}>
|
||||||
|
{ t('AUDIO_TRACKS') }
|
||||||
|
</div>
|
||||||
|
<div className={styles['list']}>
|
||||||
|
{
|
||||||
|
audioTracks.map(({ id, label, lang }, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
title={label}
|
||||||
|
className={classNames(styles['option'], { 'selected': selectedAudioTrackId === id })}
|
||||||
|
data-id={id}
|
||||||
|
onClick={onAudioTrackClick}
|
||||||
|
>
|
||||||
|
<div className={styles['info']}>
|
||||||
|
<div className={styles['lang']}>
|
||||||
|
{languages.label(lang)}
|
||||||
|
</div>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
selectedAudioTrackId === id ?
|
||||||
|
<div className={styles['icon']} />
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioMenu;
|
||||||
2
src/routes/Player/AudioMenu/index.ts
Normal file
2
src/routes/Player/AudioMenu/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import AudioMenu from './AudioMenu';
|
||||||
|
export default AudioMenu;
|
||||||
|
|
@ -35,9 +35,9 @@ const ControlBar = ({
|
||||||
onVolumeChangeRequested,
|
onVolumeChangeRequested,
|
||||||
onSeekRequested,
|
onSeekRequested,
|
||||||
onToggleSubtitlesMenu,
|
onToggleSubtitlesMenu,
|
||||||
onToggleInfoMenu,
|
onToggleAudioMenu,
|
||||||
onToggleSpeedMenu,
|
onToggleSpeedMenu,
|
||||||
onToggleVideosMenu,
|
onToggleSideDrawer,
|
||||||
onToggleOptionsMenu,
|
onToggleOptionsMenu,
|
||||||
onToggleStatisticsMenu,
|
onToggleStatisticsMenu,
|
||||||
...props
|
...props
|
||||||
|
|
@ -48,8 +48,8 @@ const ControlBar = ({
|
||||||
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
|
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||||
}, []);
|
}, []);
|
||||||
const onInfoButtonMouseDown = React.useCallback((event) => {
|
const onAudioButtonMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.infoMenuClosePrevented = true;
|
event.nativeEvent.audioMenuClosePrevented = true;
|
||||||
}, []);
|
}, []);
|
||||||
const onSpeedButtonMouseDown = React.useCallback((event) => {
|
const onSpeedButtonMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.speedMenuClosePrevented = true;
|
event.nativeEvent.speedMenuClosePrevented = true;
|
||||||
|
|
@ -151,18 +151,18 @@ const ControlBar = ({
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onToggleSpeedMenu}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onToggleSpeedMenu}>
|
||||||
<Icon className={styles['icon']} name={'speed'} />
|
<Icon className={styles['icon']} name={'speed'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': metaItem === null || metaItem.type !== 'Ready' })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onToggleInfoMenu}>
|
|
||||||
<Icon className={styles['icon']} name={'about'} />
|
|
||||||
</Button>
|
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': !chromecastServiceActive })} tabIndex={-1} onClick={onChromecastButtonClick}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': !chromecastServiceActive })} tabIndex={-1} onClick={onChromecastButtonClick}>
|
||||||
<Icon className={styles['icon']} name={'cast'} />
|
<Icon className={styles['icon']} name={'cast'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': (!Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0) && (!Array.isArray(audioTracks) || audioTracks.length === 0) })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onToggleSubtitlesMenu}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': !Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0 })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onToggleSubtitlesMenu}>
|
||||||
<Icon className={styles['icon']} name={'subtitles'} />
|
<Icon className={styles['icon']} name={'subtitles'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': !Array.isArray(audioTracks) || audioTracks.length === 0 })} tabIndex={-1} onMouseDown={onAudioButtonMouseDown} onClick={onToggleAudioMenu}>
|
||||||
|
<Icon className={styles['icon']} name={'audio-tracks'} />
|
||||||
|
</Button>
|
||||||
{
|
{
|
||||||
metaItem?.content?.videos?.length > 0 ?
|
metaItem?.content?.videos?.length > 0 ?
|
||||||
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onVideosButtonMouseDown} onClick={onToggleVideosMenu}>
|
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onVideosButtonMouseDown} onClick={onToggleSideDrawer}>
|
||||||
<Icon className={styles['icon']} name={'episodes'} />
|
<Icon className={styles['icon']} name={'episodes'} />
|
||||||
</Button>
|
</Button>
|
||||||
:
|
:
|
||||||
|
|
@ -200,9 +200,9 @@ ControlBar.propTypes = {
|
||||||
onVolumeChangeRequested: PropTypes.func,
|
onVolumeChangeRequested: PropTypes.func,
|
||||||
onSeekRequested: PropTypes.func,
|
onSeekRequested: PropTypes.func,
|
||||||
onToggleSubtitlesMenu: PropTypes.func,
|
onToggleSubtitlesMenu: PropTypes.func,
|
||||||
onToggleInfoMenu: PropTypes.func,
|
onToggleAudioMenu: PropTypes.func,
|
||||||
onToggleSpeedMenu: PropTypes.func,
|
onToggleSpeedMenu: PropTypes.func,
|
||||||
onToggleVideosMenu: PropTypes.func,
|
onToggleSideDrawer: PropTypes.func,
|
||||||
onToggleOptionsMenu: PropTypes.func,
|
onToggleOptionsMenu: PropTypes.func,
|
||||||
onToggleStatisticsMenu: PropTypes.func,
|
onToggleStatisticsMenu: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -100,14 +100,16 @@
|
||||||
|
|
||||||
.control-bar-buttons-menu-container {
|
.control-bar-buttons-menu-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.15rem;
|
right: 0rem;
|
||||||
bottom: 4.5rem;
|
bottom: 4.5rem;
|
||||||
flex-direction: column;
|
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
max-width: calc(100dvw - 1rem);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
background-color: var(--modal-background-color);
|
background-color: var(--modal-background-color);
|
||||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
&:not(:global(.open)) {
|
&:not(:global(.open)) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const classnames = require('classnames');
|
|
||||||
// const Stream = require('stremio/routes/MetaDetails/StreamsList/Stream');
|
|
||||||
// const AddonDetails = require('stremio/common/AddonDetailsModal/AddonDetails');
|
|
||||||
const { MetaPreview, CONSTANTS } = require('stremio/common');
|
|
||||||
const styles = require('./styles');
|
|
||||||
|
|
||||||
const InfoMenu = ({ className, ...props }) => {
|
|
||||||
const metaItem = React.useMemo(() => {
|
|
||||||
return props.metaItem !== null ?
|
|
||||||
{
|
|
||||||
...props.metaItem,
|
|
||||||
links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY)
|
|
||||||
}
|
|
||||||
:
|
|
||||||
null;
|
|
||||||
}, [props.metaItem]);
|
|
||||||
const onMouseDown = React.useCallback((event) => {
|
|
||||||
event.nativeEvent.infoMenuClosePrevented = true;
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div className={classnames(className, styles['info-menu-container'])} onMouseDown={onMouseDown}>
|
|
||||||
{
|
|
||||||
metaItem !== null ?
|
|
||||||
<MetaPreview
|
|
||||||
className={styles['meta-preview']}
|
|
||||||
compact={true}
|
|
||||||
name={metaItem.name}
|
|
||||||
logo={metaItem.logo}
|
|
||||||
runtime={metaItem.runtime}
|
|
||||||
releaseInfo={metaItem.releaseInfo}
|
|
||||||
released={metaItem.released}
|
|
||||||
description={metaItem.description}
|
|
||||||
links={metaItem.links}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{/* {
|
|
||||||
props.stream !== null ?
|
|
||||||
<Stream
|
|
||||||
{...props.stream}
|
|
||||||
className={classnames(styles['stream'], 'active')}
|
|
||||||
addonName={props.addon !== null ? props.addon.manifest.name : ''}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
} */}
|
|
||||||
{/* {
|
|
||||||
props.addon !== null ?
|
|
||||||
<AddonDetails
|
|
||||||
id={props.addon.manifest.id}
|
|
||||||
name={props.addon.manifest.name}
|
|
||||||
version={props.addon.manifest.version}
|
|
||||||
logo={props.addon.manifest.logo}
|
|
||||||
description={props.addon.manifest.description}
|
|
||||||
types={props.addon.manifest.types}
|
|
||||||
transportUrl={props.addon.transportUrl}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
} */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
InfoMenu.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
metaItem: PropTypes.object,
|
|
||||||
addon: PropTypes.object,
|
|
||||||
stream: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = InfoMenu;
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
const InfoMenu = require('./InfoMenu');
|
|
||||||
|
|
||||||
module.exports = InfoMenu;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
.info-menu-container {
|
|
||||||
width: 30rem;
|
|
||||||
padding: 2rem;
|
|
||||||
|
|
||||||
.stream {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,18 +8,19 @@ const langs = require('langs');
|
||||||
const { useTranslation } = require('react-i18next');
|
const { useTranslation } = require('react-i18next');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { HorizontalNavBar, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender } = require('stremio/common');
|
const { HorizontalNavBar, Transition, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender } = require('stremio/common');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||||
const Error = require('./Error');
|
const Error = require('./Error');
|
||||||
const ControlBar = require('./ControlBar');
|
const ControlBar = require('./ControlBar');
|
||||||
const NextVideoPopup = require('./NextVideoPopup');
|
const NextVideoPopup = require('./NextVideoPopup');
|
||||||
const StatisticsMenu = require('./StatisticsMenu');
|
const StatisticsMenu = require('./StatisticsMenu');
|
||||||
const InfoMenu = require('./InfoMenu');
|
|
||||||
const OptionsMenu = require('./OptionsMenu');
|
const OptionsMenu = require('./OptionsMenu');
|
||||||
const VideosMenu = require('./VideosMenu');
|
|
||||||
const SubtitlesMenu = require('./SubtitlesMenu');
|
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||||
|
const { default: AudioMenu } = require('./AudioMenu');
|
||||||
const SpeedMenu = require('./SpeedMenu');
|
const SpeedMenu = require('./SpeedMenu');
|
||||||
|
const { default: SideDrawerButton } = require('./SideDrawerButton');
|
||||||
|
const { default: SideDrawer } = require('./SideDrawer');
|
||||||
const usePlayer = require('./usePlayer');
|
const usePlayer = require('./usePlayer');
|
||||||
const useSettings = require('./useSettings');
|
const useSettings = require('./useSettings');
|
||||||
const useStatistics = require('./useStatistics');
|
const useStatistics = require('./useStatistics');
|
||||||
|
|
@ -54,23 +55,23 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
|
|
||||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
const [audioMenuOpen, , closeAudioMenu, toggleAudioMenu] = useBinaryState(false);
|
||||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||||
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
|
||||||
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||||
|
const [sideDrawerOpen, , closeSideDrawer, toggleSideDrawer] = useBinaryState(false);
|
||||||
|
|
||||||
const menusOpen = React.useMemo(() => {
|
const menusOpen = React.useMemo(() => {
|
||||||
return optionsMenuOpen || subtitlesMenuOpen || infoMenuOpen || speedMenuOpen || videosMenuOpen || statisticsMenuOpen;
|
return optionsMenuOpen || subtitlesMenuOpen || audioMenuOpen || speedMenuOpen || statisticsMenuOpen || sideDrawerOpen;
|
||||||
}, [optionsMenuOpen, subtitlesMenuOpen, infoMenuOpen, speedMenuOpen, videosMenuOpen, statisticsMenuOpen]);
|
}, [optionsMenuOpen, subtitlesMenuOpen, audioMenuOpen, speedMenuOpen, statisticsMenuOpen, sideDrawerOpen]);
|
||||||
|
|
||||||
const closeMenus = React.useCallback(() => {
|
const closeMenus = React.useCallback(() => {
|
||||||
closeOptionsMenu();
|
closeOptionsMenu();
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeInfoMenu();
|
closeAudioMenu();
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
closeVideosMenu();
|
|
||||||
closeStatisticsMenu();
|
closeStatisticsMenu();
|
||||||
|
closeSideDrawer();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const overlayHidden = React.useMemo(() => {
|
const overlayHidden = React.useMemo(() => {
|
||||||
|
|
@ -237,18 +238,17 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
|
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
}
|
}
|
||||||
if (!event.nativeEvent.infoMenuClosePrevented) {
|
if (!event.nativeEvent.audioMenuClosePrevented) {
|
||||||
closeInfoMenu();
|
closeAudioMenu();
|
||||||
}
|
}
|
||||||
if (!event.nativeEvent.speedMenuClosePrevented) {
|
if (!event.nativeEvent.speedMenuClosePrevented) {
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
}
|
}
|
||||||
if (!event.nativeEvent.videosMenuClosePrevented) {
|
|
||||||
closeVideosMenu();
|
|
||||||
}
|
|
||||||
if (!event.nativeEvent.statisticsMenuClosePrevented) {
|
if (!event.nativeEvent.statisticsMenuClosePrevented) {
|
||||||
closeStatisticsMenu();
|
closeStatisticsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeSideDrawer();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onContainerMouseMove = React.useCallback((event) => {
|
const onContainerMouseMove = React.useCallback((event) => {
|
||||||
|
|
@ -405,18 +405,16 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if ((!Array.isArray(video.state.subtitlesTracks) || video.state.subtitlesTracks.length === 0) &&
|
if ((!Array.isArray(video.state.subtitlesTracks) || video.state.subtitlesTracks.length === 0) &&
|
||||||
(!Array.isArray(video.state.extraSubtitlesTracks) || video.state.extraSubtitlesTracks.length === 0) &&
|
(!Array.isArray(video.state.extraSubtitlesTracks) || video.state.extraSubtitlesTracks.length === 0)) {
|
||||||
(!Array.isArray(video.state.audioTracks) || video.state.audioTracks.length === 0)) {
|
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
}
|
}
|
||||||
}, [video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks]);
|
}, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (player.metaItem === null || player.metaItem.type !== 'Ready') {
|
if (!Array.isArray(video.state.audioTracks) || video.state.audioTracks.length === 0) {
|
||||||
closeInfoMenu();
|
closeAudioMenu();
|
||||||
closeVideosMenu();
|
|
||||||
}
|
}
|
||||||
}, [player.metaItem]);
|
}, [video.state.audioTracks]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (video.state.playbackSpeed === null) {
|
if (video.state.playbackSpeed === null) {
|
||||||
|
|
@ -510,17 +508,24 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
case 'KeyS': {
|
case 'KeyS': {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
if ((Array.isArray(video.state.subtitlesTracks) && video.state.subtitlesTracks.length > 0) ||
|
if ((Array.isArray(video.state.subtitlesTracks) && video.state.subtitlesTracks.length > 0) ||
|
||||||
(Array.isArray(video.state.extraSubtitlesTracks) && video.state.extraSubtitlesTracks.length > 0) ||
|
(Array.isArray(video.state.extraSubtitlesTracks) && video.state.extraSubtitlesTracks.length > 0)) {
|
||||||
(Array.isArray(video.state.audioTracks) && video.state.audioTracks.length > 0)) {
|
|
||||||
toggleSubtitlesMenu();
|
toggleSubtitlesMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'KeyA': {
|
||||||
|
closeMenus();
|
||||||
|
if (Array.isArray(video.state.audioTracks) && video.state.audioTracks.length > 0) {
|
||||||
|
toggleAudioMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'KeyI': {
|
case 'KeyI': {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||||
toggleInfoMenu();
|
toggleSideDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -533,14 +538,6 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyV': {
|
|
||||||
closeMenus();
|
|
||||||
if (player.metaItem !== null && player.metaItem.type === 'Ready' && player.metaItem?.content?.videos?.length > 0) {
|
|
||||||
toggleVideosMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'KeyD': {
|
case 'KeyD': {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') {
|
if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') {
|
||||||
|
|
@ -581,7 +578,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
window.removeEventListener('wheel', onWheel);
|
window.removeEventListener('wheel', onWheel);
|
||||||
};
|
};
|
||||||
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu, toggleStatisticsMenu]);
|
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
video.events.on('error', onError);
|
video.events.on('error', onError);
|
||||||
|
|
@ -666,6 +663,15 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
|
{
|
||||||
|
player.metaItem?.type === 'Ready' ?
|
||||||
|
<SideDrawerButton
|
||||||
|
className={classnames(styles['layer'], styles['side-drawer-button-layer'])}
|
||||||
|
onClick={toggleSideDrawer}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
<ControlBar
|
<ControlBar
|
||||||
className={classnames(styles['layer'], styles['control-bar-layer'])}
|
className={classnames(styles['layer'], styles['control-bar-layer'])}
|
||||||
paused={video.state.paused}
|
paused={video.state.paused}
|
||||||
|
|
@ -690,10 +696,10 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onSeekRequested={onSeekRequested}
|
onSeekRequested={onSeekRequested}
|
||||||
onToggleOptionsMenu={toggleOptionsMenu}
|
onToggleOptionsMenu={toggleOptionsMenu}
|
||||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||||
onToggleInfoMenu={toggleInfoMenu}
|
onToggleAudioMenu={toggleAudioMenu}
|
||||||
onToggleSpeedMenu={toggleSpeedMenu}
|
onToggleSpeedMenu={toggleSpeedMenu}
|
||||||
onToggleVideosMenu={toggleVideosMenu}
|
|
||||||
onToggleStatisticsMenu={toggleStatisticsMenu}
|
onToggleStatisticsMenu={toggleStatisticsMenu}
|
||||||
|
onToggleSideDrawer={toggleSideDrawer}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
|
|
@ -718,12 +724,18 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
||||||
|
<SideDrawer
|
||||||
|
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
||||||
|
metaItem={player.metaItem?.content}
|
||||||
|
seriesInfo={player.seriesInfo}
|
||||||
|
closeSideDrawer={closeSideDrawer}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
{
|
{
|
||||||
subtitlesMenuOpen ?
|
subtitlesMenuOpen ?
|
||||||
<SubtitlesMenu
|
<SubtitlesMenu
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
audioTracks={video.state.audioTracks}
|
|
||||||
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
|
||||||
subtitlesTracks={video.state.subtitlesTracks}
|
subtitlesTracks={video.state.subtitlesTracks}
|
||||||
selectedSubtitlesTrackId={video.state.selectedSubtitlesTrackId}
|
selectedSubtitlesTrackId={video.state.selectedSubtitlesTrackId}
|
||||||
subtitlesOffset={video.state.subtitlesOffset}
|
subtitlesOffset={video.state.subtitlesOffset}
|
||||||
|
|
@ -735,7 +747,6 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
extraSubtitlesSize={video.state.extraSubtitlesSize}
|
extraSubtitlesSize={video.state.extraSubtitlesSize}
|
||||||
onSubtitlesTrackSelected={onSubtitlesTrackSelected}
|
onSubtitlesTrackSelected={onSubtitlesTrackSelected}
|
||||||
onExtraSubtitlesTrackSelected={onExtraSubtitlesTrackSelected}
|
onExtraSubtitlesTrackSelected={onExtraSubtitlesTrackSelected}
|
||||||
onAudioTrackSelected={onAudioTrackSelected}
|
|
||||||
onSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
onSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||||
onSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
onSubtitlesSizeChanged={onSubtitlesSizeChanged}
|
||||||
onExtraSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
onExtraSubtitlesOffsetChanged={onSubtitlesOffsetChanged}
|
||||||
|
|
@ -746,12 +757,12 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
infoMenuOpen ?
|
audioMenuOpen ?
|
||||||
<InfoMenu
|
<AudioMenu
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
stream={player.selected !== null ? player.selected.stream : null}
|
audioTracks={video.state.audioTracks}
|
||||||
addon={player.addon}
|
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
||||||
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
onAudioTrackSelected={onAudioTrackSelected}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
@ -766,16 +777,6 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
|
||||||
videosMenuOpen ?
|
|
||||||
<VideosMenu
|
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
|
||||||
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
|
||||||
seriesInfo={player.seriesInfo}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
optionsMenuOpen ?
|
optionsMenuOpen ?
|
||||||
<OptionsMenu
|
<OptionsMenu
|
||||||
|
|
|
||||||
115
src/routes/Player/SideDrawer/SideDrawer.less
Normal file
115
src/routes/Player/SideDrawer/SideDrawer.less
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
@import (reference) '~stremio/common/screen-sizes.less';
|
||||||
|
|
||||||
|
:import('~stremio/common/MetaPreview/styles.less') {
|
||||||
|
action-buttons-container: action-buttons-container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@padding: 1rem;
|
||||||
|
|
||||||
|
.side-drawer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: @padding;
|
||||||
|
height: 100dvh;
|
||||||
|
max-width: 35rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
border-top-left-radius: var(--border-radius);
|
||||||
|
border-bottom-left-radius: var(--border-radius);
|
||||||
|
background-color: var(--modal-background-color);
|
||||||
|
box-shadow: 0 1.35rem 2.7rem var(--color-background-dark5-40), 0 1.1rem 0.85rem var(--color-background-dark5-20);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 1.3rem;
|
||||||
|
right: 1.3rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
transition: 0.3s all ease-in-out;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: 0.3s opacity ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--overlay-color);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: @padding;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: none;
|
||||||
|
|
||||||
|
.side-drawer-meta-preview {
|
||||||
|
.action-buttons-container {
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-content {
|
||||||
|
flex: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.videos {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @small) {
|
||||||
|
.side-drawer {
|
||||||
|
max-width: 40dvw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: @xsmall) {
|
||||||
|
.side-drawer {
|
||||||
|
max-width: 100dvw;
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: landscape) and (max-width: @xsmall) {
|
||||||
|
.side-drawer {
|
||||||
|
max-width: 50dvw;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
max-height: 30dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @xxsmall) {
|
||||||
|
.side-drawer {
|
||||||
|
padding: calc(@padding / 2);
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: calc(@padding / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/routes/Player/SideDrawer/SideDrawer.tsx
Normal file
118
src/routes/Player/SideDrawer/SideDrawer.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import React, { useMemo, useCallback, useState, forwardRef, memo } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Icon from '@stremio/stremio-icons/react';
|
||||||
|
import { useServices } from 'stremio/services';
|
||||||
|
import { CONSTANTS } from 'stremio/common';
|
||||||
|
import MetaPreview from 'stremio/common/MetaPreview/MetaPreview';
|
||||||
|
import Video from 'stremio/common/Video/Video';
|
||||||
|
import SeasonsBar from 'stremio/routes/MetaDetails/VideosList/SeasonsBar';
|
||||||
|
import styles from './SideDrawer.less';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
seriesInfo: SeriesInfo;
|
||||||
|
metaItem: MetaItem;
|
||||||
|
closeSideDrawer: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SideDrawer = memo(forwardRef<HTMLDivElement, Props>(({ seriesInfo, className, closeSideDrawer, ...props }: Props, ref) => {
|
||||||
|
const { core } = useServices();
|
||||||
|
const [season, setSeason] = useState<number>(seriesInfo?.season);
|
||||||
|
const metaItem = useMemo(() => {
|
||||||
|
return seriesInfo ?
|
||||||
|
{
|
||||||
|
...props.metaItem,
|
||||||
|
links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY)
|
||||||
|
}
|
||||||
|
:
|
||||||
|
props.metaItem;
|
||||||
|
}, [props.metaItem]);
|
||||||
|
const videos = useMemo(() => {
|
||||||
|
return Array.isArray(metaItem.videos) ?
|
||||||
|
metaItem.videos.filter((video) => video.season === season)
|
||||||
|
:
|
||||||
|
metaItem.videos;
|
||||||
|
}, [metaItem, season]);
|
||||||
|
const seasons = useMemo(() => {
|
||||||
|
return props.metaItem.videos
|
||||||
|
.map(({ season }) => season)
|
||||||
|
.filter((season, index, seasons) => {
|
||||||
|
return seasons.indexOf(season) === index;
|
||||||
|
})
|
||||||
|
.sort((a, b) => (a || Number.MAX_SAFE_INTEGER) - (b || Number.MAX_SAFE_INTEGER));
|
||||||
|
}, [props.metaItem.videos]);
|
||||||
|
|
||||||
|
const seasonOnSelect = useCallback((event: { value: string }) => {
|
||||||
|
setSeason(parseInt(event.value));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMarkVideoAsWatched = useCallback((video: Video, watched: boolean) => {
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Player',
|
||||||
|
args: {
|
||||||
|
action: 'MarkVideoAsWatched',
|
||||||
|
args: [video, !watched]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMouseDown = (event: React.MouseEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={classNames(styles['side-drawer'], className)} onMouseDown={onMouseDown}>
|
||||||
|
<div className={styles['close-button']} onClick={closeSideDrawer}>
|
||||||
|
<Icon className={styles['icon']} name={'chevron-forward'} />
|
||||||
|
</div>
|
||||||
|
<div className={styles['info']}>
|
||||||
|
<MetaPreview
|
||||||
|
className={styles['side-drawer-meta-preview']}
|
||||||
|
compact={true}
|
||||||
|
name={metaItem.name}
|
||||||
|
logo={metaItem.logo}
|
||||||
|
runtime={metaItem.runtime}
|
||||||
|
releaseInfo={metaItem.releaseInfo}
|
||||||
|
released={metaItem.released}
|
||||||
|
description={metaItem.description}
|
||||||
|
links={metaItem.links}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
seriesInfo ?
|
||||||
|
<div className={styles['series-content']}>
|
||||||
|
<SeasonsBar
|
||||||
|
season={season}
|
||||||
|
seasons={seasons}
|
||||||
|
onSelect={seasonOnSelect}
|
||||||
|
/>
|
||||||
|
<div className={styles['videos']}>
|
||||||
|
{videos.map((video, index) => (
|
||||||
|
<Video
|
||||||
|
key={index}
|
||||||
|
className={styles['video']}
|
||||||
|
id={video.id}
|
||||||
|
title={video.title}
|
||||||
|
thumbnail={video.thumbnail}
|
||||||
|
episode={video.episode}
|
||||||
|
released={video.released}
|
||||||
|
upcoming={video.upcoming}
|
||||||
|
watched={video.watched}
|
||||||
|
progress={video.progress}
|
||||||
|
deepLinks={video.deepLinks}
|
||||||
|
scheduled={video.scheduled}
|
||||||
|
onMarkVideoAsWatched={onMarkVideoAsWatched}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default SideDrawer;
|
||||||
2
src/routes/Player/SideDrawer/index.ts
Normal file
2
src/routes/Player/SideDrawer/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import SideDrawer from './SideDrawer';
|
||||||
|
export default SideDrawer;
|
||||||
47
src/routes/Player/SideDrawerButton/SideDrawerButton.less
Normal file
47
src/routes/Player/SideDrawerButton/SideDrawerButton.less
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
@import (reference) '~stremio/common/screen-sizes.less';
|
||||||
|
|
||||||
|
.side-drawer-button {
|
||||||
|
height: 12.5rem;
|
||||||
|
width: 7.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: var(--modal-background-color);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
will-change: opacity;
|
||||||
|
transition: opacity 0.3s ease-in-out, border-radius 0.3s ease-in-out;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: 0.3s opacity ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @xsmall) {
|
||||||
|
.side-drawer-button {
|
||||||
|
height: 8rem;
|
||||||
|
width: 4.5rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/routes/Player/SideDrawerButton/SideDrawerButton.tsx
Normal file
21
src/routes/Player/SideDrawerButton/SideDrawerButton.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (C) 2017-2024 Smart code 203358507
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Icon from '@stremio/stremio-icons/react';
|
||||||
|
import styles from './SideDrawerButton.less';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className: string,
|
||||||
|
onClick: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SideDrawerButton = ({ className, onClick }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, styles['side-drawer-button'])} onClick={onClick}>
|
||||||
|
<Icon name={'chevron-back'} className={styles['icon']} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SideDrawerButton;
|
||||||
2
src/routes/Player/SideDrawerButton/index.ts
Normal file
2
src/routes/Player/SideDrawerButton/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import SideDrawerButton from './SideDrawerButton';
|
||||||
|
export default SideDrawerButton;
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const { Button, CONSTANTS, comparatorWithPriorities, languageNames } = require('stremio/common');
|
const { Button, CONSTANTS, comparatorWithPriorities, languages } = require('stremio/common');
|
||||||
const DiscreteSelectInput = require('./DiscreteSelectInput');
|
const DiscreteSelectInput = require('./DiscreteSelectInput');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
const { t } = require('i18next');
|
const { t } = require('i18next');
|
||||||
|
|
@ -129,49 +129,23 @@ const SubtitlesMenu = React.memo((props) => {
|
||||||
const onSubtitlesOffsetChanged = React.useCallback((event) => {
|
const onSubtitlesOffsetChanged = React.useCallback((event) => {
|
||||||
const delta = event.value === 'increment' ? 1 : -1;
|
const delta = event.value === 'increment' ? 1 : -1;
|
||||||
if (typeof props.selectedSubtitlesTrackId === 'string') {
|
if (typeof props.selectedSubtitlesTrackId === 'string') {
|
||||||
if (props.extraSubtitlesOffset !== null && !isNaN(props.extraSubtitlesOffset)) {
|
|
||||||
const offset = Math.max(0, Math.min(100, Math.floor(props.extraSubtitlesOffset + delta)));
|
|
||||||
if (typeof props.onExtraSubtitlesOffsetChanged === 'function') {
|
|
||||||
props.onExtraSubtitlesOffsetChanged(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (typeof props.selectedExtraSubtitlesTrackId === 'string') {
|
|
||||||
if (props.subtitlesOffset !== null && !isNaN(props.subtitlesOffset)) {
|
if (props.subtitlesOffset !== null && !isNaN(props.subtitlesOffset)) {
|
||||||
const offset = Math.max(0, Math.min(100, Math.floor(props.subtitlesOffset + delta)));
|
const offset = Math.max(0, Math.min(100, Math.floor(props.subtitlesOffset + delta)));
|
||||||
if (typeof props.onSubtitlesOffsetChanged === 'function') {
|
if (typeof props.onSubtitlesOffsetChanged === 'function') {
|
||||||
props.onSubtitlesOffsetChanged(offset);
|
props.onSubtitlesOffsetChanged(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (typeof props.selectedExtraSubtitlesTrackId === 'string') {
|
||||||
|
if (props.extraSubtitlesOffset !== null && !isNaN(props.extraSubtitlesOffset)) {
|
||||||
|
const offset = Math.max(0, Math.min(100, Math.floor(props.extraSubtitlesOffset + delta)));
|
||||||
|
if (typeof props.onExtraSubtitlesOffsetChanged === 'function') {
|
||||||
|
props.onExtraSubtitlesOffsetChanged(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId, props.subtitlesOffset, props.extraSubtitlesOffset, props.onSubtitlesOffsetChanged, props.onExtraSubtitlesOffsetChanged]);
|
}, [props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId, props.subtitlesOffset, props.extraSubtitlesOffset, props.onSubtitlesOffsetChanged, props.onExtraSubtitlesOffsetChanged]);
|
||||||
const audioTrackOnClick = React.useCallback((event) => {
|
|
||||||
if (typeof props.onAudioTrackSelected === 'function') {
|
|
||||||
props.onAudioTrackSelected(event.currentTarget.dataset.id);
|
|
||||||
}
|
|
||||||
}, [props.onAudioTrackSelected]);
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
<div className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||||
{
|
|
||||||
Array.isArray(props.audioTracks) && props.audioTracks.length > 1 ?
|
|
||||||
<div className={styles['languages-container']}>
|
|
||||||
<div className={styles['languages-header']}>Audio Languages</div>
|
|
||||||
<div className={styles['languages-list']}>
|
|
||||||
{props.audioTracks.map(({ id, label, lang }, index) => (
|
|
||||||
<Button key={index} title={label} className={classnames(styles['language-option'], { 'selected': props.selectedAudioTrackId === id })} data-id={id} onClick={audioTrackOnClick}>
|
|
||||||
<div className={styles['language-label']}>{typeof languageNames[lang] === 'string' ? languageNames[lang] : lang}</div>
|
|
||||||
{
|
|
||||||
props.selectedAudioTrackId === id ?
|
|
||||||
<div className={styles['icon']} />
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
<div className={styles['languages-container']}>
|
<div className={styles['languages-container']}>
|
||||||
<div className={styles['languages-header']}>{ t('PLAYER_SUBTITLES_LANGUAGES') }</div>
|
<div className={styles['languages-header']}>{ t('PLAYER_SUBTITLES_LANGUAGES') }</div>
|
||||||
<div className={styles['languages-list']}>
|
<div className={styles['languages-list']}>
|
||||||
|
|
@ -185,8 +159,8 @@ const SubtitlesMenu = React.memo((props) => {
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
{subtitlesLanguages.map((lang, index) => (
|
{subtitlesLanguages.map((lang, index) => (
|
||||||
<Button key={index} title={typeof languageNames[lang] === 'string' ? languageNames[lang] : lang} className={classnames(styles['language-option'], { 'selected': selectedSubtitlesLanguage === lang })} data-lang={lang} onClick={subtitlesLanguageOnClick}>
|
<Button key={index} title={languages.label(lang)} className={classnames(styles['language-option'], { 'selected': selectedSubtitlesLanguage === lang })} data-lang={lang} onClick={subtitlesLanguageOnClick}>
|
||||||
<div className={styles['language-label']}>{typeof languageNames[lang] === 'string' ? languageNames[lang] : lang}</div>
|
<div className={styles['language-label']}>{languages.label(lang)}</div>
|
||||||
{
|
{
|
||||||
selectedSubtitlesLanguage === lang ?
|
selectedSubtitlesLanguage === lang ?
|
||||||
<div className={styles['icon']} />
|
<div className={styles['icon']} />
|
||||||
|
|
@ -204,14 +178,15 @@ const SubtitlesMenu = React.memo((props) => {
|
||||||
<div className={styles['variants-list']}>
|
<div className={styles['variants-list']}>
|
||||||
{subtitlesTracksForLanguage.map((track, index) => (
|
{subtitlesTracksForLanguage.map((track, index) => (
|
||||||
<Button key={index} title={track.label} className={classnames(styles['variant-option'], { 'selected': props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id })} data-id={track.id} data-origin={track.origin} data-embedded={track.embedded} onClick={subtitlesTrackOnClick}>
|
<Button key={index} title={track.label} className={classnames(styles['variant-option'], { 'selected': props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id })} data-id={track.id} data-origin={track.origin} data-embedded={track.embedded} onClick={subtitlesTrackOnClick}>
|
||||||
<div className={styles['variant-label']}>
|
<div className={styles['info']}>
|
||||||
{
|
<div className={styles['variant-label']}>
|
||||||
typeof track.label === 'string' && !track.label.startsWith('http') ?
|
{
|
||||||
track.label
|
languages.label(!track.label.startsWith('http') ? track.label : track.lang)
|
||||||
:
|
}
|
||||||
track.lang
|
</div>
|
||||||
}
|
<div className={styles['variant-origin']}>
|
||||||
<div className={styles['variant-origin']}>{t(track.origin)}</div>
|
{ t(track.origin) }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id ?
|
props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id ?
|
||||||
|
|
@ -312,16 +287,8 @@ SubtitlesMenu.propTypes = {
|
||||||
extraSubtitlesOffset: PropTypes.number,
|
extraSubtitlesOffset: PropTypes.number,
|
||||||
extraSubtitlesDelay: PropTypes.number,
|
extraSubtitlesDelay: PropTypes.number,
|
||||||
extraSubtitlesSize: PropTypes.number,
|
extraSubtitlesSize: PropTypes.number,
|
||||||
audioTracks: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
lang: PropTypes.string.isRequired,
|
|
||||||
origin: PropTypes.string.isRequired,
|
|
||||||
label: PropTypes.string.isRequired
|
|
||||||
})),
|
|
||||||
selectedAudioTrackId: PropTypes.string,
|
|
||||||
onSubtitlesTrackSelected: PropTypes.func,
|
onSubtitlesTrackSelected: PropTypes.func,
|
||||||
onExtraSubtitlesTrackSelected: PropTypes.func,
|
onExtraSubtitlesTrackSelected: PropTypes.func,
|
||||||
onAudioTrackSelected: PropTypes.func,
|
|
||||||
onSubtitlesOffsetChanged: PropTypes.func,
|
onSubtitlesOffsetChanged: PropTypes.func,
|
||||||
onSubtitlesSizeChanged: PropTypes.func,
|
onSubtitlesSizeChanged: PropTypes.func,
|
||||||
onExtraSubtitlesOffsetChanged: PropTypes.func,
|
onExtraSubtitlesOffsetChanged: PropTypes.func,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@
|
||||||
|
|
||||||
.language-label, .variant-label {
|
.language-label, .variant-label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-height: 2.4em;
|
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: var(--primary-foreground-color);
|
color: var(--primary-foreground-color);
|
||||||
}
|
}
|
||||||
|
|
@ -52,10 +51,6 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.variant-label .variant-origin {
|
|
||||||
color: var(--color-placeholder-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
|
|
@ -65,6 +60,26 @@
|
||||||
background-color: var(--secondary-accent-color);
|
background-color: var(--secondary-accent-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.variant-option {
|
||||||
|
height: 4rem;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.variant-label {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-origin {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-placeholder-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
const VideosMenu = require('./VideosMenu');
|
|
||||||
|
|
||||||
module.exports = VideosMenu;
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
|
||||||
|
|
||||||
.videos-menu-container {
|
|
||||||
width: 30rem;
|
|
||||||
padding: 1rem;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||||
|
@import (reference) '~stremio/common/screen-sizes.less';
|
||||||
|
|
||||||
:import('~stremio/common/Slider/styles.less') {
|
:import('~stremio/common/Slider/styles.less') {
|
||||||
active-slider-within: active-slider-within;
|
active-slider-within: active-slider-within;
|
||||||
|
|
@ -18,7 +19,7 @@ html:not(.active-slider-within) {
|
||||||
.player-container.overlayHidden {
|
.player-container.overlayHidden {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
|
|
||||||
.nav-bar-layer, .control-bar-layer, .menu-layer {
|
.nav-bar-layer, .control-bar-layer, .menu-layer, .side-drawer-button-layer {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 200ms;
|
transition: opacity 200ms;
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +84,13 @@ html:not(.active-slider-within) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.side-drawer-button-layer {
|
||||||
|
top: 50%;
|
||||||
|
right: -4rem;
|
||||||
|
left: initial;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
&.control-bar-layer {
|
&.control-bar-layer {
|
||||||
top: initial;
|
top: initial;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
@ -101,7 +109,7 @@ html:not(.active-slider-within) {
|
||||||
&.menu-layer {
|
&.menu-layer {
|
||||||
top: initial;
|
top: initial;
|
||||||
left: initial;
|
left: initial;
|
||||||
right: 2rem;
|
right: 4rem;
|
||||||
bottom: 8rem;
|
bottom: 8rem;
|
||||||
max-height: calc(100% - 13.5rem);
|
max-height: calc(100% - 13.5rem);
|
||||||
max-width: calc(100% - 4rem);
|
max-width: calc(100% - 4rem);
|
||||||
|
|
@ -112,5 +120,33 @@ html:not(.active-slider-within) {
|
||||||
backdrop-filter: blur(15px);
|
backdrop-filter: blur(15px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.side-drawer-layer {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: initial;
|
||||||
|
bottom: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @xsmall) {
|
||||||
|
.player-container {
|
||||||
|
.layer {
|
||||||
|
&.side-drawer-button-layer {
|
||||||
|
right: -2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: @minimum) {
|
||||||
|
.player-container {
|
||||||
|
.layer {
|
||||||
|
&.menu-layer {
|
||||||
|
right: 2.5rem;
|
||||||
|
bottom: 11rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -627,6 +627,14 @@ const Settings = () => {
|
||||||
<kbd>S</kbd>
|
<kbd>S</kbd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles['option-container']}>
|
||||||
|
<div className={styles['option-name-container']}>
|
||||||
|
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_MENU_AUDIO') }</div>
|
||||||
|
</div>
|
||||||
|
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||||
|
<kbd>A</kbd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className={styles['option-container']}>
|
<div className={styles['option-container']}>
|
||||||
<div className={styles['option-name-container']}>
|
<div className={styles['option-name-container']}>
|
||||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_MENU_INFO') }</div>
|
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_MENU_INFO') }</div>
|
||||||
|
|
|
||||||
2
src/types/MetaItem.d.ts
vendored
2
src/types/MetaItem.d.ts
vendored
|
|
@ -15,7 +15,7 @@ type MetaItemPreview = {
|
||||||
posterShape: PosterShape,
|
posterShape: PosterShape,
|
||||||
releaseInfo: string | null,
|
releaseInfo: string | null,
|
||||||
runtime: string | null,
|
runtime: string | null,
|
||||||
released: string | null,
|
released: Date | null | undefined,
|
||||||
trailerStreams: TrailerStream[],
|
trailerStreams: TrailerStream[],
|
||||||
links: Link[],
|
links: Link[],
|
||||||
behaviorHints: BehaviorHints,
|
behaviorHints: BehaviorHints,
|
||||||
|
|
|
||||||
5
src/types/Video.d.ts
vendored
5
src/types/Video.d.ts
vendored
|
|
@ -14,4 +14,9 @@ type Video = {
|
||||||
episode?: number,
|
episode?: number,
|
||||||
streams: Stream[],
|
streams: Stream[],
|
||||||
trailerStreams: TrailerStream[],
|
trailerStreams: TrailerStream[],
|
||||||
|
watched: boolean,
|
||||||
|
progress: number,
|
||||||
|
upcoming: boolean,
|
||||||
|
deepLinks: VideoDeepLinks,
|
||||||
|
scheduled: boolean,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
10
src/types/models/Player.d.ts
vendored
10
src/types/models/Player.d.ts
vendored
|
|
@ -25,6 +25,11 @@ type Subtitle = {
|
||||||
url: string,
|
url: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SeriesInfo = {
|
||||||
|
episode: number,
|
||||||
|
season: number,
|
||||||
|
};
|
||||||
|
|
||||||
type Player = {
|
type Player = {
|
||||||
addon: Addon | null,
|
addon: Addon | null,
|
||||||
libraryItem: LibraryItemPlayer | null,
|
libraryItem: LibraryItemPlayer | null,
|
||||||
|
|
@ -36,10 +41,7 @@ type Player = {
|
||||||
streamRequest: ResourceRequest,
|
streamRequest: ResourceRequest,
|
||||||
subtitlesPath: ResourceRequestPath,
|
subtitlesPath: ResourceRequestPath,
|
||||||
} | null,
|
} | null,
|
||||||
seriesInfo: {
|
seriesInfo: SeriesInfo | null,
|
||||||
season: number,
|
|
||||||
episode: number,
|
|
||||||
} | null,
|
|
||||||
subtitles: Subtitle[],
|
subtitles: Subtitle[],
|
||||||
title: string | null,
|
title: string | null,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
7
src/types/types.d.ts
vendored
7
src/types/types.d.ts
vendored
|
|
@ -61,3 +61,10 @@ type Catalog<T, D = any> = {
|
||||||
installed?: boolean,
|
installed?: boolean,
|
||||||
deepLinks?: D,
|
deepLinks?: D,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AudioTrack = {
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
lang: string,
|
||||||
|
origin: string,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const threadLoader = require('thread-loader');
|
||||||
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
|
@ -14,6 +16,25 @@ const pachageJson = require('./package.json');
|
||||||
|
|
||||||
const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim();
|
const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim();
|
||||||
|
|
||||||
|
const THREAD_LOADER = {
|
||||||
|
loader: 'thread-loader',
|
||||||
|
options: {
|
||||||
|
name: 'shared-pool',
|
||||||
|
workers: os.cpus().length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
threadLoader.warmup(
|
||||||
|
THREAD_LOADER.options,
|
||||||
|
[
|
||||||
|
'babel-loader',
|
||||||
|
'ts-loader',
|
||||||
|
'css-loader',
|
||||||
|
'postcss-loader',
|
||||||
|
'less-loader',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
module.exports = (env, argv) => ({
|
module.exports = (env, argv) => ({
|
||||||
mode: argv.mode,
|
mode: argv.mode,
|
||||||
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
|
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
|
||||||
|
|
@ -30,20 +51,31 @@ module.exports = (env, argv) => ({
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: [
|
||||||
loader: 'babel-loader',
|
THREAD_LOADER,
|
||||||
options: {
|
{
|
||||||
presets: [
|
loader: 'babel-loader',
|
||||||
'@babel/preset-env',
|
options: {
|
||||||
'@babel/preset-react'
|
presets: [
|
||||||
],
|
'@babel/preset-env',
|
||||||
|
'@babel/preset-react'
|
||||||
|
],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ts|tsx)$/,
|
test: /\.(ts|tsx)$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: 'ts-loader',
|
use: [
|
||||||
|
THREAD_LOADER,
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
happyPackMode: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.less$/,
|
test: /\.less$/,
|
||||||
|
|
@ -55,6 +87,7 @@ module.exports = (env, argv) => ({
|
||||||
esModule: false
|
esModule: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
THREAD_LOADER,
|
||||||
{
|
{
|
||||||
loader: 'css-loader',
|
loader: 'css-loader',
|
||||||
options: {
|
options: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue