Merge branch 'master' into jassub

This commit is contained in:
Cas 2023-03-15 13:28:47 +01:00 committed by GitHub
commit 8848b75be0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
101 changed files with 793 additions and 560 deletions

View file

@ -26,8 +26,6 @@ jobs:
- script: 'npm ci --no-audit' - script: 'npm ci --no-audit'
displayName: 'Install Dependencies' displayName: 'Install Dependencies'
env:
SKIP_PREPARE: 'true'
- script: 'npm run build:development' - script: 'npm run build:development'
displayName: 'Build Development' displayName: 'Build Development'

View file

@ -46,6 +46,7 @@ module.exports = {
'keyword-spacing': ['error'], 'keyword-spacing': ['error'],
'no-throw-literal': ['error'], 'no-throw-literal': ['error'],
'max-statements-per-line': ['error'], 'max-statements-per-line': ['error'],
'max-params': ['error', 7],
'no-duplicate-imports': ['error'], 'no-duplicate-imports': ['error'],
'no-empty-function': ['error'], 'no-empty-function': ['error'],
'no-floating-decimal': ['error'], 'no-floating-decimal': ['error'],
@ -67,6 +68,7 @@ module.exports = {
'padded-blocks': ['error', 'never'], 'padded-blocks': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }], 'prefer-const': ['error', { 'destructuring': 'all' }],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'radix': ['error'],
'@babel/semi': ['error'], '@babel/semi': ['error'],
'space-before-blocks': ['error'], 'space-before-blocks': ['error'],
'space-infix-ops': 'error', 'space-infix-ops': 'error',

View file

@ -21,11 +21,11 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 uses: github/codeql-action/init@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 uses: github/codeql-action/autobuild@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 uses: github/codeql-action/analyze@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6

View file

@ -24,8 +24,6 @@ jobs:
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci --no-audit run: npm ci --no-audit
env:
SKIP_PREPARE: true
- name: Run a production build - name: Run a production build
run: npm run build:production run: npm run build:production
@ -50,8 +48,6 @@ jobs:
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci --no-audit run: npm ci --no-audit
env:
SKIP_PREPARE: true
- name: Run eslint - name: Run eslint
run: npm run lint run: npm run lint
@ -76,8 +72,6 @@ jobs:
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci --no-audit run: npm ci --no-audit
env:
SKIP_PREPARE: true
- name: Run stylelint - name: Run stylelint
run: npm run stylelint:css run: npm run stylelint:css
@ -102,8 +96,6 @@ jobs:
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci --no-audit run: npm ci --no-audit
env:
SKIP_PREPARE: true
- name: Run stylelint - name: Run stylelint
run: npm run stylelint:scss run: npm run stylelint:scss

View file

@ -24,8 +24,6 @@ jobs:
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci --no-audit run: npm ci --no-audit
env:
SKIP_PREPARE: true
- name: Run tsc - name: Run tsc
run: npm run build:check run: npm run build:check

View file

@ -59,6 +59,7 @@
- [Vankerkom](https://github.com/vankerkom) - [Vankerkom](https://github.com/vankerkom)
- [edvwib](https://github.com/edvwib) - [edvwib](https://github.com/edvwib)
- [Rob Farraher](https://github.com/farraherbg) - [Rob Farraher](https://github.com/farraherbg)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
# Emby Contributors # Emby Contributors

1
debian/rules vendored
View file

@ -12,6 +12,7 @@ override_dh_clistrip:
override_dh_auto_build: override_dh_auto_build:
npm ci --no-audit --unsafe-perm npm ci --no-audit --unsafe-perm
npm run build:production
mv $(CURDIR)/dist $(CURDIR)/web mv $(CURDIR)/dist $(CURDIR)/web
override_dh_auto_clean: override_dh_auto_clean:

View file

@ -8,4 +8,6 @@ RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool ma
WORKDIR ${SOURCE_DIR} WORKDIR ${SOURCE_DIR}
COPY . . COPY . .
RUN npm ci --no-audit --unsafe-perm && mv dist ${ARTIFACT_DIR} RUN npm ci --no-audit --unsafe-perm \
&& npm run build:production \
&& mv dist ${ARTIFACT_DIR}

View file

@ -1,4 +1,4 @@
FROM fedora:37 FROM fedora:38
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin

View file

@ -15,6 +15,7 @@ fi
# build archives # build archives
npm ci --no-audit --unsafe-perm npm ci --no-audit --unsafe-perm
npm run build:production
mv dist jellyfin-web_${version} mv dist jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version} tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist rm -rf dist

View file

@ -35,6 +35,7 @@ chown root:root -R .
%build %build
npm ci --no-audit --unsafe-perm npm ci --no-audit --unsafe-perm
npm run build:production
%install %install

324
package-lock.json generated
View file

@ -22,14 +22,14 @@
"classnames": "2.3.2", "classnames": "2.3.2",
"core-js": "3.29.0", "core-js": "3.29.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"dompurify": "2.4.4", "dompurify": "3.0.1",
"epubjs": "0.4.2", "epubjs": "0.4.2",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"fast-text-encoding": "1.0.6", "fast-text-encoding": "1.0.6",
"flv.js": "1.6.2", "flv.js": "1.6.2",
"headroom.js": "0.12.0", "headroom.js": "0.12.0",
"history": "5.3.0", "history": "5.3.0",
"hls.js": "0.14.17", "hls.js": "1.3.4",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"jassub": "1.5.3", "jassub": "1.5.3",
"jellyfin-apiclient": "1.10.0", "jellyfin-apiclient": "1.10.0",
@ -43,7 +43,7 @@
"pdfjs-dist": "2.16.105", "pdfjs-dist": "2.16.105",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-router-dom": "6.8.1", "react-router-dom": "6.8.2",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2", "screenfull": "6.0.2",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
@ -68,8 +68,8 @@
"@types/lodash-es": "4.17.6", "@types/lodash-es": "4.17.6",
"@types/react": "17.0.53", "@types/react": "17.0.53",
"@types/react-dom": "17.0.19", "@types/react-dom": "17.0.19",
"@typescript-eslint/eslint-plugin": "5.53.0", "@typescript-eslint/eslint-plugin": "5.54.1",
"@typescript-eslint/parser": "5.53.0", "@typescript-eslint/parser": "5.54.1",
"@uupaa/dynamic-import-polyfill": "1.0.2", "@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"babel-loader": "9.1.2", "babel-loader": "9.1.2",
@ -105,7 +105,7 @@
"stylelint": "15.2.0", "stylelint": "15.2.0",
"stylelint-config-rational-order": "0.1.2", "stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1", "stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "6.0.2", "stylelint-order": "6.0.3",
"stylelint-scss": "4.4.0", "stylelint-scss": "4.4.0",
"ts-loader": "9.4.2", "ts-loader": "9.4.2",
"typescript": "4.9.5", "typescript": "4.9.5",
@ -2606,11 +2606,11 @@
"dev": true "dev": true
}, },
"node_modules/@jellyfin/sdk": { "node_modules/@jellyfin/sdk": {
"version": "0.0.0-unstable.202302070552", "version": "0.0.0-unstable.202303130502",
"resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202302070552.tgz", "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz",
"integrity": "sha512-hwrHLLFPTCEcrMywpLWwgGKEDKBjgu3o+ruMV3qCG7uAmKAQq48kuaZ818rJD+LjWBjBIUixnLJq1qUlHsgc+A==", "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==",
"dependencies": { "dependencies": {
"axios": "1.2.6", "axios": "1.3.4",
"compare-versions": "5.0.3" "compare-versions": "5.0.3"
} }
}, },
@ -2762,9 +2762,9 @@
} }
}, },
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.3.2", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.3.tgz",
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==", "integrity": "sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
} }
@ -3195,14 +3195,14 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz",
"integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/type-utils": "5.53.0", "@typescript-eslint/type-utils": "5.54.1",
"@typescript-eslint/utils": "5.53.0", "@typescript-eslint/utils": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"grapheme-splitter": "^1.0.4", "grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
@ -3244,14 +3244,14 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz",
"integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -3271,13 +3271,13 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz",
"integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/visitor-keys": "5.53.0" "@typescript-eslint/visitor-keys": "5.54.1"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3288,13 +3288,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz",
"integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"@typescript-eslint/utils": "5.53.0", "@typescript-eslint/utils": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
@ -3315,9 +3315,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz",
"integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -3328,13 +3328,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz",
"integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/visitor-keys": "5.53.0", "@typescript-eslint/visitor-keys": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -3399,16 +3399,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz",
"integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -3440,12 +3440,12 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz",
"integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"engines": { "engines": {
@ -4106,9 +4106,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.2.6", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==", "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -5960,9 +5960,9 @@
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww=="
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "2.4.4", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz",
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw=="
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "1.7.0", "version": "1.7.0",
@ -7301,7 +7301,8 @@
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
}, },
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
@ -8467,13 +8468,9 @@
} }
}, },
"node_modules/hls.js": { "node_modules/hls.js": {
"version": "0.14.17", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.4.tgz",
"integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==", "integrity": "sha512-iFEwVqtEDk6sKotcTwtJ5OMo/nuDTk9PrpB8FI2J2WYf8EriTVfR4FaK0aNyYtwbYeRSWCXJKlz23xeREdlNYg=="
"dependencies": {
"eventemitter3": "^4.0.3",
"url-toolkit": "^2.1.6"
}
}, },
"node_modules/hoist-non-react-statics": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
@ -13131,11 +13128,11 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.8.1", "version": "6.8.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.2.tgz",
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", "integrity": "sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==",
"dependencies": { "dependencies": {
"@remix-run/router": "1.3.2" "@remix-run/router": "1.3.3"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -13145,12 +13142,12 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "6.8.1", "version": "6.8.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.2.tgz",
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", "integrity": "sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==",
"dependencies": { "dependencies": {
"@remix-run/router": "1.3.2", "@remix-run/router": "1.3.3",
"react-router": "6.8.1" "react-router": "6.8.2"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -17080,22 +17077,22 @@
} }
}, },
"node_modules/stylelint-order": { "node_modules/stylelint-order": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.2.tgz", "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz",
"integrity": "sha512-yuac0BE6toHd27wUPvYVVQicAJthKFIv1HPQFH3Q0dExiO3Z6Uam7geoO0tUd5Z9ddsATYK++1qWNDX4RxMH5Q==", "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"postcss": "^8.4.21", "postcss": "^8.4.21",
"postcss-sorting": "^8.0.1" "postcss-sorting": "^8.0.2"
}, },
"peerDependencies": { "peerDependencies": {
"stylelint": "^14.0.0 || ^15.0.0" "stylelint": "^14.0.0 || ^15.0.0"
} }
}, },
"node_modules/stylelint-order/node_modules/postcss-sorting": { "node_modules/stylelint-order/node_modules/postcss-sorting": {
"version": "8.0.1", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
"integrity": "sha512-go9Zoxx7KQH+uLrJ9xa5wRErFeXu01ydA6O8m7koPXkmAN7Ts//eRcIqjo0stBR4+Nir2gMYDOWAOx7O5EPUZA==", "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
"dev": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"postcss": "^8.4.20" "postcss": "^8.4.20"
@ -18243,11 +18240,6 @@
"deprecated": "Please see https://github.com/lydell/urix#deprecated", "deprecated": "Please see https://github.com/lydell/urix#deprecated",
"dev": true "dev": true
}, },
"node_modules/url-toolkit": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.3.tgz",
"integrity": "sha512-Da75SQoxsZ+2wXS56CZBrj2nukQ4nlGUZUP/dqUBG5E1su5GKThgT94Q00x81eVII7AyS1Pn+CtTTZ4Z0pLUtQ=="
},
"node_modules/use": { "node_modules/use": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@ -20929,11 +20921,11 @@
"dev": true "dev": true
}, },
"@jellyfin/sdk": { "@jellyfin/sdk": {
"version": "0.0.0-unstable.202302070552", "version": "0.0.0-unstable.202303130502",
"resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202302070552.tgz", "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz",
"integrity": "sha512-hwrHLLFPTCEcrMywpLWwgGKEDKBjgu3o+ruMV3qCG7uAmKAQq48kuaZ818rJD+LjWBjBIUixnLJq1qUlHsgc+A==", "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==",
"requires": { "requires": {
"axios": "1.2.6", "axios": "1.3.4",
"compare-versions": "5.0.3" "compare-versions": "5.0.3"
} }
}, },
@ -21054,9 +21046,9 @@
} }
}, },
"@remix-run/router": { "@remix-run/router": {
"version": "1.3.2", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.3.tgz",
"integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==" "integrity": "sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w=="
}, },
"@rollup/plugin-babel": { "@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
@ -21454,14 +21446,14 @@
} }
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz",
"integrity": "sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==", "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/type-utils": "5.53.0", "@typescript-eslint/type-utils": "5.54.1",
"@typescript-eslint/utils": "5.53.0", "@typescript-eslint/utils": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"grapheme-splitter": "^1.0.4", "grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
@ -21483,53 +21475,53 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz",
"integrity": "sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==", "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"debug": "^4.3.4" "debug": "^4.3.4"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz",
"integrity": "sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==", "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/visitor-keys": "5.53.0" "@typescript-eslint/visitor-keys": "5.54.1"
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz",
"integrity": "sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==", "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"@typescript-eslint/utils": "5.53.0", "@typescript-eslint/utils": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz",
"integrity": "sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==", "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==",
"dev": true "dev": true
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz",
"integrity": "sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==", "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/visitor-keys": "5.53.0", "@typescript-eslint/visitor-keys": "5.54.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -21569,16 +21561,16 @@
} }
}, },
"@typescript-eslint/utils": { "@typescript-eslint/utils": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz",
"integrity": "sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==", "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.53.0", "@typescript-eslint/scope-manager": "5.54.1",
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"@typescript-eslint/typescript-estree": "5.53.0", "@typescript-eslint/typescript-estree": "5.54.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@ -21596,12 +21588,12 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "5.53.0", "version": "5.54.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz",
"integrity": "sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==", "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "5.53.0", "@typescript-eslint/types": "5.54.1",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"dependencies": { "dependencies": {
@ -22105,9 +22097,9 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "1.2.6", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==", "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"requires": { "requires": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -23497,9 +23489,9 @@
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww=="
}, },
"dompurify": { "dompurify": {
"version": "2.4.4", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz",
"integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw=="
}, },
"domutils": { "domutils": {
"version": "1.7.0", "version": "1.7.0",
@ -24506,7 +24498,8 @@
"eventemitter3": { "eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
}, },
"events": { "events": {
"version": "3.3.0", "version": "3.3.0",
@ -25419,13 +25412,9 @@
} }
}, },
"hls.js": { "hls.js": {
"version": "0.14.17", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.4.tgz",
"integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==", "integrity": "sha512-iFEwVqtEDk6sKotcTwtJ5OMo/nuDTk9PrpB8FI2J2WYf8EriTVfR4FaK0aNyYtwbYeRSWCXJKlz23xeREdlNYg=="
"requires": {
"eventemitter3": "^4.0.3",
"url-toolkit": "^2.1.6"
}
}, },
"hoist-non-react-statics": { "hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
@ -28744,20 +28733,20 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-router": { "react-router": {
"version": "6.8.1", "version": "6.8.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.2.tgz",
"integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", "integrity": "sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==",
"requires": { "requires": {
"@remix-run/router": "1.3.2" "@remix-run/router": "1.3.3"
} }
}, },
"react-router-dom": { "react-router-dom": {
"version": "6.8.1", "version": "6.8.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.2.tgz",
"integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", "integrity": "sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==",
"requires": { "requires": {
"@remix-run/router": "1.3.2", "@remix-run/router": "1.3.3",
"react-router": "6.8.1" "react-router": "6.8.2"
} }
}, },
"read-file-stdin": { "read-file-stdin": {
@ -31990,19 +31979,19 @@
} }
}, },
"stylelint-order": { "stylelint-order": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.2.tgz", "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz",
"integrity": "sha512-yuac0BE6toHd27wUPvYVVQicAJthKFIv1HPQFH3Q0dExiO3Z6Uam7geoO0tUd5Z9ddsATYK++1qWNDX4RxMH5Q==", "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"postcss": "^8.4.21", "postcss": "^8.4.21",
"postcss-sorting": "^8.0.1" "postcss-sorting": "^8.0.2"
}, },
"dependencies": { "dependencies": {
"postcss-sorting": { "postcss-sorting": {
"version": "8.0.1", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
"integrity": "sha512-go9Zoxx7KQH+uLrJ9xa5wRErFeXu01ydA6O8m7koPXkmAN7Ts//eRcIqjo0stBR4+Nir2gMYDOWAOx7O5EPUZA==", "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
"dev": true, "dev": true,
"requires": {} "requires": {}
} }
@ -32726,11 +32715,6 @@
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
"dev": true "dev": true
}, },
"url-toolkit": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.3.tgz",
"integrity": "sha512-Da75SQoxsZ+2wXS56CZBrj2nukQ4nlGUZUP/dqUBG5E1su5GKThgT94Q00x81eVII7AyS1Pn+CtTTZ4Z0pLUtQ=="
},
"use": { "use": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",

View file

@ -19,8 +19,8 @@
"@types/lodash-es": "4.17.6", "@types/lodash-es": "4.17.6",
"@types/react": "17.0.53", "@types/react": "17.0.53",
"@types/react-dom": "17.0.19", "@types/react-dom": "17.0.19",
"@typescript-eslint/eslint-plugin": "5.53.0", "@typescript-eslint/eslint-plugin": "5.54.1",
"@typescript-eslint/parser": "5.53.0", "@typescript-eslint/parser": "5.54.1",
"@uupaa/dynamic-import-polyfill": "1.0.2", "@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"babel-loader": "9.1.2", "babel-loader": "9.1.2",
@ -56,7 +56,7 @@
"stylelint": "15.2.0", "stylelint": "15.2.0",
"stylelint-config-rational-order": "0.1.2", "stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1", "stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "6.0.2", "stylelint-order": "6.0.3",
"stylelint-scss": "4.4.0", "stylelint-scss": "4.4.0",
"ts-loader": "9.4.2", "ts-loader": "9.4.2",
"typescript": "4.9.5", "typescript": "4.9.5",
@ -81,14 +81,14 @@
"classnames": "2.3.2", "classnames": "2.3.2",
"core-js": "3.29.0", "core-js": "3.29.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"dompurify": "2.4.4", "dompurify": "3.0.1",
"epubjs": "0.4.2", "epubjs": "0.4.2",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"fast-text-encoding": "1.0.6", "fast-text-encoding": "1.0.6",
"flv.js": "1.6.2", "flv.js": "1.6.2",
"headroom.js": "0.12.0", "headroom.js": "0.12.0",
"history": "5.3.0", "history": "5.3.0",
"hls.js": "0.14.17", "hls.js": "1.3.4",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"jassub": "1.5.3", "jassub": "1.5.3",
"jellyfin-apiclient": "1.10.0", "jellyfin-apiclient": "1.10.0",
@ -102,7 +102,7 @@
"pdfjs-dist": "2.16.105", "pdfjs-dist": "2.16.105",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-router-dom": "6.8.1", "react-router-dom": "6.8.2",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2", "screenfull": "6.0.2",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
@ -131,7 +131,6 @@
"scripts": { "scripts": {
"start": "npm run serve", "start": "npm run serve",
"serve": "webpack serve --config webpack.dev.js", "serve": "webpack serve --config webpack.dev.js",
"prepare": "node ./scripts/prepare.js",
"build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js", "build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js", "build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js",
"build:check": "tsc --noEmit", "build:check": "tsc --noEmit",

View file

@ -1,12 +0,0 @@
const { execSync } = require('child_process');
/**
* The npm `prepare` script needs to run a build to support installing
* a package from git repositories (this is dumb but a limitation of how
* npm behaves). We don't want to run these in CI though because
* building is slow so this script will skip the build when the
* `SKIP_PREPARE` environment variable has been set.
*/
if (!process.env.SKIP_PREPARE) {
execSync('webpack --config webpack.prod.js', { stdio: 'inherit' });
}

View file

@ -19,7 +19,7 @@ import template from './accessSchedule.template.html';
const pct = hours % 1; const pct = hours % 1;
if (pct) { if (pct) {
minutes = parseInt(60 * pct); minutes = parseInt(60 * pct, 10);
} }
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));

View file

@ -64,10 +64,10 @@ import { toBoolean } from '../utils/string.ts';
function reloadData(instance, elem, apiClient, startIndex, limit) { function reloadData(instance, elem, apiClient, startIndex, limit) {
if (startIndex == null) { if (startIndex == null) {
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0'); startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10);
} }
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7'); limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10);
const minDate = new Date(); const minDate = new Date();
const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true);

View file

@ -18,6 +18,7 @@ import browser from '../../scripts/browser';
import { playbackManager } from '../playback/playbackmanager'; import { playbackManager } from '../playback/playbackmanager';
import itemShortcuts from '../shortcuts'; import itemShortcuts from '../shortcuts';
import imageHelper from '../../scripts/imagehelper'; import imageHelper from '../../scripts/imagehelper';
import { randomInt } from '../../utils/number.ts';
import './card.scss'; import './card.scss';
import '../../elements/emby-button/paper-icon-button-light'; import '../../elements/emby-button/paper-icon-button-light';
import '../guide/programs.scss'; import '../guide/programs.scss';
@ -640,16 +641,6 @@ import { appRouter } from '../appRouter';
}; };
} }
/**
* Generates a random integer in a given range.
* @param {number} min - Minimum of the range.
* @param {number} max - Maximum of the range.
* @returns {number} Randomly generated number.
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/** /**
* Generates an index used to select the default color of a card based on a string. * Generates an index used to select the default color of a card based on a string.
* @param {?string} [str] - String to use for generating the index. * @param {?string} [str] - String to use for generating the index.
@ -663,13 +654,13 @@ import { appRouter } from '../appRouter';
const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); const character = String(str.slice(charIndex, charIndex + 1).charCodeAt());
let sum = 0; let sum = 0;
for (let i = 0; i < character.length; i++) { for (let i = 0; i < character.length; i++) {
sum += parseInt(character.charAt(i)); sum += parseInt(character.charAt(i), 10);
} }
const index = String(sum).slice(-1); const index = String(sum).slice(-1);
return (index % numRandomColors) + 1; return (index % numRandomColors) + 1;
} else { } else {
return getRandomInt(1, numRandomColors); return randomInt(1, numRandomColors);
} }
} }
@ -773,27 +764,24 @@ import { appRouter } from '../appRouter';
* @param {Object} item - Item used to generate the footer text. * @param {Object} item - Item used to generate the footer text.
* @param {Object} apiClient - API client instance. * @param {Object} apiClient - API client instance.
* @param {Object} options - Options used to generate the footer text. * @param {Object} options - Options used to generate the footer text.
* @param {string} showTitle - Flag to show the title in the footer.
* @param {boolean} forceName - Flag to force showing the name of the item.
* @param {boolean} overlayText - Flag to show overlay text.
* @param {Object} imgUrl - Object representing the card's image URL.
* @param {string} footerClass - CSS classes of the footer element. * @param {string} footerClass - CSS classes of the footer element.
* @param {string} progressHtml - HTML markup of the progress bar element. * @param {string} progressHtml - HTML markup of the progress bar element.
* @param {string} logoUrl - URL of the logo for the item. * @param {Object} flags - Various flags for the footer
* @param {boolean} isOuterFooter - Flag to mark the text as outer footer. * @param {Object} urls - Various urls for the footer
* @returns {string} HTML markup of the card's footer text element. * @returns {string} HTML markup of the card's footer text element.
*/ */
function getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerClass, progressHtml, logoUrl, isOuterFooter) { function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) {
item = item.ProgramInfo || item; item = item.ProgramInfo || item;
let html = ''; let html = '';
if (logoUrl) { if (urls.logoUrl) {
html += '<div class="lazy cardFooterLogo" data-src="' + logoUrl + '"></div>'; html += '<div class="lazy cardFooterLogo" data-src="' + urls.logoUrl + '"></div>';
} }
const showOtherText = isOuterFooter ? !overlayText : overlayText; const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder');
const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText;
if (isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') {
html += `<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`; html += `<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`;
} }
@ -805,7 +793,7 @@ import { appRouter } from '../appRouter';
let titleAdded; let titleAdded;
if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) { if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (item.SeriesId) { if (item.SeriesId) {
lines.push(getTextActionButton({ lines.push(getTextActionButton({
Id: item.SeriesId, Id: item.SeriesId,
@ -835,7 +823,7 @@ import { appRouter } from '../appRouter';
} }
let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length); let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length);
if (!showMediaTitle && !titleAdded && (showTitle || forceName)) { if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) {
showMediaTitle = true; showMediaTitle = true;
} }
@ -856,7 +844,7 @@ import { appRouter } from '../appRouter';
if (showOtherText) { if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) { if (options.showParentTitle && parentTitleUnderneath) {
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
item.AlbumArtists[0].Type = 'MusicArtist'; item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true; item.AlbumArtists[0].IsFolder = true;
lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId));
@ -991,23 +979,23 @@ import { appRouter } from '../appRouter';
} }
} }
if ((showTitle || !imgUrl) && forceName && overlayText && lines.length === 1) { if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) {
lines = []; lines = [];
} }
if (overlayText && showTitle) { if (flags.overlayText && showTitle) {
lines = [escapeHtml(item.Name)]; lines = [escapeHtml(item.Name)];
} }
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
if (progressHtml) { if (progressHtml) {
html += progressHtml; html += progressHtml;
} }
if (html && (!isOuterFooter || logoUrl || options.cardLayout)) { if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) {
html = '<div class="' + footerClass + '">' + html; html = '<div class="' + footerClass + '">' + html;
//cardFooter //cardFooter
@ -1217,7 +1205,6 @@ import { appRouter } from '../appRouter';
const forceName = imgInfo.forceName; const forceName = imgInfo.forceName;
const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder');
const overlayText = options.overlayText; const overlayText = options.overlayText;
let cardImageContainerClass = 'cardImageContainer'; let cardImageContainerClass = 'cardImageContainer';
@ -1265,7 +1252,7 @@ import { appRouter } from '../appRouter';
logoUrl = null; logoUrl = null;
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
innerCardFooter += getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, false); innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl });
footerOverlayed = true; footerOverlayed = true;
} else if (progressHtml) { } else if (progressHtml) {
innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">'; innerCardFooter += '<div class="innerCardFooter fullInnerCardFooter innerCardFooterClear">';
@ -1292,7 +1279,7 @@ import { appRouter } from '../appRouter';
logoUrl = null; logoUrl = null;
} }
outerCardFooter = getCardFooterText(item, apiClient, options, showTitle, forceName, overlayText, imgUrl, footerCssClass, progressHtml, logoUrl, true); outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl });
} }
if (outerCardFooter && !options.cardLayout) { if (outerCardFooter && !options.cardLayout) {

View file

@ -8,6 +8,7 @@ import datetime from '../../scripts/datetime';
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import loading from '../loading/loading'; import loading from '../loading/loading';
import skinManager from '../../scripts/themeManager'; import skinManager from '../../scripts/themeManager';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import '../../elements/emby-select/emby-select'; import '../../elements/emby-select/emby-select';
import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-checkbox/emby-checkbox';
@ -35,7 +36,7 @@ import template from './displaySettings.template.html';
function loadScreensavers(context, userSettings) { function loadScreensavers(context, userSettings) {
const selectScreensaver = context.querySelector('.selectScreensaver'); const selectScreensaver = context.querySelector('.selectScreensaver');
const options = pluginManager.ofType('screensaver').map(plugin => { const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => {
return { return {
name: plugin.name, name: plugin.name,
value: plugin.id value: plugin.id

View file

@ -13,7 +13,7 @@ import ServerConnections from './ServerConnections';
const playedIndicator = card.querySelector('.playedIndicator'); const playedIndicator = card.querySelector('.playedIndicator');
const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null;
const options = { const options = {
Limit: parseInt(playedIndicatorHtml || '10'), Limit: parseInt(playedIndicatorHtml || '10', 10),
Fields: 'PrimaryImageAspectRatio,DateCreated', Fields: 'PrimaryImageAspectRatio,DateCreated',
ParentId: itemId, ParentId: itemId,
GroupItems: false GroupItems: false

View file

@ -345,7 +345,9 @@ function Guide(options) {
} }
apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) { apiClient.getLiveTvPrograms(programQuery).then(function (programsResult) {
renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender); const guideOptions = { focusProgramOnRender, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs };
renderGuide(context, date, channelsResult.Items, programsResult.Items, renderOptions, guideOptions, apiClient);
hideLoading(); hideLoading();
}); });
@ -667,7 +669,7 @@ function Guide(options) {
return (channelIndex * 10000000) + (start.getTime() / 60000); return (channelIndex * 10000000) + (start.getTime() / 60000);
} }
function renderGuide(context, date, channels, programs, renderOptions, apiClient, scrollToTimeMs, focusToTimeMs, startTimeOfDayMs, focusProgramOnRender) { function renderGuide(context, date, channels, programs, renderOptions, guideOptions, apiClient) {
programs.sort(function (a, b) { programs.sort(function (a, b) {
return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels); return getProgramSortOrder(a, channels) - getProgramSortOrder(b, channels);
}); });
@ -689,11 +691,11 @@ function Guide(options) {
items = {}; items = {};
renderPrograms(context, date, channels, programs, renderOptions); renderPrograms(context, date, channels, programs, renderOptions);
if (focusProgramOnRender) { if (guideOptions.focusProgramOnRender) {
focusProgram(context, itemId, channelRowId, focusToTimeMs, startTimeOfDayMs); focusProgram(context, itemId, channelRowId, guideOptions.focusToTimeMs, guideOptions.startTimeOfDayMs);
} }
scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs); scrollProgramGridToTimeMs(context, guideOptions.scrollToTimeMs, guideOptions.startTimeOfDayMs);
} }
function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) { function scrollProgramGridToTimeMs(context, scrollToTimeMs, startTimeOfDayMs) {
@ -1147,12 +1149,12 @@ function Guide(options) {
guideContext.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { guideContext.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) {
const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button');
const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex, 10)];
if (tabButton) { if (tabButton) {
const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex, 10)];
const date = new Date(); const date = new Date();
date.setTime(parseInt(tabButton.getAttribute('data-date'))); date.setTime(parseInt(tabButton.getAttribute('data-date'), 10));
const scrollWidth = programGrid.scrollWidth; const scrollWidth = programGrid.scrollWidth;
let scrollToTimeMs; let scrollToTimeMs;
@ -1164,7 +1166,7 @@ function Guide(options) {
if (previousButton) { if (previousButton) {
const previousDate = new Date(); const previousDate = new Date();
previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); previousDate.setTime(parseInt(previousButton.getAttribute('data-date'), 10));
scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000);
scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000);

View file

@ -96,7 +96,7 @@ import template from './imageeditor.template.html';
return apiClient.getScaledImageUrl(item.Id || item.ItemId, options); return apiClient.getScaledImageUrl(item.Id || item.ItemId, options);
} }
function getCardHtml(image, index, numImages, apiClient, imageProviders, imageSize, tagName, enableFooterButtons) { function getCardHtml(image, apiClient, options) {
// TODO move card creation code to Card component // TODO move card creation code to Card component
let html = ''; let html = '';
@ -106,7 +106,7 @@ import template from './imageeditor.template.html';
cssClass += ' backdropCard backdropCard-scalable'; cssClass += ' backdropCard backdropCard-scalable';
if (tagName === 'button') { if (options.tagName === 'button') {
cssClass += ' btnImageCard'; cssClass += ' btnImageCard';
if (layoutManager.tv) { if (layoutManager.tv) {
@ -122,7 +122,7 @@ import template from './imageeditor.template.html';
html += '<div class="' + cssClass + '"'; html += '<div class="' + cssClass + '"';
} }
html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + index + '" data-numimages="' + numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + imageProviders.length + '"'; html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + options.index + '" data-numimages="' + options.numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + options.imageProviders.length + '"';
html += '>'; html += '>';
@ -132,7 +132,7 @@ import template from './imageeditor.template.html';
html += '<div class="cardContent">'; html += '<div class="cardContent">';
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize }); const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: options.imageSize });
html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>'; html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>';
@ -151,23 +151,23 @@ import template from './imageeditor.template.html';
} }
html += '</div>'; html += '</div>';
if (enableFooterButtons) { if (options.enableFooterButtons) {
html += '<div class="cardText cardTextCentered">'; html += '<div class="cardText cardTextCentered">';
if (image.ImageType === 'Backdrop') { if (image.ImageType === 'Backdrop') {
if (index > 0) { if (options.index > 0) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
} else { } else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>';
} }
if (index < numImages - 1) { if (options.index < options.numImages - 1) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
} else { } else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
} }
} else { } else {
if (imageProviders.length) { if (options.imageProviders.length) {
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>'; html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>';
} }
} }
@ -178,7 +178,7 @@ import template from './imageeditor.template.html';
html += '</div>'; html += '</div>';
html += '</div>'; html += '</div>';
html += '</' + tagName + '>'; html += '</' + options.tagName + '>';
return html; return html;
} }
@ -226,7 +226,8 @@ import template from './imageeditor.template.html';
for (let i = 0, length = images.length; i < length; i++) { for (let i = 0, length = images.length; i < length; i++) {
const image = images[i]; const image = images[i];
html += getCardHtml(image, i, length, apiClient, imageProviders, imageSize, tagName, enableFooterButtons); const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons };
html += getCardHtml(image, apiClient, options);
} }
elem.innerHTML = html; elem.innerHTML = html;
@ -277,9 +278,9 @@ import template from './imageeditor.template.html';
const apiClient = ServerConnections.getApiClient(serverId); const apiClient = ServerConnections.getApiClient(serverId);
const type = imageCard.getAttribute('data-imagetype'); const type = imageCard.getAttribute('data-imagetype');
const index = parseInt(imageCard.getAttribute('data-index')); const index = parseInt(imageCard.getAttribute('data-index'), 10);
const providerCount = parseInt(imageCard.getAttribute('data-providers')); const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10);
const numImages = parseInt(imageCard.getAttribute('data-numimages')); const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10);
import('../actionSheet/actionSheet').then(({default: actionSheet}) => { import('../actionSheet/actionSheet').then(({default: actionSheet}) => {
const commands = []; const commands = [];
@ -384,7 +385,7 @@ import template from './imageeditor.template.html';
addListeners(context, 'btnDeleteImage', 'click', function () { addListeners(context, 'btnDeleteImage', 'click', function () {
const type = this.getAttribute('data-imagetype'); const type = this.getAttribute('data-imagetype');
let index = this.getAttribute('data-index'); let index = this.getAttribute('data-index');
index = index === 'null' ? null : parseInt(index); index = index === 'null' ? null : parseInt(index, 10);
const apiClient = ServerConnections.getApiClient(currentItem.ServerId); const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
deleteImage(context, currentItem.Id, type, index, apiClient, true); deleteImage(context, currentItem.Id, type, index, apiClient, true);
}); });

View file

@ -15,7 +15,6 @@ import toast from './toast/toast';
const user = options.user; const user = options.user;
const canPlay = playbackManager.canPlay(item); const canPlay = playbackManager.canPlay(item);
const restrictOptions = (browser.operaTv || browser.web0s) && !user.Policy.IsAdministrator;
const commands = []; const commands = [];
@ -99,8 +98,8 @@ import toast from './toast/toast';
}); });
} }
if (!restrictOptions) { if (!browser.tv) {
if (itemHelper.supportsAddingToCollection(item)) { if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) {
commands.push({ commands.push({
name: globalize.translate('AddToCollection'), name: globalize.translate('AddToCollection'),
id: 'addtocollection', id: 'addtocollection',
@ -272,7 +271,7 @@ import toast from './toast/toast';
}); });
} }
if (!restrictOptions && options.share === true && itemHelper.canShare(item, user)) { if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) {
commands.push({ commands.push({
name: globalize.translate('Share'), name: globalize.translate('Share'),
id: 'share', id: 'share',

View file

@ -138,7 +138,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`)); attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
} }
if (stream.BitRate) { if (stream.BitRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000)} kbps`)); attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`));
} }
if (stream.SampleRate) { if (stream.SampleRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`)); attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`));

View file

@ -52,7 +52,7 @@ import datetime from '../../scripts/datetime';
if (value) { if (value) {
if (identifyField[i].type === 'number') { if (identifyField[i].type === 'number') {
value = parseInt(value); value = parseInt(value, 10);
} }
lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; lookupInfo[identifyField[i].getAttribute('data-lookup')] = value;
@ -123,7 +123,7 @@ import datetime from '../../scripts/datetime';
elem.innerHTML = html; elem.innerHTML = html;
function onSearchImageClick() { function onSearchImageClick() {
const index = parseInt(this.getAttribute('data-index')); const index = parseInt(this.getAttribute('data-index'), 10);
const currentResult = results[index]; const currentResult = results[index];

View file

@ -531,7 +531,7 @@ import template from './libraryoptionseditor.template.html';
PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value, PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value,
MetadataCountryCode: parent.querySelector('#selectCountry').value, MetadataCountryCode: parent.querySelector('#selectCountry').value,
SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value, SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value,
AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value), AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10),
EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked,
EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked, EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked,
EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked,

View file

@ -168,7 +168,7 @@ import template from './mediaLibraryCreator.template.html';
function onRemoveClick(e) { function onRemoveClick(e) {
const button = dom.parentWithClass(e.target, 'btnRemovePath'); const button = dom.parentWithClass(e.target, 'btnRemovePath');
const index = parseInt(button.getAttribute('data-index')); const index = parseInt(button.getAttribute('data-index'), 10);
const location = pathInfos[index].Path; const location = pathInfos[index].Path;
const locationLower = location.toLowerCase(); const locationLower = location.toLowerCase();
pathInfos = pathInfos.filter(p => { pathInfos = pathInfos.filter(p => {

View file

@ -93,7 +93,7 @@ import template from './mediaLibraryEditor.template.html';
const listItem = dom.parentWithClass(e.target, 'listItem'); const listItem = dom.parentWithClass(e.target, 'listItem');
if (listItem) { if (listItem) {
const index = parseInt(listItem.getAttribute('data-index')); const index = parseInt(listItem.getAttribute('data-index'), 10);
const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || [];
const pathInfo = index == null ? {} : pathInfos[index] || {}; const pathInfo = index == null ? {} : pathInfos[index] || {};
const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]);

View file

@ -357,14 +357,14 @@ import template from './metadataEditor.template.html';
let index; let index;
const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson');
if (btnDeletePerson) { if (btnDeletePerson) {
index = parseInt(btnDeletePerson.getAttribute('data-index')); index = parseInt(btnDeletePerson.getAttribute('data-index'), 10);
currentItem.People.splice(index, 1); currentItem.People.splice(index, 1);
populatePeople(context, currentItem.People); populatePeople(context, currentItem.People);
} }
const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson');
if (btnEditPerson) { if (btnEditPerson) {
index = parseInt(btnEditPerson.getAttribute('data-index')); index = parseInt(btnEditPerson.getAttribute('data-index'), 10);
editPerson(context, currentItem.People[index], index); editPerson(context, currentItem.People[index], index);
} }
}); });

View file

@ -114,8 +114,8 @@ import shell from '../../scripts/shell';
const itemId = item.Id; const itemId = item.Id;
// Convert to ms // Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0); const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10);
const isPaused = playState.IsPaused || false; const isPaused = playState.IsPaused || false;
const canSeek = playState.CanSeek || false; const canSeek = playState.CanSeek || false;
@ -247,7 +247,7 @@ import shell from '../../scripts/shell';
navigator.mediaSession.setActionHandler('seekto', function (object) { navigator.mediaSession.setActionHandler('seekto', function (object) {
const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms // Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const wantedTime = object.seekTime * 1000; const wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
}); });

View file

@ -11,6 +11,7 @@ import { appHost } from '../apphost';
import Screenfull from 'screenfull'; import Screenfull from 'screenfull';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import alert from '../alert'; import alert from '../alert';
import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts'; import { includesAny } from '../../utils/container.ts';
const UNLIMITED_ITEMS = -1; const UNLIMITED_ITEMS = -1;
@ -299,20 +300,20 @@ function getAudioMaxValues(deviceProfile) {
} }
let startingPlaySession = new Date().getTime(); let startingPlaySession = new Date().getTime();
function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) { function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiClient, startPosition, maxValues) {
const url = 'Audio/' + item.Id + '/universal'; const url = 'Audio/' + item.Id + '/universal';
startingPlaySession++; startingPlaySession++;
return apiClient.getUrl(url, { return apiClient.getUrl(url, {
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
DeviceId: apiClient.deviceId(), DeviceId: apiClient.deviceId(),
MaxStreamingBitrate: maxAudioBitrate || maxBitrate, MaxStreamingBitrate: maxValues.maxAudioBitrate || maxValues.maxBitrate,
Container: directPlayContainers, Container: directPlayContainers,
TranscodingContainer: transcodingProfile.Container || null, TranscodingContainer: transcodingProfile.Container || null,
TranscodingProtocol: transcodingProfile.Protocol || null, TranscodingProtocol: transcodingProfile.Protocol || null,
AudioCodec: transcodingProfile.AudioCodec, AudioCodec: transcodingProfile.AudioCodec,
MaxAudioSampleRate: maxAudioSampleRate, MaxAudioSampleRate: maxValues.maxAudioSampleRate,
MaxAudioBitDepth: maxAudioBitDepth, MaxAudioBitDepth: maxValues.maxAudioBitDepth,
api_key: apiClient.accessToken(), api_key: apiClient.accessToken(),
PlaySessionId: startingPlaySession, PlaySessionId: startingPlaySession,
StartTimeTicks: startPosition || 0, StartTimeTicks: startPosition || 0,
@ -344,7 +345,7 @@ function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, api
const maxValues = getAudioMaxValues(deviceProfile); const maxValues = getAudioMaxValues(deviceProfile);
return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiClient, startPosition, { maxBitrate, ...maxValues });
} }
function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) { function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) {
@ -377,7 +378,7 @@ function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
let streamUrl; let streamUrl;
if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) { if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) {
streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition); streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, apiClient, startPosition, { maxBitrate, ...maxValues });
} }
streamUrls.push(streamUrl || ''); streamUrls.push(streamUrl || '');
@ -408,27 +409,12 @@ function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositio
}); });
} }
function getPlaybackInfo(player, function getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, liveStreamId, options) {
apiClient,
item,
deviceProfile,
maxBitrate,
startPosition,
isPlayback,
mediaSourceId,
audioStreamIndex,
subtitleStreamIndex,
liveStreamId,
enableDirectPlay,
enableDirectStream,
allowVideoStreamCopy,
allowAudioStreamCopy,
secondarySubtitleStreamIndex) {
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) { if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
return Promise.resolve({ return Promise.resolve({
MediaSources: [ MediaSources: [
{ {
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition), StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, options.maxBitrate, apiClient, options.startPosition),
Id: item.Id, Id: item.Id,
MediaStreams: [], MediaStreams: [],
RunTimeTicks: item.RunTimeTicks RunTimeTicks: item.RunTimeTicks
@ -446,10 +432,10 @@ function getPlaybackInfo(player,
const query = { const query = {
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
StartTimeTicks: startPosition || 0 StartTimeTicks: options.startPosition || 0
}; };
if (isPlayback) { if (options.isPlayback) {
query.IsPlayback = true; query.IsPlayback = true;
query.AutoOpenLiveStream = true; query.AutoOpenLiveStream = true;
} else { } else {
@ -457,27 +443,26 @@ function getPlaybackInfo(player,
query.AutoOpenLiveStream = false; query.AutoOpenLiveStream = false;
} }
if (audioStreamIndex != null) { if (options.audioStreamIndex != null) {
query.AudioStreamIndex = audioStreamIndex; query.AudioStreamIndex = options.audioStreamIndex;
} }
if (subtitleStreamIndex != null) { if (options.subtitleStreamIndex != null) {
query.SubtitleStreamIndex = subtitleStreamIndex; query.SubtitleStreamIndex = options.subtitleStreamIndex;
} }
if (secondarySubtitleStreamIndex != null) { if (options.secondarySubtitleStreamIndex != null) {
query.SecondarySubtitleStreamIndex = secondarySubtitleStreamIndex; query.SecondarySubtitleStreamIndex = options.secondarySubtitleStreamIndex;
} }
if (enableDirectPlay != null) { if (options.enableDirectPlay != null) {
query.EnableDirectPlay = enableDirectPlay; query.EnableDirectPlay = options.enableDirectPlay;
} }
if (options.enableDirectStream != null) {
if (enableDirectStream != null) { query.EnableDirectStream = options.enableDirectStream;
query.EnableDirectStream = enableDirectStream;
} }
if (allowVideoStreamCopy != null) { if (options.allowVideoStreamCopy != null) {
query.AllowVideoStreamCopy = allowVideoStreamCopy; query.AllowVideoStreamCopy = options.allowVideoStreamCopy;
} }
if (allowAudioStreamCopy != null) { if (options.allowAudioStreamCopy != null) {
query.AllowAudioStreamCopy = allowAudioStreamCopy; query.AllowAudioStreamCopy = options.allowAudioStreamCopy;
} }
if (mediaSourceId) { if (mediaSourceId) {
query.MediaSourceId = mediaSourceId; query.MediaSourceId = mediaSourceId;
@ -485,8 +470,8 @@ function getPlaybackInfo(player,
if (liveStreamId) { if (liveStreamId) {
query.LiveStreamId = liveStreamId; query.LiveStreamId = liveStreamId;
} }
if (maxBitrate) { if (options.maxBitrate) {
query.MaxStreamingBitrate = maxBitrate; query.MaxStreamingBitrate = options.maxBitrate;
} }
if (player.enableMediaProbe && !player.enableMediaProbe(item)) { if (player.enableMediaProbe && !player.enableMediaProbe(item)) {
query.EnableMediaProbe = false; query.EnableMediaProbe = false;
@ -537,7 +522,7 @@ function getOptimalMediaSource(apiClient, item, versions) {
}); });
} }
function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) { function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, mediaSource, options) {
const postData = { const postData = {
DeviceProfile: deviceProfile, DeviceProfile: deviceProfile,
OpenToken: mediaSource.OpenToken OpenToken: mediaSource.OpenToken
@ -545,19 +530,19 @@ function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, ma
const query = { const query = {
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
StartTimeTicks: startPosition || 0, StartTimeTicks: options.startPosition || 0,
ItemId: item.Id, ItemId: item.Id,
PlaySessionId: playSessionId PlaySessionId: playSessionId
}; };
if (maxBitrate) { if (options.maxBitrate) {
query.MaxStreamingBitrate = maxBitrate; query.MaxStreamingBitrate = options.maxBitrate;
} }
if (audioStreamIndex != null) { if (options.audioStreamIndex != null) {
query.AudioStreamIndex = audioStreamIndex; query.AudioStreamIndex = options.audioStreamIndex;
} }
if (subtitleStreamIndex != null) { if (options.subtitleStreamIndex != null) {
query.SubtitleStreamIndex = subtitleStreamIndex; query.SubtitleStreamIndex = options.subtitleStreamIndex;
} }
// lastly, enforce player overrides for special situations // lastly, enforce player overrides for special situations
@ -1706,7 +1691,7 @@ class PlaybackManager {
function changeStream(player, ticks, params) { function changeStream(player, ticks, params) {
if (canPlayerSeek(player) && params == null) { if (canPlayerSeek(player) && params == null) {
player.currentTime(parseInt(ticks / 10000)); player.currentTime(parseInt(ticks / 10000, 10));
return; return;
} }
@ -1730,20 +1715,33 @@ class PlaybackManager {
const apiClient = ServerConnections.getApiClient(currentItem.ServerId); const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
if (ticks) { if (ticks) {
ticks = parseInt(ticks); ticks = parseInt(ticks, 10);
} }
const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player);
const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions(); const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions();
getPlaybackInfo(player, apiClient, currentItem, deviceProfile, maxBitrate, ticks, true, currentMediaSource.Id, audioStreamIndex, subtitleStreamIndex, liveStreamId, params.EnableDirectPlay, params.EnableDirectStream, params.AllowVideoStreamCopy, params.AllowAudioStreamCopy).then(function (result) { const options = {
maxBitrate,
startPosition: ticks,
isPlayback: true,
audioStreamIndex,
subtitleStreamIndex,
enableDirectPlay: params.EnableDirectPlay,
enableDirectStream: params.EnableDirectStream,
allowVideoStreamCopy: params.AllowVideoStreamCopy,
allowAudioStreamCopy: params.AllowAudioStreamCopy
};
getPlaybackInfo(player, apiClient, currentItem, deviceProfile, currentMediaSource.Id, liveStreamId, options).then(function (result) {
if (validatePlaybackInfoResult(self, result)) { if (validatePlaybackInfoResult(self, result)) {
currentMediaSource = result.MediaSources[0]; currentMediaSource = result.MediaSources[0];
const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks, player); const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks, player);
streamInfo.fullscreen = currentPlayOptions.fullscreen; streamInfo.fullscreen = currentPlayOptions.fullscreen;
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery; streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
streamInfo.resetSubtitleOffset = false;
if (!streamInfo.url) { if (!streamInfo.url) {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream');
@ -2268,7 +2266,7 @@ class PlaybackManager {
function runInterceptors(item, playOptions) { function runInterceptors(item, playOptions) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const interceptors = pluginManager.ofType('preplayintercept'); const interceptors = pluginManager.ofType(PluginType.PreplayIntercept);
interceptors.sort(function (a, b) { interceptors.sort(function (a, b) {
return (a.order || 0) - (b.order || 0); return (a.order || 0) - (b.order || 0);
@ -2303,17 +2301,17 @@ class PlaybackManager {
}, reject); }, reject);
} }
function sendPlaybackListToPlayer(player, items, deviceProfile, maxBitrate, apiClient, startPositionTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, startIndex) { function sendPlaybackListToPlayer(player, items, deviceProfile, apiClient, mediaSourceId, options) {
return setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositionTicks).then(function () { return setStreamUrls(items, deviceProfile, options.maxBitrate, apiClient, options.startPosition).then(function () {
loading.hide(); loading.hide();
return player.play({ return player.play({
items: items, items,
startPositionTicks: startPositionTicks || 0, startPositionTicks: options.startPosition || 0,
mediaSourceId: mediaSourceId, mediaSourceId,
audioStreamIndex: audioStreamIndex, audioStreamIndex: options.audioStreamIndex,
subtitleStreamIndex: subtitleStreamIndex, subtitleStreamIndex: options.subtitleStreamIndex,
startIndex: startIndex startIndex: options.startIndex
}); });
}); });
} }
@ -2474,15 +2472,27 @@ class PlaybackManager {
const mediaSourceId = playOptions.mediaSourceId; const mediaSourceId = playOptions.mediaSourceId;
const audioStreamIndex = playOptions.audioStreamIndex; const audioStreamIndex = playOptions.audioStreamIndex;
const subtitleStreamIndex = playOptions.subtitleStreamIndex; const subtitleStreamIndex = playOptions.subtitleStreamIndex;
const options = {
maxBitrate,
startPosition,
isPlayback: null,
audioStreamIndex,
subtitleStreamIndex,
startIndex: playOptions.startIndex,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
if (player && !enableLocalPlaylistManagement(player)) { if (player && !enableLocalPlaylistManagement(player)) {
return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, maxBitrate, apiClient, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playOptions.startIndex); return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, apiClient, mediaSourceId, options);
} }
// this reference was only needed by sendPlaybackListToPlayer // this reference was only needed by sendPlaybackListToPlayer
playOptions.items = null; playOptions.items = null;
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => { return getPlaybackMediaSource(player, apiClient, deviceProfile, item, mediaSourceId, options).then(async (mediaSource) => {
const user = await apiClient.getCurrentUser(); const user = await apiClient.getCurrentUser();
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections); autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
@ -2540,7 +2550,20 @@ class PlaybackManager {
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType); const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
return player.getDeviceProfile(item).then(function (deviceProfile) { return player.getDeviceProfile(item).then(function (deviceProfile) {
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) { const mediaOptions = {
maxBitrate,
startPosition,
isPlayback: null,
audioStreamIndex: options.audioStreamIndex,
subtitleStreamIndex: options.subtitleStreamIndex,
startIndex: null,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
return getPlaybackMediaSource(player, apiClient, deviceProfile, item, options.mediaSourceId, mediaOptions).then(function (mediaSource) {
return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
}); });
}); });
@ -2560,7 +2583,19 @@ class PlaybackManager {
const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType); const maxBitrate = getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType);
return player.getDeviceProfile(item).then(function (deviceProfile) { return player.getDeviceProfile(item).then(function (deviceProfile) {
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, false, null, null, null, null).then(function (playbackInfoResult) { const mediaOptions = {
maxBitrate,
startPosition,
isPlayback: true,
audioStreamIndex: null,
subtitleStreamIndex: null,
enableDirectPlay: null,
enableDirectStream: null,
allowVideoStreamCopy: null,
allowAudioStreamCopy: null
};
return getPlaybackInfo(player, apiClient, item, deviceProfile, null, null, mediaOptions).then(function (playbackInfoResult) {
return playbackInfoResult.MediaSources; return playbackInfoResult.MediaSources;
}); });
}); });
@ -2701,13 +2736,18 @@ class PlaybackManager {
return tracks; return tracks;
} }
function getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex) { function getPlaybackMediaSource(player, apiClient, deviceProfile, item, mediaSourceId, options) {
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, true, mediaSourceId, audioStreamIndex, subtitleStreamIndex, null).then(function (playbackInfoResult) { options.isPlayback = true;
return getPlaybackInfo(player, apiClient, item, deviceProfile, mediaSourceId, null, options).then(function (playbackInfoResult) {
if (validatePlaybackInfoResult(self, playbackInfoResult)) { if (validatePlaybackInfoResult(self, playbackInfoResult)) {
return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) { return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) {
if (mediaSource) { if (mediaSource) {
if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) { if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) {
return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, maxBitrate, startPosition, mediaSource, null, null).then(function (openLiveStreamResult) { options.audioStreamIndex = null;
options.subtitleStreamIndex = null;
return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, mediaSource, options).then(function (openLiveStreamResult) {
return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) { return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) {
openLiveStreamResult.MediaSource.enableDirectPlay = result; openLiveStreamResult.MediaSource.enableDirectPlay = result;
return openLiveStreamResult.MediaSource; return openLiveStreamResult.MediaSource;
@ -3387,12 +3427,12 @@ class PlaybackManager {
} }
Events.on(pluginManager, 'registered', function (e, plugin) { Events.on(pluginManager, 'registered', function (e, plugin) {
if (plugin.type === 'mediaplayer') { if (plugin.type === PluginType.MediaPlayer) {
initMediaPlayer(plugin); initMediaPlayer(plugin);
} }
}); });
pluginManager.ofType('mediaplayer').forEach(initMediaPlayer); pluginManager.ofType(PluginType.MediaPlayer).forEach(initMediaPlayer);
function sendProgressUpdate(player, progressEventName, reportPlaylist) { function sendProgressUpdate(player, progressEventName, reportPlaylist) {
if (!player) { if (!player) {
@ -3423,12 +3463,6 @@ class PlaybackManager {
streamInfo.lastMediaInfoQuery = new Date().getTime(); streamInfo.lastMediaInfoQuery = new Date().getTime();
const apiClient = ServerConnections.getApiClient(serverId);
if (!apiClient.isMinServerVersion('3.2.70.7')) {
return;
}
ServerConnections.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) { ServerConnections.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) {
mediaSource.MediaStreams = info.MediaStreams; mediaSource.MediaStreams = info.MediaStreams;
Events.trigger(player, 'mediastreamschange'); Events.trigger(player, 'mediastreamschange');
@ -3613,7 +3647,7 @@ class PlaybackManager {
percent /= 100; percent /= 100;
ticks *= percent; ticks *= percent;
this.seek(parseInt(ticks), player); this.seek(parseInt(ticks, 10), player);
} }
seekMs(ms, player = this._currentPlayer) { seekMs(ms, player = this._currentPlayer) {
@ -4000,13 +4034,13 @@ class PlaybackManager {
this.setBrightness(cmd.Arguments.Brightness, player); this.setBrightness(cmd.Arguments.Brightness, player);
break; break;
case 'SetAudioStreamIndex': case 'SetAudioStreamIndex':
this.setAudioStreamIndex(parseInt(cmd.Arguments.Index), player); this.setAudioStreamIndex(parseInt(cmd.Arguments.Index, 10), player);
break; break;
case 'SetSubtitleStreamIndex': case 'SetSubtitleStreamIndex':
this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index), player); this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index, 10), player);
break; break;
case 'SetMaxStreamingBitrate': case 'SetMaxStreamingBitrate':
this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player); this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate, 10), player);
break; break;
case 'ToggleFullscreen': case 'ToggleFullscreen':
this.toggleFullscreen(player); this.toggleFullscreen(player);

View file

@ -43,7 +43,7 @@ function showQualityMenu(player, btn) {
items: menuItems, items: menuItems,
positionTo: btn positionTo: btn
}).then(function (id) { }).then(function (id) {
const bitrate = parseInt(id); const bitrate = parseInt(id, 10);
if (bitrate !== selectedBitrate) { if (bitrate !== selectedBitrate) {
playbackManager.setMaxStreamingBitrate({ playbackManager.setMaxStreamingBitrate({
enableAutomaticBitrateDetection: bitrate ? false : true, enableAutomaticBitrateDetection: bitrate ? false : true,

View file

@ -1,4 +1,4 @@
/*eslint prefer-const: "error"*/ import { randomInt } from '../../utils/number.ts';
let currentId = 0; let currentId = 0;
function addUniquePlaylistItemId(item) { function addUniquePlaylistItemId(item) {
@ -58,7 +58,7 @@ class PlayQueueManager {
const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0]; const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0];
for (let i = this._playlist.length - 1; i > 0; i--) { for (let i = this._playlist.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i); const j = randomInt(0, i - 1);
const temp = this._playlist[i]; const temp = this._playlist[i];
this._playlist[i] = this._playlist[j]; this._playlist[i] = this._playlist[j];
this._playlist[j] = temp; this._playlist[j] = temp;

View file

@ -12,8 +12,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './recordingfields.template.html'; import template from './recordingfields.template.html';
/*eslint prefer-const: "error"*/
function loadData(parent, program) { function loadData(parent, program) {
if (program.IsSeries) { if (program.IsSeries) {
parent.querySelector('.recordSeriesContainer').classList.remove('hide'); parent.querySelector('.recordSeriesContainer').classList.remove('hide');

View file

@ -5,8 +5,6 @@ import toast from '../toast/toast';
import confirm from '../confirm/confirm'; import confirm from '../confirm/confirm';
import dialog from '../dialog/dialog'; import dialog from '../dialog/dialog';
/*eslint prefer-const: "error"*/
function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) {
loading.show(); loading.show();

View file

@ -17,8 +17,6 @@ import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import template from './seriesrecordingeditor.template.html'; import template from './seriesrecordingeditor.template.html';
/*eslint prefer-const: "error"*/
let currentDialog; let currentDialog;
let recordingUpdated = false; let recordingUpdated = false;
let recordingDeleted = false; let recordingDeleted = false;

View file

@ -13,8 +13,6 @@ import '../formdialog.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
/*eslint prefer-const: "error"*/
function getEditorHtml() { function getEditorHtml() {
let html = ''; let html = '';

View file

@ -20,8 +20,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import { appRouter } from '../appRouter'; import { appRouter } from '../appRouter';
/*eslint prefer-const: "error"*/
let showMuteButton = true; let showMuteButton = true;
let showVolumeSlider = true; let showVolumeSlider = true;
@ -46,7 +44,7 @@ function showAudioMenu(context, player, button) {
items: menuItems, items: menuItems,
positionTo: button, positionTo: button,
callback: function (id) { callback: function (id) {
playbackManager.setAudioStreamIndex(parseInt(id), player); playbackManager.setAudioStreamIndex(parseInt(id, 10), player);
} }
}); });
}); });
@ -78,7 +76,7 @@ function showSubtitleMenu(context, player, button) {
items: menuItems, items: menuItems,
positionTo: button, positionTo: button,
callback: function (id) { callback: function (id) {
playbackManager.setSubtitleStreamIndex(parseInt(id), player); playbackManager.setSubtitleStreamIndex(parseInt(id, 10), player);
} }
}); });
}); });

View file

@ -150,7 +150,7 @@ import toast from './toast/toast';
StartDate: card.getAttribute('data-startdate'), StartDate: card.getAttribute('data-startdate'),
EndDate: card.getAttribute('data-enddate'), EndDate: card.getAttribute('data-enddate'),
UserData: { UserData: {
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0') PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10)
} }
}; };
} }
@ -201,7 +201,7 @@ import toast from './toast/toast';
ServerId: serverId ServerId: serverId
}); });
} else if (action === 'play' || action === 'resume') { } else if (action === 'play' || action === 'resume') {
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0'); const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10);
if (playbackManager.canPlay(item)) { if (playbackManager.canPlay(item)) {
playbackManager.play({ playbackManager.play({

View file

@ -28,9 +28,9 @@
.upNextDialog-countdownText { .upNextDialog-countdownText {
font-weight: 500; font-weight: 500;
white-space: nowrap;
} }
.upNextDialog-nextVideoText,
.upNextDialog-title { .upNextDialog-title {
width: 25.5em; width: 25.5em;
white-space: nowrap; white-space: nowrap;

View file

@ -12,7 +12,10 @@ const userDataMethods = {
markFavorite: markFavorite markFavorite: markFavorite
}; };
function getUserDataButtonHtml(method, itemId, serverId, buttonCssClass, iconCssClass, icon, tooltip, style) { function getUserDataButtonHtml(method, itemId, serverId, icon, tooltip, style, classes) {
let buttonCssClass = classes.buttonCssClass;
let iconCssClass = classes.iconCssClass;
if (style === 'fab-mini') { if (style === 'fab-mini') {
style = 'fab'; style = 'fab';
buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini'; buttonCssClass = buttonCssClass ? (buttonCssClass + ' mini') : 'mini';
@ -96,7 +99,7 @@ function getIconsHtml(options) {
} }
const iconCssClass = options.iconCssClass; const iconCssClass = options.iconCssClass;
const classes = { buttonCssClass: btnCssClass, iconCssClass: iconCssClass };
const serverId = item.ServerId; const serverId = item.ServerId;
if (includePlayed !== false) { if (includePlayed !== false) {
@ -104,18 +107,21 @@ function getIconsHtml(options) {
if (itemHelper.canMarkPlayed(item)) { if (itemHelper.canMarkPlayed(item)) {
if (userData.Played) { if (userData.Played) {
html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass + ' btnUserDataOn', iconCssClass, 'check', tooltipPlayed, style); const buttonCssClass = classes.buttonCssClass + ' btnUserDataOn';
html += getUserDataButtonHtml('markPlayed', itemId, serverId, 'check', tooltipPlayed, style, { buttonCssClass, ...classes });
} else { } else {
html += getUserDataButtonHtml('markPlayed', itemId, serverId, btnCssClass, iconCssClass, 'check', tooltipPlayed, style); html += getUserDataButtonHtml('markPlayed', itemId, serverId, 'check', tooltipPlayed, style, classes);
} }
} }
} }
const tooltipFavorite = globalize.translate('Favorite'); const tooltipFavorite = globalize.translate('Favorite');
if (userData.IsFavorite) { if (userData.IsFavorite) {
html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData btnUserDataOn', iconCssClass, 'favorite', tooltipFavorite, style); const buttonCssClass = classes.buttonCssClass + ' btnUserData btnUserDataOn';
html += getUserDataButtonHtml('markFavorite', itemId, serverId, 'favorite', tooltipFavorite, style, { buttonCssClass, ...classes });
} else { } else {
html += getUserDataButtonHtml('markFavorite', itemId, serverId, btnCssClass + ' btnUserData', iconCssClass, 'favorite', tooltipFavorite, style); classes.buttonCssClass += ' btnUserData';
html += getUserDataButtonHtml('markFavorite', itemId, serverId, 'favorite', tooltipFavorite, style, classes);
} }
return html; return html;

View file

@ -54,15 +54,11 @@ import confirm from '../../../components/confirm/confirm';
} }
function showDeviceMenu(view, btn, deviceId) { function showDeviceMenu(view, btn, deviceId) {
const menuItems = []; const menuItems = [{
name: globalize.translate('Edit'),
if (canEdit) { id: 'open',
menuItems.push({ icon: 'mode_edit'
name: globalize.translate('Edit'), }];
id: 'open',
icon: 'mode_edit'
});
}
if (canDelete(deviceId)) { if (canDelete(deviceId)) {
menuItems.push({ menuItems.push({
@ -100,7 +96,7 @@ import confirm from '../../../components/confirm/confirm';
deviceHtml += '<div class="cardBox visualCardBox">'; deviceHtml += '<div class="cardBox visualCardBox">';
deviceHtml += '<div class="cardScalable">'; deviceHtml += '<div class="cardScalable">';
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>'; deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#/device.html?id=' + device.Id : '#'}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`; deviceHtml += `<a is="emby-linkbutton" href="#/device.html?id=${device.Id}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
const iconUrl = imageHelper.getDeviceIcon(device); const iconUrl = imageHelper.getDeviceIcon(device);
if (iconUrl) { if (iconUrl) {
@ -114,7 +110,7 @@ import confirm from '../../../components/confirm/confirm';
deviceHtml += '</div>'; deviceHtml += '</div>';
deviceHtml += '<div class="cardFooter">'; deviceHtml += '<div class="cardFooter">';
if (canEdit || canDelete(device.Id)) { if (canDelete(device.Id)) {
if (globalize.getIsRTL()) if (globalize.getIsRTL())
deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">'; deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">';
else else
@ -155,7 +151,6 @@ import confirm from '../../../components/confirm/confirm';
}); });
} }
const canEdit = ApiClient.isMinServerVersion('3.4.1.31');
export default function (view) { export default function (view) {
view.querySelector('.devicesList').addEventListener('click', function (e) { view.querySelector('.devicesList').addEventListener('click', function (e) {
const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu');

View file

@ -100,7 +100,7 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>'; }).join('') + '</div>';
const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create');
$('.btnDeleteIdentificationHeader', elem).on('click', function () { $('.btnDeleteIdentificationHeader', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index')); const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.Identification.Headers.splice(itemIndex, 1); currentProfile.Identification.Headers.splice(itemIndex, 1);
renderIdentificationHeaders(page, currentProfile.Identification.Headers); renderIdentificationHeaders(page, currentProfile.Identification.Headers);
}); });
@ -154,7 +154,7 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>'; }).join('') + '</div>';
const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create');
$('.btnDeleteXmlAttribute', elem).on('click', function () { $('.btnDeleteXmlAttribute', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index')); const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.XmlRootAttributes.splice(itemIndex, 1); currentProfile.XmlRootAttributes.splice(itemIndex, 1);
renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes);
}); });
@ -198,12 +198,12 @@ import { getParameterByName } from '../../../utils/url.ts';
}).join('') + '</div>'; }).join('') + '</div>';
const elem = $('.subtitleProfileList', page).html(html).trigger('create'); const elem = $('.subtitleProfileList', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index')); const itemIndex = parseInt(this.getAttribute('data-index'), 10);
currentProfile.SubtitleProfiles.splice(itemIndex, 1); currentProfile.SubtitleProfiles.splice(itemIndex, 1);
renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); renderSubtitleProfiles(page, currentProfile.SubtitleProfiles);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const itemIndex = parseInt(this.getAttribute('data-index')); const itemIndex = parseInt(this.getAttribute('data-index'), 10);
editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]);
}); });
} }
@ -292,7 +292,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteDirectPlayProfile(page, index); deleteDirectPlayProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex')); const index = parseInt(this.getAttribute('data-profileindex'), 10);
editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]);
}); });
} }
@ -353,7 +353,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteTranscodingProfile(page, index); deleteTranscodingProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex')); const index = parseInt(this.getAttribute('data-profileindex'), 10);
editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]);
}); });
} }
@ -437,7 +437,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteContainerProfile(page, index); deleteContainerProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex')); const index = parseInt(this.getAttribute('data-profileindex'), 10);
editContainerProfile(page, currentProfile.ContainerProfiles[index]); editContainerProfile(page, currentProfile.ContainerProfiles[index]);
}); });
} }
@ -509,7 +509,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteCodecProfile(page, index); deleteCodecProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex')); const index = parseInt(this.getAttribute('data-profileindex'), 10);
editCodecProfile(page, currentProfile.CodecProfiles[index]); editCodecProfile(page, currentProfile.CodecProfiles[index]);
}); });
} }
@ -589,7 +589,7 @@ import { getParameterByName } from '../../../utils/url.ts';
deleteResponseProfile(page, index); deleteResponseProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function () {
const index = parseInt(this.getAttribute('data-profileindex')); const index = parseInt(this.getAttribute('data-profileindex'), 10);
editResponseProfile(page, currentProfile.ResponseProfiles[index]); editResponseProfile(page, currentProfile.ResponseProfiles[index]);
}); });
} }

View file

@ -98,8 +98,8 @@ import alert from '../../components/alert';
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0'); config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10);
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0'); config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10);
config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value;
config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked;
config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked;

View file

@ -80,6 +80,13 @@
</div> </div>
</div> </div>
<div class="verticalSection">
<h2>${HeaderPerformance}</h2>
<div class="inputContainer">
<input is="emby-input" id="txtParallelImageEncodingLimit" label="${LabelParallelImageEncodingLimit}" type="number" pattern="[0-9]*" min="0" step="1" />
<div class="fieldDescription">${LabelParallelImageEncodingLimitHelp}</div>
</div>
</div>
<br /> <br />
<div> <div>
<button is="emby-button" type="submit" class="raised button-submit block"> <button is="emby-button" type="submit" class="raised button-submit block">

View file

@ -21,6 +21,7 @@ import alert from '../../components/alert';
$('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) {
return '<option value="' + language.Value + '">' + language.Name + '</option>'; return '<option value="' + language.Value + '">' + language.Name + '</option>';
})).val(config.UICulture); })).val(config.UICulture);
page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || '';
loading.hide(); loading.hide();
} }
@ -36,6 +37,7 @@ import alert from '../../components/alert';
config.MetadataPath = $('#txtMetadataPath', form).val(); config.MetadataPath = $('#txtMetadataPath', form).val();
config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val(); config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val();
config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked; config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked;
config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10);
ApiClient.updateServerConfiguration(config).then(function() { ApiClient.updateServerConfiguration(config).then(function() {
ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) {

View file

@ -92,7 +92,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
function showCardMenu(page, elem, virtualFolders) { function showCardMenu(page, elem, virtualFolders) {
const card = dom.parentWithClass(elem, 'card'); const card = dom.parentWithClass(elem, 'card');
const index = parseInt(card.getAttribute('data-index')); const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index]; const virtualFolder = virtualFolders[index];
const menuItems = []; const menuItems = [];
menuItems.push({ menuItems.push({
@ -192,7 +192,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
}); });
$('.editLibrary', divVirtualFolders).on('click', function () { $('.editLibrary', divVirtualFolders).on('click', function () {
const card = $(this).parents('.card')[0]; const card = $(this).parents('.card')[0];
const index = parseInt(card.getAttribute('data-index')); const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index]; const virtualFolder = virtualFolders[index];
if (virtualFolder.ItemId) { if (virtualFolder.ItemId) {

View file

@ -68,7 +68,7 @@ function renderPackage(pkg, installedPlugins, page) {
if (installedPlugin) { if (installedPlugin) {
const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>'); const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>');
$('#pCurrentVersion', page).show().text(currentVersionText); $('#pCurrentVersion', page).show().html(currentVersionText);
} else { } else {
$('#pCurrentVersion', page).hide().text(''); $('#pCurrentVersion', page).hide().text('');
} }

View file

@ -235,7 +235,7 @@ import { getParameterByName } from '../../../utils/url.ts';
const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger');
if (btnDeleteTrigger) { if (btnDeleteTrigger) {
ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'))); ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10));
} }
}); });
view.addEventListener('viewshow', function () { view.addEventListener('viewshow', function () {

View file

@ -15,7 +15,7 @@ import Dashboard from '../../utils/dashboard';
loading.show(); loading.show();
const form = this; const form = this;
ApiClient.getServerConfiguration().then(function (config) { ApiClient.getServerConfiguration().then(function (config) {
config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0')); config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10);
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
}); });

View file

@ -1,5 +1,6 @@
import { intervalToDuration } from 'date-fns'; import { intervalToDuration } from 'date-fns';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { marked } from 'marked';
import escapeHtml from 'escape-html'; import escapeHtml from 'escape-html';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
@ -877,7 +878,7 @@ function renderOverview(page, item) {
const overviewElements = page.querySelectorAll('.overview'); const overviewElements = page.querySelectorAll('.overview');
if (overviewElements.length > 0) { if (overviewElements.length > 0) {
const overview = DOMPurify.sanitize(item.Overview || ''); const overview = DOMPurify.sanitize(marked(item.Overview || ''));
if (overview) { if (overview) {
for (const overviewElemnt of overviewElements) { for (const overviewElemnt of overviewElements) {
@ -1156,12 +1157,7 @@ function renderMoreFromArtist(view, item, apiClient) {
const section = view.querySelector('.moreFromArtistSection'); const section = view.querySelector('.moreFromArtistSection');
if (section) { if (section) {
if (item.Type === 'MusicArtist') { if (item.Type !== 'MusicArtist' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) {
if (!apiClient.isMinServerVersion('3.4.1.19')) {
section.classList.add('hide');
return;
}
} else if (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length) {
section.classList.add('hide'); section.classList.add('hide');
return; return;
} }

View file

@ -222,17 +222,17 @@ export default function (view, params) {
} }
function onBeforeTabChange(evt) { function onBeforeTabChange(evt) {
preLoadTab(view, parseInt(evt.detail.selectedTabIndex)); preLoadTab(view, parseInt(evt.detail.selectedTabIndex, 10));
} }
function onTabChange(evt) { function onTabChange(evt) {
const previousTabController = tabControllers[parseInt(evt.detail.previousIndex)]; const previousTabController = tabControllers[parseInt(evt.detail.previousIndex, 10)];
if (previousTabController && previousTabController.onHide) { if (previousTabController && previousTabController.onHide) {
previousTabController.onHide(); previousTabController.onHide();
} }
loadTab(view, parseInt(evt.detail.selectedTabIndex)); loadTab(view, parseInt(evt.detail.selectedTabIndex, 10));
} }
function getTabContainers() { function getTabContainers() {
@ -339,7 +339,7 @@ export default function (view, params) {
let isViewRestored; let isViewRestored;
const self = this; const self = this;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv')); let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'), 10);
let initialTabIndex = currentTabIndex; let initialTabIndex = currentTabIndex;
let lastFullRender = 0; let lastFullRender = 0;
[].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) { [].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) {

View file

@ -245,11 +245,11 @@ import Dashboard from '../../utils/dashboard';
} }
function onBeforeTabChange(e) { function onBeforeTabChange(e) {
preLoadTab(view, parseInt(e.detail.selectedTabIndex)); preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10));
} }
function onTabChange(e) { function onTabChange(e) {
loadTab(view, parseInt(e.detail.selectedTabIndex)); loadTab(view, parseInt(e.detail.selectedTabIndex, 10));
} }
function getTabContainers() { function getTabContainers() {
@ -350,7 +350,7 @@ import Dashboard from '../../utils/dashboard';
} }
} }
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10);
const suggestionsTabIndex = 1; const suggestionsTabIndex = 1;
this.initTab = function () { this.initTab = function () {

View file

@ -975,7 +975,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
title: globalize.translate('Audio'), title: globalize.translate('Audio'),
positionTo: positionTo positionTo: positionTo
}).then(function (id) { }).then(function (id) {
const index = parseInt(id); const index = parseInt(id, 10);
if (index !== currentIndex) { if (index !== currentIndex) {
playbackManager.setAudioStreamIndex(index, player); playbackManager.setAudioStreamIndex(index, player);
@ -1022,7 +1022,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
positionTo positionTo
}).then(function (id) { }).then(function (id) {
if (id) { if (id) {
const index = parseInt(id); const index = parseInt(id, 10);
if (index !== currentIndex) { if (index !== currentIndex) {
playbackManager.setSecondarySubtitleStreamIndex(index, player); playbackManager.setSecondarySubtitleStreamIndex(index, player);
} }
@ -1099,7 +1099,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
console.error(e); console.error(e);
} }
} else { } else {
const index = parseInt(id); const index = parseInt(id, 10);
if (index !== currentIndex) { if (index !== currentIndex) {
playbackManager.setSubtitleStreamIndex(index, player); playbackManager.setSubtitleStreamIndex(index, player);
@ -1633,7 +1633,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
ms /= 100; ms /= 100;
ms *= value; ms *= value;
ms += programStartDateMs; ms += programStartDateMs;
return '<h1 class="sliderBubbleText">' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms)), true) + '</h1>'; return '<h1 class="sliderBubbleText">' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '</h1>';
} }
return '--:--'; return '--:--';

View file

@ -224,11 +224,11 @@ import autoFocuser from '../../components/autoFocuser';
export default function (view, params) { export default function (view, params) {
function onBeforeTabChange(e) { function onBeforeTabChange(e) {
preLoadTab(view, parseInt(e.detail.selectedTabIndex)); preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10));
} }
function onTabChange(e) { function onTabChange(e) {
const newIndex = parseInt(e.detail.selectedTabIndex); const newIndex = parseInt(e.detail.selectedTabIndex, 10);
loadTab(view, newIndex); loadTab(view, newIndex);
} }
@ -340,7 +340,7 @@ import autoFocuser from '../../components/autoFocuser';
} }
const self = this; const self = this;
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10);
const suggestionsTabIndex = 1; const suggestionsTabIndex = 1;
self.initTab = function () { self.initTab = function () {

View file

@ -414,7 +414,7 @@ import Sortable from 'sortablejs';
clearRefreshInterval(itemsContainer); clearRefreshInterval(itemsContainer);
if (!intervalMs) { if (!intervalMs) {
intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0'); intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10);
} }
if (intervalMs) { if (intervalMs) {

View file

@ -3,8 +3,8 @@
const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); const ProgressBarPrototype = Object.create(HTMLDivElement.prototype);
function onAutoTimeProgress() { function onAutoTimeProgress() {
const start = parseInt(this.getAttribute('data-starttime')); const start = parseInt(this.getAttribute('data-starttime'), 10);
const end = parseInt(this.getAttribute('data-endtime')); const end = parseInt(this.getAttribute('data-endtime'), 10);
const now = new Date().getTime(); const now = new Date().getTime();
const total = end - start; const total = end - start;

View file

@ -91,7 +91,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype);
return 0; return 0;
} }
value = parseInt(value); value = parseInt(value, 10);
if (isNaN(value)) { if (isNaN(value)) {
return 0; return 0;
} }

View file

@ -75,7 +75,7 @@ const Scroller: FC<ScrollerProps> = ({
return 0; return 0;
} }
if (isNaN(parseInt(value))) { if (isNaN(parseInt(value, 10))) {
return 0; return 0;
} }

View file

@ -75,11 +75,11 @@ import '../../styles/scrollstyles.scss';
current.classList.remove(activeButtonClass); current.classList.remove(activeButtonClass);
} }
const previousIndex = current ? parseInt(current.getAttribute('data-index')) : null; const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null;
setActiveTabButton(tabButton); setActiveTabButton(tabButton);
const index = parseInt(tabButton.getAttribute('data-index')); const index = parseInt(tabButton.getAttribute('data-index'), 10);
triggerBeforeTabChange(tabs, index, previousIndex); triggerBeforeTabChange(tabs, index, previousIndex);
@ -194,7 +194,7 @@ import '../../styles/scrollstyles.scss';
initScroller(this); initScroller(this);
const current = this.querySelector('.' + activeButtonClass); const current = this.querySelector('.' + activeButtonClass);
const currentIndex = current ? parseInt(current.getAttribute('data-index')) : parseInt(this.getAttribute('data-index') || '0'); const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10);
if (currentIndex !== -1) { if (currentIndex !== -1) {
this.selectedTabIndex = currentIndex; this.selectedTabIndex = currentIndex;

View file

@ -14,7 +14,7 @@ function calculateOffset(textarea) {
let offset = 0; let offset = 0;
for (let i = 0; i < props.length; i++) { for (let i = 0; i < props.length; i++) {
offset += parseInt(style[props[i]]); offset += parseInt(style[props[i]], 10);
} }
return offset; return offset;
} }

View file

@ -1,10 +1,11 @@
/* eslint-disable indent */ /* eslint-disable indent */
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
class BackdropScreensaver { class BackdropScreensaver {
constructor() { constructor() {
this.name = 'Backdrop ScreenSaver'; this.name = 'Backdrop ScreenSaver';
this.type = 'screensaver'; this.type = PluginType.Screensaver;
this.id = 'backdropscreensaver'; this.id = 'backdropscreensaver';
this.supportsAnonymous = false; this.supportsAnonymous = false;
} }

View file

@ -9,6 +9,7 @@ import TableOfContents from './tableOfContents';
import dom from '../../scripts/dom'; import dom from '../../scripts/dom';
import { translateHtml } from '../../scripts/globalize'; import { translateHtml } from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings'; import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import '../../elements/emby-button/paper-icon-button-light'; import '../../elements/emby-button/paper-icon-button-light';
@ -19,7 +20,7 @@ import './style.scss';
export class BookPlayer { export class BookPlayer {
constructor() { constructor() {
this.name = 'Book Player'; this.name = 'Book Player';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'bookplayer'; this.id = 'bookplayer';
this.priority = 1; this.priority = 1;

View file

@ -5,6 +5,7 @@ import globalize from '../../scripts/globalize';
import castSenderApiLoader from './castSenderApi'; import castSenderApiLoader from './castSenderApi';
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert'; import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
// Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js
@ -569,7 +570,7 @@ class ChromecastPlayer {
constructor() { constructor() {
// playbackManager needs this // playbackManager needs this
this.name = PlayerName; this.name = PlayerName;
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'chromecast'; this.id = 'chromecast';
this.isLocalPlayer = false; this.isLocalPlayer = false;
this.lastPlayerData = {}; this.lastPlayerData = {};
@ -683,7 +684,7 @@ class ChromecastPlayer {
} }
seek(position) { seek(position) {
position = parseInt(position); position = parseInt(position, 10);
position = position / 10000000; position = position / 10000000;

View file

@ -6,13 +6,14 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
import { appRouter } from '../../components/appRouter'; import { appRouter } from '../../components/appRouter';
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import * as userSettings from '../../scripts/settings/userSettings'; import * as userSettings from '../../scripts/settings/userSettings';
import { PluginType } from '../../types/plugin.ts';
import './style.scss'; import './style.scss';
export class ComicsPlayer { export class ComicsPlayer {
constructor() { constructor() {
this.name = 'Comics Player'; this.name = 'Comics Player';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'comicsplayer'; this.id = 'comicsplayer';
this.priority = 1; this.priority = 1;
this.imageMap = new Map(); this.imageMap = new Map();

View file

@ -2,6 +2,7 @@ import globalize from '../../scripts/globalize';
import * as userSettings from '../../scripts/settings/userSettings'; import * as userSettings from '../../scripts/settings/userSettings';
import { appHost } from '../../components/apphost'; import { appHost } from '../../components/apphost';
import alert from '../../components/alert'; import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
// TODO: Replace with date-fns // TODO: Replace with date-fns
// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php // https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
@ -46,7 +47,7 @@ function showIsoMessage() {
class ExpirementalPlaybackWarnings { class ExpirementalPlaybackWarnings {
constructor() { constructor() {
this.name = 'Experimental playback warnings'; this.name = 'Experimental playback warnings';
this.type = 'preplayintercept'; this.type = PluginType.PreplayIntercept;
this.id = 'expirementalplaybackwarnings'; this.id = 'expirementalplaybackwarnings';
} }

View file

@ -3,6 +3,7 @@ import { appHost } from '../../components/apphost';
import * as htmlMediaHelper from '../../components/htmlMediaHelper'; import * as htmlMediaHelper from '../../components/htmlMediaHelper';
import profileBuilder from '../../scripts/browserDeviceProfile'; import profileBuilder from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
function getDefaultProfile() { function getDefaultProfile() {
@ -48,6 +49,9 @@ function supportsFade() {
function requireHlsPlayer(callback) { function requireHlsPlayer(callback) {
import('hls.js').then(({ default: hls }) => { import('hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls; window.Hls = hls;
callback(); callback();
}); });
@ -85,7 +89,7 @@ class HtmlAudioPlayer {
const self = this; const self = this;
self.name = 'Html Audio Player'; self.name = 'Html Audio Player';
self.type = 'mediaplayer'; self.type = PluginType.MediaPlayer;
self.id = 'htmlaudioplayer'; self.id = 'htmlaudioplayer';
// Let any players created by plugins take priority // Let any players created by plugins take priority

View file

@ -30,8 +30,10 @@ import ServerConnections from '../../components/ServerConnections';
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile'; import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import { includesAny } from '../../utils/container.ts'; import { includesAny } from '../../utils/container.ts';
import debounce from 'lodash-es/debounce';
/** /**
* Returns resolved URL. * Returns resolved URL.
@ -106,6 +108,9 @@ function tryRemoveElement(elem) {
function requireHlsPlayer(callback) { function requireHlsPlayer(callback) {
import('hls.js').then(({default: hls}) => { import('hls.js').then(({default: hls}) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls; window.Hls = hls;
callback(); callback();
}); });
@ -166,7 +171,7 @@ function tryRemoveElement(elem) {
/** /**
* @type {string} * @type {string}
*/ */
type = 'mediaplayer'; type = PluginType.MediaPlayer;
/** /**
* @type {string} * @type {string}
*/ */
@ -378,7 +383,7 @@ function tryRemoveElement(elem) {
this.#currentTime = null; this.#currentTime = null;
this.resetSubtitleOffset(); if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset();
return this.createMediaElement(options).then(elem => { return this.createMediaElement(options).then(elem => {
return this.updateVideoUrl(options).then(() => { return this.updateVideoUrl(options).then(() => {
@ -571,7 +576,12 @@ function tryRemoveElement(elem) {
} }
} }
setSubtitleOffset(offset) { setSubtitleOffset = debounce(this._setSubtitleOffset, 100);
/**
* @private
*/
_setSubtitleOffset(offset) {
const offsetValue = parseFloat(offset); const offsetValue = parseFloat(offset);
// if .ass currently rendering // if .ass currently rendering
@ -620,6 +630,41 @@ function tryRemoveElement(elem) {
return relativeOffset; return relativeOffset;
} }
/**
* @private
* These browsers will not clear the existing active cue when setting an offset
* for native TextTracks.
* Any previous text tracks that are on the screen when the offset changes will remain next
* to the new tracks until they reach the end time of the new offset's instance of the track.
*/
requiresHidingActiveCuesOnOffsetChange() {
return !!browser.firefox;
}
/**
* @private
*/
hideTextTrackWithActiveCues(currentTrack) {
if (currentTrack.activeCues) {
currentTrack.mode = 'hidden';
}
}
/**
* Forces the active cue to clear by disabling then re-enabling the track.
* The track mode is reverted inside of a 0ms timeout to free up the track
* and allow it to disable and clear the active cue.
* @private
*/
forceClearTextTrackActiveCues(currentTrack) {
if (currentTrack.activeCues) {
currentTrack.mode = 'disabled';
setTimeout(() => {
currentTrack.mode = 'showing';
}, 0);
}
}
/** /**
* @private * @private
*/ */
@ -629,11 +674,21 @@ function tryRemoveElement(elem) {
if (offsetValue === 0) { if (offsetValue === 0) {
return; return;
} }
const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange();
if (shouldClearActiveCues) {
this.hideTextTrackWithActiveCues(currentTrack);
}
Array.from(currentTrack.cues) Array.from(currentTrack.cues)
.forEach(function (cue) { .forEach(function (cue) {
cue.startTime -= offsetValue; cue.startTime -= offsetValue;
cue.endTime -= offsetValue; cue.endTime -= offsetValue;
}); });
if (shouldClearActiveCues) {
this.forceClearTextTrackActiveCues(currentTrack);
}
} }
} }
@ -771,6 +826,8 @@ function tryRemoveElement(elem) {
} }
destroy() { destroy() {
this.setSubtitleOffset.cancel();
destroyHlsPlayer(this); destroyHlsPlayer(this);
destroyFlvPlayer(this); destroyFlvPlayer(this);

View file

@ -1,8 +1,11 @@
import { PluginType } from '../../types/plugin.ts';
import { randomInt } from '../../utils/number.ts';
export default function () { export default function () {
const self = this; const self = this;
self.name = 'Logo ScreenSaver'; self.name = 'Logo ScreenSaver';
self.type = 'screensaver'; self.type = PluginType.Screensaver;
self.id = 'logoscreensaver'; self.id = 'logoscreensaver';
self.supportsAnonymous = true; self.supportsAnonymous = true;
@ -23,16 +26,12 @@ export default function () {
const elem = document.querySelector('.logoScreenSaverImage'); const elem = document.querySelector('.logoScreenSaverImage');
if (elem && elem.animate) { if (elem && elem.animate) {
const random = getRandomInt(0, animations.length - 1); const random = randomInt(0, animations.length - 1);
animations[random](elem, 1); animations[random](elem, 1);
} }
} }
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function bounceInLeft(elem, iterations) { function bounceInLeft(elem, iterations) {
const keyframes = [ const keyframes = [
{ transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 },

View file

@ -4,6 +4,7 @@ import keyboardnavigation from '../../scripts/keyboardNavigation';
import dialogHelper from '../../components/dialogHelper/dialogHelper'; import dialogHelper from '../../components/dialogHelper/dialogHelper';
import dom from '../../scripts/dom'; import dom from '../../scripts/dom';
import { appRouter } from '../../components/appRouter'; import { appRouter } from '../../components/appRouter';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import './style.scss'; import './style.scss';
@ -12,7 +13,7 @@ import '../../elements/emby-button/paper-icon-button-light';
export class PdfPlayer { export class PdfPlayer {
constructor() { constructor() {
this.name = 'PDF Player'; this.name = 'PDF Player';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'pdfplayer'; this.id = 'pdfplayer';
this.priority = 1; this.priority = 1;
@ -261,7 +262,7 @@ export class PdfPlayer {
for (const page of pages) { for (const page of pages) {
if (!this.pages[page]) { if (!this.pages[page]) {
this.pages[page] = document.createElement('canvas'); this.pages[page] = document.createElement('canvas');
this.renderPage(this.pages[page], parseInt(page.slice(4))); this.renderPage(this.pages[page], parseInt(page.slice(4), 10));
} }
} }

View file

@ -1,9 +1,10 @@
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
export default class PhotoPlayer { export default class PhotoPlayer {
constructor() { constructor() {
this.name = 'Photo Player'; this.name = 'Photo Player';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'photoplayer'; this.id = 'photoplayer';
this.priority = 1; this.priority = 1;
} }

View file

@ -1,6 +1,7 @@
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert'; import alert from '../../components/alert';
import { PluginType } from '../../types/plugin.ts';
function showErrorMessage() { function showErrorMessage() {
return alert(globalize.translate('MessagePlayAccessRestricted')); return alert(globalize.translate('MessagePlayAccessRestricted'));
@ -9,7 +10,7 @@ function showErrorMessage() {
class PlayAccessValidation { class PlayAccessValidation {
constructor() { constructor() {
this.name = 'Playback validation'; this.name = 'Playback validation';
this.type = 'preplayintercept'; this.type = PluginType.PreplayIntercept;
this.id = 'playaccessvalidation'; this.id = 'playaccessvalidation';
this.order = -2; this.order = -2;
} }

View file

@ -1,6 +1,7 @@
import { playbackManager } from '../../components/playback/playbackmanager'; import { playbackManager } from '../../components/playback/playbackmanager';
import serverNotifications from '../../scripts/serverNotifications'; import serverNotifications from '../../scripts/serverNotifications';
import ServerConnections from '../../components/ServerConnections'; import ServerConnections from '../../components/ServerConnections';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
function getActivePlayerId() { function getActivePlayerId() {
@ -181,7 +182,7 @@ class SessionPlayer {
const self = this; const self = this;
this.name = 'Remote Control'; this.name = 'Remote Control';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.isLocalPlayer = false; this.isLocalPlayer = false;
this.id = 'remoteplayer'; this.id = 'remoteplayer';

View file

@ -254,7 +254,7 @@ class Manager {
if (typeof cmd.When === 'string') { if (typeof cmd.When === 'string') {
cmd.When = new Date(cmd.When); cmd.When = new Date(cmd.When);
cmd.EmittedAt = new Date(cmd.EmittedAt); cmd.EmittedAt = new Date(cmd.EmittedAt);
cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks) : null; cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks, 10) : null;
} }
if (!this.isSyncPlayEnabled()) { if (!this.isSyncPlayEnabled()) {

View file

@ -5,8 +5,9 @@ import SyncPlay from './core';
import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer'; import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer'; import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer';
import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer'; import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer';
import { Plugin, PluginType } from '../../types/plugin';
class SyncPlayPlugin { class SyncPlayPlugin implements Plugin {
name: string; name: string;
id: string; id: string;
type: string; type: string;
@ -17,7 +18,7 @@ class SyncPlayPlugin {
this.id = 'syncplay'; this.id = 'syncplay';
// NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but // NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but
// SyncPlay needs refactored so it does not have an independent playback manager. // SyncPlay needs refactored so it does not have an independent playback manager.
this.type = 'syncplay'; this.type = PluginType.SyncPlay;
this.priority = 1; this.priority = 1;
this.init(); this.init();

View file

@ -2,6 +2,7 @@ import browser from '../../scripts/browser';
import { appRouter } from '../../components/appRouter'; import { appRouter } from '../../components/appRouter';
import loading from '../../components/loading/loading'; import loading from '../../components/loading/loading';
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
/* globals YT */ /* globals YT */
@ -197,7 +198,7 @@ function setCurrentSrc(instance, elem, options) {
class YoutubePlayer { class YoutubePlayer {
constructor() { constructor() {
this.name = 'Youtube Player'; this.name = 'Youtube Player';
this.type = 'mediaplayer'; this.type = PluginType.MediaPlayer;
this.id = 'youtubeplayer'; this.id = 'youtubeplayer';
// Let any players created by plugins take priority // Let any players created by plugins take priority

View file

@ -55,7 +55,7 @@ const getTabs = () => {
const Movies: FC = () => { const Movies: FC = () => {
const [ searchParams ] = useSearchParams(); const [ searchParams ] = useSearchParams();
const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString()); const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString(), 10);
const [ selectedIndex, setSelectedIndex ] = useState(currentTabIndex); const [ selectedIndex, setSelectedIndex ] = useState(currentTabIndex);
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
@ -95,7 +95,7 @@ const Movies: FC = () => {
}; };
const onTabChange = useCallback((e: { detail: { selectedTabIndex: string; }; }) => { const onTabChange = useCallback((e: { detail: { selectedTabIndex: string; }; }) => {
const newIndex = parseInt(e.detail.selectedTabIndex); const newIndex = parseInt(e.detail.selectedTabIndex, 10);
setSelectedIndex(newIndex); setSelectedIndex(newIndex);
}, []); }, []);

View file

@ -159,6 +159,7 @@ const UserEdit: FunctionComponent = () => {
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator; (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator;
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled; (page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled;
(page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden; (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden;
(page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement;
(page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl; (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl;
(page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers; (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers;
(page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading; (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading;
@ -224,12 +225,13 @@ const UserEdit: FunctionComponent = () => {
user.Policy.EnableAudioPlaybackTranscoding = (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked; user.Policy.EnableAudioPlaybackTranscoding = (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked;
user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked; user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked;
user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked; user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked;
user.Policy.EnableCollectionManagement = (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked;
user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked; user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked;
user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked; user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked;
user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked; user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked;
user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat((page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value || '0')); user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat((page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value || '0'));
user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0'); user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0', 10);
user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0'); user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0', 10);
user.Policy.AuthenticationProviderId = (page.querySelector('#selectLoginProvider') as HTMLSelectElement).value; user.Policy.AuthenticationProviderId = (page.querySelector('#selectLoginProvider') as HTMLSelectElement).value;
user.Policy.PasswordResetProviderId = (page.querySelector('#selectPasswordResetProvider') as HTMLSelectElement).value; user.Policy.PasswordResetProviderId = (page.querySelector('#selectPasswordResetProvider') as HTMLSelectElement).value;
user.Policy.EnableContentDeletion = (page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).checked; user.Policy.EnableContentDeletion = (page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).checked;
@ -375,6 +377,11 @@ const UserEdit: FunctionComponent = () => {
className='chkIsAdmin' className='chkIsAdmin'
title='OptionAllowUserToManageServer' title='OptionAllowUserToManageServer'
/> />
<CheckBoxElement
labelClassName='checkboxContainer'
className='chkEnableCollectionManagement'
title='AllowCollectionManagement'
/>
<div id='featureAccessFields' className='verticalSection'> <div id='featureAccessFields' className='verticalSection'>
<h2 className='paperListLabel'> <h2 className='paperListLabel'>
{globalize.translate('HeaderFeatureAccess')} {globalize.translate('HeaderFeatureAccess')}

View file

@ -224,7 +224,7 @@ const uaMatch = function (ua) {
version = version || match[2] || '0'; version = version || match[2] || '0';
let versionMajor = parseInt(version.split('.')[0]); let versionMajor = parseInt(version.split('.')[0], 10);
if (isNaN(versionMajor)) { if (isNaN(versionMajor)) {
versionMajor = 0; versionMajor = 0;
@ -295,7 +295,7 @@ if (browser.web0s) {
delete browser.safari; delete browser.safari;
const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/); const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/);
browser.tizenVersion = parseInt(v[1]); browser.tizenVersion = parseInt(v[1], 10);
} else { } else {
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1; browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
} }

View file

@ -50,6 +50,11 @@ const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
*/ */
const InteractiveElements = ['INPUT', 'TEXTAREA']; const InteractiveElements = ['INPUT', 'TEXTAREA'];
/**
* Types of INPUT element for which navigation shouldn't be constrained.
*/
const NonInteractiveInputElements = ['button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'reset', 'submit'];
let hasFieldKey = false; let hasFieldKey = false;
try { try {
hasFieldKey = 'key' in new KeyboardEvent('keydown'); hasFieldKey = 'key' in new KeyboardEvent('keydown');
@ -84,6 +89,24 @@ export function isNavigationKey(key) {
return NavigationKeys.indexOf(key) != -1; return NavigationKeys.indexOf(key) != -1;
} }
/**
* Returns _true_ if the element is interactive.
*
* @param {Element} element - Element.
* @return {boolean} _true_ if the element is interactive.
*/
export function isInteractiveElement(element) {
if (element && InteractiveElements.includes(element.tagName)) {
if (element.tagName === 'INPUT') {
return !NonInteractiveInputElements.includes(element.type);
}
return true;
}
return false;
}
export function enable() { export function enable() {
window.addEventListener('keydown', function (e) { window.addEventListener('keydown', function (e) {
const key = getKeyName(e); const key = getKeyName(e);
@ -97,7 +120,7 @@ export function enable() {
switch (key) { switch (key) {
case 'ArrowLeft': case 'ArrowLeft':
if (!InteractiveElements.includes(document.activeElement?.tagName)) { if (!isInteractiveElement(document.activeElement)) {
inputManager.handleCommand('left'); inputManager.handleCommand('left');
} else { } else {
capture = false; capture = false;
@ -107,7 +130,7 @@ export function enable() {
inputManager.handleCommand('up'); inputManager.handleCommand('up');
break; break;
case 'ArrowRight': case 'ArrowRight':
if (!InteractiveElements.includes(document.activeElement?.tagName)) { if (!isInteractiveElement(document.activeElement)) {
inputManager.handleCommand('right'); inputManager.handleCommand('right');
} else { } else {
capture = false; capture = false;

View file

@ -16,6 +16,7 @@ import imageHelper from './imagehelper';
import { getMenuLinks } from '../scripts/settings/webSettings'; import { getMenuLinks } from '../scripts/settings/webSettings';
import Dashboard, { pageClassOn } from '../utils/dashboard'; import Dashboard, { pageClassOn } from '../utils/dashboard';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import { PluginType } from '../types/plugin.ts';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
import { getParameterByName } from '../utils/url.ts'; import { getParameterByName } from '../utils/url.ts';
@ -159,7 +160,7 @@ import '../styles/flexstyles.scss';
// Button is present // Button is present
headerSyncButton headerSyncButton
// SyncPlay plugin is loaded // SyncPlay plugin is loaded
&& pluginManager.plugins.filter(plugin => plugin.id === 'syncplay').length > 0 && pluginManager.ofType(PluginType.SyncPlay).length > 0
// SyncPlay enabled for user // SyncPlay enabled for user
&& policy?.SyncPlayAccess !== 'None' && policy?.SyncPlayAccess !== 'None'
) { ) {

View file

@ -3,6 +3,7 @@ import { pluginManager } from '../components/pluginManager';
import inputManager from './inputManager'; import inputManager from './inputManager';
import * as userSettings from './settings/userSettings'; import * as userSettings from './settings/userSettings';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import { PluginType } from '../types/plugin.ts';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
import './screensavermanager.scss'; import './screensavermanager.scss';
@ -34,7 +35,7 @@ function getScreensaverPlugin(isLoggedIn) {
option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver'; option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver';
} }
const plugins = pluginManager.ofType('screensaver'); const plugins = pluginManager.ofType(PluginType.Screensaver);
for (const plugin of plugins) { for (const plugin of plugins) {
if (plugin.id === option) { if (plugin.id === option) {

View file

@ -98,11 +98,11 @@ function processGeneralCommand(cmd, apiClient) {
break; break;
case 'SetAudioStreamIndex': case 'SetAudioStreamIndex':
notifyApp(); notifyApp();
playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index)); playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index, 10));
break; break;
case 'SetSubtitleStreamIndex': case 'SetSubtitleStreamIndex':
notifyApp(); notifyApp();
playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index)); playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index, 10));
break; break;
case 'ToggleFullscreen': case 'ToggleFullscreen':
inputManager.handleCommand('togglefullscreen'); inputManager.handleCommand('togglefullscreen');

View file

@ -70,7 +70,7 @@ class AppSettings {
// return a huge number so that it always direct plays // return a huge number so that it always direct plays
return 150000000; return 150000000;
} else { } else {
return parseInt(this.get(key) || '0') || 1500000; return parseInt(this.get(key) || '0', 10) || 1500000;
} }
} }
@ -80,7 +80,7 @@ class AppSettings {
} }
const defaultValue = 320000; const defaultValue = 320000;
return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue; return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString(), 10) || defaultValue;
} }
maxChromecastBitrate(val) { maxChromecastBitrate(val) {
@ -89,7 +89,7 @@ class AppSettings {
} }
val = this.get('chromecastBitrate1'); val = this.get('chromecastBitrate1');
return val ? parseInt(val) : null; return val ? parseInt(val, 10) : null;
} }
/** /**

View file

@ -348,7 +348,7 @@ export class UserSettings {
return this.set('skipBackLength', val.toString()); return this.set('skipBackLength', val.toString());
} }
return parseInt(this.get('skipBackLength') || '10000'); return parseInt(this.get('skipBackLength') || '10000', 10);
} }
/** /**
@ -361,7 +361,7 @@ export class UserSettings {
return this.set('skipForwardLength', val.toString()); return this.set('skipForwardLength', val.toString());
} }
return parseInt(this.get('skipForwardLength') || '30000'); return parseInt(this.get('skipForwardLength') || '30000', 10);
} }
/** /**

View file

@ -1380,7 +1380,7 @@
"UnknownAudioStreamInfo": "Інфармацыя аб аўдыяплыні невядомая", "UnknownAudioStreamInfo": "Інфармацыя аб аўдыяплыні невядомая",
"DirectPlayError": "Узнікла памылка пры запуску прамога прайгравання", "DirectPlayError": "Узнікла памылка пры запуску прамога прайгравання",
"SelectAll": "Абраць усё", "SelectAll": "Абраць усё",
"Clip": "Художнік", "Clip": "Кліп",
"Sample": "Прыклад", "Sample": "Прыклад",
"LabelVppTonemappingBrightness": "Узмацненне яркасці танальнага адлюстравання VPP:", "LabelVppTonemappingBrightness": "Узмацненне яркасці танальнага адлюстравання VPP:",
"LabelVppTonemappingBrightnessHelp": "Прымяніць узмацненне яркасці ў танальным адлюстраванні VPP. І рэкамендаванае, і стандартнае значэнне роўна 0.", "LabelVppTonemappingBrightnessHelp": "Прымяніць узмацненне яркасці ў танальным адлюстраванні VPP. І рэкамендаванае, і стандартнае значэнне роўна 0.",
@ -1700,5 +1700,7 @@
"SubtitleMagenta": "Пурпурны", "SubtitleMagenta": "Пурпурны",
"SubtitleRed": "Чырвоны", "SubtitleRed": "Чырвоны",
"SubtitleWhite": "Белы", "SubtitleWhite": "Белы",
"SubtitleYellow": "Жоўты" "SubtitleYellow": "Жоўты",
"Featurette": "Кароткаметражка",
"Short": "Кароткаметражка"
} }

View file

@ -1622,7 +1622,7 @@
"DeletedScene": "Vymazaná scéna", "DeletedScene": "Vymazaná scéna",
"BehindTheScenes": "Z natáčení", "BehindTheScenes": "Z natáčení",
"Trailer": "Upoutávka", "Trailer": "Upoutávka",
"Clip": "Krátký film", "Clip": "Filmový klip",
"AllowEmbeddedSubtitlesHelp": "Zakázat titulky, které jsou vložené v kontejneru média. Vyžaduje kompletní přeskenování knihovny.", "AllowEmbeddedSubtitlesHelp": "Zakázat titulky, které jsou vložené v kontejneru média. Vyžaduje kompletní přeskenování knihovny.",
"AllowEmbeddedSubtitlesAllowTextOption": "Povolit textové titulky", "AllowEmbeddedSubtitlesAllowTextOption": "Povolit textové titulky",
"AllowEmbeddedSubtitlesAllowImageOption": "Povolit grafické titulky", "AllowEmbeddedSubtitlesAllowImageOption": "Povolit grafické titulky",
@ -1714,5 +1714,10 @@
"SubtitleMagenta": "Fialová", "SubtitleMagenta": "Fialová",
"SubtitleRed": "Červená", "SubtitleRed": "Červená",
"SubtitleWhite": "Bílá", "SubtitleWhite": "Bílá",
"SubtitleYellow": "Žlutá" "SubtitleYellow": "Žlutá",
"Featurette": "Středně dlouhý film",
"Short": "Krátký film",
"HeaderPerformance": "Výkon",
"LabelParallelImageEncodingLimit": "Počet paralelních kódování obrázků",
"LabelParallelImageEncodingLimitHelp": "Maximální počet kódování obrázků, které mohou běžet zároveň. Nastavením na 0 bude limit nastaven dle parametrů systému."
} }

View file

@ -121,7 +121,7 @@
"DeleteMedia": "Medien löschen", "DeleteMedia": "Medien löschen",
"DeleteUser": "Benutzer löschen", "DeleteUser": "Benutzer löschen",
"DeleteUserConfirmation": "Bist du dir sicher, dass du diesen Benutzer löschen willst?", "DeleteUserConfirmation": "Bist du dir sicher, dass du diesen Benutzer löschen willst?",
"Depressed": "gedrückt", "Depressed": "Gedrückt",
"Descending": "Absteigend", "Descending": "Absteigend",
"DetectingDevices": "Suche Geräte", "DetectingDevices": "Suche Geräte",
"DeviceAccessHelp": "Dies wird nur auf Geräte angewendet, die eindeutig identifiziert werden können, und verhindert nicht den Web-Zugriff. Gefilterter Zugriff auf Geräte verhindert die Nutzung neuer Geräte solange, bis der Zugriff für diese freigegeben wird.", "DeviceAccessHelp": "Dies wird nur auf Geräte angewendet, die eindeutig identifiziert werden können, und verhindert nicht den Web-Zugriff. Gefilterter Zugriff auf Geräte verhindert die Nutzung neuer Geräte solange, bis der Zugriff für diese freigegeben wird.",
@ -279,11 +279,11 @@
"HeaderKeepRecording": "Aufnahme behalten", "HeaderKeepRecording": "Aufnahme behalten",
"HeaderKeepSeries": "Serie behalten", "HeaderKeepSeries": "Serie behalten",
"HeaderKodiMetadataHelp": "Um NFO-Metadaten zu aktivieren oder zu deaktivieren, bearbeite eine Bibliothek und finde den Abschnitt zum Speichern von Metadaten.", "HeaderKodiMetadataHelp": "Um NFO-Metadaten zu aktivieren oder zu deaktivieren, bearbeite eine Bibliothek und finde den Abschnitt zum Speichern von Metadaten.",
"HeaderLatestEpisodes": "Neueste Episoden", "HeaderLatestEpisodes": "Kürzlich hinzugefügte Episoden",
"HeaderLatestMedia": "Neueste Medien", "HeaderLatestMedia": "Kürzlich hinzugefügte Medien",
"HeaderLatestMovies": "Neueste Filme", "HeaderLatestMovies": "Kürzlich hinzugefügte Filme",
"HeaderLatestMusic": "Neueste Musik", "HeaderLatestMusic": "Kürzlich hinzugefügte Musik",
"HeaderLatestRecordings": "Neueste Aufnahmen", "HeaderLatestRecordings": "Kürzlich hinzugefügte Aufnahmen",
"HeaderLibraries": "Bibliotheken", "HeaderLibraries": "Bibliotheken",
"HeaderLibraryAccess": "Bibliothekszugriff", "HeaderLibraryAccess": "Bibliothekszugriff",
"HeaderLibraryFolders": "Bibliotheksverzeichnisse", "HeaderLibraryFolders": "Bibliotheksverzeichnisse",
@ -301,7 +301,7 @@
"HeaderMyMediaSmall": "Meine Medien (Klein)", "HeaderMyMediaSmall": "Meine Medien (Klein)",
"HeaderNewApiKey": "Neuer API-Schlüssel", "HeaderNewApiKey": "Neuer API-Schlüssel",
"HeaderNewDevices": "Neue Geräte", "HeaderNewDevices": "Neue Geräte",
"HeaderNextEpisodePlayingInValue": "Nächste Episode wird abgespielt in {0}", "HeaderNextEpisodePlayingInValue": "Nächste Episode in {0}",
"HeaderNextVideoPlayingInValue": "Nächstes Video wird abgespielt in {0}", "HeaderNextVideoPlayingInValue": "Nächstes Video wird abgespielt in {0}",
"HeaderOnNow": "Gerade läuft", "HeaderOnNow": "Gerade läuft",
"HeaderOtherItems": "Andere Inhalte", "HeaderOtherItems": "Andere Inhalte",
@ -696,7 +696,7 @@
"LabelffmpegPathHelp": "Verzeichnis zur FFmpeg-Applikationsdatei oder zum Ordner, der FFmpeg enthält.", "LabelffmpegPathHelp": "Verzeichnis zur FFmpeg-Applikationsdatei oder zum Ordner, der FFmpeg enthält.",
"LanNetworksHelp": "Komma separierte Liste von IP-Adressen oder IP/Netzmasken die als lokale Netzwerke behandelt werden sollen, um Bandbreitenlimitationen auszusetzen. Wenn gesetzt, werden alle anderen IP-Adressen als extern behandelt und unterliegen den Bandbreitenlimitationen für externe Verbindungen. Wenn leer, wird nur das Subnetz des Servers als lokales Netzwerk behandelt.", "LanNetworksHelp": "Komma separierte Liste von IP-Adressen oder IP/Netzmasken die als lokale Netzwerke behandelt werden sollen, um Bandbreitenlimitationen auszusetzen. Wenn gesetzt, werden alle anderen IP-Adressen als extern behandelt und unterliegen den Bandbreitenlimitationen für externe Verbindungen. Wenn leer, wird nur das Subnetz des Servers als lokales Netzwerk behandelt.",
"Large": "Groß", "Large": "Groß",
"LatestFromLibrary": "Neueste {0}", "LatestFromLibrary": "Kürzlich hinzugefügt in {0}",
"LearnHowYouCanContribute": "Erfahre, wie du unterstützen kannst.", "LearnHowYouCanContribute": "Erfahre, wie du unterstützen kannst.",
"LibraryAccessHelp": "Wähle die Bibliotheken aus, die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden um alle Ordner zu bearbeiten.", "LibraryAccessHelp": "Wähle die Bibliotheken aus, die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden um alle Ordner zu bearbeiten.",
"List": "Liste", "List": "Liste",
@ -1023,7 +1023,7 @@
"TabContainers": "Container", "TabContainers": "Container",
"TabDashboard": "Übersicht", "TabDashboard": "Übersicht",
"TabDirectPlay": "Direktwiedergabe", "TabDirectPlay": "Direktwiedergabe",
"TabLatest": "Neueste", "TabLatest": "Kürzlich hinzugefügt",
"TabMusic": "Musik", "TabMusic": "Musik",
"TabMyPlugins": "Meine Plugins", "TabMyPlugins": "Meine Plugins",
"TabNetworks": "Fernsehsender", "TabNetworks": "Fernsehsender",
@ -1704,5 +1704,17 @@
"LabelDummyChapterCount": "Limit:", "LabelDummyChapterCount": "Limit:",
"LabelDummyChapterCountHelp": "Die maximale Anzahl von Kapitelbildern, die für jede Mediendatei extrahiert werden.", "LabelDummyChapterCountHelp": "Die maximale Anzahl von Kapitelbildern, die für jede Mediendatei extrahiert werden.",
"LabelChapterImageResolution": "Auflösung:", "LabelChapterImageResolution": "Auflösung:",
"LabelChapterImageResolutionHelp": "Die Auflösung der extrahierten Kapitelbilder." "LabelChapterImageResolutionHelp": "Die Auflösung der extrahierten Kapitelbilder.",
"SubtitleBlack": "Schwarz",
"SubtitleBlue": "Blau",
"SubtitleCyan": "Cyan",
"SubtitleGray": "Grau",
"SubtitleGreen": "Grün",
"SubtitleLightGray": "Hellgrau",
"SubtitleMagenta": "Magenta",
"SubtitleRed": "Rot",
"SubtitleWhite": "Weiß",
"SubtitleYellow": "Gelb",
"Featurette": "Featurette",
"Short": "Kurzfilm"
} }

View file

@ -1629,7 +1629,7 @@
"DeletedScene": "Deleted Scene", "DeletedScene": "Deleted Scene",
"BehindTheScenes": "Behind the Scenes", "BehindTheScenes": "Behind the Scenes",
"Trailer": "Trailer", "Trailer": "Trailer",
"Clip": "Featurette", "Clip": "Clip",
"ShowParentImages": "Show programme images", "ShowParentImages": "Show programme images",
"NextUpRewatching": "Rewatching", "NextUpRewatching": "Rewatching",
"MixedMoviesShows": "Mixed Films and Programmes", "MixedMoviesShows": "Mixed Films and Programmes",
@ -1714,5 +1714,7 @@
"SubtitleMagenta": "Magenta", "SubtitleMagenta": "Magenta",
"SubtitleRed": "Red", "SubtitleRed": "Red",
"SubtitleWhite": "White", "SubtitleWhite": "White",
"SubtitleYellow": "Yellow" "SubtitleYellow": "Yellow",
"Featurette": "Featurette",
"Short": "Short"
} }

View file

@ -420,6 +420,7 @@
"HeaderPassword": "Password", "HeaderPassword": "Password",
"HeaderPasswordReset": "Password Reset", "HeaderPasswordReset": "Password Reset",
"HeaderPaths": "Paths", "HeaderPaths": "Paths",
"HeaderPerformance": "Performance",
"HeaderPhotoAlbums": "Photo Albums", "HeaderPhotoAlbums": "Photo Albums",
"HeaderPinCodeReset": "Reset Easy PIN Code", "HeaderPinCodeReset": "Reset Easy PIN Code",
"HeaderPlayAll": "Play All", "HeaderPlayAll": "Play All",
@ -791,6 +792,8 @@
"LabelOriginalName": "Original name:", "LabelOriginalName": "Original name:",
"LabelOriginalTitle": "Original title:", "LabelOriginalTitle": "Original title:",
"LabelOverview": "Overview:", "LabelOverview": "Overview:",
"LabelParallelImageEncodingLimit": "Parallel image encoding limit",
"LabelParallelImageEncodingLimitHelp": "Maximum amount of image encodings that are allowed to run in parallel. Setting this to 0 will choose a limit based on your system specs.",
"LabelParentalRating": "Parental rating:", "LabelParentalRating": "Parental rating:",
"LabelParentNumber": "Parent number:", "LabelParentNumber": "Parent number:",
"LabelPassword": "Password:", "LabelPassword": "Password:",
@ -1671,7 +1674,9 @@
"UnknownAudioStreamInfo": "The audio stream info is unknown", "UnknownAudioStreamInfo": "The audio stream info is unknown",
"DirectPlayError": "There was an error starting direct playback", "DirectPlayError": "There was an error starting direct playback",
"SelectAll": "Select All", "SelectAll": "Select All",
"Clip": "Featurette", "Featurette": "Featurette",
"Short": "Short",
"Clip": "Clip",
"Trailer": "Trailer", "Trailer": "Trailer",
"BehindTheScenes": "Behind the Scenes", "BehindTheScenes": "Behind the Scenes",
"DeletedScene": "Deleted Scene", "DeletedScene": "Deleted Scene",

View file

@ -16,7 +16,7 @@
"Books": "Libros", "Books": "Libros",
"Artists": "Artistas", "Artists": "Artistas",
"Albums": "Álbumes", "Albums": "Álbumes",
"TabLatest": "Recientes", "TabLatest": "Agregado recientemente",
"HeaderUser": "Usuario", "HeaderUser": "Usuario",
"AlbumArtist": "Artista del álbum", "AlbumArtist": "Artista del álbum",
"Album": "Álbum", "Album": "Álbum",
@ -309,7 +309,7 @@
"LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.", "LibraryAccessHelp": "Selecciona las bibliotecas que deseas compartir con este usuario. Los administradores podrán editar todas las carpetas utilizando el gestor de metadatos.",
"LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.", "LeaveBlankToNotSetAPassword": "Puedes dejar este campo en blanco para no establecer ninguna contraseña.",
"LearnHowYouCanContribute": "Aprende cómo puedes contribuir.", "LearnHowYouCanContribute": "Aprende cómo puedes contribuir.",
"LatestFromLibrary": "Últimas - {0}", "LatestFromLibrary": "Agregado recientemente en {0}",
"Large": "Grande", "Large": "Grande",
"LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.", "LanNetworksHelp": "Lista separada por comas de direcciones IP o entradas de IP/máscara de red para las redes que se considerarán en la red local al aplicar las restricciones de ancho de banda. Si se establecen, todas las demás direcciones IP se considerarán como parte de la red externa y estarán sujetas a las restricciones de ancho de banda externa. Si se deja en blanco, solo se considera a la subred del servidor estar en la red local.",
"LabelffmpegPathHelp": "La ruta hacia el archivo ejecutable FFmpeg, o la carpeta que contenga FFmpeg.", "LabelffmpegPathHelp": "La ruta hacia el archivo ejecutable FFmpeg, o la carpeta que contenga FFmpeg.",
@ -571,7 +571,7 @@
"LabelSelectVersionToInstall": "Seleccionar versión a instalar:", "LabelSelectVersionToInstall": "Seleccionar versión a instalar:",
"LabelSelectUsers": "Seleccionar usuarios:", "LabelSelectUsers": "Seleccionar usuarios:",
"LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.", "LabelSelectFolderGroupsHelp": "Las carpetas sin marcar serán mostradas individualmente en su propia vista.",
"LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como Películas, Música y TV:", "LabelSelectFolderGroups": "Agrupar automáticamente el contenido de las siguientes carpetas a vistas como «Películas», «Música» y «TV»:",
"LabelSeasonNumber": "Temporada número:", "LabelSeasonNumber": "Temporada número:",
"LabelScreensaver": "Protector de pantalla:", "LabelScreensaver": "Protector de pantalla:",
"LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.", "LabelScheduledTaskLastRan": "Última ejecución {0}, tomando {1}.",
@ -875,7 +875,7 @@
"HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.", "HttpsRequiresCert": "Para habilitar las conexiones seguras, necesitarás proporcionar un certificado SSL de confianza, como el de Let's Encrypt. Por favor, proporciona un certificado o desactiva las conexiones seguras.",
"Horizontal": "Horizontal", "Horizontal": "Horizontal",
"Home": "Inicio", "Home": "Inicio",
"HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de «Últimos medios»", "HideWatchedContentFromLatestMedia": "Ocultar contenido ya visto de «Medios agregados recientemente»",
"Hide": "Ocultar", "Hide": "Ocultar",
"Help": "Ayuda", "Help": "Ayuda",
"HeaderYears": "Años", "HeaderYears": "Años",
@ -970,11 +970,11 @@
"HeaderLibraryFolders": "Carpetas de bibliotecas", "HeaderLibraryFolders": "Carpetas de bibliotecas",
"HeaderLibraryAccess": "Acceso a bibliotecas", "HeaderLibraryAccess": "Acceso a bibliotecas",
"HeaderLibraries": "Bibliotecas", "HeaderLibraries": "Bibliotecas",
"HeaderLatestRecordings": "Últimas grabaciones", "HeaderLatestRecordings": "Grabaciones agregadas recientemente",
"HeaderLatestMusic": "Última música", "HeaderLatestMusic": "Música agregada recientemente",
"HeaderLatestMovies": "Últimas películas", "HeaderLatestMovies": "Películas agregadas recientemente",
"HeaderLatestMedia": "Últimos medios", "HeaderLatestMedia": "Medios agregados recientemente",
"HeaderLatestEpisodes": "Últimos episodios", "HeaderLatestEpisodes": "Episodios agregados recientemente",
"HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edita una biblioteca y ubica la sección de 'Grabadores de metadatos'.", "HeaderKodiMetadataHelp": "Para habilitar o deshabilitar los metadatos NFO, edita una biblioteca y ubica la sección de 'Grabadores de metadatos'.",
"HeaderKeepSeries": "Conservar serie", "HeaderKeepSeries": "Conservar serie",
"HeaderKeepRecording": "Conservar grabación", "HeaderKeepRecording": "Conservar grabación",
@ -1006,7 +1006,7 @@
"LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.", "LabelMaxResumePercentageHelp": "Los medios se consideran totalmente reproducidos si se detienen después de este tiempo.",
"LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:", "LabelMaxResumePercentage": "Porcentaje máximo para la reanudación:",
"LabelMaxParentalRating": "Máxima clasificación parental permitida:", "LabelMaxParentalRating": "Máxima clasificación parental permitida:",
"LabelMaxChromecastBitrate": "Calidad de transmisión de Chromecast:", "LabelMaxChromecastBitrate": "Calidad de transmisión de Google Cast:",
"LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:", "LabelMaxBackdropsPerItem": "Número máximo de imágenes de fondo por elemento:",
"LabelMatchType": "Tipo de coincidencia:", "LabelMatchType": "Tipo de coincidencia:",
"LabelManufacturerUrl": "URL del fabricante:", "LabelManufacturerUrl": "URL del fabricante:",
@ -1022,7 +1022,7 @@
"LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:", "LabelLibraryPageSize": "Tamaño de las páginas de las bibliotecas:",
"LabelLanguage": "Idioma:", "LabelLanguage": "Idioma:",
"LabelLanNetworks": "Redes LAN:", "LabelLanNetworks": "Redes LAN:",
"LabelKodiMetadataUserHelp": "Guarda los datos de visto en archivos NFO para que otras aplicaciones los utilicen.", "LabelKodiMetadataUserHelp": "Guarda los datos de visualización en los archivos NFO para que otras aplicaciones lo usen.",
"LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:", "LabelKodiMetadataUser": "Guardar los datos de visto del usuario en archivos NFO para:",
"LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.", "LabelKodiMetadataSaveImagePathsHelp": "Esto se recomienda si tienes nombres de imágenes que no se ajustan a los lineamientos de Kodi.",
"LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO", "LabelKodiMetadataSaveImagePaths": "Guardar las rutas de las imágenes en los archivos NFO",
@ -1166,7 +1166,7 @@
"DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.", "DisplayModeHelp": "Selecciona el estilo de diseño que desea para la interfaz.",
"DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.", "DisplayMissingEpisodesWithinSeasonsHelp": "Esto también debe estar habilitado para las bibliotecas de TV en la configuración del servidor.",
"DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas", "DisplayMissingEpisodesWithinSeasons": "Mostrar episodios faltantes en las temporadas",
"DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como «Recientes» o «Continuar viendo»", "DisplayInOtherHomeScreenSections": "Mostrar en las secciones de la pantalla de inicio como «Medios agregados recientemente» o «Continuar viendo»",
"DisplayInMyMedia": "Mostrar en la pantalla de inicio", "DisplayInMyMedia": "Mostrar en la pantalla de inicio",
"Display": "Pantalla", "Display": "Pantalla",
"Disconnect": "Desconectar", "Disconnect": "Desconectar",
@ -1354,7 +1354,7 @@
"MessagePluginInstallError": "Ocurrió un error instalando el complemento.", "MessagePluginInstallError": "Ocurrió un error instalando el complemento.",
"PlaybackRate": "Tasa de reproducción", "PlaybackRate": "Tasa de reproducción",
"Data": "Datos", "Data": "Datos",
"ButtonUseQuickConnect": "Usar conexión rápida", "ButtonUseQuickConnect": "Usar «Conexión rápida»",
"ButtonActivate": "Activar", "ButtonActivate": "Activar",
"Authorize": "Autorizar", "Authorize": "Autorizar",
"LabelCurrentStatus": "Estado actual:", "LabelCurrentStatus": "Estado actual:",
@ -1366,7 +1366,7 @@
"TonemappingAlgorithmHelp": "El mapeo de tonos puede ser modificado. Si no estas familiarizado con estas opciones, solo manten el predeterminado. El valor recomendado es Hable.", "TonemappingAlgorithmHelp": "El mapeo de tonos puede ser modificado. Si no estas familiarizado con estas opciones, solo manten el predeterminado. El valor recomendado es Hable.",
"LabelTonemappingThresholdHelp": "El algoritmo de mapeo de tonos puede ser finamente ajustado para cada escena. Y el umbral es usado para detectar si la escena ha cambiado. Si los valores entre el promedio de brillo del fotograma actual y el promedio actual excede el valor de umbral, se vuelve a calcular el brillo promedio y máximo. Los valores recomendados y por defecto son entre 0.2 y 0.8.", "LabelTonemappingThresholdHelp": "El algoritmo de mapeo de tonos puede ser finamente ajustado para cada escena. Y el umbral es usado para detectar si la escena ha cambiado. Si los valores entre el promedio de brillo del fotograma actual y el promedio actual excede el valor de umbral, se vuelve a calcular el brillo promedio y máximo. Los valores recomendados y por defecto son entre 0.2 y 0.8.",
"ThumbCard": "Miniatura", "ThumbCard": "Miniatura",
"LabelMaxMuxingQueueSizeHelp": "Numero máximo de paquetes que pueden estar en búfer mientras se espera a que se inicialicen todas las trasmisiones. Intenta incrementarlos si encuentras el error \"Too many packets buffered for output stream\" en los registros de ffmpeg. El valor recomendado es 2048.", "LabelMaxMuxingQueueSizeHelp": "Numero máximo de paquetes que pueden estar en búfer mientras se espera a que se inicialicen todas las trasmisiones. Intenta incrementarlos si encuentras el error «Too many packets buffered for output stream» en los registros de ffmpeg. El valor recomendado es 2048.",
"LabelMaxMuxingQueueSize": "Tamaño máximo cola de mezclado:", "LabelMaxMuxingQueueSize": "Tamaño máximo cola de mezclado:",
"LabelTonemappingParamHelp": "Modifica el algoritmo de mapeo de tonos. Los valores recomendados y por defecto son NaN. Se puede dejar en blanco.", "LabelTonemappingParamHelp": "Modifica el algoritmo de mapeo de tonos. Los valores recomendados y por defecto son NaN. Se puede dejar en blanco.",
"LabelTonemappingParam": "Parámetro Mapeo de tonos:", "LabelTonemappingParam": "Parámetro Mapeo de tonos:",
@ -1380,7 +1380,7 @@
"LabelTonemappingAlgorithm": "Seleccione el algoritmo de mapeo de tonos:", "LabelTonemappingAlgorithm": "Seleccione el algoritmo de mapeo de tonos:",
"AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR manteniendo la calidad de imagen y los colores, que son datos muy importantes para mostrar la imagen original. Actualmente solo funciona con videos HDR10 o HLG. Esto requiere los tiempos de ejecución OpenCL o CUDA correspondientes.", "AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR manteniendo la calidad de imagen y los colores, que son datos muy importantes para mostrar la imagen original. Actualmente solo funciona con videos HDR10 o HLG. Esto requiere los tiempos de ejecución OpenCL o CUDA correspondientes.",
"EnableTonemapping": "Habilitar mapeo de tonos", "EnableTonemapping": "Habilitar mapeo de tonos",
"LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se utiliza para el mapeo de tonos. El lado izquierdo del punto es el número de plataforma y el lado derecho es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere la aplicación ffmpeg que contiene el método de aceleración de hardware OpenCL.", "LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se utiliza para el mapeo de tonos. El lado izquierdo del punto es el número de plataforma y el lado derecho es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere la aplicación FFmpeg que contiene el método de aceleración de hardware OpenCL.",
"LabelOpenclDevice": "Dispositivo OpenCL:", "LabelOpenclDevice": "Dispositivo OpenCL:",
"LabelColorPrimaries": "Colores primarios:", "LabelColorPrimaries": "Colores primarios:",
"LabelColorTransfer": "Transferencia de Color:", "LabelColorTransfer": "Transferencia de Color:",
@ -1400,22 +1400,22 @@
"SelectServer": "Seleccionar Servidor", "SelectServer": "Seleccionar Servidor",
"Restart": "Reiniciar", "Restart": "Reiniciar",
"ResetPassword": "Resetear contraseña", "ResetPassword": "Resetear contraseña",
"QuickConnectNotActive": "Conexión Rápida no esta habilitado en el servidor", "QuickConnectNotActive": "«Conexión rápida» no está habilitado en este servidor",
"QuickConnectNotAvailable": "Pregunte al administrador del servidor para habilitar Conexión Rápida", "QuickConnectNotAvailable": "Pide al administrador del servidor que habilite la «Conexión rápida»",
"QuickConnectInvalidCode": "Código Conexión Rápida inválido", "QuickConnectInvalidCode": "Código de «Conexión rápida» inválido",
"QuickConnectDescription": "Para ingresar con Conexión Rápida, seleccione el botón Conexión Rápida en el dispositivo del cual estas ingresando e ingrese el siguiente código.", "QuickConnectDescription": "Para ingresar con «Conexión rápida», seleccione el botón «Conexión rápida» en el dispositivo del cual estas ingresando e ingrese el código mostrado a continuación.",
"QuickConnectDeactivated": "Conexión Rápida se desactivó antes de que se pudiera aprobar la solicitud de inicio de sesión", "QuickConnectDeactivated": "La «Conexión rápida» se desactivó antes de que se pudiera aprobar la solicitud de inicio de sesión",
"QuickConnectAuthorizeFail": "Código Conexión Rápida desconocido", "QuickConnectAuthorizeFail": "Código de «Conexión rápida» desconocido",
"QuickConnectAuthorizeSuccess": "Requiere autorización", "QuickConnectAuthorizeSuccess": "Solicitud autorizada",
"QuickConnectAuthorizeCode": "Ingrese el código {0} para acceder", "QuickConnectAuthorizeCode": "Ingrese el código {0} para acceder",
"QuickConnectActivationSuccessful": "Activación Exitosa", "QuickConnectActivationSuccessful": "Activado exitosamente",
"QuickConnect": "Conexión Rápida", "QuickConnect": "Conexión rápida",
"Profile": "Perfil", "Profile": "Perfil",
"PosterCard": "Imagen del Cartel", "PosterCard": "Imagen del Cartel",
"Poster": "Cartel", "Poster": "Cartel",
"Photo": "Foto", "Photo": "Foto",
"MusicVideos": "Videos musicales", "MusicVideos": "Videos musicales",
"LabelQuickConnectCode": "Código conexión rápida:", "LabelQuickConnectCode": "Código de «Conexión rápida»:",
"LabelKnownProxies": "Proxies conocidos:", "LabelKnownProxies": "Proxies conocidos:",
"LabelIconMaxResHelp": "Resolución maxima de los iconos por medio de la función 'upnp:icon'.", "LabelIconMaxResHelp": "Resolución maxima de los iconos por medio de la función 'upnp:icon'.",
"EnableAutoCast": "Establecer como Predeterminado", "EnableAutoCast": "Establecer como Predeterminado",
@ -1519,7 +1519,7 @@
"LabelSlowResponseEnabled": "Log de alarma si la respuesta del servidor fue 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 utilizaran imagenes del episodio como miniaturas en lugar de miniaturas del show.",
"UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"", "UseEpisodeImagesInNextUp": "Usar imágenes de los episodios en \"Siguiente\" y \"Continuar Viendo\"",
"LabelLocalCustomCss": "El CSS personalizado aplica solo a este cliente. Puede quieras deshabilitar el CSS del servidor.", "LabelLocalCustomCss": "Código CSS personalizado para estilo que aplica solo a este cliente. Puede que quiera deshabilitar el código CSS personalizado del servidor.",
"LabelDisableCustomCss": "Desactivar el código CSS personalizado para la tematización/marca proporcionada desde el servidor.", "LabelDisableCustomCss": "Desactivar el código CSS personalizado para la tematización/marca proporcionada desde el servidor.",
"DisableCustomCss": "Deshabilitar CSS proveniente del servidor", "DisableCustomCss": "Deshabilitar CSS proveniente del servidor",
"LabelAutomaticallyAddToCollectionHelp": "Cuando al menos 2 películas tengan el mismo nombre de colección, se añadirán automáticamente a dicha colección.", "LabelAutomaticallyAddToCollectionHelp": "Cuando al menos 2 películas tengan el mismo nombre de colección, se añadirán automáticamente a dicha colección.",
@ -1564,5 +1564,16 @@
"LabelSyncPlaySettingsSyncCorrection": "Corrección de sincronización", "LabelSyncPlaySettingsSyncCorrection": "Corrección de sincronización",
"LabelSyncPlaySettingsExtraTimeOffset": "Desfase de tiempo extra:", "LabelSyncPlaySettingsExtraTimeOffset": "Desfase de tiempo extra:",
"LabelSyncPlaySettingsMinDelaySpeedToSync": "Retraso mínimo de SpeedToSync:", "LabelSyncPlaySettingsMinDelaySpeedToSync": "Retraso mínimo de SpeedToSync:",
"LabelOriginalName": "Nombre original:" "LabelOriginalName": "Nombre original:",
"Experimental": "Experimental",
"HeaderRecordingMetadataSaving": "Metadatos de grabación",
"LabelStereoDownmixAlgorithm": "Algoritmo de downmix estéreo",
"LabelDummyChapterDuration": "Intervalo:",
"LabelDummyChapterCountHelp": "El máximo número de imágenes de capítulos que serán extraídas por cada archivo de medios.",
"HeaderDummyChapter": "Imágenes de capítulos",
"LabelDummyChapterDurationHelp": "El intervalo de la extracción de las imágenes de los capítulos, en segundos.",
"LabelDummyChapterCount": "Límite:",
"LabelChapterImageResolution": "Resolución:",
"LabelChapterImageResolutionHelp": "La resolución de las imágenes de capítulos extraídas.",
"LabelMaxDaysForNextUp": "Días máximos en «A continuación»:"
} }

View file

@ -1634,7 +1634,7 @@
"ButtonSpace": "Välilyönti", "ButtonSpace": "Välilyönti",
"ThemeVideo": "Tunnusvideo", "ThemeVideo": "Tunnusvideo",
"ThemeSong": "Tunnusmusiikki", "ThemeSong": "Tunnusmusiikki",
"Clip": "Lyhytfilmi", "Clip": "Klippi",
"Scene": "Kohtaus", "Scene": "Kohtaus",
"Interview": "Haastattelu", "Interview": "Haastattelu",
"UnknownAudioStreamInfo": "Äänivirran tiedot ovat tuntemattomia", "UnknownAudioStreamInfo": "Äänivirran tiedot ovat tuntemattomia",
@ -1677,7 +1677,7 @@
"MessageNoFavoritesAvailable": "Suosikkeja ei ole tällä hetkellä käytettävissä.", "MessageNoFavoritesAvailable": "Suosikkeja ei ole tällä hetkellä käytettävissä.",
"EnableCardLayout": "Näytä visuaalinen KorttiLaatikko", "EnableCardLayout": "Näytä visuaalinen KorttiLaatikko",
"Unreleased": "Ei vielä julkaistu", "Unreleased": "Ei vielä julkaistu",
"MediaInfoDvVersionMajor": "", "MediaInfoDvVersionMajor": "DV-pääversio",
"DownloadAll": "Lataa kaikki", "DownloadAll": "Lataa kaikki",
"Experimental": "Kokeellinen", "Experimental": "Kokeellinen",
"LabelStereoDownmixAlgorithm": "Stereoäänen alasmiksausalgoritmi:", "LabelStereoDownmixAlgorithm": "Stereoäänen alasmiksausalgoritmi:",
@ -1697,5 +1697,22 @@
"ResolutionMatchSource": "Vastaa lähdettä", "ResolutionMatchSource": "Vastaa lähdettä",
"PreferEmbeddedExtrasTitlesOverFileNames": "Suosi lisämateriaaleille upotettuja otsikoita tiedostonimien sijaan", "PreferEmbeddedExtrasTitlesOverFileNames": "Suosi lisämateriaaleille upotettuja otsikoita tiedostonimien sijaan",
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Lisämateriaaleilla on usein sama otsikko kuin niiden isännällä. Valitse tämä käyttääksesi silti upotettuja otsikoita.", "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Lisämateriaaleilla on usein sama otsikko kuin niiden isännällä. Valitse tämä käyttääksesi silti upotettuja otsikoita.",
"SecondarySubtitles": "Toissijaiset tekstitykset" "SecondarySubtitles": "Toissijaiset tekstitykset",
"MediaInfoElPresentFlag": "DV EL havaittu",
"SubtitleBlack": "Musta",
"SubtitleCyan": "Syaani",
"SubtitleGray": "Harmaa",
"SubtitleGreen": "Vihreä",
"SubtitleLightGray": "Vaaleanharmaa",
"SubtitleMagenta": "Magenta",
"SubtitleRed": "Punainen",
"SubtitleWhite": "Valkoinen",
"SubtitleYellow": "Keltainen",
"MediaInfoDvBlSignalCompatibilityId": "DV BL -signaaliyhteensopivuuden ID",
"SubtitleBlue": "Sininen",
"Featurette": "Oheisfilmi",
"Short": "Lyhytfilmi",
"MediaInfoBlPresentFlag": "DV BL havaittu",
"MediaInfoRpuPresentFlag": "DV RPU havaittu",
"MediaInfoDvVersionMinor": "DV-väliversio"
} }

View file

@ -334,7 +334,7 @@
"DownloadsValue": "{0} téléchargements", "DownloadsValue": "{0} téléchargements",
"DisplayModeHelp": "Sélectionner l'agencement que vous désirez pour l'interface.", "DisplayModeHelp": "Sélectionner l'agencement que vous désirez pour l'interface.",
"DisplayMissingEpisodesWithinSeasons": "Afficher les épisodes manquants dans les saisons", "DisplayMissingEpisodesWithinSeasons": "Afficher les épisodes manquants dans les saisons",
"DisplayInOtherHomeScreenSections": "Afficher dans les sections de lécran daccueil comme « Ajouts récents » et reprendre le visionnement", "DisplayInOtherHomeScreenSections": "Afficher dans les sections de lécran daccueil comme « Ajouts récents » et «Reprendre le visionnement»",
"Directors": "Réalisateurs", "Directors": "Réalisateurs",
"Director": "Réalisateur", "Director": "Réalisateur",
"DetectingDevices": "Détection des appareils", "DetectingDevices": "Détection des appareils",
@ -349,7 +349,7 @@
"HeaderCodecProfile": "Profil de codec", "HeaderCodecProfile": "Profil de codec",
"HeaderChapterImages": "Images des chapitres", "HeaderChapterImages": "Images des chapitres",
"HeaderChannelAccess": "Accès aux chaînes", "HeaderChannelAccess": "Accès aux chaînes",
"LatestFromLibrary": "{0}, ajouts récents", "LatestFromLibrary": "{0} ajouts récents",
"HideWatchedContentFromLatestMedia": "Masquer le contenu déjà vu dans les 'Derniers Médias'", "HideWatchedContentFromLatestMedia": "Masquer le contenu déjà vu dans les 'Derniers Médias'",
"HeaderLatestRecordings": "Derniers enregistrements", "HeaderLatestRecordings": "Derniers enregistrements",
"HeaderLatestMusic": "Dernière musique", "HeaderLatestMusic": "Dernière musique",
@ -1020,5 +1020,25 @@
"LabelMaxParentalRating": "Classification parentale maximale :", "LabelMaxParentalRating": "Classification parentale maximale :",
"SpecialFeatures": "Bonus", "SpecialFeatures": "Bonus",
"Sort": "Trier", "Sort": "Trier",
"SortByValue": "Trier par" "SortByValue": "Trier par",
"LabelMovieCategories": "Catégories de films:",
"LabelNewPassword": "Nouveau mot de passe:",
"LabelOriginalName": "Nom original:",
"LabelPasswordRecoveryPinCode": "Code NIP:",
"LabelOriginalTitle": "Titre original:",
"LabelMaxStreamingBitrate": "Qualité maximale de streaming:",
"LabelNotificationEnabled": "Activer cette notification",
"LabelNewName": "Nouveau nom:",
"LabelNewPasswordConfirm": "Confirmer le nouveau mot de passe:",
"LabelPersonRole": "Rôle:",
"LabelPasswordConfirm": "Confirmer le mot de passe:",
"LabelPersonRoleHelp": "Exemple: Conducteur de camion à crème glacée",
"LabelPleaseRestart": "Les changements prendront effets après avoir rechargé manuellement le client web.",
"LabelMinAudiobookResumeHelp": "Les titres sont considérés comme non joué s'ils sont arrêtés avant cette durée.",
"LabelPassword": "Mot de passe:",
"LabelPath": "Chemin:",
"LabelMetadataPath": "Chemin des métadonnées:",
"LabelDummyChapterDuration": "Intervalle:",
"LabelDummyChapterCount": "Limite:",
"LabelChapterImageResolution": "Résolution:"
} }

View file

@ -234,7 +234,7 @@
"HeaderApiKeys": "Clés API", "HeaderApiKeys": "Clés API",
"HeaderApiKeysHelp": "Les applications externes ont besoin d'une clé d'API pour communiquer avec le serveur. Les clés sont distribuées lors d'une connexion avec un compte normal ou en accordant manuellement une clé à une application.", "HeaderApiKeysHelp": "Les applications externes ont besoin d'une clé d'API pour communiquer avec le serveur. Les clés sont distribuées lors d'une connexion avec un compte normal ou en accordant manuellement une clé à une application.",
"HeaderApp": "Application", "HeaderApp": "Application",
"HeaderAppearsOn": "Apparait dans", "HeaderAppearsOn": "Apparaît dans",
"HeaderAudioBooks": "Livres audio", "HeaderAudioBooks": "Livres audio",
"HeaderAudioSettings": "Réglages audio", "HeaderAudioSettings": "Réglages audio",
"HeaderBlockItemsWithNoRating": "Bloquer les éléments avec des informations de classification inconnues ou n'en disposant pas :", "HeaderBlockItemsWithNoRating": "Bloquer les éléments avec des informations de classification inconnues ou n'en disposant pas :",
@ -1614,7 +1614,7 @@
"AudioIsExternal": "Le flux audio est externe", "AudioIsExternal": "Le flux audio est externe",
"SelectAll": "Tout sélectionner", "SelectAll": "Tout sélectionner",
"ButtonExitApp": "Quitter l'application", "ButtonExitApp": "Quitter l'application",
"Clip": "Court-métrage", "Clip": "Clip",
"ThemeVideo": "Générique", "ThemeVideo": "Générique",
"ThemeSong": "Thème musical", "ThemeSong": "Thème musical",
"Sample": "Échantillon", "Sample": "Échantillon",
@ -1714,5 +1714,10 @@
"SubtitleMagenta": "Magenta", "SubtitleMagenta": "Magenta",
"SubtitleRed": "Rouge", "SubtitleRed": "Rouge",
"SubtitleWhite": "Blanc", "SubtitleWhite": "Blanc",
"SubtitleYellow": "Jaune" "SubtitleYellow": "Jaune",
"Featurette": "Featurette",
"Short": "Court-métrage",
"HeaderPerformance": "Performance",
"LabelParallelImageEncodingLimit": "Limite de parallélisation de l'encodage d'image",
"LabelParallelImageEncodingLimitHelp": "Nombre maximal dencodages dimage autorisés à sexécuter en parallèle. Si vous définissez cette valeur sur 0, vous choisirez une limite en fonction des spécifications de votre système."
} }

View file

@ -447,6 +447,36 @@
"Mute": "Þagga", "Mute": "Þagga",
"MusicArtist": "Tónlistarmaður", "MusicArtist": "Tónlistarmaður",
"MusicAlbum": "Tónlistaralbúm", "MusicAlbum": "Tónlistaralbúm",
"ButtonCast": "Senda efni út í tæki", "ButtonCast": "Senda út í tæki",
"AddToFavorites": "Bæta við eftirlæti" "AddToFavorites": "Bæta við eftirlæti",
"ButtonPlayer": "Spilari",
"AgeValue": "({0} ára)",
"ApiKeysCaption": "Listi yfir núverandi virkum API lyklum",
"ButtonActivate": "Virkja",
"ButtonClose": "Loka",
"ButtonSpace": "Bil",
"Authorize": "Heimila",
"ChangingMetadataImageSettingsNewContent": "Breytingar á niðurhali lýsigögnum eða myndum mun aðeins gilda um ný efni sem bætt hafa verið í safnið. Til að breyta núverandi titla þarftu að endurnýja lýsigögnin handvirkt.",
"ColorTransfer": "Litaflutningur",
"Data": "Gögn",
"ClearQueue": "Hreinsa biðröð",
"DailyAt": "Daglega um {0}",
"DashboardServerName": "Þjónn: {0}",
"DashboardVersionNumber": "Útgáfa: {0}",
"ColorSpace": "Litarými",
"Copied": "Aftritað",
"Copy": "Afrita",
"CopyFailed": "Gat ekki afritað",
"ButtonExitApp": "Fara úr forriti",
"EnableFasterAnimations": "Hraðari hreyfimyndir",
"EnablePlugin": "Virkja",
"Bwdif": "BWDIF",
"DisablePlugin": "Slökkva",
"EnableAutoCast": "Setja sem Sjálfgefið",
"EnableDetailsBanner": "Upplýsingar borði",
"DeleteAll": "Eyða Öllu",
"ButtonBackspace": "Backspace",
"ButtonUseQuickConnect": "Nota hraðtengingu",
"Digital": "Stafrænt",
"DownloadAll": "Sækja allt"
} }

View file

@ -109,7 +109,7 @@
"Connect": "Verbind", "Connect": "Verbind",
"ContinueWatching": "Verderkijken", "ContinueWatching": "Verderkijken",
"Continuing": "Wordt vervolgd", "Continuing": "Wordt vervolgd",
"CriticRating": "Critici-beoordeling", "CriticRating": "Beoordeling critici",
"CustomDlnaProfilesHelp": "Maak een aangepast profiel om een nieuw apparaat aan te maken of overschrijf een systeemprofiel.", "CustomDlnaProfilesHelp": "Maak een aangepast profiel om een nieuw apparaat aan te maken of overschrijf een systeemprofiel.",
"DateAdded": "Datum toegevoegd", "DateAdded": "Datum toegevoegd",
"DatePlayed": "Datum afgespeeld", "DatePlayed": "Datum afgespeeld",
@ -787,7 +787,7 @@
"NewEpisodesOnly": "Alleen nieuwe afleveringen", "NewEpisodesOnly": "Alleen nieuwe afleveringen",
"News": "Nieuws", "News": "Nieuws",
"Next": "Volgende", "Next": "Volgende",
"NextUp": "Hierna", "NextUp": "Volgende",
"No": "Nee", "No": "Nee",
"NoNewDevicesFound": "Er zijn geen nieuwe apparaten gevonden. Sluit deze melding en voer handmatig de apparaat gegevens in om een nieuwe tuner toe te voegen.", "NoNewDevicesFound": "Er zijn geen nieuwe apparaten gevonden. Sluit deze melding en voer handmatig de apparaat gegevens in om een nieuwe tuner toe te voegen.",
"MessageNoNextUpItems": "Niets gevonden. Start met kijken!", "MessageNoNextUpItems": "Niets gevonden. Start met kijken!",
@ -822,7 +822,7 @@
"OptionAutomaticallyGroupSeriesHelp": "Series die verspreid zijn over meerdere mappen binnen deze bibliotheek worden automatisch samengevoegd tot één serie.", "OptionAutomaticallyGroupSeriesHelp": "Series die verspreid zijn over meerdere mappen binnen deze bibliotheek worden automatisch samengevoegd tot één serie.",
"OptionBluray": "BD", "OptionBluray": "BD",
"OptionCommunityRating": "Beoordeling gemeenschap", "OptionCommunityRating": "Beoordeling gemeenschap",
"OptionCriticRating": "Beoordeling door critici", "OptionCriticRating": "Beoordeling critici",
"OptionCustomUsers": "Aangepast", "OptionCustomUsers": "Aangepast",
"OptionDaily": "Dagelijks", "OptionDaily": "Dagelijks",
"OptionDateAdded": "Datum toegevoegd", "OptionDateAdded": "Datum toegevoegd",
@ -1544,7 +1544,7 @@
"LabelSyncPlaySettingsSkipToSyncHelp": "Synchronisatie correctiemethode die bestaat uit het zoeken naar de geschatte positie. Synchronisatie Correctie moet ingeschakeld zijn.", "LabelSyncPlaySettingsSkipToSyncHelp": "Synchronisatie correctiemethode die bestaat uit het zoeken naar de geschatte positie. Synchronisatie Correctie moet ingeschakeld zijn.",
"MessageSent": "Bericht verzonden.", "MessageSent": "Bericht verzonden.",
"Mixer": "Mixer", "Mixer": "Mixer",
"UseEpisodeImagesInNextUpHelp": "'Hierna'- en 'Verderkijken'-secties zullen afleveringsafbeeldingen gebruiken als thumbnails in plaats van de primaire thumbnail van de serie.", "UseEpisodeImagesInNextUpHelp": "Secties 'Volgende' en 'Verderkijken' zullen afleveringsafbeeldingen gebruiken als miniaturen in plaats van de primaire miniatuur van de serie.",
"SetUsingLastTracks": "Ondertitel/Audio-sporen instellen met vorig item", "SetUsingLastTracks": "Ondertitel/Audio-sporen instellen met vorig item",
"SetUsingLastTracksHelp": "Probeer de ondertiteling/het audiospoor in te stellen op de video die het meest overeenkomt met de laatste video.", "SetUsingLastTracksHelp": "Probeer de ondertiteling/het audiospoor in te stellen op de video die het meest overeenkomt met de laatste video.",
"TextSent": "Tekst verzonden.", "TextSent": "Tekst verzonden.",
@ -1556,11 +1556,11 @@
"VideoProfileNotSupported": "Het profiel van de videocodec wordt niet ondersteund", "VideoProfileNotSupported": "Het profiel van de videocodec wordt niet ondersteund",
"Lyricist": "Tekstschrijver", "Lyricist": "Tekstschrijver",
"NextChapter": "Volgend hoofdstuk", "NextChapter": "Volgend hoofdstuk",
"LabelMaxDaysForNextUp": "Maximaal dagen in 'Hierna':", "LabelMaxDaysForNextUp": "Maximaal dagen in 'Volgende':",
"LabelMaxDaysForNextUpHelp": "Zet het maximaal aantal dagen dat een serie in de 'Hierna'-lijst staat zonder het te kijken.", "LabelMaxDaysForNextUpHelp": "Stel het maximaal aantal dagen in dat een serie in de 'Volgende'-lijst staat zonder het te kijken.",
"PreviousChapter": "Vorig hoofdstuk", "PreviousChapter": "Vorig hoofdstuk",
"Remixer": "Remixer", "Remixer": "Remixer",
"UseEpisodeImagesInNextUp": "Gebruik afleveringscovers in de secties 'Hierna' en 'Verderkijken'", "UseEpisodeImagesInNextUp": "Gebruik afleveringscovers in de secties 'Volgende' en 'Verderkijken'",
"EnableGamepadHelp": "Luister naar input van alle aangesloten controllers. (Vereist weergavemodus 'Tv')", "EnableGamepadHelp": "Luister naar input van alle aangesloten controllers. (Vereist weergavemodus 'Tv')",
"VideoCodecNotSupported": "De videocodec wordt niet ondersteund", "VideoCodecNotSupported": "De videocodec wordt niet ondersteund",
"AudioBitrateNotSupported": "De bitrate van de audio wordt niet ondersteund", "AudioBitrateNotSupported": "De bitrate van de audio wordt niet ondersteund",
@ -1625,11 +1625,11 @@
"ButtonBackspace": "Backspace", "ButtonBackspace": "Backspace",
"StoryArc": "Verhaallijn", "StoryArc": "Verhaallijn",
"ItemDetails": "Itemdetails", "ItemDetails": "Itemdetails",
"EnableRewatchingNextUp": "Opnieuw kijken inschakelen in Hierna", "EnableRewatchingNextUp": "Opnieuw kijken inschakelen in Volgende",
"Bold": "Vetgedrukt", "Bold": "Vetgedrukt",
"LabelTextWeight": "Tekstdikte:", "LabelTextWeight": "Tekstdikte:",
"HomeVideosPhotos": "Homevideo's en foto's", "HomeVideosPhotos": "Homevideo's en foto's",
"EnableRewatchingNextUpHelp": "Laat reeds gekeken afleveringen zien in 'Hierna'-secties.", "EnableRewatchingNextUpHelp": "Laat reeds gekeken afleveringen zien in sectie 'Volgende'.",
"ContainerBitrateExceedsLimit": "De bitrate van de video overschrijdt de limiet", "ContainerBitrateExceedsLimit": "De bitrate van de video overschrijdt de limiet",
"LabelMaxVideoResolution": "Maximaal toegestane resolutie voor transcoderingen", "LabelMaxVideoResolution": "Maximaal toegestane resolutie voor transcoderingen",
"UnknownAudioStreamInfo": "De audio stream info is onbekend", "UnknownAudioStreamInfo": "De audio stream info is onbekend",
@ -1674,7 +1674,7 @@
"DeletedScene": "Verwijderde scene", "DeletedScene": "Verwijderde scene",
"BehindTheScenes": "Achter de scenes", "BehindTheScenes": "Achter de scenes",
"Trailer": "Trailer", "Trailer": "Trailer",
"Clip": "Korte film", "Clip": "Clip",
"SelectAll": "Selecteer alles", "SelectAll": "Selecteer alles",
"DirectPlayError": "Er is een fout opgetreden tijdens het starten van direct afspelen", "DirectPlayError": "Er is een fout opgetreden tijdens het starten van direct afspelen",
"OptionDateShowAdded": "Datum Serie Toegevoegd", "OptionDateShowAdded": "Datum Serie Toegevoegd",
@ -1713,5 +1713,7 @@
"SubtitleMagenta": "Magenta", "SubtitleMagenta": "Magenta",
"SubtitleRed": "Rood", "SubtitleRed": "Rood",
"SubtitleWhite": "Wit", "SubtitleWhite": "Wit",
"SubtitleYellow": "Geel" "SubtitleYellow": "Geel",
"Featurette": "Featurette",
"Short": "Korte film"
} }

View file

@ -1439,7 +1439,7 @@
"SubtitleDownloadersHelp": "Увімкніть та оцініть бажані завантажувачі субтитрів у порядку пріоритету.", "SubtitleDownloadersHelp": "Увімкніть та оцініть бажані завантажувачі субтитрів у порядку пріоритету.",
"SubtitleAppearanceSettingsDisclaimer": "Наведені нижче налаштування не застосовуються до графічних субтитрів, згаданих вище, або субтитрів ASS/SSA, які вбудовують власні стилі.", "SubtitleAppearanceSettingsDisclaimer": "Наведені нижче налаштування не застосовуються до графічних субтитрів, згаданих вище, або субтитрів ASS/SSA, які вбудовують власні стилі.",
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Ці налаштування також застосовуються до будь-якого відтворення Google Cast, запущеного цим пристроєм.", "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Ці налаштування також застосовуються до будь-якого відтворення Google Cast, запущеного цим пристроєм.",
"Subtitle": "Підзаголовок", "Subtitle": "Субтитри",
"Studios": "Студії", "Studios": "Студії",
"StopRecording": "Зупинити запис", "StopRecording": "Зупинити запис",
"StopPlayback": "Зупинити відтворення", "StopPlayback": "Зупинити відтворення",

View file

@ -1621,7 +1621,7 @@
"DeletedScene": "删减场景", "DeletedScene": "删减场景",
"BehindTheScenes": "幕后花絮", "BehindTheScenes": "幕后花絮",
"Trailer": "预告片", "Trailer": "预告片",
"Clip": "花絮", "Clip": "片段",
"ButtonExitApp": "退出应用", "ButtonExitApp": "退出应用",
"ShowParentImages": "显示系列图片", "ShowParentImages": "显示系列图片",
"AllowEmbeddedSubtitlesAllowTextOption": "允许文本", "AllowEmbeddedSubtitlesAllowTextOption": "允许文本",
@ -1714,5 +1714,7 @@
"SubtitleGreen": "绿色", "SubtitleGreen": "绿色",
"SubtitleMagenta": "品红色", "SubtitleMagenta": "品红色",
"SubtitleRed": "红色", "SubtitleRed": "红色",
"SubtitleYellow": "黄色" "SubtitleYellow": "黄色",
"Featurette": "花絮",
"Short": "短片"
} }

View file

@ -650,7 +650,7 @@
"LabelEmbedAlbumArtDidl": "於 DIDL 中嵌入專輯封面", "LabelEmbedAlbumArtDidl": "於 DIDL 中嵌入專輯封面",
"LabelEasyPinCode": "簡易PIN代碼", "LabelEasyPinCode": "簡易PIN代碼",
"LabelDynamicExternalId": "{0} Id:", "LabelDynamicExternalId": "{0} Id:",
"LabelDropSubtitleHere": "將字幕檔到這裡,或點擊瀏覽。", "LabelDropSubtitleHere": "將字幕檔拖動到這裡,或點擊瀏覽。",
"LabelDropShadow": "陰影:", "LabelDropShadow": "陰影:",
"LabelDroppedFrames": "丟棄的幀:", "LabelDroppedFrames": "丟棄的幀:",
"LabelDropImageHere": "拖移圖片到這裡,或是點擊來選取。", "LabelDropImageHere": "拖移圖片到這裡,或是點擊來選取。",
@ -1096,5 +1096,11 @@
"LabelSyncPlaySettingsSyncCorrection": "同步校正", "LabelSyncPlaySettingsSyncCorrection": "同步校正",
"HomeVideosPhotos": "家庭影片及相片", "HomeVideosPhotos": "家庭影片及相片",
"MessageConfirmRevokeApiKey": "你是否確定要廢除此 API Key有關程序將無法再連接此伺服器。", "MessageConfirmRevokeApiKey": "你是否確定要廢除此 API Key有關程序將無法再連接此伺服器。",
"LabelSyncPlaySettingsExtraTimeOffsetHelp": "為目標裝置調較 SyncPlay 延遲時間(毫秒)以改善不同步問題,請小心調較。" "LabelSyncPlaySettingsExtraTimeOffsetHelp": "為目標裝置調較 SyncPlay 延遲時間(毫秒)以改善不同步問題,請小心調較。",
"Experimental": "試驗性",
"HeaderDummyChapter": "章節圖片",
"IgnoreDtsHelp": "禁用此選項或可解決部份問題,如無法正常播放外部音訊。",
"DownloadAll": "全部下載",
"LabelDummyChapterDuration": "間距:",
"LabelDummyChapterDurationHelp": "章節圖片擷取間距(秒)"
} }

13
src/types/plugin.ts Normal file
View file

@ -0,0 +1,13 @@
export enum PluginType {
MediaPlayer = 'mediaplayer',
PreplayIntercept = 'preplayintercept',
Screensaver = 'screensaver',
SyncPlay = 'syncplay'
}
export interface Plugin {
name: string
id: string
type: PluginType | string
priority: number
}

View file

@ -2,6 +2,16 @@ function toLocaleStringSupportsOptions() {
return !!(typeof Intl === 'object' && Intl && typeof Intl.NumberFormat === 'function'); return !!(typeof Intl === 'object' && Intl && typeof Intl.NumberFormat === 'function');
} }
/**
* Generates a random integer in a given range.
* @param {number} min - Minimum of the range.
* @param {number} max - Maximum of the range.
* @returns {number} Randomly generated number.
*/
export function randomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/** /**
* Gets the value of a number formatted as a perentage. * Gets the value of a number formatted as a perentage.
* @param {number} value The value as a number. * @param {number} value The value as a number.

Some files were not shown because too many files have changed in this diff Show more