mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into enable-airplay-audioplayer
This commit is contained in:
commit
12cf325e79
116 changed files with 5344 additions and 1453 deletions
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
|
@ -21,11 +21,11 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/init@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/autobuild@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/analyze@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
|
||||||
|
|
|
@ -59,9 +59,12 @@
|
||||||
- [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)
|
||||||
|
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
|
||||||
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
|
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
|
||||||
- [Anantharaju S](https://github.com/Anantharajus)
|
- [Anantharaju S](https://github.com/Anantharajus)
|
||||||
- [Merlin Sievers](https://github.com/dann-merlin)
|
- [Merlin Sievers](https://github.com/dann-merlin)
|
||||||
|
- [Fishbigger](https://github.com/fishbigger)
|
||||||
|
- [sleepycatcoding](https://github.com/sleepycatcoding)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
2300
package-lock.json
generated
2300
package-lock.json
generated
File diff suppressed because it is too large
Load diff
50
package.json
50
package.json
|
@ -14,10 +14,10 @@
|
||||||
"@types/escape-html": "1.0.2",
|
"@types/escape-html": "1.0.2",
|
||||||
"@types/loadable__component": "5.13.4",
|
"@types/loadable__component": "5.13.4",
|
||||||
"@types/lodash-es": "4.17.7",
|
"@types/lodash-es": "4.17.7",
|
||||||
"@types/react": "17.0.58",
|
"@types/react": "17.0.59",
|
||||||
"@types/react-dom": "17.0.20",
|
"@types/react-dom": "17.0.20",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.2",
|
"@typescript-eslint/eslint-plugin": "5.59.7",
|
||||||
"@typescript-eslint/parser": "5.59.2",
|
"@typescript-eslint/parser": "5.59.7",
|
||||||
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
"@uupaa/dynamic-import-polyfill": "1.0.2",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.2",
|
||||||
|
@ -26,10 +26,10 @@
|
||||||
"confusing-browser-globals": "1.0.11",
|
"confusing-browser-globals": "1.0.11",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "6.7.3",
|
"css-loader": "6.7.4",
|
||||||
"cssnano": "6.0.1",
|
"cssnano": "6.0.1",
|
||||||
"es-check": "7.1.1",
|
"es-check": "7.1.1",
|
||||||
"eslint": "8.39.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-plugin-compat": "4.1.4",
|
"eslint-plugin-compat": "4.1.4",
|
||||||
"eslint-plugin-eslint-comments": "3.2.0",
|
"eslint-plugin-eslint-comments": "3.2.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
|
@ -41,30 +41,32 @@
|
||||||
"expose-loader": "4.1.0",
|
"expose-loader": "4.1.0",
|
||||||
"html-loader": "4.2.0",
|
"html-loader": "4.2.0",
|
||||||
"html-webpack-plugin": "5.5.1",
|
"html-webpack-plugin": "5.5.1",
|
||||||
"mini-css-extract-plugin": "2.7.5",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.24",
|
||||||
"postcss-loader": "7.3.0",
|
"postcss-loader": "7.3.1",
|
||||||
"postcss-preset-env": "8.3.2",
|
"postcss-preset-env": "8.4.1",
|
||||||
"postcss-scss": "4.0.6",
|
"postcss-scss": "4.0.6",
|
||||||
"sass": "1.62.1",
|
"sass": "1.62.1",
|
||||||
"sass-loader": "13.2.2",
|
"sass-loader": "13.3.0",
|
||||||
"source-map-loader": "4.0.1",
|
"source-map-loader": "4.0.1",
|
||||||
"style-loader": "3.3.2",
|
"style-loader": "3.3.3",
|
||||||
"stylelint": "15.6.1",
|
"stylelint": "15.6.2",
|
||||||
"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.3",
|
"stylelint-order": "6.0.3",
|
||||||
"stylelint-scss": "5.0.0",
|
"stylelint-scss": "5.0.0",
|
||||||
"ts-loader": "9.4.2",
|
"ts-loader": "9.4.3",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
"webpack": "5.82.0",
|
"webpack": "5.84.1",
|
||||||
"webpack-cli": "5.0.2",
|
"webpack-cli": "5.1.1",
|
||||||
"webpack-dev-server": "4.13.3",
|
"webpack-dev-server": "4.15.0",
|
||||||
"webpack-merge": "5.8.0",
|
"webpack-merge": "5.9.0",
|
||||||
"workbox-webpack-plugin": "6.5.4",
|
"workbox-webpack-plugin": "6.5.4",
|
||||||
"worker-loader": "3.0.8"
|
"worker-loader": "3.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "11.11.0",
|
||||||
|
"@emotion/styled": "11.11.0",
|
||||||
"@fontsource/noto-sans": "4.5.11",
|
"@fontsource/noto-sans": "4.5.11",
|
||||||
"@fontsource/noto-sans-hk": "4.5.12",
|
"@fontsource/noto-sans-hk": "4.5.12",
|
||||||
"@fontsource/noto-sans-jp": "4.5.12",
|
"@fontsource/noto-sans-jp": "4.5.12",
|
||||||
|
@ -73,10 +75,12 @@
|
||||||
"@fontsource/noto-sans-tc": "4.5.12",
|
"@fontsource/noto-sans-tc": "4.5.12",
|
||||||
"@jellyfin/sdk": "unstable",
|
"@jellyfin/sdk": "unstable",
|
||||||
"@loadable/component": "5.15.3",
|
"@loadable/component": "5.15.3",
|
||||||
|
"@mui/icons-material": "5.11.16",
|
||||||
|
"@mui/material": "5.13.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"core-js": "3.30.1",
|
"core-js": "3.30.2",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"dompurify": "3.0.1",
|
"dompurify": "3.0.1",
|
||||||
"epubjs": "0.3.93",
|
"epubjs": "0.3.93",
|
||||||
|
@ -86,25 +90,25 @@
|
||||||
"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": "1.4.0",
|
"hls.js": "1.4.4",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"jassub": "1.5.13",
|
"jassub": "1.7.1",
|
||||||
"jellyfin-apiclient": "1.10.0",
|
"jellyfin-apiclient": "1.10.0",
|
||||||
"jquery": "3.6.4",
|
"jquery": "3.7.0",
|
||||||
"jstree": "3.3.15",
|
"jstree": "3.3.15",
|
||||||
"libarchive.js": "1.3.0",
|
"libarchive.js": "1.3.0",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"marked": "4.3.0",
|
"marked": "4.3.0",
|
||||||
"material-design-icons-iconfont": "6.7.0",
|
"material-design-icons-iconfont": "6.7.0",
|
||||||
"native-promise-only": "0.8.1",
|
"native-promise-only": "0.8.1",
|
||||||
"pdfjs-dist": "3.5.141",
|
"pdfjs-dist": "3.6.172",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-router-dom": "6.11.1",
|
"react-router-dom": "6.11.1",
|
||||||
"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",
|
||||||
"swiper": "9.2.4",
|
"swiper": "9.3.2",
|
||||||
"webcomponents.js": "0.7.24",
|
"webcomponents.js": "0.7.24",
|
||||||
"whatwg-fetch": "3.6.2",
|
"whatwg-fetch": "3.6.2",
|
||||||
"workbox-core": "6.5.4",
|
"workbox-core": "6.5.4",
|
||||||
|
|
|
@ -1,19 +1,44 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import AppHeader from '../../components/AppHeader';
|
import ConnectionRequired from 'components/ConnectionRequired';
|
||||||
import Backdrop from '../../components/Backdrop';
|
import ServerContentPage from 'components/ServerContentPage';
|
||||||
import { ExperimentalAppRoutes } from './routes/AppRoutes';
|
import { toAsyncPageRoute } from 'components/router/AsyncRoute';
|
||||||
|
import { toViewManagerPageRoute } from 'components/router/LegacyRoute';
|
||||||
|
|
||||||
const ExperimentalApp = () => (
|
import AppLayout from './AppLayout';
|
||||||
<>
|
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes';
|
||||||
<Backdrop />
|
import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes';
|
||||||
<AppHeader />
|
|
||||||
|
|
||||||
<div className='mainAnimatedPages skinBody' />
|
const ExperimentalApp = () => {
|
||||||
<div className='skinBody'>
|
return (
|
||||||
<ExperimentalAppRoutes />
|
<Routes>
|
||||||
</div>
|
<Route path='/*' element={<AppLayout />}>
|
||||||
</>
|
{/* User routes */}
|
||||||
);
|
<Route element={<ConnectionRequired />}>
|
||||||
|
{ASYNC_USER_ROUTES.map(toAsyncPageRoute)}
|
||||||
|
{LEGACY_USER_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Admin routes */}
|
||||||
|
<Route element={<ConnectionRequired isAdminRequired />}>
|
||||||
|
{ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)}
|
||||||
|
{LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
|
||||||
|
<Route path='configurationpage' element={
|
||||||
|
<ServerContentPage view='/web/configurationpage' />
|
||||||
|
} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Public routes */}
|
||||||
|
<Route element={<ConnectionRequired isUserRequired={false} />}>
|
||||||
|
<Route index element={<Navigate replace to='/home.html' />} />
|
||||||
|
|
||||||
|
{LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ExperimentalApp;
|
export default ExperimentalApp;
|
||||||
|
|
114
src/apps/experimental/AppLayout.tsx
Normal file
114
src/apps/experimental/AppLayout.tsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import AppHeader from 'components/AppHeader';
|
||||||
|
import Backdrop from 'components/Backdrop';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { useLocalStorage } from 'hooks/useLocalStorage';
|
||||||
|
|
||||||
|
import AppToolbar from './components/AppToolbar';
|
||||||
|
import AppDrawer, { DRAWER_WIDTH, isDrawerPath } from './components/drawers/AppDrawer';
|
||||||
|
import ElevationScroll from './components/ElevationScroll';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
import './AppOverrides.scss';
|
||||||
|
|
||||||
|
interface ExperimentalAppSettings {
|
||||||
|
isDrawerPinned: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_EXPERIMENTAL_APP_SETTINGS: ExperimentalAppSettings = {
|
||||||
|
isDrawerPinned: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppLayout = () => {
|
||||||
|
const [ appSettings, setAppSettings ] = useLocalStorage<ExperimentalAppSettings>('ExperimentalAppSettings', DEFAULT_EXPERIMENTAL_APP_SETTINGS);
|
||||||
|
const [ isDrawerActive, setIsDrawerActive ] = useState(appSettings.isDrawerPinned);
|
||||||
|
const { user } = useApi();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isDrawerAvailable = isDrawerPath(location.pathname);
|
||||||
|
const isDrawerOpen = isDrawerActive && isDrawerAvailable && Boolean(user);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDrawerActive !== appSettings.isDrawerPinned) {
|
||||||
|
setAppSettings({
|
||||||
|
...appSettings,
|
||||||
|
isDrawerPinned: isDrawerActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [ appSettings, isDrawerActive, setAppSettings ]);
|
||||||
|
|
||||||
|
const onToggleDrawer = useCallback(() => {
|
||||||
|
setIsDrawerActive(!isDrawerActive);
|
||||||
|
}, [ isDrawerActive, setIsDrawerActive ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<Backdrop />
|
||||||
|
|
||||||
|
<div style={{ display: 'none' }}>
|
||||||
|
{/*
|
||||||
|
* TODO: These components are not used, but views interact with them directly so the need to be
|
||||||
|
* present in the dom. We add them in a hidden element to prevent errors.
|
||||||
|
*/}
|
||||||
|
<AppHeader />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex' }}>
|
||||||
|
<ElevationScroll elevate={isDrawerOpen}>
|
||||||
|
<AppBar
|
||||||
|
position='fixed'
|
||||||
|
sx={{ zIndex: (muiTheme) => muiTheme.zIndex.drawer + 1 }}
|
||||||
|
>
|
||||||
|
<AppToolbar
|
||||||
|
isDrawerOpen={isDrawerOpen}
|
||||||
|
onDrawerButtonClick={onToggleDrawer}
|
||||||
|
/>
|
||||||
|
</AppBar>
|
||||||
|
</ElevationScroll>
|
||||||
|
|
||||||
|
<AppDrawer
|
||||||
|
open={isDrawerOpen}
|
||||||
|
onClose={onToggleDrawer}
|
||||||
|
onOpen={onToggleDrawer}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
component='main'
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
flexGrow: 1,
|
||||||
|
transition: theme.transitions.create('margin', {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
marginLeft: 0,
|
||||||
|
...(isDrawerAvailable && {
|
||||||
|
marginLeft: {
|
||||||
|
sm: `-${DRAWER_WIDTH}px`
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...(isDrawerActive && {
|
||||||
|
transition: theme.transitions.create('margin', {
|
||||||
|
easing: theme.transitions.easing.easeOut,
|
||||||
|
duration: theme.transitions.duration.enteringScreen
|
||||||
|
}),
|
||||||
|
marginLeft: 0
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='mainAnimatedPages skinBody' />
|
||||||
|
<div className='skinBody'>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
40
src/apps/experimental/AppOverrides.scss
Normal file
40
src/apps/experimental/AppOverrides.scss
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Default MUI breakpoints
|
||||||
|
// https://mui.com/material-ui/customization/breakpoints/#default-breakpoints
|
||||||
|
$mui-bp-sm: 600px;
|
||||||
|
$mui-bp-md: 900px;
|
||||||
|
$mui-bp-lg: 1200px;
|
||||||
|
$mui-bp-xl: 1536px;
|
||||||
|
|
||||||
|
// Fix main pages layout to work with drawer
|
||||||
|
.mainAnimatedPage {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix dashboard pages layout to work with drawer
|
||||||
|
.dashboardDocument .skinBody {
|
||||||
|
position: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide some items from the user "settings" page that are in the drawer
|
||||||
|
#myPreferencesMenuPage {
|
||||||
|
.lnkQuickConnectPreferences,
|
||||||
|
.adminSection,
|
||||||
|
.userSection {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the padding of some pages
|
||||||
|
.homePage.libraryPage, // Home page
|
||||||
|
.libraryPage:not(.withTabs), // Tabless library pages
|
||||||
|
.content-primary.content-primary { // Dashboard pages
|
||||||
|
padding-top: 3.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.libraryPage.withTabs {
|
||||||
|
padding-top: 6.5rem !important;
|
||||||
|
|
||||||
|
@media all and (min-width: $mui-bp-lg) {
|
||||||
|
padding-top: 3.25rem !important;
|
||||||
|
}
|
||||||
|
}
|
112
src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx
Normal file
112
src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import CastConnected from '@mui/icons-material/CastConnected';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Cast from '@mui/icons-material/Cast';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
|
||||||
|
import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import Events from 'utils/events';
|
||||||
|
|
||||||
|
import RemotePlayMenu, { ID } from './menus/RemotePlayMenu';
|
||||||
|
import RemotePlayActiveMenu, { ID as ACTIVE_ID } from './menus/RemotePlayActiveMenu';
|
||||||
|
|
||||||
|
const RemotePlayButton = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [ playerInfo, setPlayerInfo ] = useState(playbackManager.getPlayerInfo());
|
||||||
|
|
||||||
|
const updatePlayerInfo = useCallback(() => {
|
||||||
|
setPlayerInfo(playbackManager.getPlayerInfo());
|
||||||
|
}, [ setPlayerInfo ]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Events.on(playbackManager, 'playerchange', updatePlayerInfo);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Events.off(playbackManager, 'playerchange', updatePlayerInfo);
|
||||||
|
};
|
||||||
|
}, [ updatePlayerInfo ]);
|
||||||
|
|
||||||
|
const [ remotePlayMenuAnchorEl, setRemotePlayMenuAnchorEl ] = useState<null | HTMLElement>(null);
|
||||||
|
const isRemotePlayMenuOpen = Boolean(remotePlayMenuAnchorEl);
|
||||||
|
|
||||||
|
const onRemotePlayButtonClick = useCallback((event) => {
|
||||||
|
setRemotePlayMenuAnchorEl(event.currentTarget);
|
||||||
|
}, [ setRemotePlayMenuAnchorEl ]);
|
||||||
|
|
||||||
|
const onRemotePlayMenuClose = useCallback(() => {
|
||||||
|
setRemotePlayMenuAnchorEl(null);
|
||||||
|
}, [ setRemotePlayMenuAnchorEl ]);
|
||||||
|
|
||||||
|
const [ remotePlayActiveMenuAnchorEl, setRemotePlayActiveMenuAnchorEl ] = useState<null | HTMLElement>(null);
|
||||||
|
const isRemotePlayActiveMenuOpen = Boolean(remotePlayActiveMenuAnchorEl);
|
||||||
|
|
||||||
|
const onRemotePlayActiveButtonClick = useCallback((event) => {
|
||||||
|
setRemotePlayActiveMenuAnchorEl(event.currentTarget);
|
||||||
|
}, [ setRemotePlayActiveMenuAnchorEl ]);
|
||||||
|
|
||||||
|
const onRemotePlayActiveMenuClose = useCallback(() => {
|
||||||
|
setRemotePlayActiveMenuAnchorEl(null);
|
||||||
|
}, [ setRemotePlayActiveMenuAnchorEl ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(playerInfo && !playerInfo.isLocalPlayer) ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignSelf: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title={globalize.translate('ButtonCast')}>
|
||||||
|
<Button
|
||||||
|
variant='text'
|
||||||
|
size='large'
|
||||||
|
startIcon={<CastConnected />}
|
||||||
|
aria-label={globalize.translate('ButtonCast')}
|
||||||
|
aria-controls={ACTIVE_ID}
|
||||||
|
aria-haspopup='true'
|
||||||
|
onClick={onRemotePlayActiveButtonClick}
|
||||||
|
color='inherit'
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.primary.main
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{playerInfo.deviceName || playerInfo.name}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={globalize.translate('ButtonCast')}>
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
aria-label={globalize.translate('ButtonCast')}
|
||||||
|
aria-controls={ID}
|
||||||
|
aria-haspopup='true'
|
||||||
|
onClick={onRemotePlayButtonClick}
|
||||||
|
color='inherit'
|
||||||
|
>
|
||||||
|
<Cast />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<RemotePlayMenu
|
||||||
|
open={isRemotePlayMenuOpen}
|
||||||
|
anchorEl={remotePlayMenuAnchorEl}
|
||||||
|
onMenuClose={onRemotePlayMenuClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RemotePlayActiveMenu
|
||||||
|
open={isRemotePlayActiveMenuOpen}
|
||||||
|
anchorEl={remotePlayActiveMenuAnchorEl}
|
||||||
|
onMenuClose={onRemotePlayActiveMenuClose}
|
||||||
|
playerInfo={playerInfo}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RemotePlayButton;
|
|
@ -0,0 +1,64 @@
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import AppUserMenu, { ID } from './menus/AppUserMenu';
|
||||||
|
|
||||||
|
const UserMenuButton = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { api, user } = useApi();
|
||||||
|
|
||||||
|
const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState<null | HTMLElement>(null);
|
||||||
|
const isUserMenuOpen = Boolean(userMenuAnchorEl);
|
||||||
|
|
||||||
|
const onUserButtonClick = useCallback((event) => {
|
||||||
|
setUserMenuAnchorEl(event.currentTarget);
|
||||||
|
}, [ setUserMenuAnchorEl ]);
|
||||||
|
|
||||||
|
const onUserMenuClose = useCallback(() => {
|
||||||
|
setUserMenuAnchorEl(null);
|
||||||
|
}, [ setUserMenuAnchorEl ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title={globalize.translate('UserMenu')}>
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
edge='end'
|
||||||
|
aria-label={globalize.translate('UserMenu')}
|
||||||
|
aria-controls={ID}
|
||||||
|
aria-haspopup='true'
|
||||||
|
onClick={onUserButtonClick}
|
||||||
|
color='inherit'
|
||||||
|
sx={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
alt={user?.Name || undefined}
|
||||||
|
src={
|
||||||
|
api && user?.Id ?
|
||||||
|
`${api.basePath}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}` :
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
bgcolor: theme.palette.primary.dark,
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<AppUserMenu
|
||||||
|
open={isUserMenuOpen}
|
||||||
|
anchorEl={userMenuAnchorEl}
|
||||||
|
onMenuClose={onUserMenuClose}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserMenuButton;
|
117
src/apps/experimental/components/AppToolbar/index.tsx
Normal file
117
src/apps/experimental/components/AppToolbar/index.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import appIcon from 'assets/img/icon-transparent.png';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import AppTabs from '../tabs/AppTabs';
|
||||||
|
import { isDrawerPath } from '../drawers/AppDrawer';
|
||||||
|
import UserMenuButton from './UserMenuButton';
|
||||||
|
import RemotePlayButton from './RemotePlayButton';
|
||||||
|
|
||||||
|
interface AppToolbarProps {
|
||||||
|
isDrawerOpen: boolean
|
||||||
|
onDrawerButtonClick: (event: React.MouseEvent<HTMLElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppToolbar: FC<AppToolbarProps> = ({
|
||||||
|
isDrawerOpen,
|
||||||
|
onDrawerButtonClick
|
||||||
|
}) => {
|
||||||
|
const { user } = useApi();
|
||||||
|
const isUserLoggedIn = Boolean(user);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isDrawerAvailable = isDrawerPath(location.pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toolbar
|
||||||
|
variant='dense'
|
||||||
|
sx={{
|
||||||
|
flexWrap: {
|
||||||
|
xs: 'wrap',
|
||||||
|
lg: 'nowrap'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isUserLoggedIn && isDrawerAvailable && (
|
||||||
|
<Tooltip title={globalize.translate(isDrawerOpen ? 'MenuClose' : 'MenuOpen')}>
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
edge='start'
|
||||||
|
color='inherit'
|
||||||
|
aria-label={globalize.translate(isDrawerOpen ? 'MenuClose' : 'MenuOpen')}
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
onClick={onDrawerButtonClick}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
component={Link}
|
||||||
|
to='/'
|
||||||
|
color='inherit'
|
||||||
|
aria-label={globalize.translate('Home')}
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
textDecoration: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component='img'
|
||||||
|
src={appIcon}
|
||||||
|
sx={{
|
||||||
|
height: '2rem',
|
||||||
|
marginInlineEnd: 1
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant='h6'
|
||||||
|
noWrap
|
||||||
|
component='div'
|
||||||
|
sx={{ display: { xs: 'none', sm: 'inline-block' } }}
|
||||||
|
>
|
||||||
|
Jellyfin
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<AppTabs isDrawerOpen={isDrawerOpen} />
|
||||||
|
|
||||||
|
{isUserLoggedIn && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ display: 'flex', flexGrow: 1, justifyContent: 'flex-end' }}>
|
||||||
|
<RemotePlayButton />
|
||||||
|
|
||||||
|
<Tooltip title={globalize.translate('Search')}>
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
aria-label={globalize.translate('Search')}
|
||||||
|
color='inherit'
|
||||||
|
component={Link}
|
||||||
|
to='/search.html'
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 0 }}>
|
||||||
|
<UserMenuButton />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppToolbar;
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { AppSettingsAlt, Close } from '@mui/icons-material';
|
||||||
|
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||||
|
import Logout from '@mui/icons-material/Logout';
|
||||||
|
import PhonelinkLock from '@mui/icons-material/PhonelinkLock';
|
||||||
|
import Settings from '@mui/icons-material/Settings';
|
||||||
|
import Storage from '@mui/icons-material/Storage';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import React, { FC, useCallback } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { appHost } from 'components/apphost';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import Dashboard from 'utils/dashboard';
|
||||||
|
|
||||||
|
export const ID = 'app-user-menu';
|
||||||
|
|
||||||
|
interface AppUserMenuProps extends MenuProps {
|
||||||
|
onMenuClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppUserMenu: FC<AppUserMenuProps> = ({
|
||||||
|
anchorEl,
|
||||||
|
open,
|
||||||
|
onMenuClose
|
||||||
|
}) => {
|
||||||
|
const { user } = useApi();
|
||||||
|
|
||||||
|
const onClientSettingsClick = useCallback(() => {
|
||||||
|
window.NativeShell?.openClientSettings();
|
||||||
|
onMenuClose();
|
||||||
|
}, [ onMenuClose ]);
|
||||||
|
|
||||||
|
const onExitAppClick = useCallback(() => {
|
||||||
|
appHost.exit();
|
||||||
|
onMenuClose();
|
||||||
|
}, [ onMenuClose ]);
|
||||||
|
|
||||||
|
const onLogoutClick = useCallback(() => {
|
||||||
|
Dashboard.logout();
|
||||||
|
onMenuClose();
|
||||||
|
}, [ onMenuClose ]);
|
||||||
|
|
||||||
|
const onSelectServerClick = useCallback(() => {
|
||||||
|
Dashboard.selectServer();
|
||||||
|
onMenuClose();
|
||||||
|
}, [ onMenuClose ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
id={ID}
|
||||||
|
keepMounted
|
||||||
|
open={open}
|
||||||
|
onClose={onMenuClose}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
component={Link}
|
||||||
|
to={`/userprofile.html?userId=${user?.Id}`}
|
||||||
|
onClick={onMenuClose}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AccountCircle />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('Profile')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
component={Link}
|
||||||
|
to='/mypreferencesmenu.html'
|
||||||
|
onClick={onMenuClose}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Settings />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('Settings')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{appHost.supports('clientsettings') && ([
|
||||||
|
<Divider key='client-settings-divider' />,
|
||||||
|
<MenuItem
|
||||||
|
key='client-settings-button'
|
||||||
|
onClick={onClientSettingsClick}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AppSettingsAlt />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('ClientSettings')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
])}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<MenuItem
|
||||||
|
component={Link}
|
||||||
|
to='/mypreferencesquickconnect.html'
|
||||||
|
onClick={onMenuClose}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PhonelinkLock />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('QuickConnect')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{appHost.supports('multiserver') && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={onSelectServerClick}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Storage />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('SelectServer')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
onClick={onLogoutClick}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Logout />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('ButtonSignOut')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{appHost.supports('exitmenu') && ([
|
||||||
|
<Divider key='exit-menu-divider' />,
|
||||||
|
<MenuItem
|
||||||
|
key='exit-menu-button'
|
||||||
|
onClick={onExitAppClick}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Close />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('ButtonExitApp')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
])}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppUserMenu;
|
|
@ -0,0 +1,160 @@
|
||||||
|
import Check from '@mui/icons-material/Check';
|
||||||
|
import Close from '@mui/icons-material/Close';
|
||||||
|
import SettingsRemote from '@mui/icons-material/SettingsRemote';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import dialog from 'components/dialog/dialog';
|
||||||
|
import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { enable, isEnabled, supported } from 'scripts/autocast';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface RemotePlayActiveMenuProps extends MenuProps {
|
||||||
|
onMenuClose: () => void
|
||||||
|
playerInfo: {
|
||||||
|
name: string
|
||||||
|
isLocalPlayer: boolean
|
||||||
|
id?: string
|
||||||
|
deviceName?: string
|
||||||
|
playableMediaTypes?: string[]
|
||||||
|
supportedCommands?: string[]
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ID = 'app-remote-play-active-menu';
|
||||||
|
|
||||||
|
const RemotePlayActiveMenu: FC<RemotePlayActiveMenuProps> = ({
|
||||||
|
anchorEl,
|
||||||
|
open,
|
||||||
|
onMenuClose,
|
||||||
|
playerInfo
|
||||||
|
}) => {
|
||||||
|
const [ isDisplayMirrorEnabled, setIsDisplayMirrorEnabled ] = useState(playbackManager.enableDisplayMirroring());
|
||||||
|
const isDisplayMirrorSupported = playerInfo?.supportedCommands && playerInfo.supportedCommands.indexOf('DisplayContent') !== -1;
|
||||||
|
const toggleDisplayMirror = useCallback(() => {
|
||||||
|
playbackManager.enableDisplayMirroring(!isDisplayMirrorEnabled);
|
||||||
|
setIsDisplayMirrorEnabled(!isDisplayMirrorEnabled);
|
||||||
|
}, [ isDisplayMirrorEnabled, setIsDisplayMirrorEnabled ]);
|
||||||
|
|
||||||
|
const [ isAutoCastEnabled, setIsAutoCastEnabled ] = useState(isEnabled());
|
||||||
|
const isAutoCastSupported = supported();
|
||||||
|
const toggleAutoCast = useCallback(() => {
|
||||||
|
enable(!isAutoCastEnabled);
|
||||||
|
setIsAutoCastEnabled(!isAutoCastEnabled);
|
||||||
|
}, [ isAutoCastEnabled, setIsAutoCastEnabled ]);
|
||||||
|
|
||||||
|
const remotePlayerName = playerInfo?.deviceName || playerInfo?.name;
|
||||||
|
|
||||||
|
const disconnectRemotePlayer = useCallback(() => {
|
||||||
|
if (playbackManager.getSupportedCommands().indexOf('EndSession') !== -1) {
|
||||||
|
dialog.show({
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
name: globalize.translate('Yes'),
|
||||||
|
id: 'yes'
|
||||||
|
}, {
|
||||||
|
name: globalize.translate('No'),
|
||||||
|
id: 'no'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: globalize.translate('ConfirmEndPlayerSession', remotePlayerName)
|
||||||
|
}).then(id => {
|
||||||
|
onMenuClose();
|
||||||
|
|
||||||
|
if (id === 'yes') {
|
||||||
|
playbackManager.getCurrentPlayer().endSession();
|
||||||
|
}
|
||||||
|
playbackManager.setDefaultPlayerActive();
|
||||||
|
}).catch(() => {
|
||||||
|
// Dialog closed
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onMenuClose();
|
||||||
|
playbackManager.setDefaultPlayerActive();
|
||||||
|
}
|
||||||
|
}, [ onMenuClose, remotePlayerName ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
id={ID}
|
||||||
|
keepMounted
|
||||||
|
open={open}
|
||||||
|
onClose={onMenuClose}
|
||||||
|
MenuListProps={{
|
||||||
|
'aria-labelledby': 'remote-play-active-subheader',
|
||||||
|
subheader: (
|
||||||
|
<ListSubheader component='div' id='remote-play-active-subheader'>
|
||||||
|
{remotePlayerName}
|
||||||
|
</ListSubheader>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isDisplayMirrorSupported && (
|
||||||
|
<MenuItem onClick={toggleDisplayMirror}>
|
||||||
|
{isDisplayMirrorEnabled && (
|
||||||
|
<ListItemIcon>
|
||||||
|
<Check />
|
||||||
|
</ListItemIcon>
|
||||||
|
)}
|
||||||
|
<ListItemText inset={!isDisplayMirrorEnabled}>
|
||||||
|
{globalize.translate('EnableDisplayMirroring')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAutoCastSupported && (
|
||||||
|
<MenuItem onClick={toggleAutoCast}>
|
||||||
|
{isAutoCastEnabled && (
|
||||||
|
<ListItemIcon>
|
||||||
|
<Check />
|
||||||
|
</ListItemIcon>
|
||||||
|
)}
|
||||||
|
<ListItemText inset={!isAutoCastEnabled}>
|
||||||
|
{globalize.translate('EnableAutoCast')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(isDisplayMirrorSupported || isAutoCastSupported) && <Divider />}
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
component={Link}
|
||||||
|
to='/queue'
|
||||||
|
onClick={onMenuClose}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SettingsRemote />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('HeaderRemoteControl')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<MenuItem onClick={disconnectRemotePlayer}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Close />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('Disconnect')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RemotePlayActiveMenu;
|
|
@ -0,0 +1,100 @@
|
||||||
|
import Warning from '@mui/icons-material/Warning';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import Menu, { type MenuProps } from '@mui/material/Menu';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import { playbackManager } from 'components/playback/playbackmanager';
|
||||||
|
import { pluginManager } from 'components/pluginManager';
|
||||||
|
import type { PlayTarget } from 'types/playTarget';
|
||||||
|
|
||||||
|
import PlayTargetIcon from '../../PlayTargetIcon';
|
||||||
|
|
||||||
|
interface RemotePlayMenuProps extends MenuProps {
|
||||||
|
onMenuClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ID = 'app-remote-play-menu';
|
||||||
|
|
||||||
|
const RemotePlayMenu: FC<RemotePlayMenuProps> = ({
|
||||||
|
anchorEl,
|
||||||
|
open,
|
||||||
|
onMenuClose
|
||||||
|
}) => {
|
||||||
|
// TODO: Add other checks for support (Android app, secure context, etc)
|
||||||
|
const isChromecastPluginLoaded = !!pluginManager.plugins.find(plugin => plugin.id === 'chromecast');
|
||||||
|
|
||||||
|
const [ playbackTargets, setPlaybackTargets ] = useState<PlayTarget[]>([]);
|
||||||
|
|
||||||
|
const onPlayTargetClick = (target: PlayTarget) => {
|
||||||
|
playbackManager.trySetActivePlayer(target.playerName, target);
|
||||||
|
onMenuClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlaybackTargets = async () => {
|
||||||
|
setPlaybackTargets(
|
||||||
|
await playbackManager.getTargets()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchPlaybackTargets()
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[AppRemotePlayMenu] unable to get playback targets', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [ open, setPlaybackTargets ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
id={ID}
|
||||||
|
keepMounted
|
||||||
|
open={open}
|
||||||
|
onClose={onMenuClose}
|
||||||
|
>
|
||||||
|
{!isChromecastPluginLoaded && ([
|
||||||
|
<MenuItem key='cast-unsupported-item' disabled>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Warning />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{globalize.translate('GoogleCastUnsupported')}
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>,
|
||||||
|
<Divider key='cast-unsupported-divider' />
|
||||||
|
])}
|
||||||
|
|
||||||
|
{playbackTargets.map(target => (
|
||||||
|
<MenuItem
|
||||||
|
key={target.id}
|
||||||
|
// Since we are looping over targets there is no good way to avoid creating a new function here
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => onPlayTargetClick(target)}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PlayTargetIcon target={target} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={ target.appName ? `${target.name} - ${target.appName}` : target.name }
|
||||||
|
secondary={ target.user?.Name }
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RemotePlayMenu;
|
21
src/apps/experimental/components/ElevationScroll.tsx
Normal file
21
src/apps/experimental/components/ElevationScroll.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import useScrollTrigger from '@mui/material/useScrollTrigger';
|
||||||
|
import React, { ReactElement } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that changes the elevation of a child component when scrolled.
|
||||||
|
*/
|
||||||
|
const ElevationScroll = ({ children, elevate = false }: { children: ReactElement, elevate?: boolean }) => {
|
||||||
|
const trigger = useScrollTrigger({
|
||||||
|
disableHysteresis: true,
|
||||||
|
threshold: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const isElevated = elevate || trigger;
|
||||||
|
|
||||||
|
return React.cloneElement(children, {
|
||||||
|
color: isElevated ? 'primary' : 'transparent',
|
||||||
|
elevation: isElevated ? 4 : 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ElevationScroll;
|
50
src/apps/experimental/components/LibraryIcon.tsx
Normal file
50
src/apps/experimental/components/LibraryIcon.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
import Movie from '@mui/icons-material/Movie';
|
||||||
|
import MusicNote from '@mui/icons-material/MusicNote';
|
||||||
|
import Photo from '@mui/icons-material/Photo';
|
||||||
|
import LiveTv from '@mui/icons-material/LiveTv';
|
||||||
|
import Tv from '@mui/icons-material/Tv';
|
||||||
|
import Theaters from '@mui/icons-material/Theaters';
|
||||||
|
import MusicVideo from '@mui/icons-material/MusicVideo';
|
||||||
|
import Book from '@mui/icons-material/Book';
|
||||||
|
import Collections from '@mui/icons-material/Collections';
|
||||||
|
import Queue from '@mui/icons-material/Queue';
|
||||||
|
import Folder from '@mui/icons-material/Folder';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { CollectionType } from 'types/collectionType';
|
||||||
|
|
||||||
|
interface LibraryIconProps {
|
||||||
|
item: BaseItemDto
|
||||||
|
}
|
||||||
|
|
||||||
|
const LibraryIcon: FC<LibraryIconProps> = ({
|
||||||
|
item
|
||||||
|
}) => {
|
||||||
|
switch (item.CollectionType) {
|
||||||
|
case CollectionType.Movies:
|
||||||
|
return <Movie />;
|
||||||
|
case CollectionType.Music:
|
||||||
|
return <MusicNote />;
|
||||||
|
case CollectionType.HomeVideos:
|
||||||
|
case CollectionType.Photos:
|
||||||
|
return <Photo />;
|
||||||
|
case CollectionType.LiveTv:
|
||||||
|
return <LiveTv />;
|
||||||
|
case CollectionType.TvShows:
|
||||||
|
return <Tv />;
|
||||||
|
case CollectionType.Trailers:
|
||||||
|
return <Theaters />;
|
||||||
|
case CollectionType.MusicVideos:
|
||||||
|
return <MusicVideo />;
|
||||||
|
case CollectionType.Books:
|
||||||
|
return <Book />;
|
||||||
|
case CollectionType.BoxSets:
|
||||||
|
return <Collections />;
|
||||||
|
case CollectionType.Playlists:
|
||||||
|
return <Queue />;
|
||||||
|
default:
|
||||||
|
return <Folder />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LibraryIcon;
|
38
src/apps/experimental/components/PlayTargetIcon.tsx
Normal file
38
src/apps/experimental/components/PlayTargetIcon.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Cast from '@mui/icons-material/Cast';
|
||||||
|
import Computer from '@mui/icons-material/Computer';
|
||||||
|
import Devices from '@mui/icons-material/Devices';
|
||||||
|
import Smartphone from '@mui/icons-material/Smartphone';
|
||||||
|
import Tablet from '@mui/icons-material/Tablet';
|
||||||
|
import Tv from '@mui/icons-material/Tv';
|
||||||
|
|
||||||
|
import browser from 'scripts/browser';
|
||||||
|
import type { PlayTarget } from 'types/playTarget';
|
||||||
|
|
||||||
|
const PlayTargetIcon = ({ target }: { target: PlayTarget }) => {
|
||||||
|
if (!target.deviceType && target.isLocalPlayer) {
|
||||||
|
if (browser.tv) {
|
||||||
|
return <Tv />;
|
||||||
|
} else if (browser.mobile) {
|
||||||
|
return <Smartphone />;
|
||||||
|
}
|
||||||
|
return <Computer />;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (target.deviceType) {
|
||||||
|
case 'smartphone':
|
||||||
|
return <Smartphone />;
|
||||||
|
case 'tablet':
|
||||||
|
return <Tablet />;
|
||||||
|
case 'desktop':
|
||||||
|
return <Computer />;
|
||||||
|
case 'cast':
|
||||||
|
return <Cast />;
|
||||||
|
case 'tv':
|
||||||
|
return <Tv />;
|
||||||
|
default:
|
||||||
|
return <Devices />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayTargetIcon;
|
86
src/apps/experimental/components/drawers/AppDrawer.tsx
Normal file
86
src/apps/experimental/components/drawers/AppDrawer.tsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from '../../routes/asyncRoutes';
|
||||||
|
import { LEGACY_ADMIN_ROUTES, LEGACY_USER_ROUTES } from '../../routes/legacyRoutes';
|
||||||
|
|
||||||
|
import AdvancedDrawerSection from './dashboard/AdvancedDrawerSection';
|
||||||
|
import DevicesDrawerSection from './dashboard/DevicesDrawerSection';
|
||||||
|
import LiveTvDrawerSection from './dashboard/LiveTvDrawerSection';
|
||||||
|
import PluginDrawerSection from './dashboard/PluginDrawerSection';
|
||||||
|
import ServerDrawerSection from './dashboard/ServerDrawerSection';
|
||||||
|
import MainDrawerContent from './MainDrawerContent';
|
||||||
|
import ResponsiveDrawer, { ResponsiveDrawerProps } from './ResponsiveDrawer';
|
||||||
|
|
||||||
|
export const DRAWER_WIDTH = 240;
|
||||||
|
|
||||||
|
const DRAWERLESS_ROUTES = [
|
||||||
|
'edititemmetadata.html', // metadata manager
|
||||||
|
'video' // video player
|
||||||
|
];
|
||||||
|
|
||||||
|
const MAIN_DRAWER_ROUTES = [
|
||||||
|
...ASYNC_USER_ROUTES,
|
||||||
|
...LEGACY_USER_ROUTES
|
||||||
|
].filter(route => !DRAWERLESS_ROUTES.includes(route.path));
|
||||||
|
|
||||||
|
const ADMIN_DRAWER_ROUTES = [
|
||||||
|
...ASYNC_ADMIN_ROUTES,
|
||||||
|
...LEGACY_ADMIN_ROUTES,
|
||||||
|
{ path: '/configurationpage' } // Plugin configuration page
|
||||||
|
].filter(route => !DRAWERLESS_ROUTES.includes(route.path));
|
||||||
|
|
||||||
|
/** Utility function to check if a path has a drawer. */
|
||||||
|
export const isDrawerPath = (path: string) => (
|
||||||
|
MAIN_DRAWER_ROUTES.some(route => route.path === path || `/${route.path}` === path)
|
||||||
|
|| ADMIN_DRAWER_ROUTES.some(route => route.path === path || `/${route.path}` === path)
|
||||||
|
);
|
||||||
|
|
||||||
|
const AppDrawer: FC<ResponsiveDrawerProps> = ({
|
||||||
|
open = false,
|
||||||
|
onClose,
|
||||||
|
onOpen
|
||||||
|
}) => (
|
||||||
|
<Routes>
|
||||||
|
{
|
||||||
|
MAIN_DRAWER_ROUTES.map(route => (
|
||||||
|
<Route
|
||||||
|
key={route.path}
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
|
<ResponsiveDrawer
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
onOpen={onOpen}
|
||||||
|
>
|
||||||
|
<MainDrawerContent />
|
||||||
|
</ResponsiveDrawer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ADMIN_DRAWER_ROUTES.map(route => (
|
||||||
|
<Route
|
||||||
|
key={route.path}
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
|
<ResponsiveDrawer
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
onOpen={onOpen}
|
||||||
|
>
|
||||||
|
<ServerDrawerSection />
|
||||||
|
<DevicesDrawerSection />
|
||||||
|
<LiveTvDrawerSection />
|
||||||
|
<AdvancedDrawerSection />
|
||||||
|
<PluginDrawerSection />
|
||||||
|
</ResponsiveDrawer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AppDrawer;
|
45
src/apps/experimental/components/drawers/ListItemLink.tsx
Normal file
45
src/apps/experimental/components/drawers/ListItemLink.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import ListItemButton, { ListItemButtonBaseProps } from '@mui/material/ListItemButton';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Link, useLocation, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface ListItemLinkProps extends ListItemButtonBaseProps {
|
||||||
|
to: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatchingParams = (routeParams: URLSearchParams, currentParams: URLSearchParams) => {
|
||||||
|
for (const param of routeParams) {
|
||||||
|
if (currentParams.get(param[0]) !== param[1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListItemLink: FC<ListItemLinkProps> = ({
|
||||||
|
children,
|
||||||
|
to,
|
||||||
|
...params
|
||||||
|
}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const [ searchParams ] = useSearchParams();
|
||||||
|
|
||||||
|
const [ toPath, toParams ] = to.split('?');
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
const toSearchParams = new URLSearchParams(`?${toParams}`);
|
||||||
|
|
||||||
|
const selected = location.pathname === toPath && (!toParams || isMatchingParams(toSearchParams, searchParams));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItemButton
|
||||||
|
component={Link}
|
||||||
|
to={to}
|
||||||
|
selected={selected}
|
||||||
|
{...params}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ListItemButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListItemLink;
|
186
src/apps/experimental/components/drawers/MainDrawerContent.tsx
Normal file
186
src/apps/experimental/components/drawers/MainDrawerContent.tsx
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||||
|
import type { SystemInfo } from '@jellyfin/sdk/lib/generated-client/models/system-info';
|
||||||
|
import { getUserViewsApi } from '@jellyfin/sdk/lib/utils/api/user-views-api';
|
||||||
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
|
import Dashboard from '@mui/icons-material/Dashboard';
|
||||||
|
import Edit from '@mui/icons-material/Edit';
|
||||||
|
import Favorite from '@mui/icons-material/Favorite';
|
||||||
|
import Home from '@mui/icons-material/Home';
|
||||||
|
import Link from '@mui/icons-material/Link';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { useWebConfig } from 'hooks/useWebConfig';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import { appRouter } from 'components/router/appRouter';
|
||||||
|
|
||||||
|
import ListItemLink from './ListItemLink';
|
||||||
|
import LibraryIcon from '../LibraryIcon';
|
||||||
|
|
||||||
|
const MainDrawerContent = () => {
|
||||||
|
const { api, user } = useApi();
|
||||||
|
const location = useLocation();
|
||||||
|
const [ systemInfo, setSystemInfo ] = useState<SystemInfo>();
|
||||||
|
const [ userViews, setUserViews ] = useState<BaseItemDto[]>([]);
|
||||||
|
const webConfig = useWebConfig();
|
||||||
|
|
||||||
|
const isHomeSelected = location.pathname === '/home.html' && (!location.search || location.search === '?tab=0');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (api && user?.Id) {
|
||||||
|
getUserViewsApi(api)
|
||||||
|
.getUserViews({ userId: user.Id })
|
||||||
|
.then(({ data }) => {
|
||||||
|
setUserViews(data.Items || []);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('[MainDrawer] failed to fetch user views', err);
|
||||||
|
setUserViews([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
getSystemApi(api)
|
||||||
|
.getSystemInfo()
|
||||||
|
.then(({ data }) => {
|
||||||
|
setSystemInfo(data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('[MainDrawer] failed to fetch system info', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setUserViews([]);
|
||||||
|
}
|
||||||
|
}, [ api, user?.Id ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* MAIN LINKS */}
|
||||||
|
<List>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/home.html' selected={isHomeSelected}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Home />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('Home')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/home.html?tab=1'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Favorite />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('Favorites')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
{/* CUSTOM LINKS */}
|
||||||
|
{(!!webConfig.menuLinks && webConfig.menuLinks.length > 0) && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<List>
|
||||||
|
{webConfig.menuLinks.map(menuLink => (
|
||||||
|
<ListItem
|
||||||
|
key={`${menuLink.name}_${menuLink.url}`}
|
||||||
|
disablePadding
|
||||||
|
>
|
||||||
|
<ListItemButton
|
||||||
|
component='a'
|
||||||
|
href={menuLink.url}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
{/* TODO: Support custom icons */}
|
||||||
|
<Link />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={menuLink.name} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* LIBRARY LINKS */}
|
||||||
|
{userViews.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<List
|
||||||
|
aria-labelledby='libraries-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='libraries-subheader'>
|
||||||
|
{globalize.translate('HeaderLibraries')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{userViews.map(view => (
|
||||||
|
<ListItem key={view.Id} disablePadding>
|
||||||
|
<ListItemLink
|
||||||
|
to={appRouter.getRouteUrl(view, { context: view.CollectionType }).substring(1)}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LibraryIcon item={view} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={view.Name} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ADMIN LINKS */}
|
||||||
|
{user?.Policy?.IsAdministrator && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<List
|
||||||
|
aria-labelledby='admin-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='admin-subheader'>
|
||||||
|
{globalize.translate('HeaderAdmin')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/dashboard.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Dashboard />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabDashboard')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/edititemmetadata.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Edit />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('MetadataManager')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<Divider style={{ marginTop: 'auto' }} />
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={systemInfo?.ServerName ? systemInfo.ServerName : 'Jellyfin'}
|
||||||
|
secondary={systemInfo?.Version ? `v${systemInfo.Version}` : ''}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainDrawerContent;
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { Theme } from '@mui/material/styles';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Drawer from '@mui/material/Drawer';
|
||||||
|
import SwipeableDrawer from '@mui/material/SwipeableDrawer';
|
||||||
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import React, { FC, useCallback } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import browser from 'scripts/browser';
|
||||||
|
|
||||||
|
import { DRAWER_WIDTH } from './AppDrawer';
|
||||||
|
import { isTabPath } from '../tabs/tabRoutes';
|
||||||
|
|
||||||
|
export interface ResponsiveDrawerProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
onOpen: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({
|
||||||
|
children,
|
||||||
|
open = false,
|
||||||
|
onClose,
|
||||||
|
onOpen
|
||||||
|
}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm'));
|
||||||
|
const isLargeScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg'));
|
||||||
|
const isTallToolbar = isTabPath(location.pathname) && !isLargeScreen;
|
||||||
|
|
||||||
|
const getToolbarStyles = useCallback((theme: Theme) => ({
|
||||||
|
marginBottom: isTallToolbar ? theme.spacing(6) : 0
|
||||||
|
}), [ isTallToolbar ]);
|
||||||
|
|
||||||
|
return ( isSmallScreen ? (
|
||||||
|
/* DESKTOP DRAWER */
|
||||||
|
<Drawer
|
||||||
|
sx={{
|
||||||
|
width: DRAWER_WIDTH,
|
||||||
|
flexShrink: 0,
|
||||||
|
'& .MuiDrawer-paper': {
|
||||||
|
width: DRAWER_WIDTH,
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant='persistent'
|
||||||
|
anchor='left'
|
||||||
|
open={open}
|
||||||
|
>
|
||||||
|
<Toolbar
|
||||||
|
variant='dense'
|
||||||
|
sx={getToolbarStyles}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Drawer>
|
||||||
|
) : (
|
||||||
|
/* MOBILE DRAWER */
|
||||||
|
<SwipeableDrawer
|
||||||
|
anchor='left'
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
onOpen={onOpen}
|
||||||
|
// Disable swipe to open on iOS since it interferes with back navigation
|
||||||
|
disableDiscovery={browser.iOS}
|
||||||
|
ModalProps={{
|
||||||
|
keepMounted: true // Better open performance on mobile.
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar
|
||||||
|
variant='dense'
|
||||||
|
sx={getToolbarStyles}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
role='presentation'
|
||||||
|
// Close the drawer when the content is clicked
|
||||||
|
onClick={onClose}
|
||||||
|
onKeyDown={onClose}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</SwipeableDrawer>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponsiveDrawer;
|
|
@ -0,0 +1,110 @@
|
||||||
|
import Article from '@mui/icons-material/Article';
|
||||||
|
import EditNotifications from '@mui/icons-material/EditNotifications';
|
||||||
|
import ExpandLess from '@mui/icons-material/ExpandLess';
|
||||||
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||||
|
import Extension from '@mui/icons-material/Extension';
|
||||||
|
import Lan from '@mui/icons-material/Lan';
|
||||||
|
import Schedule from '@mui/icons-material/Schedule';
|
||||||
|
import VpnKey from '@mui/icons-material/VpnKey';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import ListItemLink from '../ListItemLink';
|
||||||
|
|
||||||
|
const PLUGIN_PATHS = [
|
||||||
|
'/installedplugins.html',
|
||||||
|
'/availableplugins.html',
|
||||||
|
'/repositories.html',
|
||||||
|
'/addplugin.html',
|
||||||
|
'/configurationpage'
|
||||||
|
];
|
||||||
|
|
||||||
|
const AdvancedDrawerSection = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isPluginSectionOpen = PLUGIN_PATHS.includes(location.pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
aria-labelledby='advanced-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='advanced-subheader'>
|
||||||
|
{globalize.translate('TabAdvanced')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/networking.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Lan />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabNetworking')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/apikeys.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<VpnKey />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderApiKeys')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/log.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Article />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabLogs')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/notificationsettings.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<EditNotifications />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('Notifications')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/installedplugins.html' selected={false}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Extension />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabPlugins')} />
|
||||||
|
{isPluginSectionOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={isPluginSectionOpen} timeout='auto' unmountOnExit>
|
||||||
|
<List component='div' disablePadding>
|
||||||
|
<ListItemLink to='/installedplugins.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabMyPlugins')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/availableplugins.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabCatalog')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/repositories.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabRepositories')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/scheduledtasks.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Schedule />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabScheduledTasks')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvancedDrawerSection;
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { Devices, Analytics, Input, ExpandLess, ExpandMore } from '@mui/icons-material';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import ListItemLink from '../ListItemLink';
|
||||||
|
|
||||||
|
const DLNA_PATHS = [
|
||||||
|
'/dlnasettings.html',
|
||||||
|
'/dlnaprofiles.html'
|
||||||
|
];
|
||||||
|
|
||||||
|
const DevicesDrawerSection = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isDlnaSectionOpen = DLNA_PATHS.includes(location.pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
aria-labelledby='devices-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='devices-subheader'>
|
||||||
|
{globalize.translate('HeaderDevices')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/devices.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Devices />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderDevices')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/serveractivity.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Analytics />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderActivity')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/dlnasettings.html' selected={false}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Input />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={'DLNA'} />
|
||||||
|
{isDlnaSectionOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={isDlnaSectionOpen} timeout='auto' unmountOnExit>
|
||||||
|
<List component='div' disablePadding>
|
||||||
|
<ListItemLink to='/dlnasettings.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('Settings')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/dlnaprofiles.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabProfiles')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DevicesDrawerSection;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Dvr, LiveTv } from '@mui/icons-material';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import ListItemLink from '../ListItemLink';
|
||||||
|
|
||||||
|
const LiveTvDrawerSection = () => {
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
aria-labelledby='livetv-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='livetv-subheader'>
|
||||||
|
{globalize.translate('LiveTV')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/livetvstatus.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LiveTv />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('LiveTV')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/livetvsettings.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Dvr />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderDVR')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiveTvDrawerSection;
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { ConfigurationPageInfo } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
import { getDashboardApi } from '@jellyfin/sdk/lib/utils/api/dashboard-api';
|
||||||
|
import { Folder } from '@mui/icons-material';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import Dashboard from 'utils/dashboard';
|
||||||
|
|
||||||
|
import ListItemLink from '../ListItemLink';
|
||||||
|
|
||||||
|
const PluginDrawerSection = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
const [ pagesInfo, setPagesInfo ] = useState<ConfigurationPageInfo[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPluginPages = async () => {
|
||||||
|
if (!api) return;
|
||||||
|
|
||||||
|
const pagesResponse = await getDashboardApi(api)
|
||||||
|
.getConfigurationPages({ enableInMainMenu: true });
|
||||||
|
|
||||||
|
setPagesInfo(pagesResponse.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPluginPages()
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[PluginDrawerSection] unable to fetch plugin config pages', err);
|
||||||
|
});
|
||||||
|
}, [ api ]);
|
||||||
|
|
||||||
|
if (!api || pagesInfo.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
aria-labelledby='plugins-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='plugins-subheader'>
|
||||||
|
{globalize.translate('TabPlugins')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pagesInfo.map(pageInfo => (
|
||||||
|
<ListItem key={pageInfo.PluginId} disablePadding>
|
||||||
|
<ListItemLink to={`/${Dashboard.getPluginUrl(pageInfo.Name)}`}>
|
||||||
|
<ListItemIcon>
|
||||||
|
{/* TODO: Support different icons? */}
|
||||||
|
<Folder />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={pageInfo.DisplayName} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PluginDrawerSection;
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { Dashboard, ExpandLess, ExpandMore, LibraryAdd, People, PlayCircle, Settings } from '@mui/icons-material';
|
||||||
|
import Collapse from '@mui/material/Collapse';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
|
import React from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
import ListItemLink from '../ListItemLink';
|
||||||
|
|
||||||
|
const LIBRARY_PATHS = [
|
||||||
|
'/library.html',
|
||||||
|
'/librarydisplay.html',
|
||||||
|
'/metadataimages.html',
|
||||||
|
'/metadatanfo.html'
|
||||||
|
];
|
||||||
|
|
||||||
|
const PLAYBACK_PATHS = [
|
||||||
|
'/encodingsettings.html',
|
||||||
|
'/playbackconfiguration.html',
|
||||||
|
'/streamingsettings.html'
|
||||||
|
];
|
||||||
|
|
||||||
|
const ServerDrawerSection = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isLibrarySectionOpen = LIBRARY_PATHS.includes(location.pathname);
|
||||||
|
const isPlaybackSectionOpen = PLAYBACK_PATHS.includes(location.pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
aria-labelledby='server-subheader'
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component='div' id='server-subheader'>
|
||||||
|
{globalize.translate('TabServer')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/dashboard.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Dashboard />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TabDashboard')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/dashboardgeneral.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Settings />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('General')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/userprofiles.html'>
|
||||||
|
<ListItemIcon>
|
||||||
|
<People />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderUsers')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/library.html' selected={false}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LibraryAdd />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('HeaderLibraries')} />
|
||||||
|
{isLibrarySectionOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={isLibrarySectionOpen} timeout='auto' unmountOnExit>
|
||||||
|
<List component='div' disablePadding>
|
||||||
|
<ListItemLink to='/library.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('HeaderLibraries')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/librarydisplay.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('Display')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/metadataimages.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('Metadata')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/metadatanfo.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabNfoSettings')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemLink to='/encodingsettings.html' selected={false}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PlayCircle />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={globalize.translate('TitlePlayback')} />
|
||||||
|
{isPlaybackSectionOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItem>
|
||||||
|
<Collapse in={isPlaybackSectionOpen} timeout='auto' unmountOnExit>
|
||||||
|
<List component='div' disablePadding>
|
||||||
|
<ListItemLink to='/encodingsettings.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('Transcoding')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/playbackconfiguration.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('ButtonResume')} />
|
||||||
|
</ListItemLink>
|
||||||
|
<ListItemLink to='/streamingsettings.html' sx={{ pl: 4 }}>
|
||||||
|
<ListItemText inset primary={globalize.translate('TabStreaming')} />
|
||||||
|
</ListItemLink>
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerDrawerSection;
|
90
src/apps/experimental/components/tabs/AppTabs.tsx
Normal file
90
src/apps/experimental/components/tabs/AppTabs.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import { Theme } from '@mui/material/styles';
|
||||||
|
import Tab from '@mui/material/Tab';
|
||||||
|
import Tabs from '@mui/material/Tabs';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
import React, { FC, useCallback, useEffect } from 'react';
|
||||||
|
import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import TabRoutes, { getDefaultTabIndex } from './tabRoutes';
|
||||||
|
|
||||||
|
interface AppTabsParams {
|
||||||
|
isDrawerOpen: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = debounce(() => window.dispatchEvent(new Event('resize')), 100);
|
||||||
|
|
||||||
|
const AppTabs: FC<AppTabsParams> = ({
|
||||||
|
isDrawerOpen
|
||||||
|
}) => {
|
||||||
|
const isBigScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm'));
|
||||||
|
const location = useLocation();
|
||||||
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||||
|
const searchParamsTab = searchParams.get('tab');
|
||||||
|
const libraryId = location.pathname === '/livetv.html' ?
|
||||||
|
'livetv' : searchParams.get('topParentId');
|
||||||
|
const activeTab = searchParamsTab !== null ?
|
||||||
|
parseInt(searchParamsTab, 10) :
|
||||||
|
getDefaultTabIndex(location.pathname, libraryId);
|
||||||
|
|
||||||
|
// HACK: Force resizing to workaround upstream bug with tab resizing
|
||||||
|
// https://github.com/mui/material-ui/issues/24011
|
||||||
|
useEffect(() => {
|
||||||
|
handleResize();
|
||||||
|
}, [ isDrawerOpen ]);
|
||||||
|
|
||||||
|
const onTabClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const tabIndex = event.currentTarget.dataset.tabIndex;
|
||||||
|
|
||||||
|
if (tabIndex) {
|
||||||
|
searchParams.set('tab', tabIndex);
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
}, [ searchParams, setSearchParams ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
{
|
||||||
|
TabRoutes.map(route => (
|
||||||
|
<Route
|
||||||
|
key={route.path}
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
flexShrink: {
|
||||||
|
xs: 0,
|
||||||
|
lg: 'unset'
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
xs: 100,
|
||||||
|
lg: 'unset'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant={isBigScreen ? 'standard' : 'scrollable'}
|
||||||
|
centered={isBigScreen}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
route.tabs.map(({ index, label }) => (
|
||||||
|
<Tab
|
||||||
|
key={`${route}-tab-${index}`}
|
||||||
|
label={label}
|
||||||
|
data-tab-index={`${index}`}
|
||||||
|
onClick={onTabClick}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Tabs>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppTabs;
|
190
src/apps/experimental/components/tabs/tabRoutes.ts
Normal file
190
src/apps/experimental/components/tabs/tabRoutes.ts
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import * as userSettings from 'scripts/settings/userSettings';
|
||||||
|
import { LibraryTab } from 'types/libraryTab';
|
||||||
|
|
||||||
|
interface TabDefinition {
|
||||||
|
index: number
|
||||||
|
label: string
|
||||||
|
value: LibraryTab
|
||||||
|
isDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabRoute {
|
||||||
|
path: string,
|
||||||
|
tabs: TabDefinition[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to check if a path has tabs.
|
||||||
|
*/
|
||||||
|
export const isTabPath = (path: string) => (
|
||||||
|
TabRoutes.some(route => route.path === path)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get the default tab index for a specified URL path and library.
|
||||||
|
*/
|
||||||
|
export const getDefaultTabIndex = (path: string, libraryId?: string | null) => {
|
||||||
|
if (!libraryId) return 0;
|
||||||
|
|
||||||
|
const tabs = TabRoutes.find(route => route.path === path)?.tabs ?? [];
|
||||||
|
const defaultTab = userSettings.get('landing-' + libraryId, false);
|
||||||
|
|
||||||
|
return tabs.find(tab => tab.value === defaultTab)?.index
|
||||||
|
?? tabs.find(tab => tab.isDefault)?.index
|
||||||
|
?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TabRoutes: TabRoute[] = [
|
||||||
|
{
|
||||||
|
path: '/livetv.html',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
label: globalize.translate('Programs'),
|
||||||
|
value: LibraryTab.Programs,
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
label: globalize.translate('Guide'),
|
||||||
|
value: LibraryTab.Guide
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
label: globalize.translate('Channels'),
|
||||||
|
value: LibraryTab.Channels
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
label: globalize.translate('Recordings'),
|
||||||
|
value: LibraryTab.Recordings
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
label: globalize.translate('Schedule'),
|
||||||
|
value: LibraryTab.Schedule
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
label: globalize.translate('Series'),
|
||||||
|
value: LibraryTab.Series
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/movies.html',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
label: globalize.translate('Movies'),
|
||||||
|
value: LibraryTab.Movies,
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
label: globalize.translate('Suggestions'),
|
||||||
|
value: LibraryTab.Suggestions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
label: globalize.translate('Trailers'),
|
||||||
|
value: LibraryTab.Trailers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
label: globalize.translate('Favorites'),
|
||||||
|
value: LibraryTab.Favorites
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
label: globalize.translate('Collections'),
|
||||||
|
value: LibraryTab.Collections
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
label: globalize.translate('Genres'),
|
||||||
|
value: LibraryTab.Genres
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/music.html',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
label: globalize.translate('Albums'),
|
||||||
|
value: LibraryTab.Albums,
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
label: globalize.translate('Suggestions'),
|
||||||
|
value: LibraryTab.Suggestions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
label: globalize.translate('HeaderAlbumArtists'),
|
||||||
|
value: LibraryTab.AlbumArtists
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
label: globalize.translate('Artists'),
|
||||||
|
value: LibraryTab.Artists
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
label: globalize.translate('Playlists'),
|
||||||
|
value: LibraryTab.Playlists
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
label: globalize.translate('Songs'),
|
||||||
|
value: LibraryTab.Songs
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 6,
|
||||||
|
label: globalize.translate('Genres'),
|
||||||
|
value: LibraryTab.Genres
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/tv.html',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
label: globalize.translate('Shows'),
|
||||||
|
value: LibraryTab.Shows,
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
label: globalize.translate('Suggestions'),
|
||||||
|
value: LibraryTab.Suggestions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
label: globalize.translate('TabUpcoming'),
|
||||||
|
value: LibraryTab.Upcoming
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
label: globalize.translate('Genres'),
|
||||||
|
value: LibraryTab.Genres
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
label: globalize.translate('TabNetworks'),
|
||||||
|
value: LibraryTab.Networks
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
label: globalize.translate('Episodes'),
|
||||||
|
value: LibraryTab.Episodes
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default TabRoutes;
|
|
@ -1,41 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ConnectionRequired from '../../../components/ConnectionRequired';
|
|
||||||
import ServerContentPage from '../../../components/ServerContentPage';
|
|
||||||
import { toAsyncPageRoute } from '../../../components/router/AsyncRoute';
|
|
||||||
import { toViewManagerPageRoute } from '../../../components/router/LegacyRoute';
|
|
||||||
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './asyncRoutes';
|
|
||||||
import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
|
||||||
|
|
||||||
export const ExperimentalAppRoutes = () => (
|
|
||||||
<Routes>
|
|
||||||
<Route path='/'>
|
|
||||||
{/* User routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired />}>
|
|
||||||
{ASYNC_USER_ROUTES.map(toAsyncPageRoute)}
|
|
||||||
{LEGACY_USER_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Admin routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired isAdminRequired />}>
|
|
||||||
{ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)}
|
|
||||||
{LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
|
|
||||||
<Route path='configurationpage' element={
|
|
||||||
<ServerContentPage view='/web/configurationpage' />
|
|
||||||
} />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Public routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired isUserRequired={false} />}>
|
|
||||||
<Route index element={<Navigate replace to='/home.html' />} />
|
|
||||||
|
|
||||||
{LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Suppress warnings for unhandled routes */}
|
|
||||||
<Route path='*' element={null} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
);
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
||||||
|
|
||||||
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
||||||
|
{ path: 'notificationsettings.html', page: 'dashboard/notifications' },
|
||||||
{ path: 'usernew.html', page: 'user/usernew' },
|
{ path: 'usernew.html', page: 'user/usernew' },
|
||||||
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
||||||
{ path: 'useredit.html', page: 'user/useredit' },
|
{ path: 'useredit.html', page: 'user/useredit' },
|
||||||
|
|
|
@ -111,7 +111,7 @@ const Home: FunctionComponent = () => {
|
||||||
const previousIndex = e.detail.previousIndex;
|
const previousIndex = e.detail.previousIndex;
|
||||||
|
|
||||||
const previousTabController = previousIndex == null ? null : tabControllers[previousIndex];
|
const previousTabController = previousIndex == null ? null : tabControllers[previousIndex];
|
||||||
if (previousTabController && previousTabController.onPause) {
|
if (previousTabController?.onPause) {
|
||||||
previousTabController.onPause();
|
previousTabController.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ const Home: FunctionComponent = () => {
|
||||||
|
|
||||||
if (!currentTabController) {
|
if (!currentTabController) {
|
||||||
mainTabsManager.selectedTabIndex(initialTabIndex);
|
mainTabsManager.selectedTabIndex(initialTabIndex);
|
||||||
} else if (currentTabController && currentTabController.onResume) {
|
} else if (currentTabController?.onResume) {
|
||||||
currentTabController.onResume({});
|
currentTabController.onResume({});
|
||||||
}
|
}
|
||||||
(document.querySelector('.skinHeader') as HTMLDivElement).classList.add('noHomeButtonHeader');
|
(document.querySelector('.skinHeader') as HTMLDivElement).classList.add('noHomeButtonHeader');
|
||||||
|
@ -134,7 +134,7 @@ const Home: FunctionComponent = () => {
|
||||||
|
|
||||||
const onPause = useCallback(() => {
|
const onPause = useCallback(() => {
|
||||||
const currentTabController = tabController.current;
|
const currentTabController = tabController.current;
|
||||||
if (currentTabController && currentTabController.onPause) {
|
if (currentTabController?.onPause) {
|
||||||
currentTabController.onPause();
|
currentTabController.onPause();
|
||||||
}
|
}
|
||||||
(document.querySelector('.skinHeader') as HTMLDivElement).classList.remove('noHomeButtonHeader');
|
(document.querySelector('.skinHeader') as HTMLDivElement).classList.remove('noHomeButtonHeader');
|
||||||
|
|
|
@ -103,18 +103,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [
|
||||||
controller: 'dashboard/metadatanfo',
|
controller: 'dashboard/metadatanfo',
|
||||||
view: 'dashboard/metadatanfo.html'
|
view: 'dashboard/metadatanfo.html'
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
path: 'notificationsetting.html',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'dashboard/notifications/notification/index',
|
|
||||||
view: 'dashboard/notifications/notification/index.html'
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
path: 'notificationsettings.html',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'dashboard/notifications/notifications/index',
|
|
||||||
view: 'dashboard/notifications/notifications/index.html'
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
path: 'playbackconfiguration.html',
|
path: 'playbackconfiguration.html',
|
||||||
pageProps: {
|
pageProps: {
|
||||||
|
|
|
@ -3,60 +3,26 @@ import '../../../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||||
import '../../../../elements/emby-tabs/emby-tabs';
|
import '../../../../elements/emby-tabs/emby-tabs';
|
||||||
import '../../../../elements/emby-button/emby-button';
|
import '../../../../elements/emby-button/emby-button';
|
||||||
|
|
||||||
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { FC, useEffect, useRef } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useLocation, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import * as mainTabsManager from '../../../../components/maintabsmanager';
|
|
||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
import globalize from '../../../../scripts/globalize';
|
import globalize from '../../../../scripts/globalize';
|
||||||
import libraryMenu from '../../../../scripts/libraryMenu';
|
import libraryMenu from '../../../../scripts/libraryMenu';
|
||||||
import * as userSettings from '../../../../scripts/settings/userSettings';
|
|
||||||
import CollectionsView from './CollectionsView';
|
import CollectionsView from './CollectionsView';
|
||||||
import FavoritesView from './FavoritesView';
|
import FavoritesView from './FavoritesView';
|
||||||
import GenresView from './GenresView';
|
import GenresView from './GenresView';
|
||||||
import MoviesView from './MoviesView';
|
import MoviesView from './MoviesView';
|
||||||
import SuggestionsView from './SuggestionsView';
|
import SuggestionsView from './SuggestionsView';
|
||||||
import TrailersView from './TrailersView';
|
import TrailersView from './TrailersView';
|
||||||
|
import { getDefaultTabIndex } from '../../components/tabs/tabRoutes';
|
||||||
const getDefaultTabIndex = (folderId: string | null) => {
|
|
||||||
switch (userSettings.get('landing-' + folderId, false)) {
|
|
||||||
case 'suggestions':
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case 'favorites':
|
|
||||||
return 3;
|
|
||||||
|
|
||||||
case 'collections':
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
case 'genres':
|
|
||||||
return 5;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTabs = () => {
|
|
||||||
return [{
|
|
||||||
name: globalize.translate('Movies')
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Suggestions')
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Trailers')
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Favorites')
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Collections')
|
|
||||||
}, {
|
|
||||||
name: globalize.translate('Genres')
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
|
|
||||||
const Movies: FC = () => {
|
const Movies: FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString(), 10);
|
const searchParamsTab = searchParams.get('tab');
|
||||||
const [ selectedIndex, setSelectedIndex ] = useState(currentTabIndex);
|
const currentTabIndex = searchParamsTab !== null ? parseInt(searchParamsTab, 10) :
|
||||||
|
getDefaultTabIndex(location.pathname, searchParams.get('topParentId'));
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const getTabComponent = (index: number) => {
|
const getTabComponent = (index: number) => {
|
||||||
|
@ -94,11 +60,6 @@ const Movies: FC = () => {
|
||||||
return component;
|
return component;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTabChange = useCallback((e: { detail: { selectedTabIndex: string; }; }) => {
|
|
||||||
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
|
|
||||||
setSelectedIndex(newIndex);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
|
|
||||||
|
@ -106,7 +67,7 @@ const Movies: FC = () => {
|
||||||
console.error('Unexpected null reference');
|
console.error('Unexpected null reference');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mainTabsManager.setTabs(page, selectedIndex, getTabs, undefined, undefined, onTabChange);
|
|
||||||
if (!page.getAttribute('data-title')) {
|
if (!page.getAttribute('data-title')) {
|
||||||
const parentId = searchParams.get('topParentId');
|
const parentId = searchParams.get('topParentId');
|
||||||
|
|
||||||
|
@ -116,13 +77,15 @@ const Movies: FC = () => {
|
||||||
libraryMenu.setTitle(item.Name);
|
libraryMenu.setTitle(item.Name);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('[movies] failed to fetch library', err);
|
console.error('[movies] failed to fetch library', err);
|
||||||
|
page.setAttribute('data-title', globalize.translate('Movies'));
|
||||||
|
libraryMenu.setTitle(globalize.translate('Movies'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
page.setAttribute('data-title', globalize.translate('Movies'));
|
page.setAttribute('data-title', globalize.translate('Movies'));
|
||||||
libraryMenu.setTitle(globalize.translate('Movies'));
|
libraryMenu.setTitle(globalize.translate('Movies'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [onTabChange, searchParams, selectedIndex]);
|
}, [ searchParams ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={element}>
|
<div ref={element}>
|
||||||
|
@ -131,7 +94,7 @@ const Movies: FC = () => {
|
||||||
className='mainAnimatedPage libraryPage backdropPage collectionEditorPage pageWithAbsoluteTabs withTabs'
|
className='mainAnimatedPage libraryPage backdropPage collectionEditorPage pageWithAbsoluteTabs withTabs'
|
||||||
backDropType='movie'
|
backDropType='movie'
|
||||||
>
|
>
|
||||||
{getTabComponent(selectedIndex)}
|
{getTabComponent(currentTabIndex)}
|
||||||
|
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</div>
|
||||||
|
|
53
src/apps/experimental/theme.ts
Normal file
53
src/apps/experimental/theme.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { createTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
const theme = createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: 'dark',
|
||||||
|
primary: {
|
||||||
|
main: '#00a4dc'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#aa5cc3'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: '#101010',
|
||||||
|
paper: '#202020'
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
selectedOpacity: 0.2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: '"Noto Sans", sans-serif',
|
||||||
|
button: {
|
||||||
|
textTransform: 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiButton: {
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'contained'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiFormControl: {
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'filled'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiTextField: {
|
||||||
|
defaultProps: {
|
||||||
|
variant: 'filled'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiListSubheader: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
// NOTE: Added for drawer subheaders, but maybe it won't work in other cases?
|
||||||
|
backgroundColor: 'inherit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default theme;
|
|
@ -1,19 +1,58 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import AppHeader from '../../components/AppHeader';
|
import AppHeader from 'components/AppHeader';
|
||||||
import Backdrop from '../../components/Backdrop';
|
import Backdrop from 'components/Backdrop';
|
||||||
import { AppRoutes } from './routes/AppRoutes';
|
import ServerContentPage from 'components/ServerContentPage';
|
||||||
|
import ConnectionRequired from 'components/ConnectionRequired';
|
||||||
|
import { toAsyncPageRoute } from 'components/router/AsyncRoute';
|
||||||
|
import { toViewManagerPageRoute } from 'components/router/LegacyRoute';
|
||||||
|
|
||||||
const StableApp = () => (
|
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './routes/asyncRoutes';
|
||||||
|
import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './routes/legacyRoutes';
|
||||||
|
|
||||||
|
const Layout = () => (
|
||||||
<>
|
<>
|
||||||
<Backdrop />
|
<Backdrop />
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
|
|
||||||
<div className='mainAnimatedPages skinBody' />
|
<div className='mainAnimatedPages skinBody' />
|
||||||
<div className='skinBody'>
|
<div className='skinBody'>
|
||||||
<AppRoutes />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const StableApp = () => (
|
||||||
|
<Routes>
|
||||||
|
<Route element={<Layout />}>
|
||||||
|
{/* User routes */}
|
||||||
|
<Route path='/' element={<ConnectionRequired />}>
|
||||||
|
{ASYNC_USER_ROUTES.map(toAsyncPageRoute)}
|
||||||
|
{LEGACY_USER_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Admin routes */}
|
||||||
|
<Route path='/' element={<ConnectionRequired isAdminRequired />}>
|
||||||
|
{ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)}
|
||||||
|
{LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
|
||||||
|
<Route path='configurationpage' element={
|
||||||
|
<ServerContentPage view='/web/configurationpage' />
|
||||||
|
} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Public routes */}
|
||||||
|
<Route path='/' element={<ConnectionRequired isUserRequired={false} />}>
|
||||||
|
<Route index element={<Navigate replace to='/home.html' />} />
|
||||||
|
|
||||||
|
{LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Suppress warnings for unhandled routes */}
|
||||||
|
<Route path='*' element={null} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
|
||||||
export default StableApp;
|
export default StableApp;
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ConnectionRequired from '../../../components/ConnectionRequired';
|
|
||||||
import ServerContentPage from '../../../components/ServerContentPage';
|
|
||||||
import { toAsyncPageRoute } from '../../../components/router/AsyncRoute';
|
|
||||||
import { toViewManagerPageRoute } from '../../../components/router/LegacyRoute';
|
|
||||||
import { ASYNC_ADMIN_ROUTES, ASYNC_USER_ROUTES } from './asyncRoutes';
|
|
||||||
import { LEGACY_ADMIN_ROUTES, LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
|
|
||||||
|
|
||||||
export const AppRoutes = () => (
|
|
||||||
<Routes>
|
|
||||||
<Route path='/'>
|
|
||||||
{/* User routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired />}>
|
|
||||||
{ASYNC_USER_ROUTES.map(toAsyncPageRoute)}
|
|
||||||
{LEGACY_USER_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Admin routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired isAdminRequired />}>
|
|
||||||
{ASYNC_ADMIN_ROUTES.map(toAsyncPageRoute)}
|
|
||||||
{LEGACY_ADMIN_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
|
|
||||||
<Route path='configurationpage' element={
|
|
||||||
<ServerContentPage view='/web/configurationpage' />
|
|
||||||
} />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Public routes */}
|
|
||||||
<Route path='/' element={<ConnectionRequired isUserRequired={false} />}>
|
|
||||||
<Route index element={<Navigate replace to='/home.html' />} />
|
|
||||||
|
|
||||||
{LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)}
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Suppress warnings for unhandled routes */}
|
|
||||||
<Route path='*' element={null} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
);
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
|
||||||
|
|
||||||
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
|
||||||
|
{ path: 'notificationsettings.html', page: 'dashboard/notifications' },
|
||||||
{ path: 'usernew.html', page: 'user/usernew' },
|
{ path: 'usernew.html', page: 'user/usernew' },
|
||||||
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
{ path: 'userprofiles.html', page: 'user/userprofiles' },
|
||||||
{ path: 'useredit.html', page: 'user/useredit' },
|
{ path: 'useredit.html', page: 'user/useredit' },
|
||||||
|
|
36
src/apps/stable/routes/dashboard/notifications.tsx
Normal file
36
src/apps/stable/routes/dashboard/notifications.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Page from 'components/Page';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
const PluginLink = () => (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `<a
|
||||||
|
is='emby-linkbutton'
|
||||||
|
class='button-link'
|
||||||
|
href='#/addplugin.html?name=Webhook&guid=71552a5a5c5c4350a2aeebe451a30173'
|
||||||
|
>
|
||||||
|
${globalize.translate('GetThePlugin')}
|
||||||
|
</a>`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Notifications = () => (
|
||||||
|
<Page
|
||||||
|
id='notificationSettingPage'
|
||||||
|
title={globalize.translate('Notifications')}
|
||||||
|
className='mainAnimatedPage type-interior'
|
||||||
|
>
|
||||||
|
<div className='content-primary'>
|
||||||
|
<h2>{globalize.translate('Notifications')}</h2>
|
||||||
|
<p>
|
||||||
|
{globalize.translate('NotificationsMovedMessage')}
|
||||||
|
</p>
|
||||||
|
<PluginLink />
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Notifications;
|
|
@ -103,18 +103,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [
|
||||||
controller: 'dashboard/metadatanfo',
|
controller: 'dashboard/metadatanfo',
|
||||||
view: 'dashboard/metadatanfo.html'
|
view: 'dashboard/metadatanfo.html'
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
path: 'notificationsetting.html',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'dashboard/notifications/notification/index',
|
|
||||||
view: 'dashboard/notifications/notification/index.html'
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
path: 'notificationsettings.html',
|
|
||||||
pageProps: {
|
|
||||||
controller: 'dashboard/notifications/notifications/index',
|
|
||||||
view: 'dashboard/notifications/notifications/index.html'
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
path: 'playbackconfiguration.html',
|
path: 'playbackconfiguration.html',
|
||||||
pageProps: {
|
pageProps: {
|
||||||
|
|
|
@ -143,7 +143,7 @@ const UserParentalControl: FunctionComponent = () => {
|
||||||
|
|
||||||
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
||||||
btnDelete.addEventListener('click', function () {
|
btnDelete.addEventListener('click', function () {
|
||||||
const index = parseInt(btnDelete.getAttribute('data-index') || '0', 10);
|
const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10);
|
||||||
schedules.splice(index, 1);
|
schedules.splice(index, 1);
|
||||||
const newindex = schedules.filter(function (i: number) {
|
const newindex = schedules.filter(function (i: number) {
|
||||||
return i != index;
|
return i != index;
|
||||||
|
|
|
@ -102,7 +102,7 @@ const UserProfile: FunctionComponent = () => {
|
||||||
const target = evt.target as HTMLInputElement;
|
const target = evt.target as HTMLInputElement;
|
||||||
const file = (target.files as FileList)[0];
|
const file = (target.files as FileList)[0];
|
||||||
|
|
||||||
if (!file || !file.type.match('image.*')) {
|
if (!file || !/image.*/.exec(file.type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ const ServerContentPage: FunctionComponent<ServerContentPageProps> = ({ view })
|
||||||
|
|
||||||
viewManager.tryRestoreView(viewOptions)
|
viewManager.tryRestoreView(viewOptions)
|
||||||
.catch(async (result?: RestoreViewFailResponse) => {
|
.catch(async (result?: RestoreViewFailResponse) => {
|
||||||
if (!result || !result.cancelled) {
|
if (!result?.cancelled) {
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
const apiClient = ServerConnections.currentApiClient();
|
||||||
|
|
||||||
// Fetch the view html from the server and translate it
|
// Fetch the view html from the server and translate it
|
||||||
|
|
|
@ -179,7 +179,7 @@ export class AlphaPicker {
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
const prefix = item.getAttribute('data-prefix');
|
const prefix = item.getAttribute('data-prefix');
|
||||||
if (prefix && prefix.length) {
|
if (prefix?.length) {
|
||||||
itemFocusValue = prefix[0];
|
itemFocusValue = prefix[0];
|
||||||
if (itemFocusTimeout) {
|
if (itemFocusTimeout) {
|
||||||
clearTimeout(itemFocusTimeout);
|
clearTimeout(itemFocusTimeout);
|
||||||
|
|
|
@ -109,7 +109,7 @@ export function getCommands(options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) {
|
if (itemHelper.supportsAddingToCollection(item) && (user.Policy.IsAdministrator || user.Policy.EnableCollectionManagement)) {
|
||||||
commands.push({
|
commands.push({
|
||||||
name: globalize.translate('AddToCollection'),
|
name: globalize.translate('AddToCollection'),
|
||||||
id: 'addtocollection',
|
id: 'addtocollection',
|
||||||
|
|
|
@ -416,6 +416,8 @@ export function setContentType(parent, contentType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent.querySelector('.chkEnableLUFSScan').classList.toggle('hide', contentType !== 'music');
|
||||||
|
|
||||||
if (contentType === 'tvshows') {
|
if (contentType === 'tvshows') {
|
||||||
parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide');
|
parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
|
@ -512,6 +514,7 @@ export function getLibraryOptions(parent) {
|
||||||
EnableArchiveMediaFiles: false,
|
EnableArchiveMediaFiles: false,
|
||||||
EnablePhotos: parent.querySelector('.chkEnablePhotos').checked,
|
EnablePhotos: parent.querySelector('.chkEnablePhotos').checked,
|
||||||
EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked,
|
EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked,
|
||||||
|
EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked,
|
||||||
ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked,
|
ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked,
|
||||||
EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked,
|
EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked,
|
||||||
EnableInternetProviders: true,
|
EnableInternetProviders: true,
|
||||||
|
@ -573,6 +576,7 @@ export function setLibraryOptions(parent, options) {
|
||||||
parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials';
|
parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials';
|
||||||
parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos;
|
parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos;
|
||||||
parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor;
|
parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor;
|
||||||
|
parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan;
|
||||||
parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan;
|
parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan;
|
||||||
parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction;
|
parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction;
|
||||||
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;
|
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;
|
||||||
|
|
|
@ -55,6 +55,14 @@
|
||||||
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
|
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription advanced">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" class="chkEnableLUFSScan" checked />
|
||||||
|
<span>${LabelEnableLUFSScan}</span>
|
||||||
|
</label>
|
||||||
|
<div class="fieldDescription checkboxFieldDescription">${LabelEnableLUFSScanHelp}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced">
|
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced">
|
||||||
<label>
|
<label>
|
||||||
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" />
|
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" />
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<form class="addLibraryForm" style="max-width:100%;">
|
<div class="formDialogHeader">
|
||||||
<div class="formDialogHeader">
|
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
||||||
<div class="dialogContentInner dialog-content-centered">
|
<div class="dialogContentInner dialog-content-centered">
|
||||||
|
<form class="addLibraryForm" style="max-width:100%;">
|
||||||
|
|
||||||
<div id="fldCollectionType" class="selectContainer">
|
<div id="fldCollectionType" class="selectContainer">
|
||||||
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
|
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
|
||||||
|
@ -28,12 +28,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="libraryOptions"></div>
|
<div class="libraryOptions"></div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="formDialogFooter">
|
<div class="formDialogFooter">
|
||||||
<button is="emby-button" type="submit" class="raised btnSubmit button-submit block formDialogFooterItem">
|
<button is="emby-button" type="submit" class="raised btnSubmit button-submit block formDialogFooterItem">
|
||||||
<span>${ButtonOk}</span>
|
<span>${ButtonOk}</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
38
src/components/playback/displayMirrorManager.ts
Normal file
38
src/components/playback/displayMirrorManager.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
|
||||||
|
import { playbackManager } from './playbackmanager';
|
||||||
|
|
||||||
|
interface PlaybackInfo {
|
||||||
|
item: BaseItemDto;
|
||||||
|
context?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mirrorItem(info: PlaybackInfo, player?: unknown) {
|
||||||
|
const { item } = info;
|
||||||
|
|
||||||
|
playbackManager.displayContent({
|
||||||
|
ItemName: item.Name,
|
||||||
|
ItemId: item.Id,
|
||||||
|
ItemType: item.Type,
|
||||||
|
Context: info.context
|
||||||
|
}, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mirrorIfEnabled(info: PlaybackInfo) {
|
||||||
|
if (info && playbackManager.enableDisplayMirroring()) {
|
||||||
|
const playerInfo = playbackManager.getPlayerInfo();
|
||||||
|
|
||||||
|
if (playerInfo && !playerInfo.isLocalPlayer && playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
|
||||||
|
mirrorItem(info, playbackManager.getCurrentPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('viewshow', e => {
|
||||||
|
const state = e.detail.state || {};
|
||||||
|
const { item } = state;
|
||||||
|
|
||||||
|
if (item?.ServerId) {
|
||||||
|
mirrorIfEnabled({ item });
|
||||||
|
}
|
||||||
|
});
|
|
@ -2123,7 +2123,7 @@ class PlaybackManager {
|
||||||
const getAdditionalParts = async (items) => {
|
const getAdditionalParts = async (items) => {
|
||||||
const getOneAdditionalPart = async function (item) {
|
const getOneAdditionalPart = async function (item) {
|
||||||
let retVal = [item];
|
let retVal = [item];
|
||||||
if (item.Type === 'Movie') {
|
if (item.Type === 'Movie' || item.Type === 'Episode') {
|
||||||
const client = ServerConnections.getApiClient(item.ServerId);
|
const client = ServerConnections.getApiClient(item.ServerId);
|
||||||
const user = await client.getCurrentUser();
|
const user = await client.getCurrentUser();
|
||||||
const additionalParts = await client.getAdditionalVideoParts(user.Id, item.Id);
|
const additionalParts = await client.getAdditionalVideoParts(user.Id, item.Id);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import appSettings from '../../scripts/settings/appSettings';
|
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import loading from '../loading/loading';
|
import loading from '../loading/loading';
|
||||||
|
@ -13,32 +12,6 @@ import '../../elements/emby-button/emby-button';
|
||||||
import dialog from '../dialog/dialog';
|
import dialog from '../dialog/dialog';
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
|
|
||||||
function mirrorItem(info, player) {
|
|
||||||
const item = info.item;
|
|
||||||
|
|
||||||
playbackManager.displayContent({
|
|
||||||
|
|
||||||
ItemName: item.Name,
|
|
||||||
ItemId: item.Id,
|
|
||||||
ItemType: item.Type,
|
|
||||||
Context: info.context
|
|
||||||
}, player);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mirrorIfEnabled(info) {
|
|
||||||
if (info && playbackManager.enableDisplayMirroring()) {
|
|
||||||
const getPlayerInfo = playbackManager.getPlayerInfo();
|
|
||||||
|
|
||||||
if (getPlayerInfo && !getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
|
|
||||||
mirrorItem(info, playbackManager.getCurrentPlayer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyCallback() {
|
|
||||||
// avoid console logs about uncaught promises
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTargetSecondaryText(target) {
|
function getTargetSecondaryText(target) {
|
||||||
if (target.user) {
|
if (target.user) {
|
||||||
return target.user.Name;
|
return target.user.Name;
|
||||||
|
@ -140,10 +113,14 @@ export function show(button) {
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
playbackManager.trySetActivePlayer(target.playerName, target);
|
playbackManager.trySetActivePlayer(target.playerName, target);
|
||||||
|
}).catch(() => {
|
||||||
mirrorIfEnabled();
|
// action sheet closed
|
||||||
}, emptyCallback);
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[playerSelectionMenu] failed to import action sheet', err);
|
||||||
});
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[playerSelectionMenu] failed to get playback targets', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +157,8 @@ function disconnectFromPlayer(currentDeviceName) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// dialog closed
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
playbackManager.setDefaultPlayerActive();
|
playbackManager.setDefaultPlayerActive();
|
||||||
|
@ -272,11 +251,13 @@ function showActivePlayerMenuInternal(playerInfo) {
|
||||||
|
|
||||||
dialogHelper.open(dlg).then(function () {
|
dialogHelper.open(dlg).then(function () {
|
||||||
if (destination === 'nowplaying') {
|
if (destination === 'nowplaying') {
|
||||||
appRouter.showNowPlaying();
|
return appRouter.showNowPlaying();
|
||||||
} else if (destination === 'disconnectFromPlayer') {
|
} else if (destination === 'disconnectFromPlayer') {
|
||||||
disconnectFromPlayer(currentDeviceName);
|
disconnectFromPlayer(currentDeviceName);
|
||||||
}
|
}
|
||||||
}, emptyCallback);
|
}).catch(() => {
|
||||||
|
// dialog closed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMirrorChange() {
|
function onMirrorChange() {
|
||||||
|
@ -287,23 +268,6 @@ function onAutoCastChange() {
|
||||||
enable(this.checked);
|
enable(this.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('viewshow', function (e) {
|
|
||||||
const state = e.detail.state || {};
|
|
||||||
const item = state.item;
|
|
||||||
|
|
||||||
if (item && item.ServerId) {
|
|
||||||
mirrorIfEnabled({
|
|
||||||
item: item
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.on(appSettings, 'change', function (e, name) {
|
|
||||||
if (name === 'displaymirror') {
|
|
||||||
mirrorIfEnabled();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.on(playbackManager, 'pairing', function () {
|
Events.on(playbackManager, 'pairing', function () {
|
||||||
loading.show();
|
loading.show();
|
||||||
});
|
});
|
||||||
|
|
|
@ -173,6 +173,7 @@ function loadForm(context, user, userSettings, apiClient) {
|
||||||
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
|
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
|
||||||
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
||||||
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
||||||
|
context.querySelector('.chkEnableAudioNormalization').checked = userSettings.enableAudioNormalization();
|
||||||
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
||||||
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
|
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
|
||||||
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
|
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
|
||||||
|
@ -217,7 +218,7 @@ function saveUser(context, user, userSettingsInstance, apiClient) {
|
||||||
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
|
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
|
||||||
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
|
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
|
||||||
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
||||||
|
userSettingsInstance.enableAudioNormalization(context.querySelector('.chkEnableAudioNormalization').checked);
|
||||||
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
||||||
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
|
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
|
||||||
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
|
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
|
||||||
|
|
|
@ -72,6 +72,14 @@
|
||||||
${TabAdvanced}
|
${TabAdvanced}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" class="chkEnableAudioNormalization" />
|
||||||
|
<span>${EnableAudioNormalization}</span>
|
||||||
|
</label>
|
||||||
|
<div class="fieldDescription checkboxFieldDescription">${EnableAudioNormalizationHelp}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkPreferFmp4HlsContainer" />
|
<input type="checkbox" is="emby-checkbox" class="chkPreferFmp4HlsContainer" />
|
||||||
|
|
|
@ -40,13 +40,13 @@ function getTextStyles(settings, preview) {
|
||||||
|
|
||||||
switch (settings.dropShadow || '') {
|
switch (settings.dropShadow || '') {
|
||||||
case 'raised':
|
case 'raised':
|
||||||
list.push({ name: 'text-shadow', value: '-1px -1px white, 0px -1px white, -1px 0px white, 1px 1px black, 0px 1px black, 1px 0px black' });
|
list.push({ name: 'text-shadow', value: '-0.04em -0.04em #fff, 0px -0.04em #fff, -0.04em 0px #fff, 0.04em 0.04em #000, 0px 0.04em #000, 0.04em 0px #000' });
|
||||||
break;
|
break;
|
||||||
case 'depressed':
|
case 'depressed':
|
||||||
list.push({ name: 'text-shadow', value: '1px 1px white, 0px 1px white, 1px 0px white, -1px -1px black, 0px -1px black, -1px 0px black' });
|
list.push({ name: 'text-shadow', value: '0.04em 0.04em #fff, 0px 0.04em #fff, 0.04em 0px #fff, -0.04em -0.04em #000, 0px -0.04em #000, -0.04em 0px #000' });
|
||||||
break;
|
break;
|
||||||
case 'uniform':
|
case 'uniform':
|
||||||
list.push({ name: 'text-shadow', value: '-1px 0px #000000, 0px 1px #000000, 1px 0px #000000, 0px -1px #000000' });
|
list.push({ name: 'text-shadow', value: '#000 0px 0.03em, #000 0px -0.03em, #000 0px 0.05em, #000 0px -0.05em, #000 0.03em 0px, #000 -0.03em 0px, #000 0.03em 0.03em, #000 -0.03em 0.03em, #000 0.03em -0.03em, #000 -0.03em -0.03em, #000 0.03em 0.05em, #000 -0.03em 0.05em, #000 0.03em -0.05em, #000 -0.03em -0.05em, #000 0.05em 0px, #000 -0.05em 0px, #000 0.05em 0.03em, #000 -0.05em 0.03em, #000 0.05em -0.03em, #000 -0.05em -0.03em' });
|
||||||
break;
|
break;
|
||||||
case 'none':
|
case 'none':
|
||||||
list.push({ name: 'text-shadow', value: 'none' });
|
list.push({ name: 'text-shadow', value: 'none' });
|
||||||
|
@ -93,7 +93,7 @@ function getTextStyles(settings, preview) {
|
||||||
list.push({ name: 'font-variant', value: 'small-caps' });
|
list.push({ name: 'font-variant', value: 'small-caps' });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
list.push({ name: 'font-family', value: 'inherit' });
|
list.push({ name: 'font-family', value: '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol' });
|
||||||
list.push({ name: 'font-variant', value: 'none' });
|
list.push({ name: 'font-variant', value: 'none' });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import escapeHTML from 'escape-html';
|
||||||
|
|
||||||
import datetime from '../../scripts/datetime';
|
import datetime from '../../scripts/datetime';
|
||||||
import loading from '../../components/loading/loading';
|
import loading from '../../components/loading/loading';
|
||||||
import dom from '../../scripts/dom';
|
import dom from '../../scripts/dom';
|
||||||
|
@ -23,13 +25,13 @@ function renderKeys(page, keys) {
|
||||||
let html = '';
|
let html = '';
|
||||||
html += '<tr class="detailTableBodyRow detailTableBodyRow-shaded">';
|
html += '<tr class="detailTableBodyRow detailTableBodyRow-shaded">';
|
||||||
html += '<td class="detailTableBodyCell">';
|
html += '<td class="detailTableBodyCell">';
|
||||||
html += '<button type="button" is="emby-button" data-token="' + item.AccessToken + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
|
html += '<button type="button" is="emby-button" data-token="' + escapeHTML(item.AccessToken) + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
|
||||||
html += '</td>';
|
html += '</td>';
|
||||||
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
||||||
html += item.AccessToken;
|
html += escapeHTML(item.AccessToken);
|
||||||
html += '</td>';
|
html += '</td>';
|
||||||
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
||||||
html += item.AppName || '';
|
html += escapeHTML(item.AppName) || '';
|
||||||
html += '</td>';
|
html += '</td>';
|
||||||
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
|
||||||
const date = datetime.parseISO8601Date(item.DateCreated, true);
|
const date = datetime.parseISO8601Date(item.DateCreated, true);
|
||||||
|
|
|
@ -68,7 +68,6 @@ function loadPage(page) {
|
||||||
page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || '';
|
page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || '';
|
||||||
page.querySelector('#selectCountry').value = config.MetadataCountryCode || '';
|
page.querySelector('#selectCountry').value = config.MetadataCountryCode || '';
|
||||||
page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || '';
|
page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || '';
|
||||||
page.querySelector('#valDummyChapterCount').value = config.DummyChapterCount || '';
|
|
||||||
page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || '';
|
page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || '';
|
||||||
loading.hide();
|
loading.hide();
|
||||||
});
|
});
|
||||||
|
@ -81,7 +80,6 @@ function onSubmit() {
|
||||||
config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value;
|
config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value;
|
||||||
config.MetadataCountryCode = form.querySelector('#selectCountry').value;
|
config.MetadataCountryCode = form.querySelector('#selectCountry').value;
|
||||||
config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value;
|
config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value;
|
||||||
config.DummyChapterCount = form.querySelector('#valDummyChapterCount').value;
|
|
||||||
config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value;
|
config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value;
|
||||||
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
|
ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult);
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,13 +22,9 @@
|
||||||
<div class="verticalSection">
|
<div class="verticalSection">
|
||||||
<h2>${HeaderDummyChapter}</h2>
|
<h2>${HeaderDummyChapter}</h2>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" type="number" id="valDummyChapterDuration" label="${LabelDummyChapterDuration}" min="1"></input>
|
<input is="emby-input" type="number" id="valDummyChapterDuration" label="${LabelDummyChapterDuration}" min="0"></input>
|
||||||
<div class="fieldDescription">${LabelDummyChapterDurationHelp}</div>
|
<div class="fieldDescription">${LabelDummyChapterDurationHelp}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
|
||||||
<input is="emby-input" type="number" id="valDummyChapterCount" label="${LabelDummyChapterCount}" min="0"></input>
|
|
||||||
<div class="fieldDescription">${LabelDummyChapterCountHelp}</div>
|
|
||||||
</div>
|
|
||||||
<div class="selectContainer">
|
<div class="selectContainer">
|
||||||
<select is="emby-select" id="txtChapterImageResolution" label="${LabelChapterImageResolution}"></select>
|
<select is="emby-select" id="txtChapterImageResolution" label="${LabelChapterImageResolution}"></select>
|
||||||
<div class="fieldDescription">
|
<div class="fieldDescription">
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
<div id="notificationSettingPage" data-role="page" class="page type-interior notificationConfigurationPage withTabs">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="content-primary">
|
|
||||||
|
|
||||||
<form class="notificationSettingForm">
|
|
||||||
|
|
||||||
<div class="verticalSection">
|
|
||||||
<div class="sectionTitleContainer flex align-items-center">
|
|
||||||
<h2 class="notificationType sectionTitle"></h2>
|
|
||||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/notifications">${Help}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="checkboxContainer">
|
|
||||||
<input type="checkbox" is="emby-checkbox" id="chkEnabled" />
|
|
||||||
<span>${LabelNotificationEnabled}</span>
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="monitorUsers" style="display: none;">
|
|
||||||
<div class="paperListLabel">${LabelMonitorUsers}</div>
|
|
||||||
<div class="monitorUsersList">
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="selectContainer">
|
|
||||||
<select is="emby-select" id="selectUsers" label="${LabelSendNotificationToUsers}">
|
|
||||||
<option value="All">${OptionAllUsers}</option>
|
|
||||||
<option value="Admins">${OptionAdminUsers}</option>
|
|
||||||
<option value="Custom">${OptionCustomUsers}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="selectCustomUsers" style="display: none;">
|
|
||||||
<br />
|
|
||||||
<label>${LabelSelectUsers}</label>
|
|
||||||
<div class="sendToUsersList">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label>${LabelUseNotificationServices}</label>
|
|
||||||
<div class="servicesList">
|
|
||||||
</div>
|
|
||||||
<div class="fieldDescription">${AdditionalNotificationServices}</div>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<br />
|
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
|
||||||
<span>${Save}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
|
|
||||||
<span>${ButtonCancel}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,121 +0,0 @@
|
||||||
import escapeHtml from 'escape-html';
|
|
||||||
import 'jquery';
|
|
||||||
import '../../../../elements/emby-checkbox/emby-checkbox';
|
|
||||||
import Dashboard from '../../../../utils/dashboard';
|
|
||||||
import { getParameterByName } from '../../../../utils/url.ts';
|
|
||||||
|
|
||||||
function fillItems(elem, items, cssClass, idPrefix, currentList, isEnabledList) {
|
|
||||||
let html = '<div class="checkboxList paperList" style="padding: .5em 1em;">';
|
|
||||||
html += items.map(function (u) {
|
|
||||||
const isChecked = isEnabledList ? currentList.indexOf(u.Id) != -1 : currentList.indexOf(u.Id) == -1;
|
|
||||||
const checkedHtml = isChecked ? ' checked="checked"' : '';
|
|
||||||
return '<label><input is="emby-checkbox" class="' + cssClass + '" type="checkbox" data-itemid="' + u.Id + '"' + checkedHtml + '/><span>' + escapeHtml(u.Name) + '</span></label>';
|
|
||||||
}).join('');
|
|
||||||
html += '</div>';
|
|
||||||
elem.html(html).trigger('create');
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload(page) {
|
|
||||||
const type = getParameterByName('type');
|
|
||||||
const promise1 = ApiClient.getUsers();
|
|
||||||
const promise2 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
|
|
||||||
const promise3 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
|
|
||||||
const promise4 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Services'));
|
|
||||||
Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) {
|
|
||||||
const users = responses[0];
|
|
||||||
const notificationOptions = responses[1];
|
|
||||||
const types = responses[2];
|
|
||||||
const services = responses[3];
|
|
||||||
let notificationConfig = notificationOptions.Options.filter(function (n) {
|
|
||||||
return n.Type == type;
|
|
||||||
})[0];
|
|
||||||
const typeInfo = types.filter(function (n) {
|
|
||||||
return n.Type == type;
|
|
||||||
})[0] || {};
|
|
||||||
|
|
||||||
if (typeInfo.IsBasedOnUserEvent) {
|
|
||||||
$('.monitorUsers', page).show();
|
|
||||||
} else {
|
|
||||||
$('.monitorUsers', page).hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.notificationType', page).html(escapeHtml(typeInfo.Name || '') || 'Unknown Notification');
|
|
||||||
|
|
||||||
if (!notificationConfig) {
|
|
||||||
notificationConfig = {
|
|
||||||
DisabledMonitorUsers: [],
|
|
||||||
SendToUsers: [],
|
|
||||||
DisabledServices: [],
|
|
||||||
SendToUserMode: 'Admins'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers);
|
|
||||||
fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true);
|
|
||||||
fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices);
|
|
||||||
$('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false);
|
|
||||||
$('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function save(page) {
|
|
||||||
const type = getParameterByName('type');
|
|
||||||
const promise1 = ApiClient.getNamedConfiguration(notificationsConfigurationKey);
|
|
||||||
// TODO: Check if this promise is really needed, as it's unused.
|
|
||||||
const promise2 = ApiClient.getJSON(ApiClient.getUrl('Notifications/Types'));
|
|
||||||
Promise.all([promise1, promise2]).then(function (responses) {
|
|
||||||
const notificationOptions = responses[0];
|
|
||||||
let notificationConfig = notificationOptions.Options.filter(function (n) {
|
|
||||||
return n.Type == type;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (!notificationConfig) {
|
|
||||||
notificationConfig = {
|
|
||||||
Type: type
|
|
||||||
};
|
|
||||||
notificationOptions.Options.push(notificationConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationConfig.Enabled = $('#chkEnabled', page).is(':checked');
|
|
||||||
notificationConfig.SendToUserMode = $('#selectUsers', page).val();
|
|
||||||
notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) {
|
|
||||||
return !c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-itemid');
|
|
||||||
});
|
|
||||||
notificationConfig.SendToUsers = $('.chkSendTo', page).get().filter(function (c) {
|
|
||||||
return c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-itemid');
|
|
||||||
});
|
|
||||||
notificationConfig.DisabledServices = $('.chkService', page).get().filter(function (c) {
|
|
||||||
return !c.checked;
|
|
||||||
}).map(function (c) {
|
|
||||||
return c.getAttribute('data-itemid');
|
|
||||||
});
|
|
||||||
ApiClient.updateNamedConfiguration(notificationsConfigurationKey, notificationOptions).then(function () {
|
|
||||||
Dashboard.processServerConfigurationUpdateResult();
|
|
||||||
Dashboard.navigate('notificationsettings.html');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSubmit() {
|
|
||||||
save($(this).parents('.page'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notificationsConfigurationKey = 'notifications';
|
|
||||||
$(document).on('pageinit', '#notificationSettingPage', function () {
|
|
||||||
const page = this;
|
|
||||||
$('#selectUsers', page).on('change', function () {
|
|
||||||
if (this.value == 'Custom') {
|
|
||||||
$('.selectCustomUsers', page).show();
|
|
||||||
} else {
|
|
||||||
$('.selectCustomUsers', page).hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('.notificationSettingForm').off('submit', onSubmit).on('submit', onSubmit);
|
|
||||||
}).on('pageshow', '#notificationSettingPage', function () {
|
|
||||||
reload(this);
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
<div id="notificationSettingsPage" data-role="page" class="page type-interior notificationConfigurationPage">
|
|
||||||
<div>
|
|
||||||
<div class="content-primary">
|
|
||||||
<div class="readOnlyContent">
|
|
||||||
<div class="notificationList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,61 +0,0 @@
|
||||||
import loading from '../../../../components/loading/loading';
|
|
||||||
import globalize from '../../../../scripts/globalize';
|
|
||||||
import '../../../../components/listview/listview.scss';
|
|
||||||
import '../../../../elements/emby-button/emby-button';
|
|
||||||
|
|
||||||
function reload(page) {
|
|
||||||
loading.show();
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Notifications/Types')).then(function (list) {
|
|
||||||
let html = '';
|
|
||||||
let lastCategory = '';
|
|
||||||
let showHelp = true;
|
|
||||||
html += list.map(function (notification) {
|
|
||||||
let itemHtml = '';
|
|
||||||
if (notification.Category !== lastCategory) {
|
|
||||||
lastCategory = notification.Category;
|
|
||||||
if (lastCategory) {
|
|
||||||
itemHtml += '</div>';
|
|
||||||
itemHtml += '</div>';
|
|
||||||
}
|
|
||||||
itemHtml += '<div class="verticalSection verticalSection-extrabottompadding">';
|
|
||||||
itemHtml += '<div class="sectionTitleContainer" style="margin-bottom:1em;">';
|
|
||||||
itemHtml += '<h2 class="sectionTitle">';
|
|
||||||
itemHtml += notification.Category;
|
|
||||||
itemHtml += '</h2>';
|
|
||||||
if (showHelp) {
|
|
||||||
showHelp = false;
|
|
||||||
itemHtml += '<a is="emby-linkbutton" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/notifications">';
|
|
||||||
itemHtml += globalize.translate('Help');
|
|
||||||
itemHtml += '</a>';
|
|
||||||
}
|
|
||||||
itemHtml += '</div>';
|
|
||||||
itemHtml += '<div class="paperList">';
|
|
||||||
}
|
|
||||||
itemHtml += '<a class="listItem listItem-border" is="emby-linkbutton" data-ripple="false" href="notificationsetting.html?type=' + notification.Type + '">';
|
|
||||||
if (notification.Enabled) {
|
|
||||||
itemHtml += '<span class="listItemIcon material-icons notifications_active" aria-hidden="true"></span>';
|
|
||||||
} else {
|
|
||||||
itemHtml += '<span class="listItemIcon material-icons notifications_off" aria-hidden="true" style="background-color:#999;"></span>';
|
|
||||||
}
|
|
||||||
itemHtml += '<div class="listItemBody">';
|
|
||||||
itemHtml += '<div class="listItemBodyText">' + notification.Name + '</div>';
|
|
||||||
itemHtml += '</div>';
|
|
||||||
itemHtml += '<button type="button" is="paper-icon-button-light"><span class="material-icons mode_edit" aria-hidden="true"></span></button>';
|
|
||||||
itemHtml += '</a>';
|
|
||||||
return itemHtml;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
if (list.length) {
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
page.querySelector('.notificationList').innerHTML = html;
|
|
||||||
loading.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (view) {
|
|
||||||
view.addEventListener('viewshow', function () {
|
|
||||||
reload(view);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<div id="btnInstallDiv" class="hide">
|
<div id="btnInstallDiv" class="hide">
|
||||||
<button is="emby-button" type="submit" id="btnInstall" class="raised button-submit block">
|
<button is="emby-button" type="submit" id="btnInstall" class="raised button-submit block">
|
||||||
<span>${Install}</span>
|
<span>${HeaderInstall}</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="fieldDescription">${ServerRestartNeededAfterPluginInstall}</div>
|
<div class="fieldDescription">${ServerRestartNeededAfterPluginInstall}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -852,7 +852,7 @@ function setInitialCollapsibleState(page, item, apiClient, context, user) {
|
||||||
page.querySelector('#additionalPartsCollapsible').classList.add('hide');
|
page.querySelector('#additionalPartsCollapsible').classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Type == 'MusicAlbum') {
|
if (item.Type == 'MusicAlbum' || item.Type == 'MusicArtist') {
|
||||||
renderMusicVideos(page, item, user);
|
renderMusicVideos(page, item, user);
|
||||||
} else {
|
} else {
|
||||||
page.querySelector('#musicVideosCollapsible').classList.add('hide');
|
page.querySelector('#musicVideosCollapsible').classList.add('hide');
|
||||||
|
@ -1719,14 +1719,21 @@ function renderCollectionItemType(page, parentItem, type, items) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMusicVideos(page, item, user) {
|
function renderMusicVideos(page, item, user) {
|
||||||
ServerConnections.getApiClient(item.ServerId).getItems(user.Id, {
|
const request = {
|
||||||
SortBy: 'SortName',
|
SortBy: 'SortName',
|
||||||
SortOrder: 'Ascending',
|
SortOrder: 'Ascending',
|
||||||
IncludeItemTypes: 'MusicVideo',
|
IncludeItemTypes: 'MusicVideo',
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount',
|
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount'
|
||||||
AlbumIds: item.Id
|
};
|
||||||
}).then(function (result) {
|
|
||||||
|
if (item.Type == 'MusicAlbum') {
|
||||||
|
request.AlbumIds = item.Id;
|
||||||
|
} else {
|
||||||
|
request.ArtistIds = item.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConnections.getApiClient(item.ServerId).getItems(user.Id, request).then(function (result) {
|
||||||
if (result.Items.length) {
|
if (result.Items.length) {
|
||||||
page.querySelector('#musicVideosCollapsible').classList.remove('hide');
|
page.querySelector('#musicVideosCollapsible').classList.remove('hide');
|
||||||
const musicVideosContent = page.querySelector('#musicVideosContent');
|
const musicVideosContent = page.querySelector('#musicVideosContent');
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default function (view, params, tabContent, options) {
|
||||||
elem.addEventListener('click', onPreviousPageClick);
|
elem.addEventListener('click', onPreviousPageClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
tabContent.querySelector('.btnShuffle').classList.toggle('hide', result.TotalRecordCount < 1);
|
tabContent.querySelector('.btnShuffle')?.classList.toggle('hide', result.TotalRecordCount < 1);
|
||||||
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
loading.hide();
|
loading.hide();
|
||||||
|
@ -258,7 +258,7 @@ export default function (view, params, tabContent, options) {
|
||||||
itemsContainer.refreshItems();
|
itemsContainer.refreshItems();
|
||||||
});
|
});
|
||||||
|
|
||||||
tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle);
|
tabElement.querySelector('.btnShuffle')?.addEventListener('click', shuffle);
|
||||||
};
|
};
|
||||||
|
|
||||||
let itemsContainer = tabContent.querySelector('.itemsContainer');
|
let itemsContainer = tabContent.querySelector('.itemsContainer');
|
||||||
|
|
|
@ -69,6 +69,10 @@
|
||||||
<div class="osdRatingsText">
|
<div class="osdRatingsText">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button is="emby-ratingbutton" type="button" class="btnUserRating hide autoSize paper-icon-button-light" title="${Rate}">
|
||||||
|
<span class="xlargePaperIconButton material-icons favorite" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button is="paper-icon-button-light" class="btnSubtitles hide autoSize" title="${Subtitles}">
|
<button is="paper-icon-button-light" class="btnSubtitles hide autoSize" title="${Subtitles}">
|
||||||
<span class="xlargePaperIconButton material-icons closed_caption" aria-hidden="true"></span>
|
<span class="xlargePaperIconButton material-icons closed_caption" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import keyboardnavigation from '../../../scripts/keyboardNavigation';
|
||||||
import '../../../styles/scrollstyles.scss';
|
import '../../../styles/scrollstyles.scss';
|
||||||
import '../../../elements/emby-slider/emby-slider';
|
import '../../../elements/emby-slider/emby-slider';
|
||||||
import '../../../elements/emby-button/paper-icon-button-light';
|
import '../../../elements/emby-button/paper-icon-button-light';
|
||||||
|
import '../../../elements/emby-ratingbutton/emby-ratingbutton';
|
||||||
import '../../../styles/videoosd.scss';
|
import '../../../styles/videoosd.scss';
|
||||||
import ServerConnections from '../../../components/ServerConnections';
|
import ServerConnections from '../../../components/ServerConnections';
|
||||||
import shell from '../../../scripts/shell';
|
import shell from '../../../scripts/shell';
|
||||||
|
@ -133,6 +134,17 @@ export default function (view) {
|
||||||
programStartDateMs = 0;
|
programStartDateMs = 0;
|
||||||
programEndDateMs = 0;
|
programEndDateMs = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set currently playing item for favorite button
|
||||||
|
const btnUserRating = view.querySelector('.btnUserRating');
|
||||||
|
|
||||||
|
if (itemHelper.canRate(currentItem)) {
|
||||||
|
btnUserRating.classList.remove('hide');
|
||||||
|
btnUserRating.setItem(currentItem);
|
||||||
|
} else {
|
||||||
|
btnUserRating.classList.add('hide');
|
||||||
|
btnUserRating.setItem(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayTimeWithoutAmPm(date, showSeconds) {
|
function getDisplayTimeWithoutAmPm(date, showSeconds) {
|
||||||
|
@ -1727,6 +1739,9 @@ export default function (view) {
|
||||||
view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection);
|
view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection);
|
||||||
view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection);
|
view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection);
|
||||||
|
|
||||||
|
// HACK: Remove `emby-button` from the rating button to make it look like the other buttons
|
||||||
|
view.querySelector('.btnUserRating').classList.remove('emby-button');
|
||||||
|
|
||||||
// Register to SyncPlay playback events and show big animated icon
|
// Register to SyncPlay playback events and show big animated icon
|
||||||
const showIcon = (action) => {
|
const showIcon = (action) => {
|
||||||
let primary_icon_name = '';
|
let primary_icon_name = '';
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
<div class="fieldDescription">${SelectAdminUsername}</div>
|
<div class="fieldDescription">${SelectAdminUsername}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" id="txtManualPassword" type="password" label="${LabelPassword}" />
|
<input is="emby-input" id="txtManualPassword" type="password" label="${LabelPassword}" required="required" />
|
||||||
<div class="fieldDescription">${LeaveBlankToNotSetAPassword}</div>
|
<div class="fieldDescription">${PasswordRequiredForAdmin}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" id="txtPasswordConfirm" type="password" label="${LabelPasswordConfirm}" />
|
<input is="emby-input" id="txtPasswordConfirm" type="password" label="${LabelPasswordConfirm}" required="required" />
|
||||||
</div>
|
</div>
|
||||||
<p>${MoreUsersCanBeAddedLater}</p>
|
<p>${MoreUsersCanBeAddedLater}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,27 +11,40 @@ function getApiClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextWizardPage() {
|
function nextWizardPage() {
|
||||||
Dashboard.navigate('wizardlibrary.html');
|
Dashboard.navigate('wizardlibrary.html')
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[Wizard > User] error navigating to library setup', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateUserComplete(result) {
|
function onUpdateUserComplete(result) {
|
||||||
console.debug('user update complete: ' + result);
|
console.debug('[Wizard > User] user update complete:', result);
|
||||||
loading.hide();
|
loading.hide();
|
||||||
nextWizardPage();
|
nextWizardPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onUpdateUserError(result) {
|
||||||
|
const message = await result.text();
|
||||||
|
console.warn('[Wizard > User] user update failed:', message);
|
||||||
|
toast(globalize.translate('ErrorDefault'));
|
||||||
|
loading.hide();
|
||||||
|
}
|
||||||
|
|
||||||
function submit(form) {
|
function submit(form) {
|
||||||
loading.show();
|
loading.show();
|
||||||
const apiClient = getApiClient();
|
const apiClient = getApiClient();
|
||||||
apiClient.ajax({
|
apiClient
|
||||||
type: 'POST',
|
.ajax({
|
||||||
data: JSON.stringify({
|
type: 'POST',
|
||||||
Name: form.querySelector('#txtUsername').value,
|
data: JSON.stringify({
|
||||||
Password: form.querySelector('#txtManualPassword').value
|
Name: form.querySelector('#txtUsername').value,
|
||||||
}),
|
Password: form.querySelector('#txtManualPassword').value
|
||||||
url: apiClient.getUrl('Startup/User'),
|
}),
|
||||||
contentType: 'application/json'
|
url: apiClient.getUrl('Startup/User'),
|
||||||
}).then(onUpdateUserComplete);
|
contentType: 'application/json'
|
||||||
|
})
|
||||||
|
.then(onUpdateUserComplete)
|
||||||
|
.catch(onUpdateUserError);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(e) {
|
function onSubmit(e) {
|
||||||
|
|
5
src/global.d.ts
vendored
5
src/global.d.ts
vendored
|
@ -4,5 +4,10 @@ export declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
ApiClient: ApiClient;
|
ApiClient: ApiClient;
|
||||||
Events: Events;
|
Events: Events;
|
||||||
|
NativeShell: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentEventMap {
|
||||||
|
'viewshow': CustomEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const WebConfigProvider: FC = ({ children }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetchLocal('config.json', { cache: 'no-cache' });
|
const response = await fetchLocal('config.json', { cache: 'no-store' });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('network response was not ok');
|
throw new Error('network response was not ok');
|
||||||
|
|
4
src/index.d.ts
vendored
Normal file
4
src/index.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
declare module '*.png' {
|
||||||
|
const value: any;
|
||||||
|
export = value;
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import { appHost } from './components/apphost';
|
||||||
import { getPlugins } from './scripts/settings/webSettings';
|
import { getPlugins } from './scripts/settings/webSettings';
|
||||||
import { pluginManager } from './components/pluginManager';
|
import { pluginManager } from './components/pluginManager';
|
||||||
import packageManager from './components/packageManager';
|
import packageManager from './components/packageManager';
|
||||||
|
import './components/playback/displayMirrorManager.ts';
|
||||||
import { appRouter, history } from './components/router/appRouter';
|
import { appRouter, history } from './components/router/appRouter';
|
||||||
import './elements/emby-button/emby-button';
|
import './elements/emby-button/emby-button';
|
||||||
import './scripts/autoThemes';
|
import './scripts/autoThemes';
|
||||||
|
|
|
@ -624,6 +624,7 @@ class ChromecastPlayer {
|
||||||
isLocalPlayer: false,
|
isLocalPlayer: false,
|
||||||
appName: PlayerName,
|
appName: PlayerName,
|
||||||
deviceName: appName,
|
deviceName: appName,
|
||||||
|
deviceType: 'cast',
|
||||||
supportedCommands: [
|
supportedCommands: [
|
||||||
'VolumeUp',
|
'VolumeUp',
|
||||||
'VolumeDown',
|
'VolumeDown',
|
||||||
|
|
|
@ -101,6 +101,7 @@ class HtmlAudioPlayer {
|
||||||
self._currentTime = null;
|
self._currentTime = null;
|
||||||
|
|
||||||
const elem = createMediaElement();
|
const elem = createMediaElement();
|
||||||
|
|
||||||
return setCurrentSrc(elem, options);
|
return setCurrentSrc(elem, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +111,17 @@ class HtmlAudioPlayer {
|
||||||
|
|
||||||
let val = options.url;
|
let val = options.url;
|
||||||
console.debug('playing url: ' + val);
|
console.debug('playing url: ' + val);
|
||||||
|
import('../../scripts/settings/userSettings').then((userSettings) => {
|
||||||
|
if (userSettings.enableAudioNormalization() && options.item.LUFS != null) {
|
||||||
|
const dbGain = -18 - options.item.LUFS;
|
||||||
|
self.gainNode.gain.value = Math.pow(10, (dbGain / 20));
|
||||||
|
} else {
|
||||||
|
self.gainNode.gain.value = 1;
|
||||||
|
}
|
||||||
|
console.debug('gain:' + self.gainNode.gain.value);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to add/change gainNode', err);
|
||||||
|
});
|
||||||
|
|
||||||
// Convert to seconds
|
// Convert to seconds
|
||||||
const seconds = (options.playerStartPositionTicks || 0) / 10000000;
|
const seconds = (options.playerStartPositionTicks || 0) / 10000000;
|
||||||
|
@ -245,9 +257,29 @@ class HtmlAudioPlayer {
|
||||||
|
|
||||||
self._mediaElement = elem;
|
self._mediaElement = elem;
|
||||||
|
|
||||||
|
addGainElement(elem);
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addGainElement(elem) {
|
||||||
|
try {
|
||||||
|
const AudioContext = window.AudioContext || window.webkitAudioContext; /* eslint-disable-line compat/compat */
|
||||||
|
|
||||||
|
const audioCtx = new AudioContext();
|
||||||
|
const source = audioCtx.createMediaElementSource(elem);
|
||||||
|
|
||||||
|
const gainNode = audioCtx.createGain();
|
||||||
|
|
||||||
|
source.connect(gainNode);
|
||||||
|
gainNode.connect(audioCtx.destination);
|
||||||
|
|
||||||
|
self.gainNode = gainNode;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Web Audio API is not supported in this browser', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onEnded() {
|
function onEnded() {
|
||||||
htmlMediaHelper.onEndedInternal(self, this, onError);
|
htmlMediaHelper.onEndedInternal(self, this, onError);
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,10 @@ function zoomIn(elem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTrackEventText(text, useHtml) {
|
function normalizeTrackEventText(text, useHtml) {
|
||||||
const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, '');
|
const result = text
|
||||||
|
.replace(/\\N/gi, '\n') // Correct newline characters
|
||||||
|
.replace(/\r/gi, '') // Remove carriage return characters
|
||||||
|
.replace(/{\\.*?}/gi, ''); // Remove ass/ssa tags
|
||||||
return useHtml ? result.replace(/\n/gi, '<br>') : result;
|
return useHtml ? result.replace(/\n/gi, '<br>') : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1252,37 +1255,45 @@ export class HtmlVideoPlayer {
|
||||||
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
|
||||||
api_key: apiClient.accessToken()
|
api_key: apiClient.accessToken()
|
||||||
});
|
});
|
||||||
const options = {
|
|
||||||
video: videoElement,
|
|
||||||
subUrl: getTextTrackUrl(track, item),
|
|
||||||
fonts: avaliableFonts,
|
|
||||||
fallbackFont: 'liberation sans',
|
|
||||||
availableFonts: { 'liberation sans': `${appRouter.baseUrl()}/default.woff2` },
|
|
||||||
// Disabled eslint compat, but is safe as corejs3 polyfills URL
|
|
||||||
// eslint-disable-next-line compat/compat
|
|
||||||
workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url),
|
|
||||||
// eslint-disable-next-line compat/compat
|
|
||||||
legacyWorkerUrl: new URL('jassub/dist/jassub-worker-legacy.js', import.meta.url),
|
|
||||||
timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000,
|
|
||||||
// new jassub options; override all, even defaults
|
|
||||||
blendMode: 'js',
|
|
||||||
asyncRender: true,
|
|
||||||
// firefox implements offscreen canvas, but not according to spec which causes errors
|
|
||||||
offscreenRender: !browser.firefox,
|
|
||||||
// RVFC is polyfilled everywhere, but webOS 2 reports polyfill API's as functional even tho they aren't
|
|
||||||
onDemandRender: browser.web0sVersion !== 2,
|
|
||||||
useLocalFonts: true,
|
|
||||||
dropAllAnimations: false,
|
|
||||||
libassMemoryLimit: 40,
|
|
||||||
libassGlyphLimit: 40,
|
|
||||||
targetFps: 24,
|
|
||||||
prescaleFactor: 0.8,
|
|
||||||
prescaleHeightLimit: 1080,
|
|
||||||
maxRenderHeight: 2160
|
|
||||||
};
|
|
||||||
// TODO: replace with `event-target-polyfill` once https://github.com/benlesh/event-target-polyfill/pull/12 or 11 is merged
|
// TODO: replace with `event-target-polyfill` once https://github.com/benlesh/event-target-polyfill/pull/12 or 11 is merged
|
||||||
import('event-target-polyfill').then(() => {
|
import('event-target-polyfill').then(() => {
|
||||||
import('jassub').then(({ default: JASSUB }) => {
|
import('jassub').then(({ default: JASSUB }) => {
|
||||||
|
// test SIMD support
|
||||||
|
JASSUB._test();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
video: videoElement,
|
||||||
|
subUrl: getTextTrackUrl(track, item),
|
||||||
|
fonts: avaliableFonts,
|
||||||
|
fallbackFont: 'liberation sans',
|
||||||
|
availableFonts: { 'liberation sans': `${appRouter.baseUrl()}/default.woff2` },
|
||||||
|
// Disabled eslint compat, but is safe as corejs3 polyfills URL
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url),
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url),
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url),
|
||||||
|
// eslint-disable-next-line compat/compat
|
||||||
|
modernWasmUrl : new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url),
|
||||||
|
timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000,
|
||||||
|
// new jassub options; override all, even defaults
|
||||||
|
blendMode: 'js',
|
||||||
|
asyncRender: true,
|
||||||
|
offscreenRender: true,
|
||||||
|
// RVFC is polyfilled everywhere, but webOS 2 reports polyfill API's as functional even tho they aren't
|
||||||
|
onDemandRender: browser.web0sVersion !== 2,
|
||||||
|
useLocalFonts: true,
|
||||||
|
dropAllAnimations: false,
|
||||||
|
dropAllBlur: !JASSUB._supportsSIMD,
|
||||||
|
libassMemoryLimit: 40,
|
||||||
|
libassGlyphLimit: 40,
|
||||||
|
targetFps: 24,
|
||||||
|
prescaleFactor: 0.8,
|
||||||
|
prescaleHeightLimit: 1080,
|
||||||
|
maxRenderHeight: 2160
|
||||||
|
};
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
apiClient.getNamedConfiguration('encoding'),
|
apiClient.getNamedConfiguration('encoding'),
|
||||||
// Worker in Tizen 5 doesn't resolve relative path with async request
|
// Worker in Tizen 5 doesn't resolve relative path with async request
|
||||||
|
|
|
@ -516,10 +516,9 @@ function createToolsMenuList(pluginItems) {
|
||||||
icon: 'bug_report'
|
icon: 'bug_report'
|
||||||
});
|
});
|
||||||
links.push({
|
links.push({
|
||||||
name: globalize.translate('TabNotifications'),
|
name: globalize.translate('Notifications'),
|
||||||
icon: 'notifications',
|
icon: 'notifications',
|
||||||
href: '#/notificationsettings.html',
|
href: '#/notificationsettings.html'
|
||||||
pageIds: ['notificationSettingsPage', 'notificationSettingPage']
|
|
||||||
});
|
});
|
||||||
links.push({
|
links.push({
|
||||||
name: globalize.translate('TabPlugins'),
|
name: globalize.translate('TabPlugins'),
|
||||||
|
|
|
@ -156,6 +156,19 @@ export class UserSettings {
|
||||||
return toBoolean(this.get('enableCinemaMode', false), true);
|
return toBoolean(this.get('enableCinemaMode', false), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set 'Enable Audio Normalization' state.
|
||||||
|
* @param {boolean|undefined} val - Flag to enable 'Enable Audio Normalization' or undefined.
|
||||||
|
* @return {boolean} 'Enable Audio Normalization' state.
|
||||||
|
*/
|
||||||
|
enableAudioNormalization(val) {
|
||||||
|
if (val !== undefined) {
|
||||||
|
return this.set('enableAudioNormalization', val.toString(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toBoolean(this.get('enableAudioNormalization', false), true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set 'Next Video Info Overlay' state.
|
* Get or set 'Next Video Info Overlay' state.
|
||||||
* @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined.
|
* @param {boolean|undefined} val - Flag to enable 'Next Video Info Overlay' or undefined.
|
||||||
|
@ -592,6 +605,7 @@ export const serverConfig = currentSettings.serverConfig.bind(currentSettings);
|
||||||
export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(currentSettings);
|
export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(currentSettings);
|
||||||
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
|
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
|
||||||
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
|
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
|
||||||
|
export const enableAudioNormalization = currentSettings.enableAudioNormalization.bind(currentSettings);
|
||||||
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
|
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
|
||||||
export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings);
|
export const enableVideoRemainingTime = currentSettings.enableVideoRemainingTime.bind(currentSettings);
|
||||||
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
|
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
|
||||||
|
|
|
@ -7,7 +7,7 @@ async function getConfig() {
|
||||||
if (data) return Promise.resolve(data);
|
if (data) return Promise.resolve(data);
|
||||||
try {
|
try {
|
||||||
const response = await fetchLocal('config.json', {
|
const response = await fetchLocal('config.json', {
|
||||||
cache: 'no-cache'
|
cache: 'no-store'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"AsManyAsPossible": "So veel moontlik",
|
"AsManyAsPossible": "So veel moontlik",
|
||||||
"Artists": "Kunstenare",
|
"Artists": "Kunstenare",
|
||||||
"Art": "Kuns",
|
"Art": "Kuns",
|
||||||
"AroundTime": "Ongeveer",
|
"AroundTime": "Ongeveer {0}",
|
||||||
"Anytime": "Enige tyd",
|
"Anytime": "Enige tyd",
|
||||||
"AnyLanguage": "Enige Taal",
|
"AnyLanguage": "Enige Taal",
|
||||||
"AlwaysPlaySubtitlesHelp": "Subtitels wat ooreenstem met taal voorkeur sal gelaai work ongeag oudio taal.",
|
"AlwaysPlaySubtitlesHelp": "Subtitels wat ooreenstem met taal voorkeur sal gelaai work ongeag oudio taal.",
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
"ButtonWebsite": "Webtuiste",
|
"ButtonWebsite": "Webtuiste",
|
||||||
"ButtonUseQuickConnect": "Gebruik Vinnige Konneksie",
|
"ButtonUseQuickConnect": "Gebruik Vinnige Konneksie",
|
||||||
"ButtonUninstall": "Verwyder",
|
"ButtonUninstall": "Verwyder",
|
||||||
"ButtonTrailer": "Voorskou",
|
"ButtonTrailer": "Lokprent",
|
||||||
"ButtonTogglePlaylist": "Snitlys",
|
"ButtonTogglePlaylist": "Snitlys",
|
||||||
"ButtonSubmit": "Dien In",
|
"ButtonSubmit": "Dien In",
|
||||||
"ButtonSplit": "Verdeel",
|
"ButtonSplit": "Verdeel",
|
||||||
|
@ -229,10 +229,10 @@
|
||||||
"Ended": "Geëindig",
|
"Ended": "Geëindig",
|
||||||
"EnableDetailsBannerHelp": "Wys 'n banier-beeld bo aan die item besonderhede blad.",
|
"EnableDetailsBannerHelp": "Wys 'n banier-beeld bo aan die item besonderhede blad.",
|
||||||
"EnableDetailsBanner": "Besonderhede Banier",
|
"EnableDetailsBanner": "Besonderhede Banier",
|
||||||
"EnableThemeVideosHelp": "Speel tema-videos in die agtergrond terwyl deur die versameling geblaai word.",
|
"EnableThemeVideosHelp": "Speel temavideo's in die agtergrond terwyl jy deur die biblioteek blaai.",
|
||||||
"EnableThemeSongsHelp": "Speel tema-liedjies in die agtergrond terwyl daur die versameling geblaai word.",
|
"EnableThemeSongsHelp": "Speel die temaliedjies in die agtergrond terwyl jy deur die biblioteek blaai.",
|
||||||
"EnableStreamLoopingHelp": "Aktiveer hierdie as die lewendige stroom net 'n paar sekondes data bevat en gereeld versoek moet word. Om hierdie te aktiveer wanneer dit nie nodig is nie kan probleme veroorsaak.",
|
"EnableStreamLoopingHelp": "Aktiveer die opsie as die regstreekse strome slegs 'n paar sekondes se data bevat en voortdurend aangevra moet word. Om dit te aktiveer wanneer dit nie nodig is nie, kan probleme veroorsaak.",
|
||||||
"EnableStreamLooping": "Auto-lus lewendige strome",
|
"EnableStreamLooping": "Outo-lus regstreekse strome",
|
||||||
"EnableQuickConnect": "Aktiveer vinnige-inskakel op hierdie bediener",
|
"EnableQuickConnect": "Aktiveer vinnige-inskakel op hierdie bediener",
|
||||||
"EnablePhotosHelp": "Beelde sal opgetel en gewys word saam met ander media leêrs.",
|
"EnablePhotosHelp": "Beelde sal opgetel en gewys word saam met ander media leêrs.",
|
||||||
"EnablePhotos": "Wys fotos",
|
"EnablePhotos": "Wys fotos",
|
||||||
|
@ -508,7 +508,7 @@
|
||||||
"DashboardServerName": "Bediener: {0}",
|
"DashboardServerName": "Bediener: {0}",
|
||||||
"DashboardOperatingSystem": "Bedryfstelsel: {0}",
|
"DashboardOperatingSystem": "Bedryfstelsel: {0}",
|
||||||
"DashboardArchitecture": "Argitektuur: {0}",
|
"DashboardArchitecture": "Argitektuur: {0}",
|
||||||
"ColorPrimaries": "Kleur primêre",
|
"ColorPrimaries": "Primêre kleure",
|
||||||
"ClearQueue": "Maak waglys skoon",
|
"ClearQueue": "Maak waglys skoon",
|
||||||
"ButtonPlayer": "Speler",
|
"ButtonPlayer": "Speler",
|
||||||
"ButtonCast": "Plaas media",
|
"ButtonCast": "Plaas media",
|
||||||
|
@ -540,14 +540,14 @@
|
||||||
"HeaderAddUpdateSubtitle": "Voeg ondertitel by / werk dit op",
|
"HeaderAddUpdateSubtitle": "Voeg ondertitel by / werk dit op",
|
||||||
"Framerate": "Raamkoers",
|
"Framerate": "Raamkoers",
|
||||||
"Filter": "Filtreerder",
|
"Filter": "Filtreerder",
|
||||||
"EnableTonemapping": "Aktiveer toonkaarting",
|
"EnableTonemapping": "Aktiveer kleurtoon kartering",
|
||||||
"EnableFasterAnimationsHelp": "Gebruik vinniger animasies en oorgange.",
|
"EnableFasterAnimationsHelp": "Gebruik vinniger animasies en oorgange.",
|
||||||
"EnableFasterAnimations": "Vinniger animasies",
|
"EnableFasterAnimations": "Vinniger animasies",
|
||||||
"EnableBlurHashHelp": "Beelde wat nog gelaai word, sal met 'n unieke plekhouer vertoon word.",
|
"EnableBlurHashHelp": "Beelde wat nog gelaai word, sal met 'n unieke plekhouer vertoon word.",
|
||||||
"Depressed": "Depressief",
|
"Depressed": "Ingedrukte",
|
||||||
"Bwdif": "BWDIF",
|
"Bwdif": "BWDIF",
|
||||||
"ButtonSyncPlay": "Sinkroniseer Speler",
|
"ButtonSyncPlay": "Sinkroniseer Speler",
|
||||||
"AllowTonemappingHelp": "Toonkartering kan die dinamiese omvang van 'n video van HDR na SDR verander, terwyl beelddetails en kleure behoue bly, wat baie belangrike inligting is om die oorspronklike toneel voor te stel. Werk tans slegs wanneer video's gekodeer word met ingeboude HDR10- of HLG-metadata. As die afspeel nie glad is nie of misluk, oorweeg dit om die ooreenstemmende hardeware-dekodeerder uit te skakel.",
|
"AllowTonemappingHelp": "Die kartering van kleurtoon kan die dinamiese omvang van 'n video van HDR na SDR verander, terwyl die detail van die beeld en kleur behoue bly, wat baie belangrike inligting is om die oorspronklike toneel voor te stel. Werk tans slegs wanneer video's gekodeer word met ingeboude 10bit HDR10, HLG en DoVi-metadata. Die terugspeel benodig die ooreenkomstige OpenCL of CUDA looptyd.",
|
||||||
"LabelFolder": "Vouer",
|
"LabelFolder": "Vouer",
|
||||||
"LabelFinish": "Voltooi",
|
"LabelFinish": "Voltooi",
|
||||||
"LabelFileOrUrl": "Lêer of URL",
|
"LabelFileOrUrl": "Lêer of URL",
|
||||||
|
@ -812,5 +812,55 @@
|
||||||
"ButtonExitApp": "Verlaat Program",
|
"ButtonExitApp": "Verlaat Program",
|
||||||
"ButtonClose": "Maak toe",
|
"ButtonClose": "Maak toe",
|
||||||
"ButtonBackspace": "Terug",
|
"ButtonBackspace": "Terug",
|
||||||
"AddToFavorites": "Voeg by gunstelinge"
|
"AddToFavorites": "Voeg by gunstelinge",
|
||||||
|
"EnableAudioNormalizationHelp": "Oudionormalisering sal 'n konstante versterking byvoeg om die gemiddeld op die verlangde vlak te hou (-18dB).",
|
||||||
|
"EnableAudioNormalization": "Oudionormalisering",
|
||||||
|
"RefreshQueued": "Herlaai in waglys geplaas.",
|
||||||
|
"LabelTimeLimitHours": "Tydsbeperking (ure)",
|
||||||
|
"LabelMaxMuxingQueueSize": "Maksimum muxing waglys-grootte",
|
||||||
|
"LabelModelName": "Model naam",
|
||||||
|
"LabelPasswordRecoveryPinCode": "PIN kode",
|
||||||
|
"LabelSelectFolderGroups": "Groepeer die inhoud outomaties uit die volgende lêers na 'Flieks', 'Musiek' en 'TV'",
|
||||||
|
"LabelSyncPlaySettingsExtraTimeOffsetHelp": "Pas tydsverstelling verplasing aan (in ms) met geselekteerde toestel vir tydsinkronisering. Pas versigtig aan.",
|
||||||
|
"EnableCardLayout": "Wys kaartuitleg",
|
||||||
|
"LabelLoginDisclaimer": "Teken in vrywaring",
|
||||||
|
"LabelManufacturer": "Vervaardiger",
|
||||||
|
"LabelModelUrl": "Model-URL",
|
||||||
|
"None": "Geen",
|
||||||
|
"LabelMinAudiobookResumeHelp": "Titels word as ongespeel beskou as dit voor hierdie tyd gestop is.",
|
||||||
|
"DownloadAll": "Laai alles af",
|
||||||
|
"AllowCollectionManagement": "Laat hierdie gebruiker toe om versamelings te bestuur",
|
||||||
|
"EnableRewatchingNextUp": "Aktiveer kyk weer in Volgende",
|
||||||
|
"EnableRewatchingNextUpHelp": "Aktiveer die wys van reeds gekykte episodes in 'Volgende'-afdelings.",
|
||||||
|
"LabelMaxAudiobookResume": "Oudioboek oorblywende minute om te hervat",
|
||||||
|
"LabelModelNumber": "Model nommer",
|
||||||
|
"LabelAutomaticallyAddToCollectionHelp": "Wanneer ten minste 2 flieks dieselfde versamelingsnaam het, sal hulle outomaties by die versameling gevoeg word.",
|
||||||
|
"LabelSyncPlaySettingsMaxDelaySpeedToSyncHelp": "Maksimum terugspeelvertraging (in ms) waarna SkipToSync in plaas van SpeedToSync gebruik word.",
|
||||||
|
"LabelMaxDaysForNextUpHelp": "Stel die maksimum aantal dae wat 'n program in die 'Volgende'-lys moet bly sonder om dit te kyk.",
|
||||||
|
"LabelMaxDaysForNextUp": "Maksimum dae in 'Volgende'",
|
||||||
|
"LabelLogs": "Logs",
|
||||||
|
"LabelMaxChromecastBitrate": "Google Cast-stroomkwaliteit",
|
||||||
|
"LabelOpenclDeviceHelp": "Dit is die OpenCL-toestel wat vir kleurtoon kartering gebruik word. Die linkerkant van die kolletjie is die platformnommer, en die regterkant is die toestelnommer op die platform. Die verstekwaarde is 0.0. Die FFmpeg-toepassingslêer wat die OpenCL-hardewareversnellingsmetode bevat, word vereis.",
|
||||||
|
"LabelSlowResponseEnabled": "Teken 'n waarskuwingsboodskap aan as die bediener te traag was om te reageer",
|
||||||
|
"LabelSyncPlaySettingsSpeedToSyncDurationHelp": "Aantal millisekondes wat deur SpeedToSync gebruik word om afspeelposisie reg te stel.",
|
||||||
|
"MessageDownloadQueued": "Aflaai in waglys geplaas.",
|
||||||
|
"LabelLoginDisclaimerHelp": "'n Boodskap wat onderaan die aanmeldbladsy vertoon sal word.",
|
||||||
|
"LabelMatchType": "Pas tipe",
|
||||||
|
"LabelManufacturerUrl": "Vervaardiger-URL",
|
||||||
|
"LabelMaxAudiobookResumeHelp": "Titels word aanvaar as klaar gespeel as dit gestop word en die oorblywende tyd is minder as hierdie waarde.",
|
||||||
|
"LabelMaxBackdropsPerItem": "Maksimum aantal agtergronde per item",
|
||||||
|
"LabelMaxMuxingQueueSizeHelp": "Maksimum aantal pakkies wat opgehoop kan word terwyl daar gewag word vir alle strome om te inisialiseer. Probeer om dit te verhoog as jy steeds 'Too many packets buffered for output stream'-fout in FFmpeg-logboek sien. Die aanbevole waarde is 2048.",
|
||||||
|
"LabelMetadataSaversHelp": "Kies die lêerformate om te gebruik wanneer metadata gestoor word.",
|
||||||
|
"LabelModelDescription": "Model beskrywing",
|
||||||
|
"LabelMusicStreamingTranscodingBitrateHelp": "Spesifiseer die maksimum bissnelheid as musiek gestroom word.",
|
||||||
|
"LabelNumberOfGuideDaysHelp": "Die aflaai van meer dae se gidsdata bied die vermoë om verder vooruit te skeduleer en meer items te sien, maar dit sal ook langer neem om af te laai. Die outomatiese keuse kies op grond van die aantal kanale wat daar is.",
|
||||||
|
"LabelQuickConnectCode": "Vinnige verbinding kode",
|
||||||
|
"LabelRemoteClientBitrateLimit": "Internetstroom-bissnelheidlimiet (Mbps)",
|
||||||
|
"LabelSerialNumber": "Reeks nommer",
|
||||||
|
"LabelSlowResponseTime": "Tyd in ms waarna 'n reaksie as stadig beskou word",
|
||||||
|
"LabelSonyAggregationFlagsHelp": "Bepaal die inhoud van die 'aggregationFlags'-element in die 'urn:schemas-sonycom:av'-naamruimte.",
|
||||||
|
"LabelSyncPlayAccess": "SyncPlay toegang",
|
||||||
|
"LabelSyncPlaySettingsMinDelaySpeedToSyncHelp": "Minimum terugspeelvertraging (in ms) waarna SpeedToSync probeer om terugspeelposisie reg te stel.",
|
||||||
|
"LabelSyncPlaySettingsMinDelaySkipToSyncHelp": "Minimum terugspeelvertraging (in ms) waarna SkipToSync probeer om terugspeelposisie reg te stel.",
|
||||||
|
"LabelLockItemToPreventChanges": "Sluit hierdie item om toekomstige veranderinge te voorkom"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1710,5 +1710,14 @@
|
||||||
"LabelEnableAudioVbrHelp": "Пераменны бітрэйт забяспечвае лепшае суадносіны якасці да сярэдняга, але ў некаторых рэдкіх выпадках можа выклікаць праблемы з буферызацыяй і сумяшчальнасцю.",
|
"LabelEnableAudioVbrHelp": "Пераменны бітрэйт забяспечвае лепшае суадносіны якасці да сярэдняга, але ў некаторых рэдкіх выпадках можа выклікаць праблемы з буферызацыяй і сумяшчальнасцю.",
|
||||||
"TonemappingModeHelp": "Выберыце рэжым танальнага адлюстравання. Калі вы бачыце, што блікі блякнуць, паспрабуйце пераключыцца ў рэжым RGB.",
|
"TonemappingModeHelp": "Выберыце рэжым танальнага адлюстравання. Калі вы бачыце, што блікі блякнуць, паспрабуйце пераключыцца ў рэжым RGB.",
|
||||||
"LabelTonemappingMode": "Рэжым танальнага адлюстравання",
|
"LabelTonemappingMode": "Рэжым танальнага адлюстравання",
|
||||||
"Select": "Выбраць"
|
"Select": "Выбраць",
|
||||||
|
"EnableAudioNormalizationHelp": "Нармалізацыя гуку дадасць пастаяннае ўзмацненне, каб падтрымліваць сярэдняе значэнне на жаданым узроўні (-18 дБ).",
|
||||||
|
"EnableAudioNormalization": "Нармалізацыя гуку",
|
||||||
|
"LabelEnableLUFSScan": "Уключыць сканаванне LUFS",
|
||||||
|
"LabelEnableLUFSScanHelp": "Уключыце сканаванне LUFS на наяўнасць музыкі (гэта зойме больш часу і рэсурсаў).",
|
||||||
|
"Studio": "Студыя",
|
||||||
|
"MenuOpen": "Адкрыць меню",
|
||||||
|
"MenuClose": "Зачыніць меню",
|
||||||
|
"AllowCollectionManagement": "Дазволіць гэтаму карыстальніку кіраваць калекцыямі",
|
||||||
|
"UserMenu": "Меню карыстальніка"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1459,5 +1459,15 @@
|
||||||
"IgnoreDtsHelp": "Изключването на опцията може да теши някои проблеми, напр. липсващ звук на канали с отделни звукови и видео потоци.",
|
"IgnoreDtsHelp": "Изключването на опцията може да теши някои проблеми, напр. липсващ звук на канали с отделни звукови и видео потоци.",
|
||||||
"LabelHDHomerunPortRangeHelp": "Ограничи портовете на HDHomeRun UDP до тези стойности. (По подразбиране 1024 - 645535).",
|
"LabelHDHomerunPortRangeHelp": "Ограничи портовете на HDHomeRun UDP до тези стойности. (По подразбиране 1024 - 645535).",
|
||||||
"LabelHDHomerunPortRange": "HDHomeRun диапазон на портове",
|
"LabelHDHomerunPortRange": "HDHomeRun диапазон на портове",
|
||||||
"DownloadAll": "Изтегли всички"
|
"DownloadAll": "Изтегли всички",
|
||||||
|
"LabelChapterImageResolution": "Резолюция",
|
||||||
|
"LabelMaxDaysForNextUpHelp": "Задайте максималния брой дни, през които едно шоу би трябвало да остане в списъка „Next Up“, без да го гледате.",
|
||||||
|
"LabelEnableAudioVbr": "Разреши VBR звуково кодиране",
|
||||||
|
"HeaderPerformance": "Производителност",
|
||||||
|
"LabelChapterImageResolutionHelp": "Резолюцията на извлечените снимки към раздела.",
|
||||||
|
"AllowCollectionManagement": "Позволи този потребител да управлява колекции",
|
||||||
|
"LabelDummyChapterDuration": "Интервал",
|
||||||
|
"LabelDummyChapterCount": "Граница",
|
||||||
|
"LabelEnableAudioVbrHelp": "Динамичният битрейт предлага по-добро съотношение между качество и среден битрейт, но в някои редки случаи може да причини проблеми с буферирането и съвместимостта.",
|
||||||
|
"LabelIconMaxResHelp": "Максимална резолюция на изображенията предоставена чрез \"upnp:icon\" полето."
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
"Playlists": "প্লে লিস্ট সমূহ",
|
"Playlists": "প্লে লিস্ট সমূহ",
|
||||||
"Songs": "সঙ্গীতসমূহ",
|
"Songs": "সঙ্গীতসমূহ",
|
||||||
"Sync": "সমলয় স্থাপন",
|
"Sync": "সমলয় স্থাপন",
|
||||||
"ValueSpecialEpisodeName": "বিশেষ পর্ব - {0}"
|
"ValueSpecialEpisodeName": "বিশেষ পর্ব - {0}",
|
||||||
|
"Default": "ডিফল্ট",
|
||||||
|
"Shows": "টিভি পর্ব"
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
"Authorize": "অনুমোদন",
|
"Authorize": "অনুমোদন",
|
||||||
"AspectRatio": "এস্পেক্ট রেসিও",
|
"AspectRatio": "এস্পেক্ট রেসিও",
|
||||||
"ApiKeysCaption": "বর্তমানে অনুমোদিত এ.পি.আই. কী গুলোর তালিকা",
|
"ApiKeysCaption": "বর্তমানে অনুমোদিত এ.পি.আই. কী গুলোর তালিকা",
|
||||||
"AllowTonemappingHelp": "টোন-ম্যাপিং একটি ভিডিওর গতিশীল পরিসরকে ছবির বিবরণ এবং রঙ বজায় রেখে HDR থেকে SDR-তে রূপান্তরিত করতে পারে। বর্তমানে শুধুমাত্র HDR10 বা HLG ভিডিওগুলির সাথে কাজ করে৷ যার জন্য সংশ্লিষ্ট OpenCL বা CUDA প্রয়োজন।",
|
"AllowTonemappingHelp": "টোন-ম্যাপিং একটি ভিডিওর গতিশীল পরিসরকে ছবির বিবরণ এবং রঙ বজায় রেখে HDR থেকে SDR-তে রূপান্তরিত করতে পারে। বর্তমানে শুধুমাত্র 10bit HDR10, HLG এবং DoVi ভিডিওগুলির সাথে কাজ করে৷ যার জন্য সংশ্লিষ্ট OpenCL বা CUDA প্রয়োজন।",
|
||||||
"AllowOnTheFlySubtitleExtractionHelp": "এমবেডেড সাবটাইটেল ভিডিও থেকে আলাদা করে প্লেইন টেক্সটে ক্লায়েন্টদের কাছে বিতরণ করা যেতে পারে যাতে ভিডিও ট্রান্সকোডিং এড়ানো যায়। কিন্তু কিছু সিস্টেমে এটি দীর্ঘ সময় নিতে পারে এবং এই প্রক্রিয়াটি চলাকালী্ন, ভিডিও প্লেব্যাক থেমে থাকতে পারে। ক্লায়েন্ট ডিভাইস দ্বারা সমর্থিত না হলে ভিডিও ট্রান্সকোডিংয়ের সাথে এম্বেড করা সাবটাইটেলগুলিকে বার্ন করার জন্য অপশনটি ডিসেবল করুন.",
|
"AllowOnTheFlySubtitleExtractionHelp": "এমবেডেড সাবটাইটেল ভিডিও থেকে আলাদা করে প্লেইন টেক্সটে ক্লায়েন্টদের কাছে বিতরণ করা যেতে পারে যাতে ভিডিও ট্রান্সকোডিং এড়ানো যায়। কিন্তু কিছু সিস্টেমে এটি দীর্ঘ সময় নিতে পারে এবং এই প্রক্রিয়াটি চলাকালী্ন, ভিডিও প্লেব্যাক থেমে থাকতে পারে। ক্লায়েন্ট ডিভাইস দ্বারা সমর্থিত না হলে ভিডিও ট্রান্সকোডিংয়ের সাথে এম্বেড করা সাবটাইটেলগুলিকে বার্ন করার জন্য অপশনটি ডিসেবল করুন.",
|
||||||
"AllowMediaConversionHelp": "কনভার্ট মিডিয়া ফিচারটির জন্য অনুমোদন দিন বা বাতিল করুন।",
|
"AllowMediaConversionHelp": "কনভার্ট মিডিয়া ফিচারটির জন্য অনুমোদন দিন বা বাতিল করুন।",
|
||||||
"AllowFfmpegThrottlingHelp": "যখন একটি ট্রান্সকোড বা রিমুক্স বর্তমান প্লেব্যাক অবস্থান থেকে যথেষ্ট এগিয়ে যায়, তখন প্রক্রিয়াটি থামানো যাতে এটি কম প্রসেস পাওয়ার গ্রহণ করে। প্রায়ই না টেনে (Seek) দেখার সময় এটি সবচেয়ে কার্যকর। আপনি যদি প্লেব্যাক সমস্যা অনুভব করেন তবে এটি বন্ধ করুন।",
|
"AllowFfmpegThrottlingHelp": "যখন একটি ট্রান্সকোড বা রিমুক্স বর্তমান প্লেব্যাক অবস্থান থেকে যথেষ্ট এগিয়ে যায়, তখন প্রক্রিয়াটি থামানো যাতে এটি কম প্রসেস পাওয়ার গ্রহণ করে। প্রায়ই না টেনে (Seek) দেখার সময় এটি সবচেয়ে কার্যকর। আপনি যদি প্লেব্যাক সমস্যা অনুভব করেন তবে এটি বন্ধ করুন।",
|
||||||
|
|
|
@ -639,7 +639,7 @@
|
||||||
"Absolute": "Absolut",
|
"Absolute": "Absolut",
|
||||||
"ClientSettings": "Configuració del client",
|
"ClientSettings": "Configuració del client",
|
||||||
"CinemaModeConfigurationHelp": "El mode Cinema aporta l'experiència del teatre directament a la sala d'estar amb la possibilitat de jugar a tràilers i presentacions personalitzades abans de la funció principal.",
|
"CinemaModeConfigurationHelp": "El mode Cinema aporta l'experiència del teatre directament a la sala d'estar amb la possibilitat de jugar a tràilers i presentacions personalitzades abans de la funció principal.",
|
||||||
"ChannelNameOnly": "Número de canal",
|
"ChannelNameOnly": "Només el canal {0}",
|
||||||
"ChangingMetadataImageSettingsNewContent": "Els canvis als paràmetres de descàrrega de metadades o d'imatge només s'apliquen al contingut nou afegit a la biblioteca. Per aplicar els canvis als títols existents, haureu d'actualitzar les metadades manualment.",
|
"ChangingMetadataImageSettingsNewContent": "Els canvis als paràmetres de descàrrega de metadades o d'imatge només s'apliquen al contingut nou afegit a la biblioteca. Per aplicar els canvis als títols existents, haureu d'actualitzar les metadades manualment.",
|
||||||
"ButtonTogglePlaylist": "Llista de reproducció",
|
"ButtonTogglePlaylist": "Llista de reproducció",
|
||||||
"BurnSubtitlesHelp": "Determina si el servidor hauria de gravar els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VobSub, PGS, SUB, IDX) i certs subtítols ASS o SSA.",
|
"BurnSubtitlesHelp": "Determina si el servidor hauria de gravar els subtítols en transcodificar vídeos. Evitar això millorarà molt el rendiment. Seleccioneu Automàtica per gravar formats basats en imatges (VobSub, PGS, SUB, IDX) i certs subtítols ASS o SSA.",
|
||||||
|
@ -714,7 +714,7 @@
|
||||||
"EnableDecodingColorDepth10Vp9": "Activar la descodificació de maquinari de 10 bits per a VP9",
|
"EnableDecodingColorDepth10Vp9": "Activar la descodificació de maquinari de 10 bits per a VP9",
|
||||||
"EnableDecodingColorDepth10Hevc": "Activar la descodificació de maquinari de 10 bits per a HEVC",
|
"EnableDecodingColorDepth10Hevc": "Activar la descodificació de maquinari de 10 bits per a HEVC",
|
||||||
"EnableBackdropsHelp": "Mostra imatges al fons d'algunes pàgines mentre navegueu per la biblioteca.",
|
"EnableBackdropsHelp": "Mostra imatges al fons d'algunes pàgines mentre navegueu per la biblioteca.",
|
||||||
"EnableAutoCast": "Establir com a defecte",
|
"EnableAutoCast": "Establir com a per defecte",
|
||||||
"EditMetadata": "Edita les metadades",
|
"EditMetadata": "Edita les metadades",
|
||||||
"EasyPasswordHelp": "El vostre codi Easy PIN s'utilitza per accedir fora de línia als clients compatibles i també es pot utilitzar per iniciar la sessió a la xarxa fàcilment.",
|
"EasyPasswordHelp": "El vostre codi Easy PIN s'utilitza per accedir fora de línia als clients compatibles i també es pot utilitzar per iniciar la sessió a la xarxa fàcilment.",
|
||||||
"DrmChannelsNotImported": "Els canals amb DRM no s’importaran.",
|
"DrmChannelsNotImported": "Els canals amb DRM no s’importaran.",
|
||||||
|
@ -753,7 +753,7 @@
|
||||||
"ButtonPlayer": "Reproductor",
|
"ButtonPlayer": "Reproductor",
|
||||||
"ButtonCast": "Transmetre a dispositiu",
|
"ButtonCast": "Transmetre a dispositiu",
|
||||||
"ApiKeysCaption": "Llista de les claus API activades actualment",
|
"ApiKeysCaption": "Llista de les claus API activades actualment",
|
||||||
"AllowTonemappingHelp": "El mapeig de tons pot transformar el rang dinàmic d'un vídeo d'HDR a SDR mantenint els detalls i els colors de la imatge, que són informació molt important per representar l'escena original. Actualment només funciona amb vídeos HDR10 o HLG. Això requereix el temps d'execució OpenCL o CUDA corresponent.",
|
"AllowTonemappingHelp": "El mapeig de tons pot transformar el rang dinàmic d'un vídeo d'HDR a SDR mantenint els detalls i els colors de la imatge, que són informació molt important per representar l'escena original. Actualment només funciona amb vídeos HDR10, HLG o DoVi. Això requereix el temps d'execució OpenCL o CUDA corresponent.",
|
||||||
"ErrorPleaseSelectLineup": "Seleccioneu una alineació i torneu-ho a provar. Si no hi ha cap formació disponible, comproveu que el vostre nom d’usuari, contrasenya i codi postal siguin correctes.",
|
"ErrorPleaseSelectLineup": "Seleccioneu una alineació i torneu-ho a provar. Si no hi ha cap formació disponible, comproveu que el vostre nom d’usuari, contrasenya i codi postal siguin correctes.",
|
||||||
"ErrorAddingListingsToSchedulesDirect": "S'ha produït un error en afegir la programació al vostre compte de Schedules Direct. Schedules Direct només permet un nombre limitat de programacions per compte. És possible que hagueu d’iniciar sessió al lloc web de Schedules Direct i eliminar altres llistats del vostre compte abans de continuar.",
|
"ErrorAddingListingsToSchedulesDirect": "S'ha produït un error en afegir la programació al vostre compte de Schedules Direct. Schedules Direct només permet un nombre limitat de programacions per compte. És possible que hagueu d’iniciar sessió al lloc web de Schedules Direct i eliminar altres llistats del vostre compte abans de continuar.",
|
||||||
"EnableThemeVideosHelp": "Reproduir vídeos temàtics en segon pla mentre navegues per la biblioteca.",
|
"EnableThemeVideosHelp": "Reproduir vídeos temàtics en segon pla mentre navegues per la biblioteca.",
|
||||||
|
@ -1387,7 +1387,7 @@
|
||||||
"RecommendationBecauseYouLike": "Perquè t'agrada {0}",
|
"RecommendationBecauseYouLike": "Perquè t'agrada {0}",
|
||||||
"Rate": "Rati",
|
"Rate": "Rati",
|
||||||
"Raised": "Elevat",
|
"Raised": "Elevat",
|
||||||
"QuickConnectNotAvailable": "Demani a l'administrador del servidor que activi el Quick Connect",
|
"QuickConnectNotAvailable": "Demaneu a l'administrador del servidor que activi el Quick Connect",
|
||||||
"QuickConnectNotActive": "Quick Connect no està activat en aquest servidor",
|
"QuickConnectNotActive": "Quick Connect no està activat en aquest servidor",
|
||||||
"QuickConnectInvalidCode": "Codi de Quick Connect no vàlid",
|
"QuickConnectInvalidCode": "Codi de Quick Connect no vàlid",
|
||||||
"QuickConnectDescription": "Per iniciar sessió amb Quick Connect, seleccioneu el botó 'Quick Connect' al dispositiu des del que us esteu connectant i introduïu el codi que apareix a continuació.",
|
"QuickConnectDescription": "Per iniciar sessió amb Quick Connect, seleccioneu el botó 'Quick Connect' al dispositiu des del que us esteu connectant i introduïu el codi que apareix a continuació.",
|
||||||
|
@ -1488,7 +1488,7 @@
|
||||||
"OnlyImageFormats": "Només Formats d'Imatge (VobSub, PGS, SUB)",
|
"OnlyImageFormats": "Només Formats d'Imatge (VobSub, PGS, SUB)",
|
||||||
"OneChannel": "Un canal",
|
"OneChannel": "Un canal",
|
||||||
"OnApplicationStartup": "A l'iniciar l'aplicació",
|
"OnApplicationStartup": "A l'iniciar l'aplicació",
|
||||||
"Off": "Off",
|
"Off": "Apagat",
|
||||||
"HeaderSubtitleProfile": "Perfil dels subtítols",
|
"HeaderSubtitleProfile": "Perfil dels subtítols",
|
||||||
"HeaderSubtitleDownloads": "Descàrrega dels subtítols",
|
"HeaderSubtitleDownloads": "Descàrrega dels subtítols",
|
||||||
"HeaderStopRecording": "Aturar l'enregistrament",
|
"HeaderStopRecording": "Aturar l'enregistrament",
|
||||||
|
@ -1638,7 +1638,7 @@
|
||||||
"Copy": "Copiar",
|
"Copy": "Copiar",
|
||||||
"Copied": "Copiat",
|
"Copied": "Copiat",
|
||||||
"Conductor": "Conductor",
|
"Conductor": "Conductor",
|
||||||
"Casual": "Casual",
|
"Casual": "Informal",
|
||||||
"ButtonSpace": "Espai",
|
"ButtonSpace": "Espai",
|
||||||
"ButtonExitApp": "Sortir de l'aplicació",
|
"ButtonExitApp": "Sortir de l'aplicació",
|
||||||
"ButtonClose": "Tancar",
|
"ButtonClose": "Tancar",
|
||||||
|
@ -1659,7 +1659,7 @@
|
||||||
"MessageRenameMediaFolder": "Canviar de nom una llibreria causarà que es perdi tota la metadata, continua amb precaució.",
|
"MessageRenameMediaFolder": "Canviar de nom una llibreria causarà que es perdi tota la metadata, continua amb precaució.",
|
||||||
"LabelTextWeight": "Estil del text",
|
"LabelTextWeight": "Estil del text",
|
||||||
"RememberSubtitleSelections": "Establir la pista de subtítols en funció de l'element anterior",
|
"RememberSubtitleSelections": "Establir la pista de subtítols en funció de l'element anterior",
|
||||||
"LabelVppTonemappingBrightnessHelp": "Aplicar el guany de brillantor al mapa de to VPP. Tant els valors recomanats com els per defecte són 0.",
|
"LabelVppTonemappingBrightnessHelp": "Aplicar el guany de brillantor al mapa de to VPP. Els valors recomanats i per defecte són 16 i 0.",
|
||||||
"MediaInfoRpuPresentFlag": "Flag DV rpu predeterminat",
|
"MediaInfoRpuPresentFlag": "Flag DV rpu predeterminat",
|
||||||
"MediaInfoDvBlSignalCompatibilityId": "Identificador de compatibilitat del senyal DV bl",
|
"MediaInfoDvBlSignalCompatibilityId": "Identificador de compatibilitat del senyal DV bl",
|
||||||
"Unreleased": "Encara no s'ha publicat",
|
"Unreleased": "Encara no s'ha publicat",
|
||||||
|
@ -1670,7 +1670,7 @@
|
||||||
"EnableEnhancedNvdecDecoderHelp": "Implementació experimental de NVDEC, no activeu aquesta opció tret que trobeu errors de descodificació.",
|
"EnableEnhancedNvdecDecoderHelp": "Implementació experimental de NVDEC, no activeu aquesta opció tret que trobeu errors de descodificació.",
|
||||||
"LabelVppTonemappingBrightness": "Guany de brillantor del mapa de to VPP",
|
"LabelVppTonemappingBrightness": "Guany de brillantor del mapa de to VPP",
|
||||||
"LabelVppTonemappingContrast": "Guany de contrast de mapatge de to VPP",
|
"LabelVppTonemappingContrast": "Guany de contrast de mapatge de to VPP",
|
||||||
"LabelVppTonemappingContrastHelp": "Aplicar el guany de contrast a l'assignació de to VPP. Els valors recomanats i predeterminats són 1.2 i 1.",
|
"LabelVppTonemappingContrastHelp": "Aplicar el guany de contrast a l'assignació de to VPP. Tant el valor recomanat com el predeterminat són 1.",
|
||||||
"VideoRangeTypeNotSupported": "El tipus d'interval del vídeo no és compatible",
|
"VideoRangeTypeNotSupported": "El tipus d'interval del vídeo no és compatible",
|
||||||
"LabelVideoRangeType": "Tipus d'interval de vídeo",
|
"LabelVideoRangeType": "Tipus d'interval de vídeo",
|
||||||
"MediaInfoVideoRangeType": "Tipus d'interval de vídeo",
|
"MediaInfoVideoRangeType": "Tipus d'interval de vídeo",
|
||||||
|
@ -1694,9 +1694,9 @@
|
||||||
"HeaderDummyChapter": "Imatges dels capítols",
|
"HeaderDummyChapter": "Imatges dels capítols",
|
||||||
"LabelDummyChapterCount": "Límit",
|
"LabelDummyChapterCount": "Límit",
|
||||||
"LabelDummyChapterDuration": "Interval",
|
"LabelDummyChapterDuration": "Interval",
|
||||||
"LabelDummyChapterDurationHelp": "L'interval d'extracció de les imatges dels capítols en segons.",
|
"LabelDummyChapterDurationHelp": "L'interval entre capítols ficticis. Ajusta-ho a 0 per a desactivar la generació de capítols ficticis. Canviar-ho no afectarà els capítols ficticis existents.",
|
||||||
"LabelChapterImageResolution": "Resolució",
|
"LabelChapterImageResolution": "Resolució",
|
||||||
"LabelChapterImageResolutionHelp": "La resolució de les imatges del capítol extretes.",
|
"LabelChapterImageResolutionHelp": "La resolució de les imatges del capítol extretes. Canviar-ho no afectarà els capítols ficticis existents.",
|
||||||
"ResolutionMatchSource": "Coincidir amb la font",
|
"ResolutionMatchSource": "Coincidir amb la font",
|
||||||
"SaveRecordingNFOHelp": "Desa les metadades del proveïdor de llistes EPG amb els mitjans.",
|
"SaveRecordingNFOHelp": "Desa les metadades del proveïdor de llistes EPG amb els mitjans.",
|
||||||
"HeaderPerformance": "Rendiment",
|
"HeaderPerformance": "Rendiment",
|
||||||
|
@ -1717,5 +1717,19 @@
|
||||||
"StereoDownmixAlgorithmHelp": "Algoritme utilitzat per barrejar àudio multicanal a estèreo.",
|
"StereoDownmixAlgorithmHelp": "Algoritme utilitzat per barrejar àudio multicanal a estèreo.",
|
||||||
"DownloadAll": "Descarrega-ho tot",
|
"DownloadAll": "Descarrega-ho tot",
|
||||||
"LabelEnableAudioVbr": "Activar la codificació d'àudio VBR",
|
"LabelEnableAudioVbr": "Activar la codificació d'àudio VBR",
|
||||||
"LabelEnableAudioVbrHelp": "La taxa de bits variable ofereix una millor relació entre la qualitat i velocitat de bits mitjana, però en alguns casos rars pot provocar problemes de compatibilitat i de memòria intermèdia (buffering)."
|
"LabelEnableAudioVbrHelp": "La taxa de bits variable ofereix una millor relació entre la qualitat i velocitat de bits mitjana, però en alguns casos rars pot provocar problemes de compatibilitat i de memòria intermèdia (buffering).",
|
||||||
|
"LabelTonemappingMode": "Mode de mapeig de to",
|
||||||
|
"EnableAudioNormalization": "Normalització d'àudio",
|
||||||
|
"LabelEnableLUFSScan": "Activar escaneig LUFS",
|
||||||
|
"Select": "Seleccionar",
|
||||||
|
"LabelEnableLUFSScanHelp": "Activar escaneig LUFS per a música (trigarà més i consumirà més recursos).",
|
||||||
|
"TonemappingModeHelp": "Selecciona el mode de mapeig de to. Si experimentes zones sobreexposades, prova a canviar al mode RGB.",
|
||||||
|
"EnableAudioNormalizationHelp": "La normalització de l'àudio afegirà un guany constant per mantenir la mitjana al nivell desitjat (-18dB).",
|
||||||
|
"GetThePlugin": "Obtenir el Plugin",
|
||||||
|
"MenuClose": "Tancar el menú",
|
||||||
|
"MenuOpen": "Obrir el menú",
|
||||||
|
"Notifications": "Notificacions",
|
||||||
|
"NotificationsMovedMessage": "Les funcions de notificacions s'han mogut al plugin Webhook.",
|
||||||
|
"UserMenu": "Menú d'usuari",
|
||||||
|
"AllowCollectionManagement": "Permet a aquest usuari gestionar col·leccions"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"AlwaysPlaySubtitles": "Vždy zobrazovat",
|
"AlwaysPlaySubtitles": "Vždy zobrazovat",
|
||||||
"AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.",
|
"AlwaysPlaySubtitlesHelp": "Titulky odpovídající jazykové předvolbě se načtou bez ohledu na jazyk audia.",
|
||||||
"Anytime": "Kdykoliv",
|
"Anytime": "Kdykoliv",
|
||||||
"AroundTime": "Okolo",
|
"AroundTime": "Okolo {0}",
|
||||||
"Art": "Čistý obrázek",
|
"Art": "Čistý obrázek",
|
||||||
"Artists": "Umělci",
|
"Artists": "Umělci",
|
||||||
"AsManyAsPossible": "Tolikrát jak je možné",
|
"AsManyAsPossible": "Tolikrát jak je možné",
|
||||||
|
@ -717,7 +717,7 @@
|
||||||
"MySubtitles": "Mé titulky",
|
"MySubtitles": "Mé titulky",
|
||||||
"Name": "Název",
|
"Name": "Název",
|
||||||
"NewCollection": "Nová kolekce",
|
"NewCollection": "Nová kolekce",
|
||||||
"NewCollectionHelp": "Kolekce dovolí vytvořit personalizované seskupení filmů a dalšího obsahu knihoven.",
|
"NewCollectionHelp": "Kolekce umožňují vytvořit vlastní skupiny filmů a dalšího obsahu knihoven.",
|
||||||
"NewCollectionNameExample": "Příklad: Kolekce Star Wars",
|
"NewCollectionNameExample": "Příklad: Kolekce Star Wars",
|
||||||
"NewEpisodes": "Nové episody",
|
"NewEpisodes": "Nové episody",
|
||||||
"NewEpisodesOnly": "Jen nové epizody",
|
"NewEpisodesOnly": "Jen nové epizody",
|
||||||
|
@ -1696,11 +1696,11 @@
|
||||||
"SaveRecordingImagesHelp": "Uložit obrázky elektronického programového průvodce nahrávky spolu s médii.",
|
"SaveRecordingImagesHelp": "Uložit obrázky elektronického programového průvodce nahrávky spolu s médii.",
|
||||||
"HeaderDummyChapter": "Obrázky kapitol",
|
"HeaderDummyChapter": "Obrázky kapitol",
|
||||||
"LabelDummyChapterDuration": "Rozmezí",
|
"LabelDummyChapterDuration": "Rozmezí",
|
||||||
"LabelDummyChapterDurationHelp": "Rozmezí v sekundách mezi extrakcí obrázků kapitol.",
|
"LabelDummyChapterDurationHelp": "Rozmezí v sekundách mezi kapitolami. Vytváření kapitol je možné vypnout nastavením na 0. Změna tohoto nastavení nemá vliv na existující kapitoly.",
|
||||||
"LabelDummyChapterCount": "Limit",
|
"LabelDummyChapterCount": "Limit",
|
||||||
"LabelDummyChapterCountHelp": "Maximální počet obrázků kapitol, který má být vytvořen pro jeden soubor médií.",
|
"LabelDummyChapterCountHelp": "Maximální počet obrázků kapitol, který má být vytvořen pro jeden soubor médií.",
|
||||||
"LabelChapterImageResolution": "Rozlišení",
|
"LabelChapterImageResolution": "Rozlišení",
|
||||||
"LabelChapterImageResolutionHelp": "Rozližení extrahovaných obrázků kapitol.",
|
"LabelChapterImageResolutionHelp": "Rozlišení extrahovaných obrázků kapitol. Změna tohoto nastavení nemá vliv na existující kapitoly.",
|
||||||
"ResolutionMatchSource": "Stejné jako zdroj",
|
"ResolutionMatchSource": "Stejné jako zdroj",
|
||||||
"PreferEmbeddedExtrasTitlesOverFileNames": "Preferovat vložené názvy před názvy souborů pro doplňky",
|
"PreferEmbeddedExtrasTitlesOverFileNames": "Preferovat vložené názvy před názvy souborů pro doplňky",
|
||||||
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Doplňky většinou mají stejný vložený název jako nadřazená položka. Zaškrtnutím je i přesto můžete upřednostnit.",
|
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Doplňky většinou mají stejný vložený název jako nadřazená položka. Zaškrtnutím je i přesto můžete upřednostnit.",
|
||||||
|
@ -1724,5 +1724,18 @@
|
||||||
"LabelEnableAudioVbrHelp": "Proměnlivý bitový tok (VBR) nabízí lepší poměr mezi kvalitou a průměrným bitovým tokem, ale někdy může způsobit dodatečné načítání či problémy s kompatibilitou.",
|
"LabelEnableAudioVbrHelp": "Proměnlivý bitový tok (VBR) nabízí lepší poměr mezi kvalitou a průměrným bitovým tokem, ale někdy může způsobit dodatečné načítání či problémy s kompatibilitou.",
|
||||||
"Select": "Vybrat",
|
"Select": "Vybrat",
|
||||||
"LabelTonemappingMode": "Režim mapování tónů",
|
"LabelTonemappingMode": "Režim mapování tónů",
|
||||||
"TonemappingModeHelp": "Určute režim mapování tónů. Pokud narazíte na přeexponovaná světlá místa, zkuste přepnout do režimu RGB."
|
"TonemappingModeHelp": "Určute režim mapování tónů. Pokud narazíte na přeexponovaná světlá místa, zkuste přepnout do režimu RGB.",
|
||||||
|
"MenuOpen": "Otevřít nabídku",
|
||||||
|
"MenuClose": "Zavřít nabídku",
|
||||||
|
"UserMenu": "Uživatelská nabídka",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"AllowCollectionManagement": "Povolit tomuto uživateli spravovat kolekce",
|
||||||
|
"EnableAudioNormalizationHelp": "Normalizace hlasitosti udržuje průměrnou hlasitost na požadované úrovni (-18 dB) přidáním konstantního zisku.",
|
||||||
|
"EnableAudioNormalization": "Normalizace hlasitosti",
|
||||||
|
"LabelEnableLUFSScan": "Povolit skenování LUFS",
|
||||||
|
"LabelEnableLUFSScanHelp": "Povolit tvorbu informací LUFS při skenování hudby. Prodlužuje skenování a je náročnější na výkon.",
|
||||||
|
"GetThePlugin": "Získat zásuvný modul",
|
||||||
|
"Notifications": "Oznámení",
|
||||||
|
"NotificationsMovedMessage": "Funkce oznámení se přesunula do zásuvného modulu Webhook.",
|
||||||
|
"PasswordRequiredForAdmin": "Administrátorské účty musejí mít nastaveno heslo."
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"AllowRemoteAccessHelp": "Hvis ikke markeret, vil alle forbindelser udefra blive blokeret.",
|
"AllowRemoteAccessHelp": "Hvis ikke markeret, vil alle forbindelser udefra blive blokeret.",
|
||||||
"AllowedRemoteAddressesHelp": "Komma separeret liste over IP-adresser eller IP/rundsendings-adresser til netværk som har tilladelse til at forbinde udefra. Hvis dette felt er tomt, er alle eksterne adresser tilladt.",
|
"AllowedRemoteAddressesHelp": "Komma separeret liste over IP-adresser eller IP/rundsendings-adresser til netværk som har tilladelse til at forbinde udefra. Hvis dette felt er tomt, er alle eksterne adresser tilladt.",
|
||||||
"Anytime": "Altid",
|
"Anytime": "Altid",
|
||||||
"AroundTime": "Omkring",
|
"AroundTime": "Cirka [0]",
|
||||||
"AsManyAsPossible": "Så mange som muligt",
|
"AsManyAsPossible": "Så mange som muligt",
|
||||||
"AspectRatio": "Billedformat",
|
"AspectRatio": "Billedformat",
|
||||||
"Audio": "Lyd",
|
"Audio": "Lyd",
|
||||||
|
|
|
@ -1204,7 +1204,7 @@
|
||||||
"LabelAudioBitrate": "Audio-Bitrate",
|
"LabelAudioBitrate": "Audio-Bitrate",
|
||||||
"ButtonAddImage": "Bild hinzufügen",
|
"ButtonAddImage": "Bild hinzufügen",
|
||||||
"LabelSize": "Größe",
|
"LabelSize": "Größe",
|
||||||
"LabelTranscodes": "Transcodes",
|
"LabelTranscodes": "Transkodierungen",
|
||||||
"LabelTranscodingProgress": "Transkodierungsfortschritt",
|
"LabelTranscodingProgress": "Transkodierungsfortschritt",
|
||||||
"LabelAudioBitDepth": "Audio-Bittiefe",
|
"LabelAudioBitDepth": "Audio-Bittiefe",
|
||||||
"LabelPleaseRestart": "Die Änderungen werden nach dem manuellen Neuladen des Webclients wirksam.",
|
"LabelPleaseRestart": "Die Änderungen werden nach dem manuellen Neuladen des Webclients wirksam.",
|
||||||
|
@ -1621,7 +1621,7 @@
|
||||||
"DeletedScene": "Gelöschte Szene",
|
"DeletedScene": "Gelöschte Szene",
|
||||||
"BehindTheScenes": "Hinter den Kulissen",
|
"BehindTheScenes": "Hinter den Kulissen",
|
||||||
"Trailer": "Vorschau",
|
"Trailer": "Vorschau",
|
||||||
"Clip": "Clip",
|
"Clip": "Ausschnitt",
|
||||||
"ShowParentImages": "Serienbilder anzeigen",
|
"ShowParentImages": "Serienbilder anzeigen",
|
||||||
"AllowEmbeddedSubtitlesAllowTextOption": "Text erlauben",
|
"AllowEmbeddedSubtitlesAllowTextOption": "Text erlauben",
|
||||||
"AllowEmbeddedSubtitlesAllowImageOption": "Bilder erlauben",
|
"AllowEmbeddedSubtitlesAllowImageOption": "Bilder erlauben",
|
||||||
|
@ -1724,5 +1724,10 @@
|
||||||
"LabelEnableAudioVbrHelp": "Eine variable Bitrate bietet bessere Qualität im Vergleich zu einer konstanten, kann jedoch in manchen Fällen zu Problemen beim Buffering und der Kompatibilität führen.",
|
"LabelEnableAudioVbrHelp": "Eine variable Bitrate bietet bessere Qualität im Vergleich zu einer konstanten, kann jedoch in manchen Fällen zu Problemen beim Buffering und der Kompatibilität führen.",
|
||||||
"Select": "Auswählen",
|
"Select": "Auswählen",
|
||||||
"LabelTonemappingMode": "Dynamikkompressionsmodus",
|
"LabelTonemappingMode": "Dynamikkompressionsmodus",
|
||||||
"TonemappingModeHelp": "Dynamikkompressionsmodus auswählen. Falls etwas überbelichtet ist, versuche in den RGB-Modus zu wechseln."
|
"TonemappingModeHelp": "Dynamikkompressionsmodus auswählen. Falls etwas überbelichtet ist, versuche in den RGB-Modus zu wechseln.",
|
||||||
|
"MenuOpen": "Menü öffnen",
|
||||||
|
"MenuClose": "Menü schließen",
|
||||||
|
"UserMenu": "Benutzermenü",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"AllowCollectionManagement": "Dieser Benutzer darf Sammlungen verwalten"
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
"AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.",
|
"AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.",
|
||||||
"AnyLanguage": "Any Language",
|
"AnyLanguage": "Any Language",
|
||||||
"Anytime": "Anytime",
|
"Anytime": "Anytime",
|
||||||
"AroundTime": "Around",
|
"AroundTime": "Around {0}",
|
||||||
"Art": "Clearart",
|
"Art": "Clearart",
|
||||||
"AsManyAsPossible": "As many as possible",
|
"AsManyAsPossible": "As many as possible",
|
||||||
"Ascending": "Ascending",
|
"Ascending": "Ascending",
|
||||||
|
@ -1427,7 +1427,7 @@
|
||||||
"ButtonUseQuickConnect": "Use Quick Connect",
|
"ButtonUseQuickConnect": "Use Quick Connect",
|
||||||
"ButtonActivate": "Activate",
|
"ButtonActivate": "Activate",
|
||||||
"Authorize": "Authorise",
|
"Authorize": "Authorise",
|
||||||
"EnableAutoCast": "Set as Default",
|
"EnableAutoCast": "Set as default",
|
||||||
"EnableFallbackFontHelp": "Enable custom alternative fonts. This can avoid the problem of incorrect subtitle rendering.",
|
"EnableFallbackFontHelp": "Enable custom alternative fonts. This can avoid the problem of incorrect subtitle rendering.",
|
||||||
"EnableFallbackFont": "Enable fallback fonts",
|
"EnableFallbackFont": "Enable fallback fonts",
|
||||||
"LabelFallbackFontPathHelp": "Specify a path containing fallback fonts for rendering ASS/SSA subtitles. The maximum allowed total font size is 20 MB. Lightweight and web-friendly font formats such as woff2 are recommended.",
|
"LabelFallbackFontPathHelp": "Specify a path containing fallback fonts for rendering ASS/SSA subtitles. The maximum allowed total font size is 20 MB. Lightweight and web-friendly font formats such as woff2 are recommended.",
|
||||||
|
@ -1696,10 +1696,10 @@
|
||||||
"SaveRecordingImages": "Save recording EPG images",
|
"SaveRecordingImages": "Save recording EPG images",
|
||||||
"SaveRecordingImagesHelp": "Save images from EPG listings provider along side media.",
|
"SaveRecordingImagesHelp": "Save images from EPG listings provider along side media.",
|
||||||
"HeaderDummyChapter": "Chapter Images",
|
"HeaderDummyChapter": "Chapter Images",
|
||||||
"LabelDummyChapterDurationHelp": "The chapter image extraction interval in seconds.",
|
"LabelDummyChapterDurationHelp": "The interval between dummy chapters. Set to 0 to disable dummy chapter generation. Changing this will have no effect on existing dummy chapters.",
|
||||||
"LabelDummyChapterCount": "Limit",
|
"LabelDummyChapterCount": "Limit",
|
||||||
"LabelChapterImageResolution": "Resolution",
|
"LabelChapterImageResolution": "Resolution",
|
||||||
"LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images.",
|
"LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images. Changing this will have no effect on existing dummy chapters.",
|
||||||
"ResolutionMatchSource": "Match Source",
|
"ResolutionMatchSource": "Match Source",
|
||||||
"SaveRecordingNFO": "Save recording EPG metadata in NFO",
|
"SaveRecordingNFO": "Save recording EPG metadata in NFO",
|
||||||
"PreferEmbeddedExtrasTitlesOverFileNames": "Prefer embedded titles over filenames for extras",
|
"PreferEmbeddedExtrasTitlesOverFileNames": "Prefer embedded titles over filenames for extras",
|
||||||
|
@ -1724,5 +1724,18 @@
|
||||||
"LabelEnableAudioVbrHelp": "Variable bitrate offers better quality to average bitrate ratio, but in some rare cases may cause buffering and compatibility issues.",
|
"LabelEnableAudioVbrHelp": "Variable bitrate offers better quality to average bitrate ratio, but in some rare cases may cause buffering and compatibility issues.",
|
||||||
"LabelTonemappingMode": "Tone mapping mode",
|
"LabelTonemappingMode": "Tone mapping mode",
|
||||||
"Select": "Select",
|
"Select": "Select",
|
||||||
"TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode."
|
"TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.",
|
||||||
|
"UserMenu": "User Menu",
|
||||||
|
"MenuOpen": "Open Menu",
|
||||||
|
"MenuClose": "Close Menu",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"EnableAudioNormalizationHelp": "Audio normalisation will add a constant gain to keep the average at a desired level (-18dB).",
|
||||||
|
"EnableAudioNormalization": "Audio Normalisation",
|
||||||
|
"LabelEnableLUFSScan": "Enable LUFS scan",
|
||||||
|
"LabelEnableLUFSScanHelp": "Enable LUFS scan for music (This will take longer and more resources).",
|
||||||
|
"AllowCollectionManagement": "Allow this user to manage collections",
|
||||||
|
"GetThePlugin": "Get the Plugin",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"NotificationsMovedMessage": "The notifications functionality has moved to the Webhook plugin.",
|
||||||
|
"PasswordRequiredForAdmin": "A password is required for admin accounts."
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"Actor": "Actor",
|
"Actor": "Actor",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"AddedOnValue": "Added {0}",
|
"AddedOnValue": "Added {0}",
|
||||||
"AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services.",
|
|
||||||
"AddToCollection": "Add to collection",
|
"AddToCollection": "Add to collection",
|
||||||
"AddToFavorites": "Add to favorites",
|
"AddToFavorites": "Add to favorites",
|
||||||
"AddToPlaylist": "Add to playlist",
|
"AddToPlaylist": "Add to playlist",
|
||||||
|
@ -23,6 +22,7 @@
|
||||||
"AllLanguages": "All languages",
|
"AllLanguages": "All languages",
|
||||||
"AllLibraries": "All libraries",
|
"AllLibraries": "All libraries",
|
||||||
"AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.",
|
"AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.",
|
||||||
|
"AllowCollectionManagement": "Allow this user to manage collections",
|
||||||
"AllowFfmpegThrottling": "Throttle Transcodes",
|
"AllowFfmpegThrottling": "Throttle Transcodes",
|
||||||
"AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.",
|
"AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.",
|
||||||
"AllowHWTranscodingHelp": "Allow the tuner to transcode streams on the fly. This may help reduce transcoding required by the server.",
|
"AllowHWTranscodingHelp": "Allow the tuner to transcode streams on the fly. This may help reduce transcoding required by the server.",
|
||||||
|
@ -132,6 +132,7 @@
|
||||||
"ChannelNumber": "Channel number",
|
"ChannelNumber": "Channel number",
|
||||||
"Channels": "Channels",
|
"Channels": "Channels",
|
||||||
"CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
|
"CinemaModeConfigurationHelp": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
|
||||||
|
"EnableAudioNormalizationHelp": "Audio normalization will add a constant gain to keep the average at a desired level (-18dB).",
|
||||||
"ClearQueue": "Clear queue",
|
"ClearQueue": "Clear queue",
|
||||||
"ClientSettings": "Client Settings",
|
"ClientSettings": "Client Settings",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
|
@ -215,11 +216,12 @@
|
||||||
"EditImages": "Edit images",
|
"EditImages": "Edit images",
|
||||||
"EditMetadata": "Edit metadata",
|
"EditMetadata": "Edit metadata",
|
||||||
"EditSubtitles": "Edit subtitles",
|
"EditSubtitles": "Edit subtitles",
|
||||||
"EnableAutoCast": "Set as Default",
|
"EnableAutoCast": "Set as default",
|
||||||
"EnableBackdropsHelp": "Display the backdrops in the background of some pages while browsing the library.",
|
"EnableBackdropsHelp": "Display the backdrops in the background of some pages while browsing the library.",
|
||||||
"EnableBlurHash": "Enable blurred placeholders for images",
|
"EnableBlurHash": "Enable blurred placeholders for images",
|
||||||
"EnableBlurHashHelp": "Images that are still being loaded will be displayed with a unique placeholder.",
|
"EnableBlurHashHelp": "Images that are still being loaded will be displayed with a unique placeholder.",
|
||||||
"EnableCinemaMode": "Cinema mode",
|
"EnableCinemaMode": "Cinema mode",
|
||||||
|
"EnableAudioNormalization": "Audio Normalization",
|
||||||
"EnableColorCodedBackgrounds": "Color coded backgrounds",
|
"EnableColorCodedBackgrounds": "Color coded backgrounds",
|
||||||
"EnableDecodingColorDepth10Hevc": "Enable 10-bit hardware decoding for HEVC",
|
"EnableDecodingColorDepth10Hevc": "Enable 10-bit hardware decoding for HEVC",
|
||||||
"EnableDecodingColorDepth10Vp9": "Enable 10-bit hardware decoding for VP9",
|
"EnableDecodingColorDepth10Vp9": "Enable 10-bit hardware decoding for VP9",
|
||||||
|
@ -290,6 +292,7 @@
|
||||||
"General": "General",
|
"General": "General",
|
||||||
"Genre": "Genre",
|
"Genre": "Genre",
|
||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
|
"GetThePlugin": "Get the Plugin",
|
||||||
"GoogleCastUnsupported": "Google Cast Unsupported",
|
"GoogleCastUnsupported": "Google Cast Unsupported",
|
||||||
"GroupBySeries": "Group by series",
|
"GroupBySeries": "Group by series",
|
||||||
"GroupVersions": "Group versions",
|
"GroupVersions": "Group versions",
|
||||||
|
@ -628,11 +631,9 @@
|
||||||
"LabelDropShadow": "Drop shadow",
|
"LabelDropShadow": "Drop shadow",
|
||||||
"LabelDropSubtitleHere": "Drop subtitle here, or click to browse.",
|
"LabelDropSubtitleHere": "Drop subtitle here, or click to browse.",
|
||||||
"LabelDummyChapterDuration": "Interval",
|
"LabelDummyChapterDuration": "Interval",
|
||||||
"LabelDummyChapterDurationHelp": "The chapter image extraction interval in seconds.",
|
"LabelDummyChapterDurationHelp": "The interval between dummy chapters. Set to 0 to disable dummy chapter generation. Changing this will have no effect on existing dummy chapters.",
|
||||||
"LabelDummyChapterCount": "Limit",
|
|
||||||
"LabelDummyChapterCountHelp": "The maximum number of chapter images that will be extracted for each media file.",
|
|
||||||
"LabelChapterImageResolution": "Resolution",
|
"LabelChapterImageResolution": "Resolution",
|
||||||
"LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images.",
|
"LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images. Changing this will have no effect on existing dummy chapters.",
|
||||||
"LabelDynamicExternalId": "{0} Id",
|
"LabelDynamicExternalId": "{0} Id",
|
||||||
"LabelEasyPinCode": "Easy PIN code",
|
"LabelEasyPinCode": "Easy PIN code",
|
||||||
"LabelEmbedAlbumArtDidl": "Embed album art in DIDL",
|
"LabelEmbedAlbumArtDidl": "Embed album art in DIDL",
|
||||||
|
@ -658,6 +659,8 @@
|
||||||
"LabelEnableIP4Help": "Enable IPv4 functionality.",
|
"LabelEnableIP4Help": "Enable IPv4 functionality.",
|
||||||
"LabelEnableIP6": "Enable IPv6",
|
"LabelEnableIP6": "Enable IPv6",
|
||||||
"LabelEnableIP6Help": "Enable IPv6 functionality.",
|
"LabelEnableIP6Help": "Enable IPv6 functionality.",
|
||||||
|
"LabelEnableLUFSScan": "Enable LUFS scan",
|
||||||
|
"LabelEnableLUFSScanHelp": "Enable LUFS scan for music (This will take longer and more resources).",
|
||||||
"LabelEnableRealtimeMonitor": "Enable real time monitoring",
|
"LabelEnableRealtimeMonitor": "Enable real time monitoring",
|
||||||
"LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.",
|
"LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.",
|
||||||
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
|
"LabelEnableSingleImageInDidlLimit": "Limit to single embedded image",
|
||||||
|
@ -770,7 +773,6 @@
|
||||||
"LabelModelName": "Model name",
|
"LabelModelName": "Model name",
|
||||||
"LabelModelNumber": "Model number",
|
"LabelModelNumber": "Model number",
|
||||||
"LabelModelUrl": "Model URL",
|
"LabelModelUrl": "Model URL",
|
||||||
"LabelMonitorUsers": "Monitor activity from",
|
|
||||||
"LabelMovieCategories": "Movie categories",
|
"LabelMovieCategories": "Movie categories",
|
||||||
"LabelMoviePrefix": "Movie prefix",
|
"LabelMoviePrefix": "Movie prefix",
|
||||||
"LabelMoviePrefixHelp": "If a prefix is applied to movie titles, enter it here so the server can handle it properly.",
|
"LabelMoviePrefixHelp": "If a prefix is applied to movie titles, enter it here so the server can handle it properly.",
|
||||||
|
@ -782,7 +784,6 @@
|
||||||
"LabelNewPassword": "New password",
|
"LabelNewPassword": "New password",
|
||||||
"LabelNewPasswordConfirm": "New password confirm",
|
"LabelNewPasswordConfirm": "New password confirm",
|
||||||
"LabelNewsCategories": "News categories",
|
"LabelNewsCategories": "News categories",
|
||||||
"LabelNotificationEnabled": "Enable this notification",
|
|
||||||
"LabelNumber": "Number",
|
"LabelNumber": "Number",
|
||||||
"LabelNumberOfGuideDays": "Number of days of guide data to download",
|
"LabelNumberOfGuideDays": "Number of days of guide data to download",
|
||||||
"LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will pick based on the number of channels.",
|
"LabelNumberOfGuideDaysHelp": "Downloading more days worth of guide data provides the ability to schedule out further in advance and view more listings, but it will also take longer to download. Auto will pick based on the number of channels.",
|
||||||
|
@ -855,9 +856,7 @@
|
||||||
"LabelSeasonNumber": "Season number",
|
"LabelSeasonNumber": "Season number",
|
||||||
"LabelSelectFolderGroups": "Automatically group content from the following folders into views such as 'Movies', 'Music' and 'TV'",
|
"LabelSelectFolderGroups": "Automatically group content from the following folders into views such as 'Movies', 'Music' and 'TV'",
|
||||||
"LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.",
|
"LabelSelectFolderGroupsHelp": "Folders that are unchecked will be displayed by themselves in their own view.",
|
||||||
"LabelSelectUsers": "Select users",
|
|
||||||
"LabelSelectVersionToInstall": "Select version to install",
|
"LabelSelectVersionToInstall": "Select version to install",
|
||||||
"LabelSendNotificationToUsers": "Send the notification to",
|
|
||||||
"LabelSerialNumber": "Serial number",
|
"LabelSerialNumber": "Serial number",
|
||||||
"LabelSeriesRecordingPath": "Series recording path",
|
"LabelSeriesRecordingPath": "Series recording path",
|
||||||
"LabelServerHost": "Host",
|
"LabelServerHost": "Host",
|
||||||
|
@ -968,7 +967,6 @@
|
||||||
"LabelUDPPortRange": "UDP Communication Range",
|
"LabelUDPPortRange": "UDP Communication Range",
|
||||||
"LabelUDPPortRangeHelp": "Restrict Jellyfin to use this port range when making UDP connections. (Default is 1024 - 645535).<br/> Note: Certain function require fixed ports that may be outside of this range.",
|
"LabelUDPPortRangeHelp": "Restrict Jellyfin to use this port range when making UDP connections. (Default is 1024 - 645535).<br/> Note: Certain function require fixed ports that may be outside of this range.",
|
||||||
"LabelUnstable": "Unstable",
|
"LabelUnstable": "Unstable",
|
||||||
"LabelUseNotificationServices": "Use the following services",
|
|
||||||
"LabelUser": "User",
|
"LabelUser": "User",
|
||||||
"LabelUserAgent": "User agent",
|
"LabelUserAgent": "User agent",
|
||||||
"LabelUserLibrary": "User library",
|
"LabelUserLibrary": "User library",
|
||||||
|
@ -1047,6 +1045,8 @@
|
||||||
"MediaInfoVideoRange": "Video range",
|
"MediaInfoVideoRange": "Video range",
|
||||||
"MediaIsBeingConverted": "The media is being converted into a format that is compatible with the device that is playing the media.",
|
"MediaIsBeingConverted": "The media is being converted into a format that is compatible with the device that is playing the media.",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
|
"MenuOpen": "Open Menu",
|
||||||
|
"MenuClose": "Close Menu",
|
||||||
"MessageAddRepository": "If you wish to add a repository, click the button next to the header and fill out the requested information.",
|
"MessageAddRepository": "If you wish to add a repository, click the button next to the header and fill out the requested information.",
|
||||||
"MessageAlreadyInstalled": "This version is already installed.",
|
"MessageAlreadyInstalled": "This version is already installed.",
|
||||||
"MessageAreYouSureDeleteSubtitles": "Are you sure you wish to delete this subtitle file?",
|
"MessageAreYouSureDeleteSubtitles": "Are you sure you wish to delete this subtitle file?",
|
||||||
|
@ -1170,6 +1170,8 @@
|
||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
"NoSubtitleSearchResultsFound": "No results found.",
|
"NoSubtitleSearchResultsFound": "No results found.",
|
||||||
"NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.",
|
"NoSubtitlesHelp": "Subtitles will not be loaded by default. They can still be turned on manually during playback.",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"NotificationsMovedMessage": "The notifications functionality has moved to the Webhook plugin.",
|
||||||
"NumLocationsValue": "{0} folders",
|
"NumLocationsValue": "{0} folders",
|
||||||
"Off": "Off",
|
"Off": "Off",
|
||||||
"OnApplicationStartup": "On application startup",
|
"OnApplicationStartup": "On application startup",
|
||||||
|
@ -1179,7 +1181,6 @@
|
||||||
"OnlyImageFormats": "Only Image Formats (VobSub, PGS, SUB)",
|
"OnlyImageFormats": "Only Image Formats (VobSub, PGS, SUB)",
|
||||||
"OnWakeFromSleep": "On wake from sleep",
|
"OnWakeFromSleep": "On wake from sleep",
|
||||||
"Option3D": "3D",
|
"Option3D": "3D",
|
||||||
"OptionAdminUsers": "Administrators",
|
|
||||||
"OptionAllowAudioPlaybackTranscoding": "Allow audio playback that requires transcoding",
|
"OptionAllowAudioPlaybackTranscoding": "Allow audio playback that requires transcoding",
|
||||||
"OptionAllowBrowsingLiveTv": "Allow Live TV access",
|
"OptionAllowBrowsingLiveTv": "Allow Live TV access",
|
||||||
"OptionAllowContentDownload": "Allow media downloads",
|
"OptionAllowContentDownload": "Allow media downloads",
|
||||||
|
@ -1196,14 +1197,12 @@
|
||||||
"OptionAllowUserToManageServer": "Allow this user to manage the server",
|
"OptionAllowUserToManageServer": "Allow this user to manage the server",
|
||||||
"OptionAllowVideoPlaybackRemuxing": "Allow video playback that requires conversion without re-encoding",
|
"OptionAllowVideoPlaybackRemuxing": "Allow video playback that requires conversion without re-encoding",
|
||||||
"OptionAllowVideoPlaybackTranscoding": "Allow video playback that requires transcoding",
|
"OptionAllowVideoPlaybackTranscoding": "Allow video playback that requires transcoding",
|
||||||
"OptionAllUsers": "All users",
|
|
||||||
"OptionAutomaticallyGroupSeries": "Automatically merge series that are spread across multiple folders",
|
"OptionAutomaticallyGroupSeries": "Automatically merge series that are spread across multiple folders",
|
||||||
"OptionAutomaticallyGroupSeriesHelp": "Series that are spread across multiple folders within this library will be automatically merged into a single series.",
|
"OptionAutomaticallyGroupSeriesHelp": "Series that are spread across multiple folders within this library will be automatically merged into a single series.",
|
||||||
"OptionBluray": "BD",
|
"OptionBluray": "BD",
|
||||||
"OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)",
|
"OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)",
|
||||||
"OptionCommunityRating": "Community Rating",
|
"OptionCommunityRating": "Community Rating",
|
||||||
"OptionCriticRating": "Critics Rating",
|
"OptionCriticRating": "Critics Rating",
|
||||||
"OptionCustomUsers": "Custom",
|
|
||||||
"OptionDaily": "Daily",
|
"OptionDaily": "Daily",
|
||||||
"OptionDateAdded": "Date Added",
|
"OptionDateAdded": "Date Added",
|
||||||
"OptionDateEpisodeAdded": "Date Episode Added",
|
"OptionDateEpisodeAdded": "Date Episode Added",
|
||||||
|
@ -1288,6 +1287,7 @@
|
||||||
"PackageInstallFailed": "{0} (version {1}) installation failed.",
|
"PackageInstallFailed": "{0} (version {1}) installation failed.",
|
||||||
"ParentalRating": "Parental rating",
|
"ParentalRating": "Parental rating",
|
||||||
"PasswordMatchError": "Password and password confirmation must match.",
|
"PasswordMatchError": "Password and password confirmation must match.",
|
||||||
|
"PasswordRequiredForAdmin": "A password is required for admin accounts.",
|
||||||
"PasswordResetComplete": "The password has been reset.",
|
"PasswordResetComplete": "The password has been reset.",
|
||||||
"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
|
"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
|
||||||
"PasswordResetProviderHelp": "Pick a password reset provider to be used when this user requests a password reset.",
|
"PasswordResetProviderHelp": "Pick a password reset provider to be used when this user requests a password reset.",
|
||||||
|
@ -1463,6 +1463,7 @@
|
||||||
"StoryArc": "Story Arc",
|
"StoryArc": "Story Arc",
|
||||||
"StopPlayback": "Stop playback",
|
"StopPlayback": "Stop playback",
|
||||||
"StopRecording": "Stop recording",
|
"StopRecording": "Stop recording",
|
||||||
|
"Studio": "Studio",
|
||||||
"Studios": "Studios",
|
"Studios": "Studios",
|
||||||
"Subtitle": "Subtitle",
|
"Subtitle": "Subtitle",
|
||||||
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "These settings also apply to any Google Cast playback started by this device.",
|
"SubtitleAppearanceSettingsAlsoPassedToCastDevices": "These settings also apply to any Google Cast playback started by this device.",
|
||||||
|
@ -1501,7 +1502,6 @@
|
||||||
"TabNetworking": "Networking",
|
"TabNetworking": "Networking",
|
||||||
"TabNetworks": "TV Networks",
|
"TabNetworks": "TV Networks",
|
||||||
"TabNfoSettings": "NFO Settings",
|
"TabNfoSettings": "NFO Settings",
|
||||||
"TabNotifications": "Notifications",
|
|
||||||
"TabOther": "Other",
|
"TabOther": "Other",
|
||||||
"TabParentalControl": "Parental Control",
|
"TabParentalControl": "Parental Control",
|
||||||
"TabPlugins": "Plugins",
|
"TabPlugins": "Plugins",
|
||||||
|
@ -1560,6 +1560,7 @@
|
||||||
"UseEpisodeImagesInNextUp": "Use episode images in 'Next Up' and 'Continue Watching' sections",
|
"UseEpisodeImagesInNextUp": "Use episode images in 'Next Up' and 'Continue Watching' sections",
|
||||||
"UseEpisodeImagesInNextUpHelp": "'Next Up' and 'Continue Watching' sections will use episode images as thumbnails instead of the primary thumbnail of the show.",
|
"UseEpisodeImagesInNextUpHelp": "'Next Up' and 'Continue Watching' sections will use episode images as thumbnails instead of the primary thumbnail of the show.",
|
||||||
"UserAgentHelp": "Supply a custom 'User-Agent' HTTP header.",
|
"UserAgentHelp": "Supply a custom 'User-Agent' HTTP header.",
|
||||||
|
"UserMenu": "User Menu",
|
||||||
"UserProfilesIntro": "Jellyfin includes support for user profiles with granular display settings, play state, and parental controls.",
|
"UserProfilesIntro": "Jellyfin includes support for user profiles with granular display settings, play state, and parental controls.",
|
||||||
"ValueAlbumCount": "{0} albums",
|
"ValueAlbumCount": "{0} albums",
|
||||||
"ValueAudioCodec": "Audio Codec: {0}",
|
"ValueAudioCodec": "Audio Codec: {0}",
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
"AllowHWTranscodingHelp": "Permita que el sintonizador transcodifique transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.",
|
"AllowHWTranscodingHelp": "Permita que el sintonizador transcodifique transmisiones sobre la marcha. Esto puede ayudar a reducir la transcodificación requerida por el servidor.",
|
||||||
"AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP o IP/máscara de red para redes a las que se les permitirá conectarse de forma remota. Si se deja vacía, todas las direcciones remotas serán permitidas.",
|
"AllowedRemoteAddressesHelp": "Lista separada por comas de direcciones IP o IP/máscara de red para redes a las que se les permitirá conectarse de forma remota. Si se deja vacía, todas las direcciones remotas serán permitidas.",
|
||||||
"AlwaysPlaySubtitlesHelp": "Los subtítulos que concuerden con la preferencia de idioma se cargarán independientemente del idioma del audio.",
|
"AlwaysPlaySubtitlesHelp": "Los subtítulos que concuerden con la preferencia de idioma se cargarán independientemente del idioma del audio.",
|
||||||
"AroundTime": "Alrededor",
|
"AroundTime": "Alrededor de {0}",
|
||||||
"Art": "Arte",
|
"Art": "Arte",
|
||||||
"AsManyAsPossible": "Tantos como sea posible",
|
"AsManyAsPossible": "Tantos como sea posible",
|
||||||
"AspectRatio": "Relación de aspecto",
|
"AspectRatio": "Relación de aspecto",
|
||||||
|
@ -1723,5 +1723,13 @@
|
||||||
"SubtitleYellow": "Amarillo",
|
"SubtitleYellow": "Amarillo",
|
||||||
"Short": "Cortometraje",
|
"Short": "Cortometraje",
|
||||||
"LabelTonemappingMode": "Modo de mapeo de tono",
|
"LabelTonemappingMode": "Modo de mapeo de tono",
|
||||||
"TonemappingModeHelp": "Seleccionar el modo de mapeo de tono. Si experimenta sobreexposición intente cambiar al modo RGB."
|
"TonemappingModeHelp": "Seleccionar el modo de mapeo de tono. Si experimenta sobreexposición intente cambiar al modo RGB.",
|
||||||
|
"MenuOpen": "Abrir menú",
|
||||||
|
"MenuClose": "Cerrar menú",
|
||||||
|
"UserMenu": "Menú del usuario",
|
||||||
|
"Studio": "Estudio",
|
||||||
|
"AllowCollectionManagement": "Permitir que este usuario administre colecciones",
|
||||||
|
"EnableAudioNormalization": "Normalización de audio",
|
||||||
|
"LabelEnableLUFSScan": "Habilitar escaneo LUFS",
|
||||||
|
"GetThePlugin": "Obtener el complemento"
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
"ConfirmDeleteItem": "Al borrar este elemento se borrará del sistema de archivos y de la biblioteca. ¿Quieres continuar?",
|
"ConfirmDeleteItem": "Al borrar este elemento se borrará del sistema de archivos y de la biblioteca. ¿Quieres continuar?",
|
||||||
"ConfirmDeleteItems": "Al borrar este elemento se borrará del sistema de archivos y de la biblioteca. ¿Quieres continuar?",
|
"ConfirmDeleteItems": "Al borrar este elemento se borrará del sistema de archivos y de la biblioteca. ¿Quieres continuar?",
|
||||||
"ConfirmDeletion": "Confirmar borrado",
|
"ConfirmDeletion": "Confirmar borrado",
|
||||||
"ConfirmEndPlayerSession": "¿Quieres cerrar Jellyfin en el dispositivo?",
|
"ConfirmEndPlayerSession": "¿Quieres cerrar Jellyfin en el dispositivo {0}?",
|
||||||
"Connect": "Conectar",
|
"Connect": "Conectar",
|
||||||
"Continuing": "Continuando",
|
"Continuing": "Continuando",
|
||||||
"CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.",
|
"CustomDlnaProfilesHelp": "Crear un perfil personalizado para un nuevo dispositivo o reemplazar un perfil del sistema.",
|
||||||
|
@ -1055,7 +1055,7 @@
|
||||||
"Aired": "Emitido",
|
"Aired": "Emitido",
|
||||||
"AnyLanguage": "Cualquier idioma",
|
"AnyLanguage": "Cualquier idioma",
|
||||||
"Anytime": "En cualquier momento",
|
"Anytime": "En cualquier momento",
|
||||||
"AroundTime": "Aproximadamente",
|
"AroundTime": "Aproximadamente {0}",
|
||||||
"Ascending": "Ascendente",
|
"Ascending": "Ascendente",
|
||||||
"Audio": "Audio",
|
"Audio": "Audio",
|
||||||
"Auto": "Automático",
|
"Auto": "Automático",
|
||||||
|
@ -1724,5 +1724,10 @@
|
||||||
"HeaderPerformance": "Rendimiento",
|
"HeaderPerformance": "Rendimiento",
|
||||||
"LabelTonemappingMode": "Modo de mapeo de tono",
|
"LabelTonemappingMode": "Modo de mapeo de tono",
|
||||||
"TonemappingModeHelp": "Seleccione el modo de mapeado de tono. Si experimenta sobreiluminación intente cambiar al modo RGB.",
|
"TonemappingModeHelp": "Seleccione el modo de mapeado de tono. Si experimenta sobreiluminación intente cambiar al modo RGB.",
|
||||||
"Select": "Seleccionar"
|
"Select": "Seleccionar",
|
||||||
|
"MenuOpen": "Abrir Menú",
|
||||||
|
"MenuClose": "Cerrar Menú",
|
||||||
|
"UserMenu": "Menú de Usuario",
|
||||||
|
"Studio": "Estudio",
|
||||||
|
"AllowCollectionManagement": "Permitir que este usuario administre colecciones"
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,7 +209,7 @@
|
||||||
"Songs": "Kappaleet",
|
"Songs": "Kappaleet",
|
||||||
"Shows": "Sarjat",
|
"Shows": "Sarjat",
|
||||||
"CopyStreamURLSuccess": "Osoite kopioitu onnistuneesti.",
|
"CopyStreamURLSuccess": "Osoite kopioitu onnistuneesti.",
|
||||||
"DeathDateValue": "Kuoli: {}",
|
"DeathDateValue": "Kuoli: {0}",
|
||||||
"CustomDlnaProfilesHelp": "Luo uusi profiili kohdistaaksesi uuteen laitteeseen tai ohittaaksesi järjestelmäprofiilin.",
|
"CustomDlnaProfilesHelp": "Luo uusi profiili kohdistaaksesi uuteen laitteeseen tai ohittaaksesi järjestelmäprofiilin.",
|
||||||
"ErrorAddingMediaPathToVirtualFolder": "Lisättäessä mediasijaintia tapahtui virhe. Varmista, että se on oikein ja Jellyfinillä on sen käyttöoikeus.",
|
"ErrorAddingMediaPathToVirtualFolder": "Lisättäessä mediasijaintia tapahtui virhe. Varmista, että se on oikein ja Jellyfinillä on sen käyttöoikeus.",
|
||||||
"Episodes": "Jaksot",
|
"Episodes": "Jaksot",
|
||||||
|
@ -1007,7 +1007,7 @@
|
||||||
"ValueTimeLimitSingleHour": "Aikaraja: 1 tunti",
|
"ValueTimeLimitSingleHour": "Aikaraja: 1 tunti",
|
||||||
"ValueTimeLimitMultiHour": "Aikaraja: {0} tuntia",
|
"ValueTimeLimitMultiHour": "Aikaraja: {0} tuntia",
|
||||||
"ValueContainer": "Säiliö: {0}",
|
"ValueContainer": "Säiliö: {0}",
|
||||||
"ValueConditions": "Ehdot: {0]",
|
"ValueConditions": "Ehdot: {0}",
|
||||||
"ValueCodec": "Codec: {0}",
|
"ValueCodec": "Codec: {0}",
|
||||||
"ValueAudioCodec": "Audio Codec: {0}",
|
"ValueAudioCodec": "Audio Codec: {0}",
|
||||||
"SeriesYearToPresent": "{0} - Nykyhetki",
|
"SeriesYearToPresent": "{0} - Nykyhetki",
|
||||||
|
@ -1285,7 +1285,7 @@
|
||||||
"LabelTonemappingDesatHelp": "Käytä desaturaatiota korostuksille, jotka ylittävät tämän kirkkaustason. Korkeampi arvo säilyttää enemmän värejä. Asetus auttaa estämään kirkkaiden kohtien epäluonnollisen ylikorostuneet värit muuttamalla ne (pehmeästi) valkoiseksi. Tekee kuvasta luonnollisemman vähentämällä teitoja väriavaruuden ulkopuolisista väreistä. Suositellut ja oletusarovot ovat 0 ja 0.5.",
|
"LabelTonemappingDesatHelp": "Käytä desaturaatiota korostuksille, jotka ylittävät tämän kirkkaustason. Korkeampi arvo säilyttää enemmän värejä. Asetus auttaa estämään kirkkaiden kohtien epäluonnollisen ylikorostuneet värit muuttamalla ne (pehmeästi) valkoiseksi. Tekee kuvasta luonnollisemman vähentämällä teitoja väriavaruuden ulkopuolisista väreistä. Suositellut ja oletusarovot ovat 0 ja 0.5.",
|
||||||
"LabelTonemappingDesat": "Sävykartoituksen desaturaatio",
|
"LabelTonemappingDesat": "Sävykartoituksen desaturaatio",
|
||||||
"LabelTonemappingAlgorithm": "Valitse käytettävä sävykartoitusalgoritmi",
|
"LabelTonemappingAlgorithm": "Valitse käytettävä sävykartoitusalgoritmi",
|
||||||
"LabelSyncPlayTimeSyncOffset": "Ajan säätö",
|
"LabelSyncPlayTimeSyncOffset": "Aika poikkeama",
|
||||||
"LabelSyncPlayTimeSyncDevice": "Synkronoi aika",
|
"LabelSyncPlayTimeSyncDevice": "Synkronoi aika",
|
||||||
"LabelSyncPlaySyncMethod": "Synkronointimetodi",
|
"LabelSyncPlaySyncMethod": "Synkronointimetodi",
|
||||||
"LabelSyncPlayResumePlaybackDescription": "Liitä takaisin ryhmätoisto",
|
"LabelSyncPlayResumePlaybackDescription": "Liitä takaisin ryhmätoisto",
|
||||||
|
@ -1325,7 +1325,7 @@
|
||||||
"LabelPublishedServerUriHelp": "Ohita Jellyfinin käyttämä URI perustuen käyttöliittymän tai asiakasohjelman IP-osoitteeseen.",
|
"LabelPublishedServerUriHelp": "Ohita Jellyfinin käyttämä URI perustuen käyttöliittymän tai asiakasohjelman IP-osoitteeseen.",
|
||||||
"LabelPublishedServerUri": "Julkaistut palvelimen URIt",
|
"LabelPublishedServerUri": "Julkaistut palvelimen URIt",
|
||||||
"LabelPostProcessorArgumentsHelp": "Käytä sijaintia {path} tallenteen tiedostosijaintina.",
|
"LabelPostProcessorArgumentsHelp": "Käytä sijaintia {path} tallenteen tiedostosijaintina.",
|
||||||
"LabelOpenclDeviceHelp": "Tämä on savykartoitukseen käytettävä OpenCL-laite. Pisteen vasemmalla puolella on alustanumero ja oikealla alustan laitenumero. Oletusarvo on 0.0. Vaatii OpenCL-laitteistokiihdytyksen sisältävän FFmpeg-ohjelmatiedoston.",
|
"LabelOpenclDeviceHelp": "Tämä on sävykartoitukseen käytettävä OpenCL-laite. Pisteen vasemmalla puolella on alustanumero ja oikealla alustan laitenumero. Oletusarvo on 0.0. Vaatii OpenCL-laitteistokiihdytyksen sisältävän FFmpeg-ohjelmatiedoston.",
|
||||||
"LabelOpenclDevice": "OpenCL-laite",
|
"LabelOpenclDevice": "OpenCL-laite",
|
||||||
"LabelNumberOfGuideDaysHelp": "Lataamalla ohjelmaoppaan useammalle päivälle, voidaan näyttää kattavammat ohjelmatiedot ja ajoittaa ajastuksia aiemmin, mutta lataukset myös kestävät pidempään. Automaattinen asetus määrittää aikajakson kanavien määrän perusteella.",
|
"LabelNumberOfGuideDaysHelp": "Lataamalla ohjelmaoppaan useammalle päivälle, voidaan näyttää kattavammat ohjelmatiedot ja ajoittaa ajastuksia aiemmin, mutta lataukset myös kestävät pidempään. Automaattinen asetus määrittää aikajakson kanavien määrän perusteella.",
|
||||||
"LabelNumberOfGuideDays": "Päivien lukumäärä jolta ohjelmaoppaan tiedot ladataan",
|
"LabelNumberOfGuideDays": "Päivien lukumäärä jolta ohjelmaoppaan tiedot ladataan",
|
||||||
|
@ -1333,7 +1333,7 @@
|
||||||
"LabelMoviePrefix": "Elokuvan etuliite",
|
"LabelMoviePrefix": "Elokuvan etuliite",
|
||||||
"LabelMonitorUsers": "Monitoroi toimintaa",
|
"LabelMonitorUsers": "Monitoroi toimintaa",
|
||||||
"LabelMinAudiobookResume": "Äänikirjan toiston jatkamisen vähimmäismäärä minuutteina",
|
"LabelMinAudiobookResume": "Äänikirjan toiston jatkamisen vähimmäismäärä minuutteina",
|
||||||
"LabelMaxMuxingQueueSizeHelp": "Suoratoiston alustusta odotettaessa puskuroitavien pakettien enimmäismäärä. Kokeile arovon korotusta, jos havaitset \"Too many packets buffered for output stream\" -virheen FFmpeg-lokissa. Suositeltava arvo on 2048.",
|
"LabelMaxMuxingQueueSizeHelp": "Suoratoiston alustusta odotettaessa puskuroitavien pakettien enimmäismäärä. Kokeile arvon korotusta, jos havaitset \"Too many packets buffered for output stream\" -virheen FFmpeg-lokissa. Suositeltava arvo on 2048.",
|
||||||
"LabelMaxMuxingQueueSize": "Muxausjonon enimmäispituus",
|
"LabelMaxMuxingQueueSize": "Muxausjonon enimmäispituus",
|
||||||
"LabelMatchType": "Täsmäystyyppi",
|
"LabelMatchType": "Täsmäystyyppi",
|
||||||
"LabelKodiMetadataUserHelp": "Tallenna NFO-tiedostoihin katselutiedot, joita muut sovellukset voivat hyödyntää.",
|
"LabelKodiMetadataUserHelp": "Tallenna NFO-tiedostoihin katselutiedot, joita muut sovellukset voivat hyödyntää.",
|
||||||
|
@ -1689,11 +1689,11 @@
|
||||||
"SaveRecordingImagesHelp": "Tallenna ohjelmaoppaan tietolähteen kuvat mediatiedoston oheen.",
|
"SaveRecordingImagesHelp": "Tallenna ohjelmaoppaan tietolähteen kuvat mediatiedoston oheen.",
|
||||||
"HeaderDummyChapter": "Kappalekuvat",
|
"HeaderDummyChapter": "Kappalekuvat",
|
||||||
"LabelDummyChapterDuration": "Aikaväli",
|
"LabelDummyChapterDuration": "Aikaväli",
|
||||||
"LabelDummyChapterDurationHelp": "Kappalekuvien purkuväli sekunteina.",
|
"LabelDummyChapterDurationHelp": "Kappalekuvien purkuväli sekunteina. Poista kappalekuvien luonti käytöstä asettamalla arvoksi 0. Tämän muutos ei vaikuta olemassa oleviin kappaleisiin.",
|
||||||
"LabelDummyChapterCount": "Enimmäismäärä",
|
"LabelDummyChapterCount": "Enimmäismäärä",
|
||||||
"LabelDummyChapterCountHelp": "Yhdestä mediatiedostosta purettavien kappalekuvien enimmäismäärä.",
|
"LabelDummyChapterCountHelp": "Yhdestä mediatiedostosta purettavien kappalekuvien enimmäismäärä.",
|
||||||
"LabelChapterImageResolution": "Resoluutio",
|
"LabelChapterImageResolution": "Resoluutio",
|
||||||
"LabelChapterImageResolutionHelp": "Purettujen kappalekuvien resoluutio.",
|
"LabelChapterImageResolutionHelp": "Purettujen kappalekuvien resoluutio. Tämän muutos ei vaikuta olemassa oleviin kappaleisiin.",
|
||||||
"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.",
|
||||||
|
@ -1722,5 +1722,17 @@
|
||||||
"LabelEnableAudioVbrHelp": "Muuttuva bittinopeus (variable bitrate, VBR) tuottaa keskitasoista paremman laadun, mutta saattaa joissakin harvoissa tapauksissa aiheuttaa puskurointi ja yhteensopivuusongelmia.",
|
"LabelEnableAudioVbrHelp": "Muuttuva bittinopeus (variable bitrate, VBR) tuottaa keskitasoista paremman laadun, mutta saattaa joissakin harvoissa tapauksissa aiheuttaa puskurointi ja yhteensopivuusongelmia.",
|
||||||
"Select": "Valitse",
|
"Select": "Valitse",
|
||||||
"LabelTonemappingMode": "Sävykartoituksen tila",
|
"LabelTonemappingMode": "Sävykartoituksen tila",
|
||||||
"TonemappingModeHelp": "Valitse sävykartoituksen tila. Jos esiintyy ylikorostuneita värejä, kokeile RGB>-tilaa."
|
"TonemappingModeHelp": "Valitse sävykartoituksen tila. Jos esiintyy ylikorostuneita värejä, kokeile RGB>-tilaa.",
|
||||||
|
"MenuOpen": "Avaa valikko",
|
||||||
|
"MenuClose": "Sulje valikko",
|
||||||
|
"UserMenu": "Käyttäjä-valikko",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"AllowCollectionManagement": "Salli tämän käyttäjän hallita kokoelmia",
|
||||||
|
"EnableAudioNormalizationHelp": "Äänen normalisointi asettaa äänelle kiinteän vahvistuksen keskivoimakkuuden vakiotasolla (-18 dB).",
|
||||||
|
"EnableAudioNormalization": "Äänen normalisointi",
|
||||||
|
"LabelEnableLUFSScan": "Suorita LUFS-tarkistus",
|
||||||
|
"LabelEnableLUFSScanHelp": "Käytä musiikin LUFS-tarkistusta (tämä vaatii enemmän aikaa ja resurseja).",
|
||||||
|
"GetThePlugin": "Hanki laajennus",
|
||||||
|
"Notifications": "Ilmoitukset",
|
||||||
|
"NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-laajennukseen."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1040,5 +1040,11 @@
|
||||||
"LabelMetadataPath": "Chemin des métadonnées",
|
"LabelMetadataPath": "Chemin des métadonnées",
|
||||||
"LabelDummyChapterDuration": "Intervalle",
|
"LabelDummyChapterDuration": "Intervalle",
|
||||||
"LabelDummyChapterCount": "Limite",
|
"LabelDummyChapterCount": "Limite",
|
||||||
"LabelChapterImageResolution": "Résolution"
|
"LabelChapterImageResolution": "Résolution",
|
||||||
|
"HeaderPerformance": "Performance",
|
||||||
|
"LabelEnableAudioVbrHelp": "Le débit binaire variable offre une qualité supérieure à la moyenne mais peut, dans de rares cas, causer des problèmes de mise en mémoire tampon et de compatibilité.",
|
||||||
|
"HeaderRecordingMetadataSaving": "Enregistrement des métadonnées",
|
||||||
|
"HeaderDummyChapter": "Images des chapitres",
|
||||||
|
"LabelDummyChapterCountHelp": "Le nombre maximum d'images de chapitre qui seront extraites pour chaque fichier média.",
|
||||||
|
"LabelChapterImageResolutionHelp": "La résolution des images de chapitre."
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"AlwaysPlaySubtitlesHelp": "Les sous-titres correspondant à la langue préférée seront chargés indépendamment de la langue de l'audio.",
|
"AlwaysPlaySubtitlesHelp": "Les sous-titres correspondant à la langue préférée seront chargés indépendamment de la langue de l'audio.",
|
||||||
"AnyLanguage": "N'importe quel langage",
|
"AnyLanguage": "N'importe quel langage",
|
||||||
"Anytime": "N'importe quand",
|
"Anytime": "N'importe quand",
|
||||||
"AroundTime": "Aux environs de",
|
"AroundTime": "Aux environs de {0}",
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AsManyAsPossible": "Autant que possible",
|
"AsManyAsPossible": "Autant que possible",
|
||||||
"Ascending": "Croissant",
|
"Ascending": "Croissant",
|
||||||
|
@ -1421,7 +1421,7 @@
|
||||||
"Authorize": "Autoriser",
|
"Authorize": "Autoriser",
|
||||||
"LabelTonemappingParam": "Paramètre de mappage tonal",
|
"LabelTonemappingParam": "Paramètre de mappage tonal",
|
||||||
"LabelTonemappingPeakHelp": "Redéfinir la valeur crête de référence. Utile lorsque la valeur crête intégrée dans les métadonnées d'affichage n'est pas fiable ou lors du mappage tonal d'une gamme inférieure vers une gamme supérieure. La valeur recommandée est 100, la valeur par défaut est 0.",
|
"LabelTonemappingPeakHelp": "Redéfinir la valeur crête de référence. Utile lorsque la valeur crête intégrée dans les métadonnées d'affichage n'est pas fiable ou lors du mappage tonal d'une gamme inférieure vers une gamme supérieure. La valeur recommandée est 100, la valeur par défaut est 0.",
|
||||||
"EnableAutoCast": "Définir comme par défaut",
|
"EnableAutoCast": "Définir comme valeur par défaut",
|
||||||
"LabelMaxMuxingQueueSizeHelp": "Nombre maximal de paquets pouvant être mis en mémoire tampon lors de l'initialisation des flux. Augmenter la valeur si des messages \"Trop de paquets en mémoire tampon pour le flux de sortie\" apparaissent dans les journaux FFmpeg. La valeur recommandée est 2048.",
|
"LabelMaxMuxingQueueSizeHelp": "Nombre maximal de paquets pouvant être mis en mémoire tampon lors de l'initialisation des flux. Augmenter la valeur si des messages \"Trop de paquets en mémoire tampon pour le flux de sortie\" apparaissent dans les journaux FFmpeg. La valeur recommandée est 2048.",
|
||||||
"LabelMaxMuxingQueueSize": "Taille maximale de la queue de multiplexage",
|
"LabelMaxMuxingQueueSize": "Taille maximale de la queue de multiplexage",
|
||||||
"OptionMaxActiveSessionsHelp": "Une valeur nulle désactive la fonctionnalité.",
|
"OptionMaxActiveSessionsHelp": "Une valeur nulle désactive la fonctionnalité.",
|
||||||
|
@ -1694,10 +1694,10 @@
|
||||||
"HeaderRecordingMetadataSaving": "Enregistrement des métadonnées",
|
"HeaderRecordingMetadataSaving": "Enregistrement des métadonnées",
|
||||||
"SaveRecordingNFO": "Enregistrer les métadonnées du fournisseur dans le fichier NFO",
|
"SaveRecordingNFO": "Enregistrer les métadonnées du fournisseur dans le fichier NFO",
|
||||||
"SaveRecordingImages": "Enregistrer les images des métadonnées du fournisseur",
|
"SaveRecordingImages": "Enregistrer les images des métadonnées du fournisseur",
|
||||||
"LabelDummyChapterDurationHelp": "Intervalle d’extraction des images de chapitre en secondes.",
|
"LabelDummyChapterDurationHelp": "Intervalle entre deux chapitres factices ou 0 pour désactiver la génération. Changer la valeur n’affectera pas les chapitres existants.",
|
||||||
"LabelDummyChapterCount": "Limite",
|
"LabelDummyChapterCount": "Limite",
|
||||||
"LabelChapterImageResolution": "Résolution",
|
"LabelChapterImageResolution": "Résolution",
|
||||||
"LabelChapterImageResolutionHelp": "La résolution des images de chapitre.",
|
"LabelChapterImageResolutionHelp": "La résolution d'image des chapitre factices. Changer la valeur n’aura aucun effet sur les chapitres existants.",
|
||||||
"ResolutionMatchSource": "Résolution de la source",
|
"ResolutionMatchSource": "Résolution de la source",
|
||||||
"HeaderDummyChapter": "Images des chapitres",
|
"HeaderDummyChapter": "Images des chapitres",
|
||||||
"LabelDummyChapterDuration": "Intervalle",
|
"LabelDummyChapterDuration": "Intervalle",
|
||||||
|
@ -1715,7 +1715,7 @@
|
||||||
"SubtitleRed": "Rouge",
|
"SubtitleRed": "Rouge",
|
||||||
"SubtitleWhite": "Blanc",
|
"SubtitleWhite": "Blanc",
|
||||||
"SubtitleYellow": "Jaune",
|
"SubtitleYellow": "Jaune",
|
||||||
"Featurette": "Moyen-Métrage",
|
"Featurette": "Featurette",
|
||||||
"Short": "Court-métrage",
|
"Short": "Court-métrage",
|
||||||
"HeaderPerformance": "Performance",
|
"HeaderPerformance": "Performance",
|
||||||
"LabelParallelImageEncodingLimit": "Limite de parallélisation de l'encodage d'image",
|
"LabelParallelImageEncodingLimit": "Limite de parallélisation de l'encodage d'image",
|
||||||
|
@ -1724,5 +1724,18 @@
|
||||||
"LabelEnableAudioVbrHelp": "Le débit binaire variable offre une qualité supérieure à la moyenne mais peut, dans de rares cas, causer des problèmes de mise en mémoire tampon et de compatibilité.",
|
"LabelEnableAudioVbrHelp": "Le débit binaire variable offre une qualité supérieure à la moyenne mais peut, dans de rares cas, causer des problèmes de mise en mémoire tampon et de compatibilité.",
|
||||||
"LabelTonemappingMode": "Mode de mappage tonal",
|
"LabelTonemappingMode": "Mode de mappage tonal",
|
||||||
"TonemappingModeHelp": "Sélectionnez le mode de mappage tonal. Si vous rencontrez des reflets soufflés, essayez le mode RVB.",
|
"TonemappingModeHelp": "Sélectionnez le mode de mappage tonal. Si vous rencontrez des reflets soufflés, essayez le mode RVB.",
|
||||||
"Select": "Sélectionner"
|
"Select": "Sélectionner",
|
||||||
|
"MenuOpen": "Ouvrir le menu",
|
||||||
|
"MenuClose": "Fermer le menu",
|
||||||
|
"UserMenu": "Menu utilisateur",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"AllowCollectionManagement": "Autoriser cet utilisateur à gérer les collections",
|
||||||
|
"EnableAudioNormalizationHelp": "La normalisation audio ajoutera un gain constant pour maintenir la moyenne au niveau souhaité (-18dB).",
|
||||||
|
"EnableAudioNormalization": "Normalisation audio",
|
||||||
|
"LabelEnableLUFSScan": "Activer l’analyse LUFS",
|
||||||
|
"LabelEnableLUFSScanHelp": "Activer l’analyse LUFS pour la musique (cela prendra plus de temps et de ressources).",
|
||||||
|
"GetThePlugin": "Obtenir le plugin",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée au plugin Webhook.",
|
||||||
|
"PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur."
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,5 +207,368 @@
|
||||||
"ButtonScanAllLibraries": "Escanear tódalas bibliotecas",
|
"ButtonScanAllLibraries": "Escanear tódalas bibliotecas",
|
||||||
"BurnSubtitlesHelp": "Indica se o servidor debería incluír os subtítulos ao recodificar os vídeos. Se non fas esto mellorarás moito o rendemento. Elixe Auto para incluir formatos baseados na imaxe (VobSub, PGS, SUB, IDX, etc.) e certos subtítulos ASS ou SSA.",
|
"BurnSubtitlesHelp": "Indica se o servidor debería incluír os subtítulos ao recodificar os vídeos. Se non fas esto mellorarás moito o rendemento. Elixe Auto para incluir formatos baseados na imaxe (VobSub, PGS, SUB, IDX, etc.) e certos subtítulos ASS ou SSA.",
|
||||||
"ButtonAddScheduledTaskTrigger": "Engadir activador",
|
"ButtonAddScheduledTaskTrigger": "Engadir activador",
|
||||||
"ButtonForgotPassword": "Contrasinal esquecido"
|
"ButtonForgotPassword": "Contrasinal esquecido",
|
||||||
|
"DownloadsValue": "{0} descargas",
|
||||||
|
"EditImages": "Editar imaxes",
|
||||||
|
"EnableHardwareEncoding": "Habilitar codificación por hardware",
|
||||||
|
"EnableNextVideoInfoOverlay": "Mostrar próximo vídeo durante a reprodución",
|
||||||
|
"EnableTonemapping": "Habilitar mapeamento de Ton",
|
||||||
|
"BookLibraryHelp": "Libros de audio e texto soportados. Revise a guía {0} de nomenclatura de libros {1}.",
|
||||||
|
"ButtonPlayer": "Reprodutor",
|
||||||
|
"ButtonTogglePlaylist": "Lista de reprodución",
|
||||||
|
"Bwdif": "BWDIF",
|
||||||
|
"ChannelNumber": "Número da canle",
|
||||||
|
"AllowTonemappingHelp": "O mapeamento de tons pode transformar o rango dinámico do vídeo de HDR a SDR, á vez que se manteñen detalles da imaxe e cor. Sendo estas moi importantes para a reconstrución da escena orixinal. Actualmente só funciona con vídeos 10bit HDR10, HLG e Dovi. Isto require as correspondentes librarías de OpenCL ou Cuda.",
|
||||||
|
"ClientSettings": "Configuración do cliente",
|
||||||
|
"ColorTransfer": "Transferencia de cor",
|
||||||
|
"ConfirmDeleteItem": "Borrar este elemento eliminarao do sistema e da súa biblioteca. Está seguro?",
|
||||||
|
"ConfirmDeletion": "Confirmar borrado",
|
||||||
|
"Copied": "Copiado",
|
||||||
|
"CopyFailed": "Non se puido copiar",
|
||||||
|
"CopyStreamURL": "Copiar URL de transmisión",
|
||||||
|
"CustomDlnaProfilesHelp": "Cre un perfil personalizado para un novo dispositivo ou substituía un perfil do sistema.",
|
||||||
|
"DashboardVersionNumber": "Versión: {0}",
|
||||||
|
"Data": "Datos",
|
||||||
|
"DatePlayed": "Reproducido o",
|
||||||
|
"DateAdded": "Engadido o",
|
||||||
|
"DeleteDeviceConfirmation": "Está seguro de querer borrar este dispositivo? Volverá aparecer a próxima vez que o usuario inicie sesión con el.",
|
||||||
|
"DeleteDevicesConfirmation": "Está seguro de querer borrar todos os dispositivos? Todas as sesións serán fechadas. Os dispositivos reaparecerán a próxima vez que os usuarios inicien sesión.",
|
||||||
|
"DetectingDevices": "Detectando dispositivos",
|
||||||
|
"DeviceAccessHelp": "Isto só aplica a dispositivos que poidan ser completamente identificados e non evitará o acceso co navegador web. Filtrar o acceso a dispositivos a un usuario, evitará o seu uso sen a súa aprobación.",
|
||||||
|
"Arranger": "Organizador",
|
||||||
|
"DisablePlugin": "Deshabilitar",
|
||||||
|
"EditMetadata": "Editar metadatos",
|
||||||
|
"Backdrop": "Imaxe de fondo",
|
||||||
|
"Box": "Caixa",
|
||||||
|
"ButtonActivate": "Activar",
|
||||||
|
"ChannelNameOnly": "Só a canle {0}",
|
||||||
|
"ConfirmDeleteItems": "Borrar estes elementos eliminaraos do sistema e da súa biblioteca. Está seguro?",
|
||||||
|
"CriticRating": "Puntuación dos críticos",
|
||||||
|
"BoxRear": "Caixa (reverso)",
|
||||||
|
"DeleteImage": "Borrar imaxe",
|
||||||
|
"DeleteMedia": "Borrar multimedia",
|
||||||
|
"Episode": "Episodio",
|
||||||
|
"DisplayMissingEpisodesWithinSeasons": "Mostrar episodios perdidos nas temporadas",
|
||||||
|
"DeleteUserConfirmation": "Está seguro de querer borrar este usuario?",
|
||||||
|
"EnableBlurHashHelp": "As imaxes que se están a carregar serán mostradas cun marcador único.",
|
||||||
|
"EnableAutoCast": "Definir como predeterminado",
|
||||||
|
"EnableCinemaMode": "Modo cinema",
|
||||||
|
"ClearQueue": "Borrar fila",
|
||||||
|
"EnableFasterAnimations": "Animacións máis rápidas",
|
||||||
|
"EndsAtValue": "Termina ás {0}",
|
||||||
|
"DirectPlaying": "Reprodución directa",
|
||||||
|
"ButtonUseQuickConnect": "Usar Conexión rápida",
|
||||||
|
"Backdrops": "Imaxes de fondo",
|
||||||
|
"DeathDateValue": "Faleceu: {0}",
|
||||||
|
"EasyPasswordHelp": "O código PIN é usado para inciar sesión sen conexión en clientes soportados, así como para un fácil inicio de sesión na rede local.",
|
||||||
|
"CinemaModeConfigurationHelp": "O modo cinema trae a experiencia do cinema á súa sala, permitindo a reprodución de tráilers e introducións personalizadas antes do acto principal.",
|
||||||
|
"DeleteUser": "Borrar usuario",
|
||||||
|
"Directors": "Directores",
|
||||||
|
"EnableDecodingColorDepth10Hevc": "Habilitar decodificación por hardware de 10 bits para HEVC",
|
||||||
|
"ConfirmDeleteImage": "Borrar a imaxe?",
|
||||||
|
"DailyAt": "Diariamente, ás {0}",
|
||||||
|
"DashboardServerName": "Servidor: {0}",
|
||||||
|
"ColorSpace": "Espazo de cor",
|
||||||
|
"EnableDisplayMirroring": "Duplicado de pantallas",
|
||||||
|
"ColorPrimaries": "Cores primarias",
|
||||||
|
"EditSubtitles": "Editar subtítulos",
|
||||||
|
"DefaultMetadataLangaugeDescription": "Estes son os valores por defecto, mais poden ser personalizados por cada biblioteca.",
|
||||||
|
"CopyStreamURLSuccess": "URL copiada exitosamente.",
|
||||||
|
"DeleteAll": "Borrar todo",
|
||||||
|
"BoxSet": "Colección",
|
||||||
|
"ConfirmEndPlayerSession": "Desexa apagar Jellyfin en {0}?",
|
||||||
|
"ButtonBackspace": "Retroceso",
|
||||||
|
"ButtonClose": "Fechar",
|
||||||
|
"ButtonSpace": "Espazo",
|
||||||
|
"Casual": "Casual",
|
||||||
|
"Console": "Consola",
|
||||||
|
"DoNotRecord": "Non gravar",
|
||||||
|
"DeleteImageConfirmation": "Está seguro de querer borrar esta imaxe?",
|
||||||
|
"EnablePhotosHelp": "As imaxes serán borradas e mostradas xunto outros arquivos multimedia.",
|
||||||
|
"Cursive": "Cursiva",
|
||||||
|
"DirectStreamHelp1": "Esta transmisión de vídeo é compatíbel con este dispositivo, mais posúen un formato de audio (DTS, Dolby TrueHD, etc.) ou número de canles de audio incompatíbeis. A transmisión de vídeo será retransmitida se perdas e en tempo real, antes de ser enviada ao dispositivo. Só a transmisión de audio será transcodificada.",
|
||||||
|
"Copy": "Copiar",
|
||||||
|
"EnablePhotos": "Mostrar fotos",
|
||||||
|
"CommunityRating": "Avaliación da comunidade",
|
||||||
|
"Digital": "Dixital",
|
||||||
|
"DisplayMissingEpisodesWithinSeasonsHelp": "Isto tamén debe ser habilitado para as bibliotecas de TV na configuración do servidor.",
|
||||||
|
"EnableExternalVideoPlayers": "Reprodutores externos",
|
||||||
|
"DirectPlayHelp": "O arquivo de orixe é completamente compatíbel con este cliente, e esta sesión está a recibir o arquivo sen modificacións.",
|
||||||
|
"EnablePlugin": "Habilitar",
|
||||||
|
"DisableCustomCss": "Desactivar o código CSS personalizado provisto polo servidor",
|
||||||
|
"DrmChannelsNotImported": "Canles con DRM non serán importadas.",
|
||||||
|
"EnableExternalVideoPlayersHelp": "O menú de reprodución externo será mostrado ao inicio da repordución do vídeo.",
|
||||||
|
"ButtonExitApp": "Saír da Aplicación",
|
||||||
|
"ChannelAccessHelp": "Seleccione as canles a compartillar con este usuario. Os administradores poderán editar todas as canles usando o xestor de metadatos.",
|
||||||
|
"ConfigureDateAdded": "Configure a forma de determinar os metadatos no Panel de control > Librarías > Configuración NFO",
|
||||||
|
"DownloadAll": "Descarregar todo",
|
||||||
|
"EncoderPresetHelp": "Escolla un valor máis rápido para mellorar o rendemento, ou un menor para mellorar a calidade.",
|
||||||
|
"Engineer": "Enxeñeiro de son",
|
||||||
|
"ErrorPlayerNotFound": "Non foi detectado ningún reprodutor para a multimedia solicitada.",
|
||||||
|
"HeaderCodecProfileHelp": "Os perfís dos códecs indican as limitacións do dispositivo ao reproducir códecs específicos. Se unha limitación ocorrer, o ficheiro multimedia sería transcodificado, aínda se o codec estiver configurado para reprodución directa.",
|
||||||
|
"EnableColorCodedBackgrounds": "Fondos con códigos de cores",
|
||||||
|
"GroupVersions": "Agrupar versións",
|
||||||
|
"HeaderDeleteDevice": "Borrar Dispositivo",
|
||||||
|
"HeaderDeleteItems": "Borrar Elementos",
|
||||||
|
"HeaderDirectPlayProfile": "Perfil de Reprodución Directa",
|
||||||
|
"EnableBackdropsHelp": "Mostrar as imaxes de fondo nalgunhas páxinas durante a navegación pola Biblioteca.",
|
||||||
|
"EnableThemeVideosHelp": "Reproducir vídeos temáticos durante a exploración da bilblioteca.",
|
||||||
|
"EveryNDays": "Cada {0} días",
|
||||||
|
"GroupBySeries": "Agrupar por series",
|
||||||
|
"ExtraLarge": "Extragrande",
|
||||||
|
"DirectStreamHelp2": "A enerxía consumida pola transmisión en directo dependerá xeralmente do perfil de audio. Só a transmisión de vídeo será sen perdas.",
|
||||||
|
"HeaderAddUpdateImage": "Engadir/Actualizar imaxe",
|
||||||
|
"ErrorStartHourGreaterThanEnd": "A hora de finalización debe ser maior ca de incio.",
|
||||||
|
"Genre": "Xénero",
|
||||||
|
"HeaderAddToPlaylist": "Engadir á Lista de Reprodución",
|
||||||
|
"Filter": "Filtro",
|
||||||
|
"HeaderConfirmPluginInstallation": "Configurar Instalación de Plugin",
|
||||||
|
"ErrorSavingTvProvider": "Houbo un erro ao gardar o provedor de servizos de TV. Por favor, verifique a accesibilidade aos contidos do provedor.",
|
||||||
|
"HeaderApiKeys": "Claves da API",
|
||||||
|
"HeaderAddUpdateSubtitle": "Engadir/Actualizar Subtítulo",
|
||||||
|
"Fullscreen": "Pantalla completa",
|
||||||
|
"HeaderAccessSchedule": "Restrición horaria",
|
||||||
|
"ErrorDefault": "Houbo un erro durante o procesamento da petición. Por favor, probe máis tarde.",
|
||||||
|
"HeaderCancelSeries": "Cancelar Series",
|
||||||
|
"HeaderAllowMediaDeletionFrom": "Permitir borrado de multimedia desde",
|
||||||
|
"HeaderConnectionFailure": "Fallo de Conexión",
|
||||||
|
"HeaderConnectToServer": "Conectar ao Servidor",
|
||||||
|
"DirectStreaming": "Transmisión directa",
|
||||||
|
"ErrorAddingXmlTvFile": "Houbo un erro ao acceder ao ficheiro XMLTV. Por favor, verifique a súa existencia e tenteo outra vez.",
|
||||||
|
"EveryXMinutes": "Cada {0} minutos",
|
||||||
|
"HeaderAudioSettings": "Configuracións de Audio",
|
||||||
|
"HeaderApiKey": "Clave da API",
|
||||||
|
"FormatValue": "Formato: {0}",
|
||||||
|
"HeaderContainerProfile": "Perfil do Contedor",
|
||||||
|
"GuestStar": "Estrela invitada",
|
||||||
|
"HeaderChapterImages": "Imaxes dos Capítulos",
|
||||||
|
"HeaderAddToCollection": "Engadir á Colección",
|
||||||
|
"HeaderActiveRecordings": "Gravacións activas",
|
||||||
|
"HeaderDebugging": "Depuración e Rastrexamento",
|
||||||
|
"EnableQuickConnect": "Activar Ligazón Rápida neste servidor",
|
||||||
|
"FetchingData": "Obtendo datos adicionais",
|
||||||
|
"HeaderCodecProfile": "Perfil do Códec",
|
||||||
|
"HeaderCastAndCrew": "Elenco e Persoal",
|
||||||
|
"EveryHour": "Cada hora",
|
||||||
|
"ErrorPleaseSelectLineup": "Por favor, seleccione a programación outra vez. Se a programación non estiver dispoñíbel, revise que o seu nome de usuario contrasinal, e código postal sexan correctos.",
|
||||||
|
"EveryXHours": "Cada {0} horas",
|
||||||
|
"FFmpegSavePathNotFound": "Non foi posíbel atopar o FFmeg en base a localización que proveu. FFprobe tamén é necesario e debe estar no mesmo directorio. Estes compoñentes son, xeralmente, instalados conxuntamente. Por favor, verifique a localización e tenteo outra vez.",
|
||||||
|
"FileReadCancelled": "A lectura do arquivo foi cancelada.",
|
||||||
|
"FileReadError": "Aconteceu un erro durante a lectura do ficheiro.",
|
||||||
|
"Framerate": "Taxa de cadros",
|
||||||
|
"HDPrograms": "Programas en HD",
|
||||||
|
"HardwareAccelerationWarning": "Habilitar a aceleración por hardware pode causar inestabilidade nalgúns entornos. Verifique que o seu sistema operativo e drivers de vídeo estean actualizados. Se tiver dificultades durante a reprodución após a súa habilitación, precisará cambiar a configuración novamente a Ningunha.",
|
||||||
|
"EnableDecodingColorDepth10Vp9": "Habilitar decodificación en hardware de 10 bits para VP9",
|
||||||
|
"EnableFasterAnimationsHelp": "Usar animacións e transicións máis rápidas",
|
||||||
|
"GoogleCastUnsupported": "Google Cast non soportado",
|
||||||
|
"HeaderActiveDevices": "Dispositivos activos",
|
||||||
|
"HeaderConfirmProfileDeletion": "Configurar Borrado de Perfil",
|
||||||
|
"EnableThemeSongsHelp": "Reproducir música temática durante a exploración da bilblioteca.",
|
||||||
|
"Experimental": "Experimental",
|
||||||
|
"HeaderAdditionalParts": "Partes adicionais",
|
||||||
|
"HeaderAppearsOn": "Aparece en",
|
||||||
|
"HeaderAddUser": "Engadir Usuario",
|
||||||
|
"HeaderApiKeysHelp": "As aplicacións externas requiren o uso dunha clave API co fin de se comunicar co servidor. As claves son emitidas durante o inicio de sesión cunha conta de usuario normal.",
|
||||||
|
"HeaderCancelRecording": "Cancelar Gravación",
|
||||||
|
"HeaderChannelAccess": "Acceso ás Canles",
|
||||||
|
"HeaderCustomDlnaProfiles": "Perfís Personalizados",
|
||||||
|
"HeaderDateIssued": "Data de Emisión",
|
||||||
|
"HeaderDeleteDevices": "Borrar Todos os Dispositivos",
|
||||||
|
"HeaderDeleteItem": "Borrar Elemento",
|
||||||
|
"ErrorAddingMediaPathToVirtualFolder": "Houbo un erro ao engadir a localización da multimedia. Por favor, verifique que a localización é válida e que Jellyfin posúe acceso a ela.",
|
||||||
|
"ExitFullscreen": "Saír da pantalla completa",
|
||||||
|
"AllowCollectionManagement": "Permitir a este usuario xestionar coleccións",
|
||||||
|
"ErrorDeletingItem": "Houbo un erro durante o borrado do elemento do servidor. Por favor, verifique que Jellyfin posúe permisos de escritura sobre o ficheiro multimedia antes de o intentar outra vez.",
|
||||||
|
"ErrorGettingTvLineups": "Houbo un erro ao descarregar a programación da TV. Por favor, verifique a información e tenteo novamente.",
|
||||||
|
"FileNotFound": "Arquivo non encontrado.",
|
||||||
|
"HeaderConfigureRemoteAccess": "Configurar Acceso Remoto",
|
||||||
|
"HeaderContinueReading": "Continuar Lendo",
|
||||||
|
"HeaderContinueListening": "Continuar Escoitando",
|
||||||
|
"HeaderDeleteProvider": "Borrar Provedor",
|
||||||
|
"HeaderDetectMyDevices": "Detectar os Meus Dispositivos",
|
||||||
|
"HeaderDeviceAccess": "Acceso ao Dispositivo",
|
||||||
|
"H264CrfHelp": "O 'Factor de Velocidade Constante' (CRF) é a configuración por defecto para os codificadores x264 e x265. Pode definir os valores entre 0 e 51, onde valores baixos resultan nunha mellor calidade (coa consecuencia de ficheiros máis grandes). Os valores recomendados están entre 18 e 28. Por defecto para x264 usa 23, e x265 28, polo que pode usar iso como punto de partida.",
|
||||||
|
"HeaderAccessScheduleHelp": "Cre unha restrición horaria para limitar o acceso durante determinadas horas.",
|
||||||
|
"HeaderAudioBooks": "Audiolibros",
|
||||||
|
"HeaderConfirmRevokeApiKey": "Revocar Clave da API",
|
||||||
|
"HeaderContainerProfileHelp": "Os Perfís do Contedor indican as limitacións do dispositivo ao reproducir formatos específicos. Se unha limitación ocorrer, o ficheiro multimedia sería transcodificado, aínda se o formato estiver configurado para reprodución directa.",
|
||||||
|
"Conductor": "Condutor",
|
||||||
|
"EnableStreamLooping": "Habilitar bucle automático de emisións en directo",
|
||||||
|
"HeaderDefaultRecordingSettings": "Configuración por defecto das gravacións",
|
||||||
|
"AllowFfmpegThrottlingHelp": "Cando unha transcodificación ou remux estiver suficientemente adiantado da actual reprodución, pause o proceso para consumir menos recursos. Isto é moi útil ao ver sen saltar frecuentemente a outros tramos do vídeo. Desactíveo se experimenta problemas na reprodución.",
|
||||||
|
"HeaderBlockItemsWithNoRating": "Bloquear elementos sen ou con valoracións non recoñecidas",
|
||||||
|
"EnableAudioNormalizationHelp": "A normalización do audio engadirá unha ganancia constante para así manter a media ao nivel desexado (-18dB).",
|
||||||
|
"GuideProviderSelectListings": "Seleccionar Listados",
|
||||||
|
"DisplayModeHelp": "Seleccione o tipo de pantalla a usar pola interface.",
|
||||||
|
"EnableAudioNormalization": "Normalización do Audio",
|
||||||
|
"GetThePlugin": "Obter o Plugin",
|
||||||
|
"HeaderAutoDiscovery": "Descoberta de rede",
|
||||||
|
"HeaderDownloadSync": "Descarregar e sincronizar",
|
||||||
|
"LabelIconMaxWidth": "Largura máxima do icono",
|
||||||
|
"LabelSyncPlayNewGroup": "Novo grupo",
|
||||||
|
"MediaInfoSampleRate": "Taxa de amostraxe",
|
||||||
|
"LabelManufacturer": "Fabricante",
|
||||||
|
"Menu": "Menú",
|
||||||
|
"MediaInfoForced": "Forzado",
|
||||||
|
"LabelGroupMoviesIntoCollections": "Agrupar filmes en coleccións",
|
||||||
|
"PreviousTrack": "Saltar ao anterior",
|
||||||
|
"Movie": "Filme",
|
||||||
|
"Quality": "Calidade",
|
||||||
|
"LabelSyncPlayResumePlayback": "Retomar a reprodución local",
|
||||||
|
"MediaInfoColorTransfer": "Transferencia de cor",
|
||||||
|
"Preview": "Vista previa",
|
||||||
|
"MediaInfoInterlaced": "Entrelazado",
|
||||||
|
"MediaInfoLanguage": "Língua",
|
||||||
|
"MessageNoRepositories": "Sen repositorios.",
|
||||||
|
"Primary": "Principal",
|
||||||
|
"LabelIdentificationFieldHelp": "Expresión ou substring regex que non diferencia as minúsculas das maíusculas.",
|
||||||
|
"LabelSyncPlaySettingsDescription": "Cambiar as preferencias de SyncPlay",
|
||||||
|
"QuickConnect": "Ligazón Rápida",
|
||||||
|
"TabResponses": "Respostas",
|
||||||
|
"LabelSubtitleFormatHelp": "Exemplo: srt",
|
||||||
|
"LabelHDHomerunPortRangeHelp": "Restrinxe o intervalo de portos UDP de HDHomeRun a este valor (Por defecto: 1024 - 65535).",
|
||||||
|
"MediaInfoContainer": "Contedor",
|
||||||
|
"LabelTypeText": "Texto",
|
||||||
|
"MediaInfoChannels": "Canles",
|
||||||
|
"OptionDateAdded": "Engadido o",
|
||||||
|
"ManageLibrary": "Xestionar biblioteca",
|
||||||
|
"MediaInfoTimestamp": "Data e hora",
|
||||||
|
"MediaInfoPath": "Localización",
|
||||||
|
"MediaInfoVideoRange": "Rango de vídeo",
|
||||||
|
"OptionDisableUser": "Deshabilite este usuario",
|
||||||
|
"PluginFromRepo": "{0} do repositorio {1}",
|
||||||
|
"TabParentalControl": "Control Parental",
|
||||||
|
"LabelFormat": "Formato",
|
||||||
|
"LabelFriendlyName": "Nome amigábel",
|
||||||
|
"LabelMessageText": "Mensaxe de texto",
|
||||||
|
"LabelMetadata": "Metadatos",
|
||||||
|
"LabelSyncPlayTimeSyncOffset": "Desfase horario",
|
||||||
|
"LabelSyncPlayTimeSyncDevice": "Sincronizando o tempo con",
|
||||||
|
"LabelSyncPlaySettingsExtraTimeOffset": "Compensación do tempo extra",
|
||||||
|
"LabelTime": "Tempo",
|
||||||
|
"LabelTimeLimitHours": "Límite de tempo (horas)",
|
||||||
|
"LabelTitle": "Título",
|
||||||
|
"LabelTonemappingParam": "Parámetro de mapeamento de ton",
|
||||||
|
"LabelVaapiDevice": "Dispositivo VA-API",
|
||||||
|
"LabelVideoRange": "Rango de vídeo",
|
||||||
|
"LabelVersion": "Versión",
|
||||||
|
"TagsValue": "Etiquetas: {0}",
|
||||||
|
"MediaInfoLevel": "Nivel",
|
||||||
|
"MarkUnplayed": "Marcar como Non visto",
|
||||||
|
"MediaInfoDefault": "Por defecto",
|
||||||
|
"LabelMessageTitle": "Título da mensaxe",
|
||||||
|
"ProductionLocations": "Localizacións da produción",
|
||||||
|
"MediaInfoExternal": "Externo",
|
||||||
|
"Profile": "Perfil",
|
||||||
|
"LabelTheme": "Tema",
|
||||||
|
"MediaInfoSize": "Tamaño",
|
||||||
|
"LabelSubtitleVerticalPosition": "Posición veritcal",
|
||||||
|
"LabelSyncPlayAccessNone": "Deshabilitado por este usuario",
|
||||||
|
"MoveRight": "Mover á dereita",
|
||||||
|
"LabelH264Crf": "CRF da codificación H.264",
|
||||||
|
"TabOther": "Outros",
|
||||||
|
"LabelSyncPlayLeaveGroupDescription": "Deshabilitar SyncPlay",
|
||||||
|
"LabelImageType": "Tipo de imaxe",
|
||||||
|
"TabScheduledTasks": "Tarefas programadas",
|
||||||
|
"Person": "Persoa",
|
||||||
|
"LabelMaxResumePercentage": "Porcentaxe máximo de retomada",
|
||||||
|
"Producer": "Produtor",
|
||||||
|
"MediaInfoColorPrimaries": "Cores primarias",
|
||||||
|
"Photo": "Fotografía",
|
||||||
|
"LabelVideoBitrate": "Taxa de bits do vídeo",
|
||||||
|
"LabelVideoCodec": "Codec do vídeo",
|
||||||
|
"LabelUnstable": "Inestábel",
|
||||||
|
"LabelUser": "Usuario",
|
||||||
|
"LabelForgotPasswordUsernameHelp": "Introduza o nome de usuario, se o lembra.",
|
||||||
|
"LabelHardwareEncoding": "Codificación por hardware",
|
||||||
|
"LabelMaxStreamingBitrateHelp": "Especifique unha taxa de bits máxima para transmisión.",
|
||||||
|
"LabelUsername": "Nome de usuario",
|
||||||
|
"HeaderSelectServerCachePathHelp": "Procure ou introduza a localización do cartafol a usar como caché do servidor. Debe ter permiso de escritura.",
|
||||||
|
"LabelMetadataDownloadLanguage": "Idioma preferido de descarga",
|
||||||
|
"LabelMatchType": "Tipo de correspondencia",
|
||||||
|
"LabelSyncPlayAccess": "Acceso SyncPlay",
|
||||||
|
"LabelSyncPlayHaltPlayback": "Parar a reprodución local",
|
||||||
|
"LabelSyncPlayLeaveGroup": "Deixar o grupo",
|
||||||
|
"LabelSyncPlayNewGroupDescription": "Crear un novo grupo",
|
||||||
|
"LabelSyncPlayPlaybackDiff": "Diferenza de tempo de reprodución",
|
||||||
|
"LabelSyncPlaySyncMethod": "Método de sincronización",
|
||||||
|
"LabelTextSize": "Tamaño do texto",
|
||||||
|
"LabelLogs": "Rexistros",
|
||||||
|
"LabelManufacturerUrl": "URL do fabricante",
|
||||||
|
"LabelMetadataPath": "Localización dos metadatos",
|
||||||
|
"LabelStatus": "Estado",
|
||||||
|
"LabelTagline": "Lema",
|
||||||
|
"LabelTextBackgroundColor": "Cor de fondo do texto",
|
||||||
|
"LabelTonemappingRange": "Rango de mapeamento do ton",
|
||||||
|
"LabelUserAgent": "Axente de usuario",
|
||||||
|
"Save": "Gardar",
|
||||||
|
"Tags": "Etiquetas",
|
||||||
|
"LabelStopping": "A parar",
|
||||||
|
"LabelStopWhenPossible": "Parar cando sexa posíbel",
|
||||||
|
"LabelStreamType": "Tipo de fluxo",
|
||||||
|
"LabelSubtitlePlaybackMode": "Modo do subtítulo",
|
||||||
|
"LabelTag": "Etiqueta",
|
||||||
|
"LabelTextColor": "Cor do texto",
|
||||||
|
"LabelTextWeight": "Estilo do texto",
|
||||||
|
"LabelTranscodes": "Transcodificacións",
|
||||||
|
"LabelType": "Tipo",
|
||||||
|
"LabelVideoResolution": "Resolución do vídeo",
|
||||||
|
"LabelWeb": "Web",
|
||||||
|
"MediaInfoBitrate": "Taxa de bits",
|
||||||
|
"MediaInfoTitle": "Título",
|
||||||
|
"OptionDaily": "Diario",
|
||||||
|
"Production": "Produción",
|
||||||
|
"Saturday": "Sábado",
|
||||||
|
"TabPlugins": "Plugins",
|
||||||
|
"TabProfiles": "Perfís",
|
||||||
|
"TabRepositories": "Repositorios",
|
||||||
|
"TabServer": "Servidor",
|
||||||
|
"TextSent": "Texto enviado.",
|
||||||
|
"ThemeSongs": "Banda sonora",
|
||||||
|
"VideoResolutionNotSupported": "A resolución do vídeo non é compatíbel",
|
||||||
|
"LabelHardwareAccelerationTypeHelp": "A aceleración por hardware require configuración adicional.",
|
||||||
|
"LabelHttpsPort": "Número de porto HTTPS local",
|
||||||
|
"LabelHttpsPortHelp": "O número de porto TCP para o servidor HTTPS.",
|
||||||
|
"LabelSyncPlayAccessCreateAndJoinGroups": "Permitir que o usuario cre e se una a grupos",
|
||||||
|
"LabelSyncPlayAccessJoinGroups": "Permitir que o usuario se xunte a grupos",
|
||||||
|
"LabelValue": "Valor",
|
||||||
|
"Lyricist": "Letrista",
|
||||||
|
"MarkPlayed": "Marcar como visto",
|
||||||
|
"MediaInfoResolution": "Resolución",
|
||||||
|
"PreviousChapter": "Capítulo anterior",
|
||||||
|
"TabStreaming": "Transmisión",
|
||||||
|
"MenuOpen": "Abrir Menú",
|
||||||
|
"MenuClose": "Fechar Menú",
|
||||||
|
"OptionDatePlayed": "Data de reprodución",
|
||||||
|
"Programs": "Programas",
|
||||||
|
"SaveChanges": "Gardar cambios",
|
||||||
|
"LabelIsForced": "Forzados",
|
||||||
|
"LabelSyncPlaySettingsSkipToSync": "Habilitar SkipToSync",
|
||||||
|
"Bold": "Negrita",
|
||||||
|
"MediaInfoProfile": "Perfil",
|
||||||
|
"MediaInfoColorSpace": "Espazo de cor",
|
||||||
|
"MediaInfoCodec": "Codec",
|
||||||
|
"MessageAlreadyInstalled": "Esta versión xa está instalada.",
|
||||||
|
"LabelIconMaxResHelp": "Resolución máxima dos iconos expostos por medio da propiedade 'upnp:icon'.",
|
||||||
|
"ManageRecording": "Xestionar gravación",
|
||||||
|
"LabelKodiMetadataEnablePathSubstitution": "Habilitar substitución de localizacións",
|
||||||
|
"LabelSyncPlaySettingsSyncCorrection": "Corrección da sincronización",
|
||||||
|
"LabelMaxResumePercentageHelp": "Os títulos seran considerados como vistos se son parados pasado este momento.",
|
||||||
|
"PersonRole": "como {0}",
|
||||||
|
"LabelVersionInstalled": "{0} instalado",
|
||||||
|
"LabelH265Crf": "CRF da codificación H.265",
|
||||||
|
"LabelHardwareAccelerationType": "Aceleración por hardware",
|
||||||
|
"LabelHDHomerunPortRange": "Intervalo de portos HDHomeRun",
|
||||||
|
"LabelIconMaxHeight": "Altura máxima do icono",
|
||||||
|
"LabelImportOnlyFavoriteChannels": "Restrinxir as canles marcadas como favoritas",
|
||||||
|
"LabelKidsCategories": "Categorías para nenos",
|
||||||
|
"LabelKnownProxies": "Proxies coñecidos",
|
||||||
|
"LabelInternetQuality": "Calidade do Internet",
|
||||||
|
"LabelKodiMetadataDateFormat": "Formato da data de lanzamento",
|
||||||
|
"LabelKeepUpTo": "Manter ata",
|
||||||
|
"LabelMaxStreamingBitrate": "Calidade máxima de transmisión",
|
||||||
|
"LabelUserLibrary": "Biblioteca do usuario",
|
||||||
|
"LearnHowYouCanContribute": "Descubra como vostede pode contribuír.",
|
||||||
|
"MediaInfoPixelFormat": "Formato de píxeles",
|
||||||
|
"MediaInfoCodecTag": "Etiqueta do codec"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1039,7 +1039,7 @@
|
||||||
"ClearQueue": "Hapus antrian",
|
"ClearQueue": "Hapus antrian",
|
||||||
"Bwdif": "BWDIF",
|
"Bwdif": "BWDIF",
|
||||||
"ButtonCast": "Putar ke perangkat",
|
"ButtonCast": "Putar ke perangkat",
|
||||||
"AllowTonemappingHelp": "Pemetaan corak dapat mengubah rentang dinamis sebuah video dari HDR menjadi SDR sembari mempertahankan detail dan warna gambar, yang merupakan informasi yang sangat penting untuk merepresentasikan adegan orisinal. Saat ini hanya bekerja dengan video HDR10 atau HLG. Hal ini memerlukan waktu proses OpenCL atau CUDA yang sesuai.",
|
"AllowTonemappingHelp": "Tone-mapping dapat mengubah jangkauan dinamis video dari HDR ke SDR dengan tetap mempertahankan detail dan warna gambar, yang merupakan informasi yang sangat penting untuk merepresentasikan pemandangan aslinya. Saat ini hanya berfungsi dengan video 10bit HDR10,HLG dan DoVi. Fitur ini membutuhkan runtime OpenCL atau CUDA yang sesuai.",
|
||||||
"HeaderPortRanges": "Pengaturan Proxy dan Firewall",
|
"HeaderPortRanges": "Pengaturan Proxy dan Firewall",
|
||||||
"HeaderUninstallPlugin": "Copot Plugin",
|
"HeaderUninstallPlugin": "Copot Plugin",
|
||||||
"LabelTonemappingAlgorithm": "Pilih algoritma Tone Mapping untuk digunakan",
|
"LabelTonemappingAlgorithm": "Pilih algoritma Tone Mapping untuk digunakan",
|
||||||
|
|
|
@ -1402,7 +1402,7 @@
|
||||||
"OptionMaxActiveSessionsHelp": "Il valore 0 disabilita la funzionalità.",
|
"OptionMaxActiveSessionsHelp": "Il valore 0 disabilita la funzionalità.",
|
||||||
"OptionMaxActiveSessions": "Imposta il numero massimo di connessioni utente simultanee.",
|
"OptionMaxActiveSessions": "Imposta il numero massimo di connessioni utente simultanee.",
|
||||||
"LabelUserMaxActiveSessions": "Numero massimo di sessioni utente contemporanee",
|
"LabelUserMaxActiveSessions": "Numero massimo di sessioni utente contemporanee",
|
||||||
"EnableAutoCast": "Imposta come Default",
|
"EnableAutoCast": "Imposta come default",
|
||||||
"LabelTonemappingDesat": "Desaturazione mappatura dei toni",
|
"LabelTonemappingDesat": "Desaturazione mappatura dei toni",
|
||||||
"TonemappingRangeHelp": "Seleziona l'intervallo di colore in uscita. Auto imposta lo stesso del valore di entrata.",
|
"TonemappingRangeHelp": "Seleziona l'intervallo di colore in uscita. Auto imposta lo stesso del valore di entrata.",
|
||||||
"LabelTonemappingRange": "Intervallo mappatura dei toni",
|
"LabelTonemappingRange": "Intervallo mappatura dei toni",
|
||||||
|
@ -1703,9 +1703,9 @@
|
||||||
"SubtitleMagenta": "Viola",
|
"SubtitleMagenta": "Viola",
|
||||||
"SubtitleRed": "Rosso",
|
"SubtitleRed": "Rosso",
|
||||||
"LabelDummyChapterDuration": "Intervallo",
|
"LabelDummyChapterDuration": "Intervallo",
|
||||||
"LabelDummyChapterDurationHelp": "Intervallo in secondi tra le estrazioni delle immagini in capitoli.",
|
"LabelDummyChapterDurationHelp": "Intervallo in secondi tra i finti capitoli. Imposta a 0 per disabilitare la generazione. La modifica di quest'opzione non avrà effetto sui capitoli già generati.",
|
||||||
"LabelDummyChapterCountHelp": "Numero massimo di capitoli da estrarre per ciascun file.",
|
"LabelDummyChapterCountHelp": "Numero massimo di capitoli da estrarre per ciascun file.",
|
||||||
"LabelChapterImageResolutionHelp": "Risoluzione delle immagini per l'estrazione.",
|
"LabelChapterImageResolutionHelp": "Risoluzione delle immagini per l'estrazione dei capitoli. La modifica di quest'opzione non avrà effetto sui capitoli già generati.",
|
||||||
"HeaderDummyChapter": "Immagini Capitolo",
|
"HeaderDummyChapter": "Immagini Capitolo",
|
||||||
"SaveRecordingNFO": "Salva i metadati EPG in NFO",
|
"SaveRecordingNFO": "Salva i metadati EPG in NFO",
|
||||||
"HeaderRecordingMetadataSaving": "Registrazione Metadati",
|
"HeaderRecordingMetadataSaving": "Registrazione Metadati",
|
||||||
|
@ -1724,5 +1724,12 @@
|
||||||
"LabelParallelImageEncodingLimitHelp": "Numero massimo di codifiche di immagini che possono essere eseguite in parallelo. Impostandolo su 0 sceglierai un limite basato sulle specifiche del tuo sistema.",
|
"LabelParallelImageEncodingLimitHelp": "Numero massimo di codifiche di immagini che possono essere eseguite in parallelo. Impostandolo su 0 sceglierai un limite basato sulle specifiche del tuo sistema.",
|
||||||
"PreferEmbeddedExtrasTitlesOverFileNames": "Preferisci i titoli incorporati ai nomi dei file per gli extra",
|
"PreferEmbeddedExtrasTitlesOverFileNames": "Preferisci i titoli incorporati ai nomi dei file per gli extra",
|
||||||
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Gli extra hanno spesso lo stesso nome incorporato del parent, controlla questo per usare comunque titoli incorporati per loro.",
|
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Gli extra hanno spesso lo stesso nome incorporato del parent, controlla questo per usare comunque titoli incorporati per loro.",
|
||||||
"SaveRecordingImagesHelp": "Salva le immagini dal fornitore di elenchi EPG insieme ai media."
|
"SaveRecordingImagesHelp": "Salva le immagini dal fornitore di elenchi EPG insieme ai media.",
|
||||||
|
"MenuOpen": "Apri Menù",
|
||||||
|
"MenuClose": "Chiudi Menù",
|
||||||
|
"UserMenu": "Menù Utente",
|
||||||
|
"Studio": "Studio",
|
||||||
|
"GetThePlugin": "Ottieni il Plugin",
|
||||||
|
"Notifications": "Notifiche",
|
||||||
|
"AllowCollectionManagement": "Permetti a questo utente di gestire le collezioni"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1236,7 +1236,7 @@
|
||||||
"Poster": "포스터",
|
"Poster": "포스터",
|
||||||
"PleaseConfirmPluginInstallation": "위 내용을 읽었으며 플러그인 설치를 계속하려면 확인을 클릭하십시오.",
|
"PleaseConfirmPluginInstallation": "위 내용을 읽었으며 플러그인 설치를 계속하려면 확인을 클릭하십시오.",
|
||||||
"PlaybackErrorNoCompatibleStream": "이 클라이언트는 미디어와 호환되지 않아 서버가 데이터를 전송하지 않았습니다.",
|
"PlaybackErrorNoCompatibleStream": "이 클라이언트는 미디어와 호환되지 않아 서버가 데이터를 전송하지 않았습니다.",
|
||||||
"PlaybackRate": "재생율",
|
"PlaybackRate": "재생 속도",
|
||||||
"PlaceFavoriteChannelsAtBeginning": "처음에 즐겨찾는 채널 배치",
|
"PlaceFavoriteChannelsAtBeginning": "처음에 즐겨찾는 채널 배치",
|
||||||
"Photo": "사진",
|
"Photo": "사진",
|
||||||
"Person": "사람",
|
"Person": "사람",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"AddToPlaylist": "Pridėti į grojaraštį",
|
"AddToPlaylist": "Pridėti į grojaraštį",
|
||||||
"AdditionalNotificationServices": "Naršykite įskiepių katalogą ir įsidiekite papildomų pranešimų paslaugų.",
|
"AdditionalNotificationServices": "Naršykite įskiepių katalogą ir įsidiekite papildomų pranešimų paslaugų.",
|
||||||
"AllChannels": "Visi kanalai",
|
"AllChannels": "Visi kanalai",
|
||||||
"AllEpisodes": "Visas serijas",
|
"AllEpisodes": "Visos serijos",
|
||||||
"Anytime": "Bet kada",
|
"Anytime": "Bet kada",
|
||||||
"AroundTime": "Maždaug {0}",
|
"AroundTime": "Maždaug {0}",
|
||||||
"AsManyAsPossible": "Kiek įmanoma",
|
"AsManyAsPossible": "Kiek įmanoma",
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
"CancelRecording": "Atšaukti įrašymą",
|
"CancelRecording": "Atšaukti įrašymą",
|
||||||
"CancelSeries": "Atšaukti laidą",
|
"CancelSeries": "Atšaukti laidą",
|
||||||
"Categories": "Kategorijos",
|
"Categories": "Kategorijos",
|
||||||
"ChannelAccessHelp": "Pasirinkite kanalus, kuriuos norite dalintis su šiuo vartotoju. Administratoriai galės redaguoti visus kanalus per metaduomenų valdymą.",
|
"ChannelAccessHelp": "Pasirinkite kanalus, kuriuos norite dalintis su šiuo vartotoju. Administratoriai galės redaguoti visus kanalus naudojantis metaduomenų valdymą.",
|
||||||
"ChannelNameOnly": "Kanalas tik {0}",
|
"ChannelNameOnly": "Kanalas tik {0}",
|
||||||
"ChannelNumber": "Kanalo numeris",
|
"ChannelNumber": "Kanalo numeris",
|
||||||
"Composer": "Kompozitorius",
|
"Composer": "Kompozitorius",
|
||||||
|
@ -72,14 +72,14 @@
|
||||||
"HeaderAddToPlaylist": "Pridėti į grojaraštį",
|
"HeaderAddToPlaylist": "Pridėti į grojaraštį",
|
||||||
"HeaderAdditionalParts": "Papildomos dalys",
|
"HeaderAdditionalParts": "Papildomos dalys",
|
||||||
"HeaderCancelRecording": "Atšaukti įrašymą",
|
"HeaderCancelRecording": "Atšaukti įrašymą",
|
||||||
"HeaderCancelSeries": "Atšaukti laidą",
|
"HeaderCancelSeries": "Atšaukti serialą",
|
||||||
"HeaderContinueWatching": "Žiūrėti toliau",
|
"HeaderContinueWatching": "Žiūrėti toliau",
|
||||||
"HeaderCustomDlnaProfiles": "Kiti profiliai",
|
"HeaderCustomDlnaProfiles": "Kiti profiliai",
|
||||||
"HeaderDeleteItem": "Ištrinti elementą",
|
"HeaderDeleteItem": "Ištrinti elementą",
|
||||||
"HeaderDeleteItems": "Ištrinti elementus",
|
"HeaderDeleteItems": "Ištrinti elementus",
|
||||||
"HeaderDeviceAccess": "Įrenginio prieiga",
|
"HeaderDeviceAccess": "Įrenginio prieiga",
|
||||||
"HeaderEasyPinCode": "Lengvas Pin kodas",
|
"HeaderEasyPinCode": "Lengvas Pin kodas",
|
||||||
"HeaderEditImages": "Redaguoti paveikslus",
|
"HeaderEditImages": "Redaguoti atvaizdus",
|
||||||
"HeaderEnabledFields": "Įjungti laukeliai",
|
"HeaderEnabledFields": "Įjungti laukeliai",
|
||||||
"HeaderEnabledFieldsHelp": "Nuimkite varnelę nuo lauko kad jį užrakinti ir neleisti keisti jo duomenų.",
|
"HeaderEnabledFieldsHelp": "Nuimkite varnelę nuo lauko kad jį užrakinti ir neleisti keisti jo duomenų.",
|
||||||
"HeaderFeatureAccess": "Prieiga prie funkcijų",
|
"HeaderFeatureAccess": "Prieiga prie funkcijų",
|
||||||
|
@ -329,9 +329,9 @@
|
||||||
"OptionWeekly": "Savaitinis",
|
"OptionWeekly": "Savaitinis",
|
||||||
"OriginalAirDateValue": "Pirmo eterio data: {0}",
|
"OriginalAirDateValue": "Pirmo eterio data: {0}",
|
||||||
"Overview": "Apžvalga",
|
"Overview": "Apžvalga",
|
||||||
"PackageInstallCancelled": "{0} įdiegimas atšauktas.",
|
"PackageInstallCancelled": "{0} (versija {1}) įdiegimas atšauktas.",
|
||||||
"PackageInstallCompleted": "{0} įdiegimas baigtas.",
|
"PackageInstallCompleted": "{0} (versija {1}) įdiegimas baigtas.",
|
||||||
"PackageInstallFailed": "{0} įdiegimas nepavyko.",
|
"PackageInstallFailed": "{0} (versija {1}) įdiegimas nepavyko.",
|
||||||
"ParentalRating": "Tėvų reitingas",
|
"ParentalRating": "Tėvų reitingas",
|
||||||
"People": "Žmonės",
|
"People": "Žmonės",
|
||||||
"PlaceFavoriteChannelsAtBeginning": "Mėgstamiausius kanalus į pradžią",
|
"PlaceFavoriteChannelsAtBeginning": "Mėgstamiausius kanalus į pradžią",
|
||||||
|
@ -840,7 +840,7 @@
|
||||||
"ButtonSplit": "Skirstyti",
|
"ButtonSplit": "Skirstyti",
|
||||||
"AskAdminToCreateLibrary": "Paprašykite administratoriaus sukurti biblioteką.",
|
"AskAdminToCreateLibrary": "Paprašykite administratoriaus sukurti biblioteką.",
|
||||||
"Album": "Albumas",
|
"Album": "Albumas",
|
||||||
"ButtonSyncPlay": "SyncPlay",
|
"ButtonSyncPlay": "\"SyncPlay\"",
|
||||||
"MusicVideos": "Muzikiniai vaizdo įrašai",
|
"MusicVideos": "Muzikiniai vaizdo įrašai",
|
||||||
"TagsValue": "Žymės: {0}",
|
"TagsValue": "Žymės: {0}",
|
||||||
"AddToFavorites": "Pridėti į mėgstamiausius",
|
"AddToFavorites": "Pridėti į mėgstamiausius",
|
||||||
|
@ -978,7 +978,7 @@
|
||||||
"ButtonSpace": "Tarpas",
|
"ButtonSpace": "Tarpas",
|
||||||
"ButtonActivate": "Aktyvuoti",
|
"ButtonActivate": "Aktyvuoti",
|
||||||
"AllowFfmpegThrottlingHelp": "Kai perkodavimas arba pakartotinis atkodavimas (Remux) yra pakankamai toli nuo dabartinės atkūrimo pozicijos, pristabdykite procesą, kad jis sunaudotų mažiau išteklių. Tai naudingiausia žiūrint be dažno persukimo. Išjunkite šią funkciją, jei kyla atkūrimo problemų.",
|
"AllowFfmpegThrottlingHelp": "Kai perkodavimas arba pakartotinis atkodavimas (Remux) yra pakankamai toli nuo dabartinės atkūrimo pozicijos, pristabdykite procesą, kad jis sunaudotų mažiau išteklių. Tai naudingiausia žiūrint be dažno persukimo. Išjunkite šią funkciją, jei kyla atkūrimo problemų.",
|
||||||
"AllowTonemappingHelp": "Tonų atvaizdavimas gali pakeisti vaizdo įrašo dinaminį diapazoną iš HDR į SDR, išlaikant vaizdo detales ir spalvas, kurios yra labai svarbi informacija, kad būtų galima atvaizduoti pradinę sceną. Šiuo metu veikia tik su HDR10 arba HLG vaizdo įrašais. Tam reikia atitinkamos \"OpenCL\" arba CUDA paleidimo programos.",
|
"AllowTonemappingHelp": "Tonų atvaizdavimas gali pakeisti vaizdo įrašo dinaminį diapazoną iš HDR į SDR, išlaikant vaizdo detales ir spalvas, kurios yra labai svarbi informacija, kad būtų galima atvaizduoti pradinę sceną. Šiuo metu veikia tik su 10 bitų HDR10, HLG ir DoVi vaizdo įrašais. Tam reikia atitinkamos \"OpenCL\" arba CUDA paleidimo programos.",
|
||||||
"Arranger": "Organizatorius",
|
"Arranger": "Organizatorius",
|
||||||
"EnableBlurHashHelp": "Vaizdai, kurie vis dar įkeliami, bus rodomi su unikalia rezervuota vieta.",
|
"EnableBlurHashHelp": "Vaizdai, kurie vis dar įkeliami, bus rodomi su unikalia rezervuota vieta.",
|
||||||
"EnableDetailsBannerHelp": "Rodyti reklamjuostės vaizdą elemento išsamios informacijos puslapio viršuje.",
|
"EnableDetailsBannerHelp": "Rodyti reklamjuostės vaizdą elemento išsamios informacijos puslapio viršuje.",
|
||||||
|
@ -1049,7 +1049,7 @@
|
||||||
"PinCodeResetConfirmation": "Ar tikrai norite iš naujo nustatyti \"Easy PIN\" kodą?",
|
"PinCodeResetConfirmation": "Ar tikrai norite iš naujo nustatyti \"Easy PIN\" kodą?",
|
||||||
"LabelScreensaver": "Ekrano užsklanda",
|
"LabelScreensaver": "Ekrano užsklanda",
|
||||||
"LabelAutoDiscoveryTracingHelp": "Kai ši funkcija įjungta, automatinio aptikimo prievadu gauti paketai bus registruojami.",
|
"LabelAutoDiscoveryTracingHelp": "Kai ši funkcija įjungta, automatinio aptikimo prievadu gauti paketai bus registruojami.",
|
||||||
"LabelBaseUrlHelp": "",
|
"LabelBaseUrlHelp": "Pridėti subdirektorija prie serverio adreso. Pavyzdžiui: <code>http://example.com/<b><baseurl></b></code>",
|
||||||
"LabelBindToLocalNetworkAddressHelp": "Pakeiskite vietinį HTTP serverio IP adresą. Jei paliekama tuščia, serveris bus susietas su visais prieinamais adresais. Keičiant šią reikšmę reikia iš naujo paleisti serverį.",
|
"LabelBindToLocalNetworkAddressHelp": "Pakeiskite vietinį HTTP serverio IP adresą. Jei paliekama tuščia, serveris bus susietas su visais prieinamais adresais. Keičiant šią reikšmę reikia iš naujo paleisti serverį.",
|
||||||
"LabelEnableHttpsHelp": "Klausykitės sukonfigūruotame HTTPS prievade. Taip pat turi būti pateiktas galiojantis sertifikatas, kad tai įsigaliotų.",
|
"LabelEnableHttpsHelp": "Klausykitės sukonfigūruotame HTTPS prievade. Taip pat turi būti pateiktas galiojantis sertifikatas, kad tai įsigaliotų.",
|
||||||
"LabelExtractChaptersDuringLibraryScanHelp": "Generuoti skyrių vaizdus, kai bibliotekos skenavimo metu importuojami vaizdo įrašai. Priešingu atveju jie bus išskirti atliekant suplanuotą skyrių vaizdų užduotį, todėl įprastas bibliotekos skenavimas bus baigtas greičiau.",
|
"LabelExtractChaptersDuringLibraryScanHelp": "Generuoti skyrių vaizdus, kai bibliotekos skenavimo metu importuojami vaizdo įrašai. Priešingu atveju jie bus išskirti atliekant suplanuotą skyrių vaizdų užduotį, todėl įprastas bibliotekos skenavimas bus baigtas greičiau.",
|
||||||
|
@ -1131,5 +1131,36 @@
|
||||||
"LabelDateAddedBehaviorHelp": "Jei yra metaduomenų reikšmė, ji visada bus naudojama prieš bet kurią iš šių parinkčių.",
|
"LabelDateAddedBehaviorHelp": "Jei yra metaduomenų reikšmė, ji visada bus naudojama prieš bet kurią iš šių parinkčių.",
|
||||||
"LabelDefaultUserHelp": "Nustatykite, kuri naudotojo biblioteka turėtų būti rodoma prijungtuose įrenginiuose. Tai galima pakeisti kiekvienam įrenginiui naudojant profilius.",
|
"LabelDefaultUserHelp": "Nustatykite, kuri naudotojo biblioteka turėtų būti rodoma prijungtuose įrenginiuose. Tai galima pakeisti kiekvienam įrenginiui naudojant profilius.",
|
||||||
"LabelDeinterlaceMethod": "Pertempimo panaikinimo metodas",
|
"LabelDeinterlaceMethod": "Pertempimo panaikinimo metodas",
|
||||||
"LabelDownMixAudioScaleHelp": "Jei reikšmė lygi vienetui, bus išsaugotas originalus garsumas."
|
"LabelDownMixAudioScaleHelp": "Jei reikšmė lygi vienetui, bus išsaugotas originalus garsumas.",
|
||||||
|
"LabelLibraryPageSize": "Mediatekos puslapio dydis",
|
||||||
|
"LabelKodiMetadataSaveImagePaths": "Išsaugoti atvaizdų nuorodas NFO failuose",
|
||||||
|
"EnableAudioNormalization": "Garso normalizacija",
|
||||||
|
"LabelEnableAudioVbr": "Įjungti VBR garso kodavimą",
|
||||||
|
"HeaderPerformance": "Našumas",
|
||||||
|
"AllowCollectionManagement": "Leisti šiam vartotojui redaguoti kolekcijas",
|
||||||
|
"EnableAudioNormalizationHelp": "Garso normalizavimas pridės stabilų daugiklį kad palaikyti vidurkį ties norimu lygiu (-18dB).",
|
||||||
|
"LabelKnownProxies": "Žinomi \"proxy\" serveriai",
|
||||||
|
"LabelKidsCategories": "Kategorijos vaikams",
|
||||||
|
"LabelKodiMetadataUser": "Išsaugoti vartotojo peržiūrų informaciją NFO failuose",
|
||||||
|
"LabelLanNetworks": "LAN tinklai",
|
||||||
|
"LabelMaxDaysForNextUp": "Max dienų sekančių eilėje",
|
||||||
|
"LabelLineup": "Rikiuotė",
|
||||||
|
"LabelLoginDisclaimer": "Prisijungimo išsižadėjimas",
|
||||||
|
"LabelMaxAudiobookResume": "Audioknygos likęs pratęsimo laikas",
|
||||||
|
"LabelMaxMuxingQueueSize": "Maksimalus multipleksavimo eilės dydis",
|
||||||
|
"LabelLibraryPageSizeHelp": "Nustatyti rodomų elementų kiekį bibliotekos puslapyje. Įrašius 0 bus išjungti puslapiai.",
|
||||||
|
"LabelLoginDisclaimerHelp": "Žinutė kuri bus rodoma prisijungimo puslapio apačioje.",
|
||||||
|
"LabelKodiMetadataEnablePathSubstitution": "Įjungti kelio pakeitimą",
|
||||||
|
"LabelKodiMetadataUserHelp": "Išsaugoti peržiūrų informaciją NFO failuose, kad kitos aplikacijos galėtų naudotis.",
|
||||||
|
"LabelKodiMetadataSaveImagePathsHelp": "Tai rekomenduojama jei nuotraukų failų pavadinimai neatitinka Kodi rekomenduojamų nuostatų.",
|
||||||
|
"LabelMaxAudiobookResumeHelp": "Turinys skaitysis pilnai peržiūrėtas, jei bus sustabdytas kai likęs laikas bus mažesnis už šią vertę.",
|
||||||
|
"LabelMaxDaysForNextUpHelp": "Nustatyti maksimalų dienų kiekį kiek TV Laida liks sekančių eilėje jos neperžiūrėjus.",
|
||||||
|
"LabelIsForced": "Priverstas",
|
||||||
|
"LabelEnableLUFSScan": "Įjungti LUFS skanavimą",
|
||||||
|
"LabelEnableLUFSScanHelp": "Įjungti LUFS skanavimą muzikai (Užtruks ilgiau ir naudos daugiau resursų).",
|
||||||
|
"LabelImportOnlyFavoriteChannels": "Apriboti kanalus pažymėtus kaip mėgstamiausi",
|
||||||
|
"LabelMaxVideoResolution": "Maksimali leistina video transkodavimo resoliucija",
|
||||||
|
"LabelEnableAudioVbrHelp": "Kintama bitų sparta siūlo geresnę kokybę lyginant su vidutine bitų sparta, bet retais atvejais gali sukelti krovimo ir palaikymo problemas.",
|
||||||
|
"LabelKodiMetadataEnablePathSubstitutionHelp": "Įjungti kelio pakeitimą nuotraukoms naudojant serverio kelio pakeitimo nustatymus.",
|
||||||
|
"LabelKodiMetadataDateFormatHelp": "Visos datos iš NFO failų bus ištraukiamos šiuo formatu."
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,7 +541,7 @@
|
||||||
"Off": "Izslēgts",
|
"Off": "Izslēgts",
|
||||||
"NumLocationsValue": "{0} mapes",
|
"NumLocationsValue": "{0} mapes",
|
||||||
"Normal": "Normāls",
|
"Normal": "Normāls",
|
||||||
"None": "Nekāds",
|
"None": "Nav",
|
||||||
"NoSubtitleSearchResultsFound": "Nav atrasti rezultāti.",
|
"NoSubtitleSearchResultsFound": "Nav atrasti rezultāti.",
|
||||||
"No": "Nr",
|
"No": "Nr",
|
||||||
"Next": "Nākamais",
|
"Next": "Nākamais",
|
||||||
|
@ -1232,7 +1232,7 @@
|
||||||
"Cursive": "Kursīvs",
|
"Cursive": "Kursīvs",
|
||||||
"Console": "Konsole",
|
"Console": "Konsole",
|
||||||
"Conductor": "Diriģents",
|
"Conductor": "Diriģents",
|
||||||
"Casual": "Casual",
|
"Casual": "Ikdienišks",
|
||||||
"Arranger": "Aranžetājs",
|
"Arranger": "Aranžetājs",
|
||||||
"AgeValue": "({0} gadu/s vecs)",
|
"AgeValue": "({0} gadu/s vecs)",
|
||||||
"LabelPublishedServerUriHelp": "Ignorēt Jellyfin izmantoto URI pamatojoties uz saskarni vai klienta IP adresi.",
|
"LabelPublishedServerUriHelp": "Ignorēt Jellyfin izmantoto URI pamatojoties uz saskarni vai klienta IP adresi.",
|
||||||
|
@ -1345,11 +1345,140 @@
|
||||||
"MessageChangeRecordingPath": "Ierakstu mapes maiņa automātiski nepārvietos jau pastāvošus ierakstus no vecās uz jauno atrašanās vietu. Tev vajadzēs pašam tos manuāli pārvietot vajadzības gadījumā.",
|
"MessageChangeRecordingPath": "Ierakstu mapes maiņa automātiski nepārvietos jau pastāvošus ierakstus no vecās uz jauno atrašanās vietu. Tev vajadzēs pašam tos manuāli pārvietot vajadzības gadījumā.",
|
||||||
"MessageEnablingOptionLongerScans": "Šīs opcijas iespējošana var izraisīt krienti ilgākas krātuves skenēšanas.",
|
"MessageEnablingOptionLongerScans": "Šīs opcijas iespējošana var izraisīt krienti ilgākas krātuves skenēšanas.",
|
||||||
"HeaderPerformance": "Veiktspēja",
|
"HeaderPerformance": "Veiktspēja",
|
||||||
"LabelDummyChapterDurationHelp": "Nodaļas attēla ekstrakcijas intervāls sekundēs.",
|
"LabelDummyChapterDurationHelp": "Intervāls starp fiktīvajām nodaļām. Iestatiet 0, lai atslēgtu fiktīvo nodaļu ģenerēšanu. Šīs vērtības maiņa neietekmēs esošās fiktīvās nodaļas.",
|
||||||
"LabelDummyChapterCountHelp": "Maksimālais nodaļu attēlu skaits, kas tiks ekstraktēts no katra multivides faila.",
|
"LabelDummyChapterCountHelp": "Maksimālais nodaļu attēlu skaits, kas tiks ekstraktēts no katra multivides faila.",
|
||||||
"LabelChapterImageResolutionHelp": "Ekstraktēto attēlu izšķirtspēja.",
|
"LabelChapterImageResolutionHelp": "Izvilkto nodaļu attēlu izšķirtspēja. Šīs vērtības maiņa neietekmēs esošās fiktīvās nodaļas.",
|
||||||
"LabelParallelImageEncodingLimit": "Paralēlas attēlu kodēšanas limits",
|
"LabelParallelImageEncodingLimit": "Paralēlas attēlu kodēšanas limits",
|
||||||
"LabelParallelImageEncodingLimitHelp": "Maksimālais daudzums ar attēlu kodēšanas procesiem, kas drīkst darboties vienlaicīgi. Iestatot šo uz 0, limits automātiski tiks izvēlēts balstoties uz jūsu sistēmas specifikācijām.",
|
"LabelParallelImageEncodingLimitHelp": "Maksimālais daudzums ar attēlu kodēšanas procesiem, kas drīkst darboties vienlaicīgi. Iestatot šo uz 0, limits automātiski tiks izvēlēts balstoties uz jūsu sistēmas specifikācijām.",
|
||||||
"HeaderDummyChapter": "Nodaļu Attēli",
|
"HeaderDummyChapter": "Nodaļu Attēli",
|
||||||
"EnableCardLayout": "Padarīt redzamu CardBox"
|
"EnableCardLayout": "Padarīt redzamu CardBox",
|
||||||
|
"MessageConfirmDeleteGuideProvider": "Vai esat pārliecināti, ka vēlaties izdzēst šo ceļveža pakalpojumu sniedzēju?",
|
||||||
|
"MessageForgotPasswordInNetworkRequired": "Lūdzu, mēģiniet vēlreiz savā mājas tīklā, lai uzsāktu paroles atiestatīšanas procesu.",
|
||||||
|
"MessageNoGenresAvailable": "Ļaujiet dažiem metadatu sniedzējiem iegūt žanrus no interneta.",
|
||||||
|
"MessagePluginInstallError": "Instalējot spraudni, notika kļūda.",
|
||||||
|
"MessageGetInstalledPluginsError": "Instalēto spraudņu saraksta iegūšanas laikā notika kļūda.",
|
||||||
|
"MessageNoNextUpItems": "Neviens netika atrasts. Sāciet skatīties savus raidījumus!",
|
||||||
|
"MessageNoTrailersFound": "Instalējiet treileru kanālu, lai uzlabotu savu filmu pieredzi, pievienojot interneta treileru bibliotēku.",
|
||||||
|
"EnableRewatchingNextUp": "Atkārtotas skatīšanas iespējošana sadaļā Nākamais",
|
||||||
|
"EnableAudioNormalizationHelp": "Audio normalizācija pievienos konstantu skaļuma pastiprinājumu, lai saglabātu vidējo skaļumu vēlamajā līmenī (-18dB).",
|
||||||
|
"EnableAudioNormalization": "Audio Normalizācija",
|
||||||
|
"LabelEnableLUFSScan": "Iespējot LUFS skenēšanu",
|
||||||
|
"LabelEnableLUFSScanHelp": "Iespējot LUFS skenēšanu mūzikai (Tas aizņems vairāk laika un resursu).",
|
||||||
|
"MessageNoItemsAvailable": "Pašlaik nav pieejams neviens vienums.",
|
||||||
|
"MessageNoCollectionsAvailable": "Kolekcijas ļauj izmantot personalizētas filmu, seriālu un albumu grupas. Noklikšķiniet uz pogas \"+\", lai sāktu veidot kolekcijas.",
|
||||||
|
"MessageNoRepositories": "Nav repozitoriju.",
|
||||||
|
"MessageReenableUser": "Skatiet zemāk, lai atkārtoti aktivizētu",
|
||||||
|
"MessageSyncPlayCreateGroupDenied": "Nepieciešama atļauja, lai izveidotu grupu.",
|
||||||
|
"MessageSyncPlayErrorAccessingGroups": "Piekļūstot grupu sarakstam, notika kļūda.",
|
||||||
|
"MessageSyncPlayErrorMissingSession": "Neizdevās iespējot SyncPlay! Trūkst sesija.",
|
||||||
|
"MessageRenameMediaFolder": "Pārdēvējot multivides bibliotēku, visi metadati tiks zaudēti, tāpēc rīkojieties piesardzīgi.",
|
||||||
|
"MenuOpen": "Atvērt Izvēlni",
|
||||||
|
"MenuClose": "Aizvērt Izvēlni",
|
||||||
|
"MessageNoFavoritesAvailable": "Pašlaik nav pieejami nekādi favorīti.",
|
||||||
|
"MessageImageTypeNotSelected": "Lūdzu, izvēlaties attēla veidu no nolaižamās izvēlnes.",
|
||||||
|
"MessageLeaveEmptyToInherit": "Atstājiet tukšu, lai mantotu iestatījumus no vecākā elementa vai globālās noklusējuma vērtības.",
|
||||||
|
"MessagePluginInstallDisclaimer": "Kopienas dalībnieku izveidoti spraudņi ir lielisks veids, kā uzlabot savu pieredzi, izmantojot papildu funkcijas un priekšrocības. Pirms instalēšanas ņemiet vērā to iespējamo ietekmi uz jūsu serveri, piemēram, ilgāku bibliotēkas skenēšanu, papildu fona apstrādi un sistēmas stabilitātes samazināšanos.",
|
||||||
|
"MessagePasswordResetForUsers": "Šādu lietotāju paroles ir atiestatītas. Tagad viņi var pierakstīties, izmantojot Easy PIN kodus, kas tika izmantoti atiestatīšanai.",
|
||||||
|
"MessagePluginInstalled": "Spraudnis tika veiksmīgi instalēts. Lai izmaiņas stātos spēkā, serveris ir jārestartē.",
|
||||||
|
"HeaderRecordingMetadataSaving": "Metadatu Ierakstīšana",
|
||||||
|
"AllowCollectionManagement": "Ļaut konkrētajam lietotājam pārvaldīt kolekciju",
|
||||||
|
"MediaInfoRefFrames": "Atskaites kadri",
|
||||||
|
"MessageDeleteTaskTrigger": "Vai esat pārliecināti, ka vēlaties izdzēst šo uzdevuma trigeri?",
|
||||||
|
"SelectAdminUsername": "Izvēlieties lietotājvārdu administratora kontam.",
|
||||||
|
"Settings": "Iestatījumi",
|
||||||
|
"ServerNameIsRestarting": "Serveris {0} tiek restartēts.",
|
||||||
|
"SelectServer": "Izvēlēties Serveri",
|
||||||
|
"Season": "Sezona",
|
||||||
|
"SendMessage": "Sūtīt ziņu",
|
||||||
|
"ServerUpdateNeeded": "Nepieciešams atjaunināt serveri. Lai lejuplādētu jaunāko versiju, lūdzu dodieties uz {0}",
|
||||||
|
"SettingsSaved": "Iestatījumi ir saglabāti.",
|
||||||
|
"MessageSyncPlayNoGroupsAvailable": "Nav pieejama neviena grupa. Vispirms sāciet kaut ko skatīties.",
|
||||||
|
"MessageUnsetContentHelp": "Saturs tiks parādīts kā vienkāršas mapes. Lai iegūtu labākos rezultātus, izmantojiet metadatu pārvaldnieku, lai iestatītu apakšmapju satura veidus.",
|
||||||
|
"GetThePlugin": "Iegūt Paplašinājumu",
|
||||||
|
"MessageSyncPlayErrorNoActivePlayer": "Nav atrasts aktīvs atskaņotājs. SyncPlay tika izslēgts.",
|
||||||
|
"MessageSyncPlayPlaybackPermissionRequired": "Nepieciešama atskaņošanas atļauja.",
|
||||||
|
"MessageSyncPlayUserLeft": "{0} pameta grupu.",
|
||||||
|
"MessageSyncPlayIsDisabled": "Nepieciešama atļauja, lai lietotu SyncPlay.",
|
||||||
|
"MessageSyncPlayUserJoined": "{0} pievienojās grupai.",
|
||||||
|
"ServerRestartNeededAfterPluginInstall": "Jellyfin būs jārestartē pēc spraudņa instalēšanas.",
|
||||||
|
"MessageUnauthorizedUser": "Pašlaik jums nav tiesību piekļūt serverim. Lai iegūtu papildinformāciju, lūdzu, sazinieties ar sava servera administratoru.",
|
||||||
|
"Small": "Mazs",
|
||||||
|
"ServerNameIsShuttingDown": "Serveris {0} tiek izslēgts.",
|
||||||
|
"OptionReportByteRangeSeekingWhenTranscoding": "Ziņot, ka serveris atbalsta baitu meklēšanu pārkodēšanas laikā",
|
||||||
|
"OptionRequirePerfectSubtitleMatchHelp": "Ja tiek prasīta ideāla atbilstība, tiks filtrēti tikai tie subtitri, kas ir pārbaudīti un verificēti ar precīzu video failu. Ja noņemsiet šo izvēles rūtiņu, palielināsies iespēja, ka subtitri tiks lejupielādēti, taču palielināsies kļūdaina vai nepareiza subtitru teksta iespējamība.",
|
||||||
|
"PasswordRequiredForAdmin": "Administratora kontiem ir nepieciešama parole.",
|
||||||
|
"PleaseConfirmPluginInstallation": "Lūdzu, noklikšķiniet uz OK, lai apstiprinātu, ka esat izlasījis iepriekš minēto informāciju un vēlaties turpināt spraudņa instalēšanu.",
|
||||||
|
"PreferEmbeddedTitlesOverFileNames": "Dot priekšroku iegultajiem nosaukumiem, nevis failu nosaukumiem",
|
||||||
|
"AllowEmbeddedSubtitlesHelp": "Atslēgt subtitrus, kas ir iekļauti multivides konteineros. Nepieciešama pilnīga bibliotēkas atjaunošana.",
|
||||||
|
"PreviousTrack": "Pāriet uz iepriekšējo",
|
||||||
|
"MoveLeft": "Pārvietoties pa kreisi",
|
||||||
|
"PreferEmbeddedEpisodeInfosOverFileNames": "Priekšroku dot iegultai epizodes informācijai, nevis failu nosaukumiem",
|
||||||
|
"PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Izmantojiet informāciju par epizodi no iegultajiem metadatiem, ja tā ir pieejama.",
|
||||||
|
"Production": "Produkcija",
|
||||||
|
"PluginFromRepo": "{0} no repozitorija {1}",
|
||||||
|
"Premieres": "Pirmizrādes",
|
||||||
|
"PreferEmbeddedExtrasTitlesOverFileNames": "Priekšroku dodiet iegultajiem nosaukumiem, nevis ekstru failu nosaukumiem",
|
||||||
|
"MessageSyncPlayGroupWait": "{0} buferējas…",
|
||||||
|
"MoveRight": "Pārvietoties pa labi",
|
||||||
|
"NewCollectionHelp": "Kolekcijas ļauj izveidot personalizētas filmu un cita bibliotēkas satura grupas.",
|
||||||
|
"NextChapter": "Nākamā nodaļa",
|
||||||
|
"NextTrack": "Pāriet uz nākamo",
|
||||||
|
"NoNewDevicesFound": "Jaunas ierīces nav atrastas. Lai pievienotu jaunu uztvērēju, aizveriet šo dialoglodziņu un ievadiet ierīces informāciju manuāli.",
|
||||||
|
"OptionMaxActiveSessionsHelp": "Ja vērtība ir 0, funkcija tiek atspējota.",
|
||||||
|
"QuickConnectNotAvailable": "Lūdziet servera administratoram iespējot Quick Connect",
|
||||||
|
"PlaybackErrorNoCompatibleStream": "Šis klients nav saderīgs ar multivides formātu, un serveris nesūta saderīgu multivides formātu.",
|
||||||
|
"AllowEmbeddedSubtitles": "Dažādu veidu iegulto subtitru atspējošana",
|
||||||
|
"OnWakeFromSleep": "Pēc pamošanās no miega režīma",
|
||||||
|
"MusicLibraryHelp": "Pārskatiet {0}mūzikas nosaukšanas ceļvedi{1}.",
|
||||||
|
"OptionAllowMediaPlaybackTranscodingHelp": "Ierobežojot piekļuvi pārkodēšanai, klientiem var rasties atskaņošanas kļūmes neatbalstītu multivides formātu dēļ.",
|
||||||
|
"OptionMaxActiveSessions": "Iestatiet maksimālo vienlaicīgo lietotāju sesiju skaitu.",
|
||||||
|
"Print": "Drukāt",
|
||||||
|
"QuickConnect": "Quick Connect",
|
||||||
|
"OptionHasThemeSong": "Raksturīgā Dziesma",
|
||||||
|
"QuickConnectAuthorizeFail": "Nezināms Quick Connect kods",
|
||||||
|
"PlaceFavoriteChannelsAtBeginning": "Iecienītāko kanālu ievietošana sākumā",
|
||||||
|
"OptionDisplayFolderView": "Rādīt mapju skatījumu, lai parādītu vienkāršas multivides mapes",
|
||||||
|
"Primary": "Primārais",
|
||||||
|
"OptionResumable": "Rezumējams",
|
||||||
|
"PathNotFound": "Ceļš netika atrasts. Lūdzu, pārliecinieties, ka ceļš ir derīgs, un mēģiniet vēlreiz.",
|
||||||
|
"PleaseAddAtLeastOneFolder": "Lūdzu, pievienojiet šai bibliotēkai vismaz vienu mapi, noklikšķinot uz pogas \"+\" sadaļā \"Mapes\".",
|
||||||
|
"OptionSubstring": "Apakšvirkne",
|
||||||
|
"PosterCard": "Plakāta karte",
|
||||||
|
"Notifications": "Paziņojumi",
|
||||||
|
"NotificationsMovedMessage": "Paziņojumu funkcionalitāte ir pārcelta uz Webhook spraudni.",
|
||||||
|
"OnApplicationStartup": "Lietojumprogrammas palaišanas laikā",
|
||||||
|
"OptionAutomaticallyGroupSeriesHelp": "Sērijas, kas šajā bibliotēkā ir sadalītas vairākās mapēs, tiks automātiski apvienotas vienā šovā.",
|
||||||
|
"OptionExtractChapterImage": "Iespējot nodaļu attēlu ieguvi",
|
||||||
|
"QuickConnectNotActive": "Quick Connect šajā serverī nav aktīvs",
|
||||||
|
"New": "Jauns",
|
||||||
|
"OptionDisplayFolderViewHelp": "Rādīt mapes kopā ar citām multivides bibliotēkām. Tas var būt noderīgi, ja vēlaties vienkāršotu mapju skatu.",
|
||||||
|
"OptionPlainStorageFolders": "Rādīt visas mapes kā vienkāršas glabāšanas mapes",
|
||||||
|
"OptionPlainStorageFoldersHelp": "Visas mapes tiek attēlotas iekš DIDL kā \"object.container.storageFolder\", nevis kā specifiskāks tips, piemēram, \"object.container.person.musicArtist\".",
|
||||||
|
"OptionPlainVideoItems": "Rādīt visus videoklipus kā vienkāršus video vienumus",
|
||||||
|
"OptionPlainVideoItemsHelp": "Visi videoklipi ir attēloti iekš DIDL kā \"object.item.videoItem\", nevis kā specifiskāks tips, piemēram, \"object.item.videoItem.movie\".",
|
||||||
|
"PersonRole": "kā {0}",
|
||||||
|
"OptionReportByteRangeSeekingWhenTranscodingHelp": "Tas ir nepieciešams dažās ierīcēs, kas neveic laika meklēšanu ļoti labi.",
|
||||||
|
"OptionSaveMetadataAsHiddenHelp": "Ja to mainīsiet, tas attieksies uz jauniem metadatiem, kas saglabāti turpmāk. Esošie metadatu faili tiks atjaunināti nākamajā reizē, kad tos saglabās serveris.",
|
||||||
|
"Premiere": "Pirmizrāde",
|
||||||
|
"OriginalAirDate": "Sākotnējais Izlaišanas Datums",
|
||||||
|
"OtherArtist": "Cits mākslinieks",
|
||||||
|
"PasswordResetProviderHelp": "Izvēlieties paroles atiestatīšanas pakalpojumu sniedzēju, kas tiks izmantots, kad šis lietotājs pieprasīs paroles atiestatīšanu.",
|
||||||
|
"PlaybackErrorPlaceHolder": "Tas ir fizisku datu nesēju aizstājējs, kurus Jellyfin nevar atskaņot. Lūdzu, ievietojiet disku, lai to atskaņotu.",
|
||||||
|
"QuickConnectDescription": "Lai pierakstītos, izmantojot Quick Connect, ierīcē, no kuras piesakāties, izvēlieties pogu \"Quick Connect\" un ievadiet tālāk norādīto kodu.",
|
||||||
|
"QuickConnectDeactivated": "Quick Connect tika deaktivizēts, pirms varēja apstiprināt pieteikšanās pieprasījumu",
|
||||||
|
"Other": "Citi",
|
||||||
|
"MetadataSettingChangeHelp": "Metadatu iestatījumu maiņa ietekmēs turpmāk pievienoto jauno saturu. Lai atsvaidzinātu esošo saturu, atveriet detalizēto ekrānu un noklikšķiniet uz pogas \"Atsvaidzināt\" vai veiciet masveida atsvaidzināšanu, izmantojot \"Metadatu Pārvaldnieku\".",
|
||||||
|
"MixedMoviesShows": "Jauktās Filmas un Šovi",
|
||||||
|
"Mixer": "Mikseris",
|
||||||
|
"MovieLibraryHelp": "Pārskatiet {0}filmu nosaukšanas rokasgrāmatu{1}.",
|
||||||
|
"OptionAllowLinkSharingHelp": "Tiek kopīgotas tikai tīmekļa lapas, kurās ir multivides informācija. Multivides faili nekad netiek kopīgoti publiski. Koplietošanas laiks ir ierobežots un beidzas pēc {0} dienām.",
|
||||||
|
"OptionAllowContentDownloadHelp": "Lietotāji var lejupielādēt multivides failus un saglabāt tos savās ierīcēs. Tas nav tas pats, kas sinhronizācijas funkcija. Lai grāmatu bibliotēkas darbotos pareizi, šī funkcija ir jāaktivizē.",
|
||||||
|
"PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Ekstrām bieži vien ir tāds pats iegultais nosaukums kā galvenajai vienībai, tāpēc atzīmējiet šo iespēju, lai tām izmantotu iegultos nosaukumus.",
|
||||||
|
"ProductionLocations": "Produkcijas vietas",
|
||||||
|
"PinCodeResetConfirmation": "Vai esat pārliecināts, ka vēlaties atiestatīt Easy PIN kodu?",
|
||||||
|
"PreviousChapter": "Iepriekšējā nodaļa",
|
||||||
|
"QuickConnectInvalidCode": "Nederīgs Quick Connect kods",
|
||||||
|
"OptionAutomaticallyGroupSeries": "Automātiski apvienot sērijas, kuras izvietotas vairākās mapēs",
|
||||||
|
"PreferEmbeddedTitlesOverFileNamesHelp": "Noteikt redzamo nosaukumu, kas jāizmanto, ja nav pieejami interneta metadati vai vietējie metadati.",
|
||||||
|
"OptionSpecialEpisode": "Speciālizlaidumi"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1696,5 +1696,19 @@
|
||||||
"HeaderPerformance": "Ytelse",
|
"HeaderPerformance": "Ytelse",
|
||||||
"LabelDummyChapterDurationHelp": "Tid mellom innsamling av kapittelbilder i sekunder.",
|
"LabelDummyChapterDurationHelp": "Tid mellom innsamling av kapittelbilder i sekunder.",
|
||||||
"LabelEnableAudioVbrHelp": "Variabel bithastighet tilbyr bedre forhold mellom kvalitet og gjennomsnittlig bithastighet, men kan i visse tilfeller forårsake buffering og problemer med kompatibilitet.",
|
"LabelEnableAudioVbrHelp": "Variabel bithastighet tilbyr bedre forhold mellom kvalitet og gjennomsnittlig bithastighet, men kan i visse tilfeller forårsake buffering og problemer med kompatibilitet.",
|
||||||
"LabelEnableAudioVbr": "Aktiver VBR lydkoding"
|
"LabelEnableAudioVbr": "Aktiver VBR lydkoding",
|
||||||
|
"SubtitleBlack": "Svart",
|
||||||
|
"SubtitleBlue": "Blå",
|
||||||
|
"SubtitleGray": "Grå",
|
||||||
|
"SubtitleGreen": "Grønn",
|
||||||
|
"SubtitleLightGray": "Lyse grå",
|
||||||
|
"SubtitleMagenta": "Magenta",
|
||||||
|
"MenuOpen": "Åpne menyen",
|
||||||
|
"MenuClose": "Lukk menyen",
|
||||||
|
"SecondarySubtitles": "Sekundære undertekster",
|
||||||
|
"Short": "Kort",
|
||||||
|
"SubtitleRed": "Rød",
|
||||||
|
"SubtitleYellow": "Gul",
|
||||||
|
"SubtitleWhite": "Hvit",
|
||||||
|
"Select": "Velg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,5 +32,6 @@
|
||||||
"AddToPlaylist": "प्लेलिस्टमा थप्नुहोस्",
|
"AddToPlaylist": "प्लेलिस्टमा थप्नुहोस्",
|
||||||
"AddToFavorites": "मनपर्ने मा थप्नुहोस्",
|
"AddToFavorites": "मनपर्ने मा थप्नुहोस्",
|
||||||
"Add": "थप्नुहोस्",
|
"Add": "थप्नुहोस्",
|
||||||
"Actor": "अभिनेता"
|
"Actor": "अभिनेता",
|
||||||
|
"Absolute": "निरपेक्ष"
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue