Merge branch 'master' into jassub
This commit is contained in:
commit
cd52499849
11 changed files with 658 additions and 257 deletions
286
package-lock.json
generated
286
package-lock.json
generated
|
@ -20,9 +20,9 @@
|
|||
"blurhash": "2.0.4",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.27.2",
|
||||
"core-js": "3.28.0",
|
||||
"date-fns": "2.29.3",
|
||||
"dompurify": "2.4.3",
|
||||
"dompurify": "2.4.4",
|
||||
"epubjs": "0.4.2",
|
||||
"escape-html": "1.0.3",
|
||||
"fast-text-encoding": "1.0.6",
|
||||
|
@ -43,7 +43,7 @@
|
|||
"pdfjs-dist": "2.16.105",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-router-dom": "6.8.0",
|
||||
"react-router-dom": "6.8.1",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"screenfull": "6.0.2",
|
||||
"sortablejs": "1.15.0",
|
||||
|
@ -67,9 +67,9 @@
|
|||
"@types/loadable__component": "5.13.4",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/react": "17.0.53",
|
||||
"@types/react-dom": "17.0.18",
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.51.0",
|
||||
"@types/react-dom": "17.0.19",
|
||||
"@typescript-eslint/eslint-plugin": "5.52.0",
|
||||
"@typescript-eslint/parser": "5.52.0",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-loader": "9.1.2",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"postcss-loader": "7.0.2",
|
||||
"postcss-preset-env": "8.0.1",
|
||||
"postcss-scss": "4.0.6",
|
||||
"sass": "1.58.0",
|
||||
"sass": "1.58.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"source-map-loader": "4.0.1",
|
||||
"style-loader": "3.3.1",
|
||||
|
@ -106,7 +106,7 @@
|
|||
"stylelint-config-rational-order": "0.1.2",
|
||||
"stylelint-no-browser-hacks": "1.2.1",
|
||||
"stylelint-order": "6.0.2",
|
||||
"stylelint-scss": "4.3.0",
|
||||
"stylelint-scss": "4.4.0",
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript": "4.9.5",
|
||||
"webpack": "5.75.0",
|
||||
|
@ -2737,9 +2737,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz",
|
||||
"integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz",
|
||||
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
|
@ -3064,9 +3064,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "17.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz",
|
||||
"integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==",
|
||||
"version": "17.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
|
||||
"integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "^17"
|
||||
|
@ -3170,14 +3170,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz",
|
||||
"integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz",
|
||||
"integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/type-utils": "5.51.0",
|
||||
"@typescript-eslint/utils": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/type-utils": "5.52.0",
|
||||
"@typescript-eslint/utils": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
|
@ -3219,14 +3219,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz",
|
||||
"integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz",
|
||||
"integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -3246,13 +3246,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz",
|
||||
"integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz",
|
||||
"integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/visitor-keys": "5.51.0"
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/visitor-keys": "5.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
|
@ -3263,13 +3263,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz",
|
||||
"integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz",
|
||||
"integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/utils": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"@typescript-eslint/utils": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
|
@ -3290,9 +3290,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz",
|
||||
"integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz",
|
||||
"integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
|
@ -3303,13 +3303,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz",
|
||||
"integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz",
|
||||
"integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/visitor-keys": "5.51.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/visitor-keys": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -3374,16 +3374,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz",
|
||||
"integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz",
|
||||
"integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
|
@ -3415,12 +3415,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz",
|
||||
"integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz",
|
||||
"integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -5136,9 +5136,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.27.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
|
||||
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==",
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz",
|
||||
"integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==",
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
@ -5935,9 +5935,9 @@
|
|||
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww=="
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz",
|
||||
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ=="
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "1.7.0",
|
||||
|
@ -13100,11 +13100,11 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz",
|
||||
"integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
|
||||
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.3.1"
|
||||
"@remix-run/router": "1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
@ -13114,12 +13114,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz",
|
||||
"integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz",
|
||||
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.3.1",
|
||||
"react-router": "6.8.0"
|
||||
"@remix-run/router": "1.3.2",
|
||||
"react-router": "6.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
@ -13680,9 +13680,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.58.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz",
|
||||
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
|
||||
"version": "1.58.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
|
||||
"integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
|
@ -17067,9 +17067,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/stylelint-scss": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz",
|
||||
"integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.4.0.tgz",
|
||||
"integrity": "sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -17079,7 +17079,7 @@
|
|||
"postcss-value-parser": "^4.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": "^14.5.1"
|
||||
"stylelint": "^14.5.1 || ^15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint/node_modules/array-union": {
|
||||
|
@ -20959,9 +20959,9 @@
|
|||
}
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz",
|
||||
"integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw=="
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz",
|
||||
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA=="
|
||||
},
|
||||
"@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
|
@ -21254,9 +21254,9 @@
|
|||
}
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"version": "17.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.18.tgz",
|
||||
"integrity": "sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw==",
|
||||
"version": "17.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
|
||||
"integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "^17"
|
||||
|
@ -21359,14 +21359,14 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz",
|
||||
"integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz",
|
||||
"integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/type-utils": "5.51.0",
|
||||
"@typescript-eslint/utils": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/type-utils": "5.52.0",
|
||||
"@typescript-eslint/utils": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
|
@ -21388,53 +21388,53 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz",
|
||||
"integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz",
|
||||
"integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz",
|
||||
"integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz",
|
||||
"integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/visitor-keys": "5.51.0"
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/visitor-keys": "5.52.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz",
|
||||
"integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz",
|
||||
"integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/utils": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"@typescript-eslint/utils": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz",
|
||||
"integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz",
|
||||
"integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz",
|
||||
"integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz",
|
||||
"integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/visitor-keys": "5.51.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/visitor-keys": "5.52.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -21474,16 +21474,16 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz",
|
||||
"integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz",
|
||||
"integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.51.0",
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/typescript-estree": "5.51.0",
|
||||
"@typescript-eslint/scope-manager": "5.52.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"@typescript-eslint/typescript-estree": "5.52.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
|
@ -21501,12 +21501,12 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz",
|
||||
"integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz",
|
||||
"integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.51.0",
|
||||
"@typescript-eslint/types": "5.52.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -22825,9 +22825,9 @@
|
|||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.27.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
|
||||
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
|
||||
"version": "3.28.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz",
|
||||
"integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.25.3",
|
||||
|
@ -23402,9 +23402,9 @@
|
|||
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww=="
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz",
|
||||
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
|
@ -28643,20 +28643,20 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz",
|
||||
"integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz",
|
||||
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.3.1"
|
||||
"@remix-run/router": "1.3.2"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz",
|
||||
"integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz",
|
||||
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.3.1",
|
||||
"react-router": "6.8.0"
|
||||
"@remix-run/router": "1.3.2",
|
||||
"react-router": "6.8.1"
|
||||
}
|
||||
},
|
||||
"read-file-stdin": {
|
||||
|
@ -29084,9 +29084,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.58.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz",
|
||||
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
|
||||
"version": "1.58.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
|
||||
"integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
|
@ -31874,9 +31874,9 @@
|
|||
}
|
||||
},
|
||||
"stylelint-scss": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz",
|
||||
"integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.4.0.tgz",
|
||||
"integrity": "sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.21",
|
||||
|
|
16
package.json
16
package.json
|
@ -18,9 +18,9 @@
|
|||
"@types/loadable__component": "5.13.4",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/react": "17.0.53",
|
||||
"@types/react-dom": "17.0.18",
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.51.0",
|
||||
"@types/react-dom": "17.0.19",
|
||||
"@typescript-eslint/eslint-plugin": "5.52.0",
|
||||
"@typescript-eslint/parser": "5.52.0",
|
||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-loader": "9.1.2",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"postcss-loader": "7.0.2",
|
||||
"postcss-preset-env": "8.0.1",
|
||||
"postcss-scss": "4.0.6",
|
||||
"sass": "1.58.0",
|
||||
"sass": "1.58.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"source-map-loader": "4.0.1",
|
||||
"style-loader": "3.3.1",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"stylelint-config-rational-order": "0.1.2",
|
||||
"stylelint-no-browser-hacks": "1.2.1",
|
||||
"stylelint-order": "6.0.2",
|
||||
"stylelint-scss": "4.3.0",
|
||||
"stylelint-scss": "4.4.0",
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript": "4.9.5",
|
||||
"webpack": "5.75.0",
|
||||
|
@ -79,9 +79,9 @@
|
|||
"blurhash": "2.0.4",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.27.2",
|
||||
"core-js": "3.28.0",
|
||||
"date-fns": "2.29.3",
|
||||
"dompurify": "2.4.3",
|
||||
"dompurify": "2.4.4",
|
||||
"epubjs": "0.4.2",
|
||||
"escape-html": "1.0.3",
|
||||
"fast-text-encoding": "1.0.6",
|
||||
|
@ -102,7 +102,7 @@
|
|||
"pdfjs-dist": "2.16.105",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-router-dom": "6.8.0",
|
||||
"react-router-dom": "6.8.1",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"screenfull": "6.0.2",
|
||||
"sortablejs": "1.15.0",
|
||||
|
|
|
@ -422,7 +422,8 @@ function getPlaybackInfo(player,
|
|||
enableDirectPlay,
|
||||
enableDirectStream,
|
||||
allowVideoStreamCopy,
|
||||
allowAudioStreamCopy) {
|
||||
allowAudioStreamCopy,
|
||||
secondarySubtitleStreamIndex) {
|
||||
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
|
||||
return Promise.resolve({
|
||||
MediaSources: [
|
||||
|
@ -462,6 +463,9 @@ function getPlaybackInfo(player,
|
|||
if (subtitleStreamIndex != null) {
|
||||
query.SubtitleStreamIndex = subtitleStreamIndex;
|
||||
}
|
||||
if (secondarySubtitleStreamIndex != null) {
|
||||
query.SecondarySubtitleStreamIndex = secondarySubtitleStreamIndex;
|
||||
}
|
||||
if (enableDirectPlay != null) {
|
||||
query.EnableDirectPlay = enableDirectPlay;
|
||||
}
|
||||
|
@ -876,25 +880,49 @@ class PlaybackManager {
|
|||
});
|
||||
};
|
||||
|
||||
function getCurrentSubtitleStream(player) {
|
||||
self.playerHasSecondarySubtitleSupport = function (player = self._currentPlayer) {
|
||||
if (!player) return false;
|
||||
return Boolean(player.supports('SecondarySubtitles'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if:
|
||||
* - the track can be used directly as a secondary subtitle
|
||||
* - or if it can be paired with a secondary subtitle when used as a primary subtitle
|
||||
*/
|
||||
self.trackHasSecondarySubtitleSupport = function (track, player = self._currentPlayer) {
|
||||
if (!player || !track) return false;
|
||||
const format = (track.Codec || '').toLowerCase();
|
||||
// Currently, only non-SSA/non-ASS external subtitles are supported.
|
||||
// Showing secondary subtitles does not work with any SSA/ASS subtitle combinations because
|
||||
// of the complexity of how they are rendered and the risk of the subtitles overlapping
|
||||
return format !== 'ssa' && format !== 'ass' && getDeliveryMethod(track) === 'External';
|
||||
};
|
||||
|
||||
self.secondarySubtitleTracks = function (player = self._currentPlayer) {
|
||||
const streams = self.subtitleTracks(player);
|
||||
return streams.filter((stream) => self.trackHasSecondarySubtitleSupport(stream, player));
|
||||
};
|
||||
|
||||
function getCurrentSubtitleStream(player, isSecondaryStream = false) {
|
||||
if (!player) {
|
||||
throw new Error('player cannot be null');
|
||||
}
|
||||
|
||||
const index = getPlayerData(player).subtitleStreamIndex;
|
||||
const index = isSecondaryStream ? getPlayerData(player).secondarySubtitleStreamIndex : getPlayerData(player).subtitleStreamIndex;
|
||||
|
||||
if (index == null || index === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getSubtitleStream(player, index);
|
||||
return self.getSubtitleStream(player, index);
|
||||
}
|
||||
|
||||
function getSubtitleStream(player, index) {
|
||||
self.getSubtitleStream = function (player, index) {
|
||||
return self.subtitleTracks(player).filter(function (s) {
|
||||
return s.Type === 'Subtitle' && s.Index === index;
|
||||
})[0];
|
||||
}
|
||||
};
|
||||
|
||||
self.getPlaylist = function (player) {
|
||||
player = player || self._currentPlayer;
|
||||
|
@ -1463,6 +1491,24 @@ class PlaybackManager {
|
|||
return getPlayerData(player).subtitleStreamIndex;
|
||||
};
|
||||
|
||||
self.getSecondarySubtitleStreamIndex = function (player) {
|
||||
player = player || self._currentPlayer;
|
||||
|
||||
if (!player) {
|
||||
throw new Error('player cannot be null');
|
||||
}
|
||||
|
||||
try {
|
||||
if (!enableLocalPlaylistManagement(player)) {
|
||||
return player.getSecondarySubtitleStreamIndex();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[playbackmanager] Failed to get secondary stream index:', e);
|
||||
}
|
||||
|
||||
return getPlayerData(player).secondarySubtitleStreamIndex;
|
||||
};
|
||||
|
||||
function getDeliveryMethod(subtitleStream) {
|
||||
// This will be null for internal subs for local items
|
||||
if (subtitleStream.DeliveryMethod) {
|
||||
|
@ -1480,7 +1526,7 @@ class PlaybackManager {
|
|||
|
||||
const currentStream = getCurrentSubtitleStream(player);
|
||||
|
||||
const newStream = getSubtitleStream(player, index);
|
||||
const newStream = self.getSubtitleStream(player, index);
|
||||
|
||||
if (!currentStream && !newStream) {
|
||||
return;
|
||||
|
@ -1522,9 +1568,48 @@ class PlaybackManager {
|
|||
|
||||
player.setSubtitleStreamIndex(selectedTrackElementIndex);
|
||||
|
||||
// Also disable secondary subtitles when disabling the primary
|
||||
// subtitles, or if it doesn't support a secondary pair
|
||||
if (selectedTrackElementIndex === -1 || !self.trackHasSecondarySubtitleSupport(newStream)) {
|
||||
self.setSecondarySubtitleStreamIndex(-1);
|
||||
}
|
||||
|
||||
getPlayerData(player).subtitleStreamIndex = index;
|
||||
};
|
||||
|
||||
self.setSecondarySubtitleStreamIndex = function (index, player) {
|
||||
player = player || self._currentPlayer;
|
||||
if (!self.playerHasSecondarySubtitleSupport(player)) return;
|
||||
if (player && !enableLocalPlaylistManagement(player)) {
|
||||
try {
|
||||
return player.setSecondarySubtitleStreamIndex(index);
|
||||
} catch (e) {
|
||||
console.error('[playbackmanager] AutoSet - Failed to set secondary track:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const currentStream = getCurrentSubtitleStream(player, true);
|
||||
|
||||
const newStream = self.getSubtitleStream(player, index);
|
||||
|
||||
if (!currentStream && !newStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Secondary subtitles are currently only handled client side
|
||||
// Changes to the server code are required before we can handle other delivery methods
|
||||
if (newStream && !self.trackHasSecondarySubtitleSupport(newStream, player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
player.setSecondarySubtitleStreamIndex(index);
|
||||
getPlayerData(player).secondarySubtitleStreamIndex = index;
|
||||
} catch (e) {
|
||||
console.error('[playbackmanager] AutoSet - Failed to set secondary track:', e);
|
||||
}
|
||||
};
|
||||
|
||||
self.supportSubtitleOffset = function (player) {
|
||||
player = player || self._currentPlayer;
|
||||
return player && 'setSubtitleOffset' in player;
|
||||
|
@ -1548,7 +1633,7 @@ class PlaybackManager {
|
|||
};
|
||||
|
||||
self.isSubtitleStreamExternal = function (index, player) {
|
||||
const stream = getSubtitleStream(player, index);
|
||||
const stream = self.getSubtitleStream(player, index);
|
||||
return stream ? getDeliveryMethod(stream) === 'External' : false;
|
||||
};
|
||||
|
||||
|
@ -1639,6 +1724,7 @@ class PlaybackManager {
|
|||
}).then(function (deviceProfile) {
|
||||
const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex;
|
||||
const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex;
|
||||
const secondarySubtitleStreamIndex = params.SecondarySubtitleStreamIndex == null ? getPlayerData(player).secondarySubtitleStreamIndex : params.SecondarySubtitleStreamIndex;
|
||||
|
||||
let currentMediaSource = self.currentMediaSource(player);
|
||||
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
|
||||
|
@ -1665,6 +1751,7 @@ class PlaybackManager {
|
|||
}
|
||||
|
||||
getPlayerData(player).subtitleStreamIndex = subtitleStreamIndex;
|
||||
getPlayerData(player).secondarySubtitleStreamIndex = secondarySubtitleStreamIndex;
|
||||
getPlayerData(player).audioStreamIndex = audioStreamIndex;
|
||||
getPlayerData(player).maxStreamingBitrate = maxBitrate;
|
||||
|
||||
|
@ -1950,6 +2037,7 @@ class PlaybackManager {
|
|||
state.PlayState.PlaybackRate = self.getPlaybackRate(player);
|
||||
|
||||
state.PlayState.SubtitleStreamIndex = self.getSubtitleStreamIndex(player);
|
||||
state.PlayState.SecondarySubtitleStreamIndex = self.getSecondarySubtitleStreamIndex(player);
|
||||
state.PlayState.AudioStreamIndex = self.getAudioStreamIndex(player);
|
||||
state.PlayState.BufferedRanges = self.getBufferedRanges(player);
|
||||
|
||||
|
@ -2230,11 +2318,16 @@ class PlaybackManager {
|
|||
});
|
||||
}
|
||||
|
||||
function rankStreamType(prevIndex, prevSource, mediaSource, streamType) {
|
||||
function rankStreamType(prevIndex, prevSource, mediaSource, streamType, isSecondarySubtitle) {
|
||||
if (prevIndex == -1) {
|
||||
console.debug(`AutoSet ${streamType} - No Stream Set`);
|
||||
if (streamType == 'Subtitle')
|
||||
mediaSource.DefaultSubtitleStreamIndex = -1;
|
||||
if (streamType == 'Subtitle') {
|
||||
if (isSecondarySubtitle) {
|
||||
mediaSource.DefaultSecondarySubtitleStreamIndex = -1;
|
||||
} else {
|
||||
mediaSource.DefaultSubtitleStreamIndex = -1;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2292,8 +2385,13 @@ class PlaybackManager {
|
|||
|
||||
if (bestStreamIndex != null) {
|
||||
console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`);
|
||||
if (streamType == 'Subtitle')
|
||||
mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex;
|
||||
if (streamType == 'Subtitle') {
|
||||
if (isSecondarySubtitle) {
|
||||
mediaSource.DefaultSecondarySubtitleStreamIndex = bestStreamIndex;
|
||||
} else {
|
||||
mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex;
|
||||
}
|
||||
}
|
||||
if (streamType == 'Audio')
|
||||
mediaSource.DefaultAudioStreamIndex = bestStreamIndex;
|
||||
} else {
|
||||
|
@ -2317,6 +2415,10 @@ class PlaybackManager {
|
|||
if (subtitle && typeof prevSource.DefaultSubtitleStreamIndex == 'number') {
|
||||
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
|
||||
}
|
||||
|
||||
if (subtitle && typeof prevSource.DefaultSecondarySubtitleStreamIndex == 'number') {
|
||||
rankStreamType(prevSource.DefaultSecondarySubtitleStreamIndex, prevSource, mediaSource, 'Subtitle', true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`AutoSet - Caught unexpected error: ${e}`);
|
||||
}
|
||||
|
@ -2384,6 +2486,19 @@ class PlaybackManager {
|
|||
const user = await apiClient.getCurrentUser();
|
||||
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
|
||||
|
||||
if (mediaSource.DefaultSubtitleStreamIndex == null || mediaSource.DefaultSubtitleStreamIndex < 0) {
|
||||
mediaSource.DefaultSubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex;
|
||||
mediaSource.DefaultSecondarySubtitleStreamIndex = -1;
|
||||
}
|
||||
|
||||
const subtitleTrack1 = mediaSource.MediaStreams[mediaSource.DefaultSubtitleStreamIndex];
|
||||
const subtitleTrack2 = mediaSource.MediaStreams[mediaSource.DefaultSecondarySubtitleStreamIndex];
|
||||
|
||||
if (!self.trackHasSecondarySubtitleSupport(subtitleTrack1, player)
|
||||
|| !self.trackHasSecondarySubtitleSupport(subtitleTrack2, player)) {
|
||||
mediaSource.DefaultSecondarySubtitleStreamIndex = -1;
|
||||
}
|
||||
|
||||
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
|
||||
|
||||
streamInfo.fullscreen = playOptions.fullscreen;
|
||||
|
@ -2751,7 +2866,8 @@ class PlaybackManager {
|
|||
return {
|
||||
...prevSource,
|
||||
DefaultAudioStreamIndex: prevPlayerData.audioStreamIndex,
|
||||
DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex
|
||||
DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex,
|
||||
DefaultSecondarySubtitleStreamIndex: prevPlayerData.secondarySubtitleStreamIndex
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2910,9 +3026,11 @@ class PlaybackManager {
|
|||
if (mediaSource) {
|
||||
playerData.audioStreamIndex = mediaSource.DefaultAudioStreamIndex;
|
||||
playerData.subtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex;
|
||||
playerData.secondarySubtitleStreamIndex = mediaSource.DefaultSecondarySubtitleStreamIndex;
|
||||
} else {
|
||||
playerData.audioStreamIndex = null;
|
||||
playerData.subtitleStreamIndex = null;
|
||||
playerData.secondarySubtitleStreamIndex = null;
|
||||
}
|
||||
|
||||
self._playNextAfterEnded = true;
|
||||
|
|
|
@ -988,9 +988,57 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
});
|
||||
}
|
||||
|
||||
function showSecondarySubtitlesMenu(actionsheet, positionTo) {
|
||||
const player = currentPlayer;
|
||||
if (!playbackManager.playerHasSecondarySubtitleSupport(player)) return;
|
||||
let currentIndex = playbackManager.getSecondarySubtitleStreamIndex(player);
|
||||
const streams = playbackManager.secondarySubtitleTracks(player);
|
||||
|
||||
if (currentIndex == null) {
|
||||
currentIndex = -1;
|
||||
}
|
||||
|
||||
streams.unshift({
|
||||
Index: -1,
|
||||
DisplayTitle: globalize.translate('Off')
|
||||
});
|
||||
|
||||
const menuItems = streams.map(function (stream) {
|
||||
const opt = {
|
||||
name: stream.DisplayTitle,
|
||||
id: stream.Index
|
||||
};
|
||||
|
||||
if (stream.Index === currentIndex) {
|
||||
opt.selected = true;
|
||||
}
|
||||
|
||||
return opt;
|
||||
});
|
||||
|
||||
actionsheet.show({
|
||||
title: globalize.translate('SecondarySubtitles'),
|
||||
items: menuItems,
|
||||
positionTo
|
||||
}).then(function (id) {
|
||||
if (id) {
|
||||
const index = parseInt(id);
|
||||
if (index !== currentIndex) {
|
||||
playbackManager.setSecondarySubtitleStreamIndex(index, player);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
||||
setTimeout(resetIdle, 0);
|
||||
}
|
||||
|
||||
function showSubtitleTrackSelection() {
|
||||
const player = currentPlayer;
|
||||
const streams = playbackManager.subtitleTracks(player);
|
||||
const secondaryStreams = playbackManager.secondarySubtitleTracks(player);
|
||||
let currentIndex = playbackManager.getSubtitleStreamIndex(player);
|
||||
|
||||
if (currentIndex == null) {
|
||||
|
@ -1013,6 +1061,29 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
|
||||
return opt;
|
||||
});
|
||||
|
||||
/**
|
||||
* Only show option if:
|
||||
* - player has support
|
||||
* - has more than 1 subtitle track
|
||||
* - has valid secondary tracks
|
||||
* - primary subtitle is not off
|
||||
* - primary subtitle has support
|
||||
*/
|
||||
const currentTrackCanAddSecondarySubtitle = playbackManager.playerHasSecondarySubtitleSupport(player)
|
||||
&& streams.length > 1
|
||||
&& secondaryStreams.length > 0
|
||||
&& currentIndex !== -1
|
||||
&& playbackManager.trackHasSecondarySubtitleSupport(playbackManager.getSubtitleStream(player, currentIndex), player);
|
||||
|
||||
if (currentTrackCanAddSecondarySubtitle) {
|
||||
const secondarySubtitleMenuItem = {
|
||||
name: globalize.translate('SecondarySubtitles'),
|
||||
id: 'secondarysubtitle'
|
||||
};
|
||||
menuItems.unshift(secondarySubtitleMenuItem);
|
||||
}
|
||||
|
||||
const positionTo = this;
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
|
@ -1021,10 +1092,18 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
items: menuItems,
|
||||
positionTo: positionTo
|
||||
}).then(function (id) {
|
||||
const index = parseInt(id);
|
||||
if (id === 'secondarysubtitle') {
|
||||
try {
|
||||
showSecondarySubtitlesMenu(actionsheet, positionTo);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} else {
|
||||
const index = parseInt(id);
|
||||
|
||||
if (index !== currentIndex) {
|
||||
playbackManager.setSubtitleStreamIndex(index, player);
|
||||
if (index !== currentIndex) {
|
||||
playbackManager.setSubtitleStreamIndex(index, player);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSubtitleSync();
|
||||
|
|
|
@ -155,6 +155,9 @@ function tryRemoveElement(elem) {
|
|||
return profileBuilder({});
|
||||
}
|
||||
|
||||
const PRIMARY_TEXT_TRACK_INDEX = 0;
|
||||
const SECONDARY_TEXT_TRACK_INDEX = 1;
|
||||
|
||||
export class HtmlVideoPlayer {
|
||||
/**
|
||||
* @type {string}
|
||||
|
@ -178,7 +181,6 @@ function tryRemoveElement(elem) {
|
|||
* @type {boolean}
|
||||
*/
|
||||
isFetching = false;
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement | null | undefined}
|
||||
*/
|
||||
|
@ -187,6 +189,10 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#subtitleTrackIndexToSetOnPlaying;
|
||||
/**
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
#secondarySubtitleTrackIndexToSetOnPlaying;
|
||||
/**
|
||||
* @type {number | null}
|
||||
*/
|
||||
|
@ -207,6 +213,10 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#customTrackIndex;
|
||||
/**
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
#customSecondaryTrackIndex;
|
||||
/**
|
||||
* @type {boolean | undefined}
|
||||
*/
|
||||
|
@ -215,14 +225,26 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#currentTrackOffset;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#secondaryTrackOffset;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#videoSubtitlesElem;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#videoSecondarySubtitlesElem;
|
||||
/**
|
||||
* @type {any | null | undefined}
|
||||
*/
|
||||
#currentTrackEvents;
|
||||
/**
|
||||
* @type {any | null | undefined}
|
||||
*/
|
||||
#currentSecondaryTrackEvents;
|
||||
/**
|
||||
* @type {string[] | undefined}
|
||||
*/
|
||||
|
@ -448,18 +470,39 @@ function tryRemoveElement(elem) {
|
|||
destroyFlvPlayer(this);
|
||||
destroyCastPlayer(this);
|
||||
|
||||
let secondaryTrackValid = true;
|
||||
|
||||
this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex;
|
||||
if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying];
|
||||
if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') {
|
||||
this.#subtitleTrackIndexToSetOnPlaying = -1;
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
// secondary track should not be shown if primary track is no longer a valid pair
|
||||
if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) {
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
} else {
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
|
||||
this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex;
|
||||
|
||||
this._currentPlayOptions = options;
|
||||
|
||||
if (secondaryTrackValid) {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex;
|
||||
if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying];
|
||||
if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = -1;
|
||||
}
|
||||
|
||||
const crossOrigin = getCrossOriginValue(options.mediaSource);
|
||||
if (crossOrigin) {
|
||||
elem.crossOrigin = crossOrigin;
|
||||
|
@ -490,8 +533,13 @@ function tryRemoveElement(elem) {
|
|||
this.setCurrentTrackElement(index);
|
||||
}
|
||||
|
||||
setSecondarySubtitleStreamIndex(index) {
|
||||
this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX);
|
||||
}
|
||||
|
||||
resetSubtitleOffset() {
|
||||
this.#currentTrackOffset = 0;
|
||||
this.#secondaryTrackOffset = 0;
|
||||
this.#showTrackOffset = false;
|
||||
}
|
||||
|
||||
|
@ -510,11 +558,11 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
getTextTrack() {
|
||||
getTextTracks() {
|
||||
const videoElement = this.#mediaElement;
|
||||
if (videoElement) {
|
||||
return Array.from(videoElement.textTracks)
|
||||
.find(function (trackElement) {
|
||||
.filter(function (trackElement) {
|
||||
// get showing .vtt textTack
|
||||
return trackElement.mode === 'showing';
|
||||
});
|
||||
|
@ -523,9 +571,6 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setSubtitleOffset(offset) {
|
||||
const offsetValue = parseFloat(offset);
|
||||
|
||||
|
@ -534,12 +579,15 @@ function tryRemoveElement(elem) {
|
|||
this.updateCurrentTrackOffset(offsetValue);
|
||||
this.#currentJASSUB.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue;
|
||||
} else {
|
||||
const trackElement = this.getTextTrack();
|
||||
const trackElements = this.getTextTracks();
|
||||
// if .vtt currently rendering
|
||||
if (trackElement) {
|
||||
this.setTextTrackSubtitleOffset(trackElement, offsetValue);
|
||||
} else if (this.#currentTrackEvents) {
|
||||
this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue);
|
||||
if (trackElements?.length > 0) {
|
||||
trackElements.forEach((trackElement, index) => {
|
||||
this.setTextTrackSubtitleOffset(trackElement, offsetValue, index);
|
||||
});
|
||||
} else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) {
|
||||
this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX);
|
||||
this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX);
|
||||
} else {
|
||||
console.debug('No available track, cannot apply offset: ', offsetValue);
|
||||
}
|
||||
|
@ -549,13 +597,25 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
updateCurrentTrackOffset(offsetValue) {
|
||||
updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
let offsetToCompare = this.#currentTrackOffset;
|
||||
if (this.isSecondaryTrack(currentTrackIndex)) {
|
||||
offsetToCompare = this.#secondaryTrackOffset;
|
||||
}
|
||||
|
||||
let relativeOffset = offsetValue;
|
||||
const newTrackOffset = offsetValue;
|
||||
if (this.#currentTrackOffset) {
|
||||
relativeOffset -= this.#currentTrackOffset;
|
||||
|
||||
if (offsetToCompare) {
|
||||
relativeOffset -= offsetToCompare;
|
||||
}
|
||||
this.#currentTrackOffset = newTrackOffset;
|
||||
|
||||
if (this.isSecondaryTrack(currentTrackIndex)) {
|
||||
this.#secondaryTrackOffset = newTrackOffset;
|
||||
} else {
|
||||
this.#currentTrackOffset = newTrackOffset;
|
||||
}
|
||||
|
||||
// relative to currentTrackOffset
|
||||
return relativeOffset;
|
||||
}
|
||||
|
@ -563,9 +623,12 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setTextTrackSubtitleOffset(currentTrack, offsetValue) {
|
||||
setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) {
|
||||
if (currentTrack.cues) {
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue);
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex);
|
||||
if (offsetValue === 0) {
|
||||
return;
|
||||
}
|
||||
Array.from(currentTrack.cues)
|
||||
.forEach(function (cue) {
|
||||
cue.startTime -= offsetValue;
|
||||
|
@ -577,9 +640,12 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setTrackEventsSubtitleOffset(trackEvents, offsetValue) {
|
||||
setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) {
|
||||
if (Array.isArray(trackEvents)) {
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue) * 1e7; // ticks
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks
|
||||
if (offsetValue === 0) {
|
||||
return;
|
||||
}
|
||||
trackEvents.forEach(function (trackEvent) {
|
||||
trackEvent.StartPositionTicks -= offsetValue;
|
||||
trackEvent.EndPositionTicks -= offsetValue;
|
||||
|
@ -591,6 +657,14 @@ function tryRemoveElement(elem) {
|
|||
return this.#currentTrackOffset;
|
||||
}
|
||||
|
||||
isPrimaryTrack(textTrackIndex) {
|
||||
return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX;
|
||||
}
|
||||
|
||||
isSecondaryTrack(textTrackIndex) {
|
||||
return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -819,6 +893,16 @@ function tryRemoveElement(elem) {
|
|||
if (this.#audioTrackIndexToSetOnPlaying != null && this.canSetAudioStreamIndex()) {
|
||||
this.setAudioStreamIndex(this.#audioTrackIndexToSetOnPlaying);
|
||||
}
|
||||
|
||||
if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
/**
|
||||
* Using a 0ms timeout to set the secondary subtitles because of some weird race condition when
|
||||
* setting both primary and secondary tracks at the same time.
|
||||
* The `TextTrack` content and cues will somehow get mixed up and each track will play a mix of both languages.
|
||||
* Putting this in a timeout fixes it completely.
|
||||
*/
|
||||
setTimeout(() => this.setSecondarySubtitleStreamIndex(this.#secondarySubtitleTrackIndexToSetOnPlaying), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -954,27 +1038,75 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyCustomTrack(videoElement) {
|
||||
if (this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = this.#videoSubtitlesElem.parentNode;
|
||||
if (subtitlesContainer) {
|
||||
tryRemoveElement(subtitlesContainer);
|
||||
destroyCustomRenderedTrackElements(targetTrackIndex) {
|
||||
if (this.isPrimaryTrack(targetTrackIndex)) {
|
||||
if (this.#videoSubtitlesElem) {
|
||||
tryRemoveElement(this.#videoSubtitlesElem);
|
||||
this.#videoSubtitlesElem = null;
|
||||
}
|
||||
} else if (this.isSecondaryTrack(targetTrackIndex)) {
|
||||
if (this.#videoSecondarySubtitlesElem) {
|
||||
tryRemoveElement(this.#videoSecondarySubtitlesElem);
|
||||
this.#videoSecondarySubtitlesElem = null;
|
||||
}
|
||||
} else { // destroy all
|
||||
if (this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = this.#videoSubtitlesElem.parentNode;
|
||||
if (subtitlesContainer) {
|
||||
tryRemoveElement(subtitlesContainer);
|
||||
}
|
||||
this.#videoSubtitlesElem = null;
|
||||
this.#videoSecondarySubtitlesElem = null;
|
||||
}
|
||||
this.#videoSubtitlesElem = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.#currentTrackEvents = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyNativeTracks(videoElement, targetTrackIndex) {
|
||||
if (videoElement) {
|
||||
const destroySingleTrack = typeof targetTrackIndex === 'number';
|
||||
const allTracks = videoElement.textTracks || []; // get list of tracks
|
||||
for (const track of allTracks) {
|
||||
for (let index = 0; index < allTracks.length; index++) {
|
||||
const track = allTracks[index];
|
||||
// Skip all other tracks if we are targeting just one
|
||||
if (destroySingleTrack && targetTrackIndex !== index) {
|
||||
continue;
|
||||
}
|
||||
if (track.label.includes('manualTrack')) {
|
||||
track.mode = 'disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyStoredTrackInfo(targetTrackIndex) {
|
||||
if (this.isPrimaryTrack(targetTrackIndex)) {
|
||||
this.#customTrackIndex = -1;
|
||||
this.#currentTrackEvents = null;
|
||||
} else if (this.isSecondaryTrack(targetTrackIndex)) {
|
||||
this.#customSecondaryTrackIndex = -1;
|
||||
this.#currentSecondaryTrackEvents = null;
|
||||
} else { // destroy all
|
||||
this.#customTrackIndex = -1;
|
||||
this.#customSecondaryTrackIndex = -1;
|
||||
this.#currentTrackEvents = null;
|
||||
this.#currentSecondaryTrackEvents = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyCustomTrack(videoElement, targetTrackIndex) {
|
||||
this.destroyCustomRenderedTrackElements(targetTrackIndex);
|
||||
this.destroyNativeTracks(videoElement, targetTrackIndex);
|
||||
this.destroyStoredTrackInfo(targetTrackIndex);
|
||||
|
||||
this.#customTrackIndex = -1;
|
||||
this.#currentClock = null;
|
||||
this._currentAspectRatio = null;
|
||||
|
||||
|
@ -1027,23 +1159,34 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setTrackForDisplay(videoElement, track) {
|
||||
setTrackForDisplay(videoElement, track, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
if (!track) {
|
||||
this.destroyCustomTrack(videoElement);
|
||||
// Destroy all tracks by passing undefined if there is no valid primary track
|
||||
this.destroyCustomTrack(videoElement, this.isSecondaryTrack(targetTextTrackIndex) ? targetTextTrackIndex : undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let targetTrackIndex = this.#customTrackIndex;
|
||||
if (this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
targetTrackIndex = this.#customSecondaryTrackIndex;
|
||||
}
|
||||
|
||||
// skip if already playing this track
|
||||
if (this.#customTrackIndex === track.Index) {
|
||||
if (targetTrackIndex === track.Index) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetSubtitleOffset();
|
||||
const item = this._currentPlayOptions.item;
|
||||
|
||||
this.destroyCustomTrack(videoElement);
|
||||
this.#customTrackIndex = track.Index;
|
||||
this.renderTracksEvents(videoElement, track, item);
|
||||
this.destroyCustomTrack(videoElement, targetTextTrackIndex);
|
||||
|
||||
if (this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
this.#customSecondaryTrackIndex = track.Index;
|
||||
} else {
|
||||
this.#customTrackIndex = track.Index;
|
||||
}
|
||||
this.renderTracksEvents(videoElement, track, item, targetTextTrackIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1152,16 +1295,39 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
renderSubtitlesWithCustomElement(videoElement, track, item) {
|
||||
this.fetchSubtitles(track, item).then((data) => {
|
||||
if (!this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = document.createElement('div');
|
||||
subtitlesContainer.classList.add('videoSubtitles');
|
||||
subtitlesContainer.innerHTML = '<div class="videoSubtitlesInner"></div>';
|
||||
this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner');
|
||||
renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) {
|
||||
Promise.all([import('../../scripts/settings/userSettings'), this.fetchSubtitles(track, item)]).then((results) => {
|
||||
const [userSettings, subtitleData] = results;
|
||||
const subtitleAppearance = userSettings.getSubtitleAppearanceSettings();
|
||||
const subtitleVerticalPosition = parseInt(subtitleAppearance.verticalPosition, 10);
|
||||
|
||||
if (!this.#videoSubtitlesElem && !this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
let subtitlesContainer = document.querySelector('.videoSubtitles');
|
||||
if (!subtitlesContainer) {
|
||||
subtitlesContainer = document.createElement('div');
|
||||
subtitlesContainer.classList.add('videoSubtitles');
|
||||
}
|
||||
const subtitlesElement = document.createElement('div');
|
||||
subtitlesElement.classList.add('videoSubtitlesInner');
|
||||
subtitlesContainer.appendChild(subtitlesElement);
|
||||
this.#videoSubtitlesElem = subtitlesElement;
|
||||
this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem);
|
||||
videoElement.parentNode.appendChild(subtitlesContainer);
|
||||
this.#currentTrackEvents = data.TrackEvents;
|
||||
this.#currentTrackEvents = subtitleData.TrackEvents;
|
||||
} else if (!this.#videoSecondarySubtitlesElem && this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
const subtitlesContainer = document.querySelector('.videoSubtitles');
|
||||
if (!subtitlesContainer) return;
|
||||
const secondarySubtitlesElement = document.createElement('div');
|
||||
secondarySubtitlesElement.classList.add('videoSecondarySubtitlesInner');
|
||||
// determine the order of the subtitles
|
||||
if (subtitleVerticalPosition < 0) {
|
||||
subtitlesContainer.insertBefore(secondarySubtitlesElement, subtitlesContainer.firstChild);
|
||||
} else {
|
||||
subtitlesContainer.appendChild(secondarySubtitlesElement);
|
||||
}
|
||||
this.#videoSecondarySubtitlesElem = secondarySubtitlesElement;
|
||||
this.setSubtitleAppearance(subtitlesContainer, this.#videoSecondarySubtitlesElem);
|
||||
this.#currentSecondaryTrackEvents = subtitleData.TrackEvents;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1208,7 +1374,7 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
renderTracksEvents(videoElement, track, item) {
|
||||
renderTracksEvents(videoElement, track, item, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
if (!itemHelper.isLocalItem(item) || track.IsExternal) {
|
||||
const format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
|
@ -1217,15 +1383,15 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
|
||||
if (this.requiresCustomSubtitlesElement()) {
|
||||
this.renderSubtitlesWithCustomElement(videoElement, track, item);
|
||||
this.renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let trackElement = null;
|
||||
if (videoElement.textTracks && videoElement.textTracks.length > 0) {
|
||||
trackElement = videoElement.textTracks[0];
|
||||
|
||||
const updatingTrack = videoElement.textTracks && videoElement.textTracks.length > (this.isSecondaryTrack(targetTextTrackIndex) ? 1 : 0);
|
||||
if (updatingTrack) {
|
||||
trackElement = videoElement.textTracks[targetTextTrackIndex];
|
||||
// This throws an error in IE, but is fine in chrome
|
||||
// In IE it's not necessary anyway because changing the src seems to be enough
|
||||
try {
|
||||
|
@ -1285,24 +1451,29 @@ function tryRemoveElement(elem) {
|
|||
return;
|
||||
}
|
||||
|
||||
const trackEvents = this.#currentTrackEvents;
|
||||
const subtitleTextElement = this.#videoSubtitlesElem;
|
||||
const allTrackEvents = [this.#currentTrackEvents, this.#currentSecondaryTrackEvents];
|
||||
const subtitleTextElements = [this.#videoSubtitlesElem, this.#videoSecondarySubtitlesElem];
|
||||
|
||||
if (trackEvents && subtitleTextElement) {
|
||||
const ticks = timeMs * 10000;
|
||||
let selectedTrackEvent;
|
||||
for (const trackEvent of trackEvents) {
|
||||
if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) {
|
||||
selectedTrackEvent = trackEvent;
|
||||
break;
|
||||
for (let i = 0; i < allTrackEvents.length; i++) {
|
||||
const trackEvents = allTrackEvents[i];
|
||||
const subtitleTextElement = subtitleTextElements[i];
|
||||
|
||||
if (trackEvents && subtitleTextElement) {
|
||||
const ticks = timeMs * 10000;
|
||||
let selectedTrackEvent;
|
||||
for (const trackEvent of trackEvents) {
|
||||
if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) {
|
||||
selectedTrackEvent = trackEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTrackEvent && selectedTrackEvent.Text) {
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
|
||||
subtitleTextElement.classList.remove('hide');
|
||||
} else {
|
||||
subtitleTextElement.classList.add('hide');
|
||||
if (selectedTrackEvent && selectedTrackEvent.Text) {
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
|
||||
subtitleTextElement.classList.remove('hide');
|
||||
} else {
|
||||
subtitleTextElement.classList.add('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1310,7 +1481,7 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setCurrentTrackElement(streamIndex) {
|
||||
setCurrentTrackElement(streamIndex, targetTextTrackIndex) {
|
||||
console.debug(`setting new text track index to: ${streamIndex}`);
|
||||
|
||||
const mediaStreamTextTracks = getMediaStreamTextTracks(this._currentPlayOptions.mediaSource);
|
||||
|
@ -1319,7 +1490,7 @@ function tryRemoveElement(elem) {
|
|||
return t.Index === streamIndex;
|
||||
})[0];
|
||||
|
||||
this.setTrackForDisplay(this.#mediaElement, track);
|
||||
this.setTrackForDisplay(this.#mediaElement, track, targetTextTrackIndex);
|
||||
if (enableNativeTrackSupport(this.#currentSrc, track)) {
|
||||
if (streamIndex !== -1) {
|
||||
this.setCueAppearance();
|
||||
|
@ -1497,6 +1668,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
list.push('SetBrightness');
|
||||
list.push('SetAspectRatio');
|
||||
list.push('SecondarySubtitles');
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -65,13 +65,22 @@ video[controls]::-webkit-media-controls {
|
|||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.videoSubtitlesInner {
|
||||
max-width: 70%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.videoSecondarySubtitlesInner {
|
||||
max-width: 70%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
min-height: 0 !important;
|
||||
margin-top: 0.5em !important;
|
||||
margin-bottom: 0.5em !important;
|
||||
}
|
||||
|
||||
@keyframes htmlvideoplayer-zoomin {
|
||||
|
|
|
@ -1412,6 +1412,7 @@
|
|||
"SearchForSubtitles": "Search for Subtitles",
|
||||
"SearchResults": "Search Results",
|
||||
"Season": "Season",
|
||||
"SecondarySubtitles": "Secondary Subtitles",
|
||||
"SelectAdminUsername": "Please select a username for the admin account.",
|
||||
"SelectServer": "Select Server",
|
||||
"SendMessage": "Send message",
|
||||
|
|
|
@ -1235,7 +1235,7 @@
|
|||
"TitleHostingSettings": "Configuraciones de alojamiento",
|
||||
"TitleHardwareAcceleration": "Aceleración por hardware",
|
||||
"Thursday": "Jueves",
|
||||
"Thumb": "Pulgar",
|
||||
"Thumb": "Miniatura",
|
||||
"TheseSettingsAffectSubtitlesOnThisDevice": "Esta configuración afecta los subtítulos en este dispositivo",
|
||||
"ThemeVideos": "Videos temáticos",
|
||||
"ThemeSongs": "Canciones temáticas",
|
||||
|
@ -1353,7 +1353,7 @@
|
|||
"NextTrack": "Pasar al siguiente",
|
||||
"LabelUnstable": "Inestable",
|
||||
"Video": "Video",
|
||||
"ThumbCard": "Tarjeta de pulgar",
|
||||
"ThumbCard": "Tarjeta de miniatura",
|
||||
"Subtitle": "Subtítulo",
|
||||
"SpecialFeatures": "Características especiales",
|
||||
"SelectServer": "Seleccionar servidor",
|
||||
|
@ -1517,7 +1517,7 @@
|
|||
"MessageSent": "Mensaje enviado.",
|
||||
"LabelSlowResponseTime": "Tiempo en ms después de lo cual una respuesta es considerada lenta:",
|
||||
"LabelSlowResponseEnabled": "Log de alarma si la respuesta del servidor fue lenta",
|
||||
"UseEpisodeImagesInNextUpHelp": "Las secciones Siguiente y Continuar viendo utilizaran imagenes del episodio como miniaturas en lugar de miniaturas del show.",
|
||||
"UseEpisodeImagesInNextUpHelp": "Las secciones 'Siguiente' y 'Continuar viendo' utilizarán imágenes del episodio como miniaturas en lugar de miniaturas del show.",
|
||||
"UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"",
|
||||
"LabelAutomaticallyAddToCollection": "Agregar automáticamente a la colección",
|
||||
"HeaderSyncPlayTimeSyncSettings": "Sincronización de tiempo",
|
||||
|
|
|
@ -1525,7 +1525,7 @@
|
|||
"VideoFramerateNotSupported": "Videoens bildefrekvens støttes ikke",
|
||||
"VideoBitDepthNotSupported": "Videoens bitdybde støttes ikke",
|
||||
"RefFramesNotSupported": "Referanse-bilder støttes ikke",
|
||||
"EnableGamepadHelp": "Lytt til inndata fra tilkoblet kontroller.",
|
||||
"EnableGamepadHelp": "Lytt til inndata fra tilkoblet kontroller. (Krever: \"TV\"-visningsmodus)",
|
||||
"LabelEnableGamepad": "Aktiver spillkontroller",
|
||||
"AudioBitDepthNotSupported": "Lydens bitdybde støttes ikke",
|
||||
"ThemeSong": "Tema-låt",
|
||||
|
@ -1685,5 +1685,12 @@
|
|||
"MessageNoItemsAvailable": "Ingen filer er tilgjengelige for øyeblikket.",
|
||||
"OptionDateShowAdded": "Dato serien ble lagt til",
|
||||
"Experimental": "Eksperimentell",
|
||||
"DownloadAll": "Laste ned alt"
|
||||
"DownloadAll": "Laste ned alt",
|
||||
"LabelDummyChapterCountHelp": "Maksimalt antall kapittelbilder som vil bli ekstrahert for hver mediefil.",
|
||||
"LabelStereoDownmixAlgorithm": "Stereo nedmiksingsalgoritme",
|
||||
"HeaderDummyChapter": "Kapittel Bilder",
|
||||
"LabelDummyChapterCount": "Grense:",
|
||||
"LabelChapterImageResolution": "Oppløsning:",
|
||||
"LabelDummyChapterDuration": "Intervall:",
|
||||
"HeaderRecordingMetadataSaving": "Opptak metadata"
|
||||
}
|
||||
|
|
|
@ -1693,5 +1693,5 @@
|
|||
"LabelChapterImageResolution": "Разрешение изображения:",
|
||||
"HeaderDummyChapter": "Изображения глав",
|
||||
"LabelDummyChapterCount": "Лимит:",
|
||||
"HeaderRecordingMetadataSaving": "Метадата записей"
|
||||
"HeaderRecordingMetadataSaving": "Метаданные записей"
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
"DeathDateValue": "Помер: {0}",
|
||||
"Favorite": "Обране",
|
||||
"HeaderDeleteDevice": "Видаліть пристрій",
|
||||
"HeaderLatestEpisodes": "Нещодавно переглянуті епізоди",
|
||||
"HeaderLatestMedia": "Нещодавно переглянуті",
|
||||
"HeaderLatestMovies": "Нещодавні фільми",
|
||||
"HeaderLatestMusic": "Остання музика",
|
||||
"HeaderLatestEpisodes": "Нещодавно додані серії",
|
||||
"HeaderLatestMedia": "Нещодавно додані медіа",
|
||||
"HeaderLatestMovies": "Нещодавно додані фільми",
|
||||
"HeaderLatestMusic": "Нещодавно додана музика",
|
||||
"HeaderSeasons": "Сезони",
|
||||
"HeaderTracks": "Доріжки",
|
||||
"HeaderUsers": "Користувачі",
|
||||
|
@ -274,7 +274,7 @@
|
|||
"DrmChannelsNotImported": "Канали з DRM не імпортуватимуться.",
|
||||
"DisplayModeHelp": "Виберіть бажаний стиль макету інтерфейсу.",
|
||||
"DisplayMissingEpisodesWithinSeasonsHelp": "Також, це має бути включено для ТВ-медіатек у конфігурації сервера.",
|
||||
"DisplayInOtherHomeScreenSections": "Показувати на головному екрані такі розділи як \"Останні медіа\" і \"Продовження перегляду\"",
|
||||
"DisplayInOtherHomeScreenSections": "Відображення в розділах головного екрана, таких як «Нещодавно додані медіа» та «Продовжити перегляд»",
|
||||
"DeleteDevicesConfirmation": "Ви впевнені, що хочете видалити всі пристрої? Усі інші сеанси будуть завершені. Пристрої знову з’являться, після того як користувач увійде в систему.",
|
||||
"DeleteAll": "Видалити все",
|
||||
"Data": "Дані",
|
||||
|
@ -583,7 +583,7 @@
|
|||
"Identify": "Ідентифікувати",
|
||||
"Horizontal": "Горизонтально",
|
||||
"Home": "Головна",
|
||||
"HideWatchedContentFromLatestMedia": "Приховати переглянуте з останніх медіа",
|
||||
"HideWatchedContentFromLatestMedia": "Приховати переглянутий вміст із «Нещодавно доданих медіа»",
|
||||
"Hide": "Приховати",
|
||||
"Help": "Допомога",
|
||||
"HeaderYears": "Роки",
|
||||
|
@ -723,7 +723,7 @@
|
|||
"HeaderLibraryAccess": "Доступ до медіатеки",
|
||||
"HeaderLibraryFolders": "Папки медіатеки",
|
||||
"HeaderLibraryOrder": "Порядок медіатек",
|
||||
"HeaderLatestRecordings": "Останні записи",
|
||||
"HeaderLatestRecordings": "Нещодавно додані записи",
|
||||
"HeaderKodiMetadataHelp": "Щоб увімкнути або вимкнути метадані NFO, відкрийте меню редагування медіатеки та знайдіть розділ \"Збереження метаданих\".",
|
||||
"HeaderKeepSeries": "Зберегти серіал",
|
||||
"HeaderKeepRecording": "Продовжуйте записувати",
|
||||
|
@ -909,7 +909,7 @@
|
|||
"LibraryAccessHelp": "Виберіть медіатеки, якими хочете поділитися з цим користувачем. Адміністратори зможуть редагувати всі папки за допомогою менеджера метаданих.",
|
||||
"LeaveBlankToNotSetAPassword": "Ви можете залишити це поле порожнім, щоб не встановлювати пароль.",
|
||||
"LearnHowYouCanContribute": "Дізнайтеся, як ви можете зробити свій внесок.",
|
||||
"LatestFromLibrary": "Нове в {0}",
|
||||
"LatestFromLibrary": "Нещодавно додано в {0}",
|
||||
"LastSeen": "Востаннє був {0}",
|
||||
"Larger": "Більший",
|
||||
"LabelZipCode": "Індекс:",
|
||||
|
@ -1049,7 +1049,7 @@
|
|||
"LabelSelectFolderGroupsHelp": "Папки, для яких не встановлено прапорець, відображатимуться самі по собі у власному поданні.",
|
||||
"LabelSeasonNumber": "Номер сезону:",
|
||||
"LabelScreensaver": "Заставка:",
|
||||
"LabelScheduledTaskLastRan": "Останній раз запускалося: {0}, час виконання: {1}.",
|
||||
"LabelScheduledTaskLastRan": "Останній запуск: {0}, час виконання: {1}.",
|
||||
"LabelSaveLocalMetadata": "Збережіть ілюстрацію в медіа-папках",
|
||||
"LabelRuntimeMinutes": "Час виконання:",
|
||||
"LabelRequireHttps": "Вимагати HTTPS",
|
||||
|
@ -1284,7 +1284,7 @@
|
|||
"PackageInstallFailed": "Помилка встановлення {0} (версія {1}).",
|
||||
"PackageInstallCompleted": "Установлення {0} (версія {1}) завершено.",
|
||||
"PackageInstallCancelled": "Установлення {0} (версія {1}) скасовано.",
|
||||
"OtherArtist": "Інший художник",
|
||||
"OtherArtist": "Інший виконавець",
|
||||
"OriginalAirDateValue": "Початкова дата ефіру: {0}",
|
||||
"OptionWakeFromSleep": "Пробудити",
|
||||
"OptionUnairedEpisode": "Невипущені епізоди",
|
||||
|
@ -1329,7 +1329,7 @@
|
|||
"OptionEnableM2tsMode": "Увімкнути режим M2TS",
|
||||
"OptionEnableForAllTuners": "Увімкнути для всіх пристроїв тюнера",
|
||||
"OptionEnableExternalContentInSuggestionsHelp": "Дозволити включати інтернет-трейлери та телепрограми в прямому ефірі до запропонованого вмісту.",
|
||||
"OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)",
|
||||
"OptionCaptionInfoExSamsung": "Функція CaptionInfoEx (Samsung)",
|
||||
"OptionEnableExternalContentInSuggestions": "Увімкнути зовнішній вміст у пропозиціях",
|
||||
"OptionEnableAccessToAllLibraries": "Увімкнути доступ до всіх медіатек",
|
||||
"OptionEnableAccessToAllChannels": "Увімкнути доступ до всіх каналів",
|
||||
|
@ -1422,7 +1422,7 @@
|
|||
"TabMyPlugins": "Мої плагіни",
|
||||
"TabMusic": "Музика",
|
||||
"TabLogs": "Журнали",
|
||||
"TabLatest": "Останні",
|
||||
"TabLatest": "Нещодавно додані",
|
||||
"TabDashboard": "Панель",
|
||||
"TabContainers": "Контейнери",
|
||||
"TabCodecs": "Кодеки",
|
||||
|
@ -1518,7 +1518,7 @@
|
|||
"SubtitleCodecNotSupported": "Кодек субтитрів не підтримується",
|
||||
"ContainerNotSupported": "Контейнер не підтримується",
|
||||
"AudioCodecNotSupported": "Аудіокодек не підтримується",
|
||||
"EnableGamepadHelp": "Слухайте вхід з будь-яких підключених контролерів.",
|
||||
"EnableGamepadHelp": "Очікуйте вхідних даних від будь-яких підключених контролерів. (Вимагається: режим відображення «TV»)",
|
||||
"LabelEnableGamepad": "Увімкнути геймпад",
|
||||
"Controls": "Елементи керування",
|
||||
"AllowVppTonemappingHelp": "Повне відображення тонів на основі драйверів Intel. Наразі працює лише на певному обладнанні з відео HDR10. Це має вищий пріоритет порівняно з іншою реалізацією OpenCL.",
|
||||
|
@ -1559,8 +1559,8 @@
|
|||
"XmlTvMovieCategoriesHelp": "Програми з цими категоріями відображатимуться як фільми. Розділіть множинні символом \"|\".",
|
||||
"XmlTvKidsCategoriesHelp": "Програми з цими категоріями відображатимуться як програми для дітей. Розділіть множинні символом \"|\".",
|
||||
"XmlDocumentAttributeListHelp": "Ці атрибути застосовуються до кореневого елемента кожної відповіді XML.",
|
||||
"Writers": "Письменники",
|
||||
"Writer": "Письменник",
|
||||
"Writers": "Сценаристи",
|
||||
"Writer": "Сценарист",
|
||||
"WizardCompleted": "Це все, що нам зараз потрібно. Jellyfin почав збирати інформацію про вашу медіатеку. Перегляньте деякі з наших програм, а потім натисніть <b>Готово</b>, щоб переглянути <b>інформаційну панель</b>.",
|
||||
"Whitelist": "Білий список",
|
||||
"WelcomeToProject": "Ласкаво просимо до Jellyfin!",
|
||||
|
@ -1685,5 +1685,20 @@
|
|||
"MessageNoFavoritesAvailable": "Зараз немає доступних улюблених.",
|
||||
"Experimental": "Експериментальний",
|
||||
"LabelStereoDownmixAlgorithm": "Stereo Downmix алгоритм",
|
||||
"StereoDownmixAlgorithmHelp": "Алгоритм мікшування багатоканального аудіо у стерео."
|
||||
"StereoDownmixAlgorithmHelp": "Алгоритм мікшування багатоканального аудіо у стерео.",
|
||||
"LabelChapterImageResolutionHelp": "Роздільна здатність витягнутих зображень розділу.",
|
||||
"PreferEmbeddedExtrasTitlesOverFileNames": "Віддавайте перевагу вбудованим назвам, а не назвам файлів для додаткових функцій",
|
||||
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Додатки часто мають таке ж вбудоване ім’я, що й батьківське, позначте це, щоб усе одно використовувати для них вбудовані заголовки.",
|
||||
"ResolutionMatchSource": "Джерело відповідності",
|
||||
"SaveRecordingNFO": "Збережіть метадані EPG запису в NFO",
|
||||
"SaveRecordingNFOHelp": "Зберігайте метадані від постачальника списків EPG разом із бічними носіями.",
|
||||
"SaveRecordingImages": "Зберегти записані зображення EPG",
|
||||
"SaveRecordingImagesHelp": "Зберігайте зображення з постачальника списків EPG разом із носієм.",
|
||||
"HeaderDummyChapter": "Зображення розділів",
|
||||
"LabelDummyChapterDuration": "Інтервал:",
|
||||
"LabelDummyChapterDurationHelp": "Інтервал вилучення зображення розділу в секундах.",
|
||||
"LabelDummyChapterCount": "Ліміт:",
|
||||
"LabelDummyChapterCountHelp": "Максимальна кількість зображень розділів, які буде видобуто для кожного медіафайлу.",
|
||||
"LabelChapterImageResolution": "Роздільна здатність:",
|
||||
"HeaderRecordingMetadataSaving": "Метадані запису"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue