mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-01-15 08:23:36 -03:00
Add constants for app features
This commit is contained in:
@@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import { useThemes } from 'hooks/useThemes';
|
||||
import globalize from 'lib/globalize';
|
||||
@@ -32,7 +33,7 @@ export function DisplayPreferences({ onChange, values }: Readonly<DisplayPrefere
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
|
||||
|
||||
{ appHost.supports('displaymode') && (
|
||||
{ appHost.supports(AppFeature.DisplayMode) && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-layout-label'>{globalize.translate('LabelDisplayMode')}</InputLabel>
|
||||
<Select
|
||||
@@ -124,7 +125,7 @@ export function DisplayPreferences({ onChange, values }: Readonly<DisplayPrefere
|
||||
</FormControl>
|
||||
) }
|
||||
|
||||
{ screensavers.length > 0 && appHost.supports('screensaver') && (
|
||||
{ screensavers.length > 0 && appHost.supports(AppFeature.Screensaver) && (
|
||||
<Fragment>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-screensaver-label'>{globalize.translate('LabelScreensaver')}</InputLabel>
|
||||
|
||||
@@ -10,8 +10,9 @@ import React from 'react';
|
||||
|
||||
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from 'apps/experimental/features/preferences/constants/locales';
|
||||
import { appHost } from 'components/apphost';
|
||||
import datetime from 'scripts/datetime';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import globalize from 'lib/globalize';
|
||||
import datetime from 'scripts/datetime';
|
||||
|
||||
import type { DisplaySettingsValues } from '../types/displaySettingsValues';
|
||||
|
||||
@@ -21,14 +22,14 @@ interface LocalizationPreferencesProps {
|
||||
}
|
||||
|
||||
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
|
||||
if (!appHost.supports('displaylanguage') && !datetime.supportsLocalization()) {
|
||||
if (!appHost.supports(AppFeature.DisplayLanguage) && !datetime.supportsLocalization()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
|
||||
|
||||
{ appHost.supports('displaylanguage') && (
|
||||
{ appHost.supports(AppFeature.DisplayLanguage) && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-language-label'>{globalize.translate('LabelDisplayLanguage')}</InputLabel>
|
||||
<Select
|
||||
@@ -46,7 +47,7 @@ export function LocalizationPreferences({ onChange, values }: Readonly<Localizat
|
||||
</Select>
|
||||
<FormHelperText component={Stack} id='display-settings-language-description'>
|
||||
<span>{globalize.translate('LabelDisplayLanguageHelp')}</span>
|
||||
{ appHost.supports('externallinks') && (
|
||||
{ appHost.supports(AppFeature.ExternalLinks) && (
|
||||
<Link
|
||||
href='https://github.com/jellyfin/jellyfin'
|
||||
rel='noopener noreferrer'
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import themeManager from 'scripts/themeManager';
|
||||
import { currentSettings, UserSettings } from 'scripts/settings/userSettings';
|
||||
@@ -120,7 +121,7 @@ async function saveDisplaySettings({
|
||||
}: SaveDisplaySettingsParams) {
|
||||
const user = await api.getUser(userId);
|
||||
|
||||
if (appHost.supports('displaylanguage')) {
|
||||
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||
userSettings.language(normalizeValue(newDisplaySettings.language));
|
||||
}
|
||||
userSettings.customCss(normalizeValue(newDisplaySettings.customCss));
|
||||
|
||||
@@ -6,6 +6,7 @@ import { appHost } from 'components/apphost';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import Page from 'components/Page';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import LinkButton from 'elements/emby-button/LinkButton';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
||||
@@ -185,7 +186,7 @@ const UserSettingsPage: FC = () => {
|
||||
</div>
|
||||
</LinkButton>
|
||||
|
||||
{appHost.supports('clientsettings') && (
|
||||
{appHost.supports(AppFeature.ClientSettings) && (
|
||||
<LinkButton
|
||||
onClick={shell.openClientSettings}
|
||||
className='clientSettings listItem-border'
|
||||
@@ -290,7 +291,7 @@ const UserSettingsPage: FC = () => {
|
||||
{globalize.translate('HeaderUser')}
|
||||
</h2>
|
||||
|
||||
{appHost.supports('multiserver') && (
|
||||
{appHost.supports(AppFeature.MultiServer) && (
|
||||
<LinkButton
|
||||
onClick={Dashboard.selectServer}
|
||||
className='selectServer listItem-border'
|
||||
@@ -330,7 +331,7 @@ const UserSettingsPage: FC = () => {
|
||||
</div>
|
||||
</LinkButton>
|
||||
|
||||
{appHost.supports('exitmenu') && (
|
||||
{appHost.supports(AppFeature.ExitMenu) && (
|
||||
<LinkButton
|
||||
onClick={appHost.exit}
|
||||
className='exitApp listItem-border'
|
||||
|
||||
@@ -12,6 +12,7 @@ import UserPasswordForm from '../../../../components/dashboard/users/UserPasswor
|
||||
import loading from '../../../../components/loading/loading';
|
||||
import toast from '../../../../components/toast/toast';
|
||||
import Page from '../../../../components/Page';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
|
||||
const UserProfile: FunctionComponent = () => {
|
||||
const [ searchParams ] = useSearchParams();
|
||||
@@ -61,7 +62,7 @@ const UserProfile: FunctionComponent = () => {
|
||||
if (user.PrimaryImageTag) {
|
||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide');
|
||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.remove('hide');
|
||||
} else if (appHost.supports('fileinput') && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
} else if (appHost.supports(AppFeature.FileInput) && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.add('hide');
|
||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { FC, useEffect, useState } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import Page from 'components/Page';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import LinkButton from 'elements/emby-button/LinkButton';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ConnectionState } from 'lib/jellyfin-apiclient';
|
||||
@@ -50,7 +51,7 @@ const ConnectionErrorPage: FC<ConnectionErrorPageProps> = ({
|
||||
{message && (
|
||||
<p>{message}</p>
|
||||
)}
|
||||
{appHost.supports('multiserver') && (
|
||||
{appHost.supports(AppFeature.MultiServer) && (
|
||||
<LinkButton
|
||||
className='raised'
|
||||
href='/selectserver'
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as htmlMediaHelper from '../components/htmlMediaHelper';
|
||||
import * as webSettings from '../scripts/settings/webSettings';
|
||||
import globalize from '../lib/globalize';
|
||||
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
|
||||
const appName = 'Jellyfin Web';
|
||||
|
||||
@@ -169,14 +170,6 @@ function getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
function supportsVoiceInput() {
|
||||
if (!browser.tv) {
|
||||
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function supportsFullscreen() {
|
||||
if (browser.tv) {
|
||||
return false;
|
||||
@@ -235,77 +228,65 @@ const supportedFeatures = function () {
|
||||
const features = [];
|
||||
|
||||
if (navigator.share) {
|
||||
features.push('sharing');
|
||||
features.push(AppFeature.Sharing);
|
||||
}
|
||||
|
||||
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('filedownload');
|
||||
features.push(AppFeature.FileDownload);
|
||||
}
|
||||
|
||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||
features.push('exit');
|
||||
} else {
|
||||
features.push('plugins');
|
||||
features.push(AppFeature.Exit);
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
||||
features.push('externallinks');
|
||||
features.push('externalpremium');
|
||||
}
|
||||
|
||||
if (!browser.operaTv) {
|
||||
features.push('externallinkdisplay');
|
||||
}
|
||||
|
||||
if (supportsVoiceInput()) {
|
||||
features.push('voiceinput');
|
||||
features.push(AppFeature.ExternalLinks);
|
||||
}
|
||||
|
||||
if (supportsHtmlMediaAutoplay()) {
|
||||
features.push('htmlaudioautoplay');
|
||||
features.push('htmlvideoautoplay');
|
||||
features.push(AppFeature.HtmlAudioAutoplay);
|
||||
features.push(AppFeature.HtmlVideoAutoplay);
|
||||
}
|
||||
|
||||
if (supportsFullscreen()) {
|
||||
features.push('fullscreenchange');
|
||||
features.push(AppFeature.Fullscreen);
|
||||
}
|
||||
|
||||
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile || browser.ipad) {
|
||||
features.push('physicalvolumecontrol');
|
||||
features.push(AppFeature.PhysicalVolumeControl);
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('remotecontrol');
|
||||
features.push(AppFeature.RemoteControl);
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
||||
features.push('remotevideo');
|
||||
features.push(AppFeature.RemoteVideo);
|
||||
}
|
||||
|
||||
features.push('displaylanguage');
|
||||
features.push('otherapppromotions');
|
||||
features.push('displaymode');
|
||||
features.push('targetblank');
|
||||
features.push('screensaver');
|
||||
features.push(AppFeature.DisplayLanguage);
|
||||
features.push(AppFeature.DisplayMode);
|
||||
features.push(AppFeature.TargetBlank);
|
||||
features.push(AppFeature.Screensaver);
|
||||
|
||||
webSettings.getMultiServer().then(enabled => {
|
||||
if (enabled) features.push('multiserver');
|
||||
if (enabled) features.push(AppFeature.MultiServer);
|
||||
});
|
||||
|
||||
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
features.push('subtitleappearancesettings');
|
||||
features.push(AppFeature.SubtitleAppearance);
|
||||
}
|
||||
|
||||
if (!browser.orsay) {
|
||||
features.push('subtitleburnsettings');
|
||||
features.push(AppFeature.SubtitleBurnIn);
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
||||
features.push('fileinput');
|
||||
features.push(AppFeature.FileInput);
|
||||
}
|
||||
|
||||
if (browser.chrome || browser.edgeChromium) {
|
||||
features.push('chromecast');
|
||||
features.push(AppFeature.Chromecast);
|
||||
}
|
||||
|
||||
return features;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import browser from '../../scripts/browser';
|
||||
import layoutManager from '../layoutManager';
|
||||
import { pluginManager } from '../pluginManager';
|
||||
@@ -68,19 +70,19 @@ function showOrHideMissingEpisodesField(context) {
|
||||
}
|
||||
|
||||
function loadForm(context, user, userSettings) {
|
||||
if (appHost.supports('displaylanguage')) {
|
||||
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||
context.querySelector('.languageSection').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.languageSection').classList.add('hide');
|
||||
}
|
||||
|
||||
if (appHost.supports('displaymode')) {
|
||||
if (appHost.supports(AppFeature.DisplayMode)) {
|
||||
context.querySelector('.fldDisplayMode').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.fldDisplayMode').classList.add('hide');
|
||||
}
|
||||
|
||||
if (appHost.supports('externallinks')) {
|
||||
if (appHost.supports(AppFeature.ExternalLinks)) {
|
||||
context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
|
||||
@@ -88,7 +90,7 @@ function loadForm(context, user, userSettings) {
|
||||
|
||||
context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator);
|
||||
|
||||
if (appHost.supports('screensaver')) {
|
||||
if (appHost.supports(AppFeature.Screensaver)) {
|
||||
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
|
||||
context.querySelector('.txtBackdropScreensaverIntervalContainer').classList.remove('hide');
|
||||
context.querySelector('.txtScreensaverTimeContainer').classList.remove('hide');
|
||||
@@ -143,7 +145,7 @@ function loadForm(context, user, userSettings) {
|
||||
function saveUser(context, user, userSettingsInstance, apiClient) {
|
||||
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
|
||||
|
||||
if (appHost.supports('displaylanguage')) {
|
||||
if (appHost.supports(AppFeature.DisplayLanguage)) {
|
||||
userSettingsInstance.language(context.querySelector('#selectLanguage').value);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import dom from '../../scripts/dom';
|
||||
import loading from '../loading/loading';
|
||||
import { appHost } from '../apphost';
|
||||
@@ -205,7 +206,7 @@ function getRemoteImageHtml(image, imageType) {
|
||||
html += '<div class="cardPadder-' + shape + '"></div>';
|
||||
html += '<div class="cardContent">';
|
||||
|
||||
if (layoutManager.tv || !appHost.supports('externallinks')) {
|
||||
if (layoutManager.tv || !appHost.supports(AppFeature.ExternalLinks)) {
|
||||
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
|
||||
} else {
|
||||
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import loading from '../loading/loading';
|
||||
import dom from '../../scripts/dom';
|
||||
@@ -339,7 +340,7 @@ function showActionSheet(context, imageCard) {
|
||||
|
||||
function initEditor(context, options) {
|
||||
const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
|
||||
const isFileInputSupported = appHost.supports('fileinput');
|
||||
const isFileInputSupported = appHost.supports(AppFeature.FileInput);
|
||||
for (let i = 0, length = uploadButtons.length; i < length; i++) {
|
||||
if (isFileInputSupported) {
|
||||
uploadButtons[i].classList.remove('hide');
|
||||
|
||||
@@ -11,6 +11,7 @@ import { playbackManager } from './playback/playbackmanager';
|
||||
import toast from './toast/toast';
|
||||
import * as userSettings from '../scripts/settings/userSettings';
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
|
||||
function getDeleteLabel(type) {
|
||||
switch (type) {
|
||||
@@ -169,7 +170,7 @@ export async function getCommands(options) {
|
||||
});
|
||||
}
|
||||
|
||||
if (appHost.supports('filedownload')) {
|
||||
if (appHost.supports(AppFeature.FileDownload)) {
|
||||
// CanDownload should probably be updated to return true for these items?
|
||||
if (user.Policy.EnableContentDownloading && (item.Type === 'Season' || item.Type == 'Series')) {
|
||||
commands.push({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type'
|
||||
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
||||
|
||||
import { appHost } from './apphost';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
@@ -238,7 +239,7 @@ export function canShare (item, user) {
|
||||
if (isLocalItem(item)) {
|
||||
return false;
|
||||
}
|
||||
return user.Policy.EnablePublicSharing && appHost.supports('sharing');
|
||||
return user.Policy.EnablePublicSharing && appHost.supports(AppFeature.Sharing);
|
||||
}
|
||||
|
||||
export function enableDateAddedDisplay (item) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import browser from '../../scripts/browser';
|
||||
import { appHost } from '../apphost';
|
||||
import loading from '../loading/loading';
|
||||
@@ -198,7 +199,7 @@ function showMenuForSelectedItems(e) {
|
||||
});
|
||||
}
|
||||
|
||||
if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) {
|
||||
if (user.Policy.EnableContentDownloading && appHost.supports(AppFeature.FileDownload)) {
|
||||
// Disabled because there is no callback for this item
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||
import { appRouter, isLyricsPage } from 'components/router/appRouter';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
|
||||
import datetime from '../../scripts/datetime';
|
||||
@@ -244,7 +245,7 @@ function bindEvents(elem) {
|
||||
|
||||
toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons');
|
||||
|
||||
volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol'));
|
||||
volumeSliderContainer.classList.toggle('hide', appHost.supports(AppFeature.PhysicalVolumeControl));
|
||||
|
||||
volumeSlider.addEventListener('input', (e) => {
|
||||
if (currentPlayer) {
|
||||
@@ -441,7 +442,7 @@ function updatePlayerVolumeState(isMuted, volumeLevel) {
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
||||
if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||
showMuteButton = false;
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdrop
|
||||
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
|
||||
import { bindMediaSessionSubscriber } from 'apps/stable/features/playback/utils/mediaSessionSubscriber';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
import { getMediaError } from 'utils/mediaError';
|
||||
@@ -41,7 +42,7 @@ function enableLocalPlaylistManagement(player) {
|
||||
}
|
||||
|
||||
function supportsPhysicalVolumeControl(player) {
|
||||
return player.isLocalPlayer && appHost.supports('physicalvolumecontrol');
|
||||
return player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl);
|
||||
}
|
||||
|
||||
function bindToFullscreenChange(player) {
|
||||
@@ -317,7 +318,7 @@ function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, apiCl
|
||||
PlaySessionId: startingPlaySession,
|
||||
StartTimeTicks: startPosition || 0,
|
||||
EnableRedirection: true,
|
||||
EnableRemoteMedia: appHost.supports('remoteaudio'),
|
||||
EnableRemoteMedia: appHost.supports(AppFeature.RemoteAudio),
|
||||
EnableAudioVbrEncoding: transcodingProfile.EnableAudioVbrEncoding
|
||||
});
|
||||
}
|
||||
@@ -598,7 +599,7 @@ function supportsDirectPlay(apiClient, item, mediaSource) {
|
||||
const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd';
|
||||
|
||||
if (mediaSource.SupportsDirectPlay || isFolderRip) {
|
||||
if (mediaSource.IsRemote && !appHost.supports('remotevideo')) {
|
||||
if (mediaSource.IsRemote && !appHost.supports(AppFeature.RemoteVideo)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
@@ -3689,7 +3690,7 @@ export class PlaybackManager {
|
||||
return streamInfo ? streamInfo.playbackStartTimeTicks : null;
|
||||
};
|
||||
|
||||
if (appHost.supports('remotecontrol')) {
|
||||
if (appHost.supports(AppFeature.RemoteControl)) {
|
||||
import('../../scripts/serverNotifications').then(({ default: serverNotifications }) => {
|
||||
Events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self));
|
||||
Events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self));
|
||||
@@ -4060,7 +4061,7 @@ export class PlaybackManager {
|
||||
'PlayTrailers'
|
||||
];
|
||||
|
||||
if (appHost.supports('fullscreenchange')) {
|
||||
if (appHost.supports(AppFeature.Fullscreen)) {
|
||||
list.push('ToggleFullscreen');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import Events from '../../utils/events.ts';
|
||||
import browser from '../../scripts/browser';
|
||||
import loading from '../loading/loading';
|
||||
@@ -96,7 +97,7 @@ export function show(button) {
|
||||
|
||||
// Unfortunately we can't allow the url to change or chromecast will throw a security error
|
||||
// Might be able to solve this in the future by moving the dialogs to hashbangs
|
||||
if (!(!browser.chrome && !browser.edgeChromium || appHost.supports('castmenuhashchange'))) {
|
||||
if (!(!browser.chrome && !browser.edgeChromium || appHost.supports(AppFeature.CastMenuHashChange))) {
|
||||
menuOptions.enableHistory = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import escapeHTML from 'escape-html';
|
||||
|
||||
import { MediaSegmentAction } from 'apps/stable/features/playback/constants/mediaSegmentAction';
|
||||
import { getId, getMediaSegmentAction } from 'apps/stable/features/playback/utils/mediaSegmentSettings';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
|
||||
import appSettings from '../../scripts/settings/appSettings';
|
||||
@@ -147,7 +148,7 @@ function showHideQualityFields(context, user, apiClient) {
|
||||
context.querySelector('.videoQualitySection').classList.add('hide');
|
||||
}
|
||||
|
||||
if (appHost.supports('multiserver')) {
|
||||
if (appHost.supports(AppFeature.MultiServer)) {
|
||||
context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide');
|
||||
context.querySelector('.fldVideoInternetQuality').classList.remove('hide');
|
||||
|
||||
@@ -204,7 +205,7 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) {
|
||||
context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false;
|
||||
});
|
||||
|
||||
if (appHost.supports('externalplayerintent') && userId === loggedInUserId) {
|
||||
if (appHost.supports(AppFeature.ExternalPlayerIntent) && userId === loggedInUserId) {
|
||||
context.querySelector('.fldExternalPlayer').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.fldExternalPlayer').classList.add('hide');
|
||||
@@ -213,7 +214,7 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) {
|
||||
if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) {
|
||||
context.querySelector('.qualitySections').classList.remove('hide');
|
||||
|
||||
if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) {
|
||||
if (appHost.supports(AppFeature.Chromecast) && user.Policy.EnableVideoPlaybackTranscoding) {
|
||||
context.querySelector('.fldChromecastQuality').classList.remove('hide');
|
||||
} else {
|
||||
context.querySelector('.fldChromecastQuality').classList.add('hide');
|
||||
|
||||
@@ -2,6 +2,7 @@ import escapeHtml from 'escape-html';
|
||||
|
||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
|
||||
import datetime from '../../scripts/datetime';
|
||||
import { clearBackdrop, setBackdrops } from '../backdrop/backdrop';
|
||||
@@ -371,7 +372,7 @@ export default function () {
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
||||
if (currentPlayer.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||
showMuteButton = false;
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Image viewer component
|
||||
* @module components/slideshow/slideshow
|
||||
*/
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import inputManager from '../../scripts/inputManager';
|
||||
@@ -172,10 +173,10 @@ export default function (options) {
|
||||
if (actionButtonsOnTop) {
|
||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true);
|
||||
|
||||
if (appHost.supports('filedownload') && slideshowOptions.user?.Policy.EnableContentDownloading) {
|
||||
if (appHost.supports(AppFeature.FileDownload) && slideshowOptions.user?.Policy.EnableContentDownloading) {
|
||||
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||
}
|
||||
if (appHost.supports('sharing')) {
|
||||
if (appHost.supports(AppFeature.Sharing)) {
|
||||
html += getIcon('share', 'btnShare slideshowButton', true);
|
||||
}
|
||||
if (screenfull.isEnabled) {
|
||||
@@ -190,10 +191,10 @@ export default function (options) {
|
||||
html += '<div class="slideshowBottomBar hide">';
|
||||
|
||||
html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true);
|
||||
if (appHost.supports('filedownload') && slideshowOptions?.user.Policy.EnableContentDownloading) {
|
||||
if (appHost.supports(AppFeature.FileDownload) && slideshowOptions?.user.Policy.EnableContentDownloading) {
|
||||
html += getIcon('file_download', 'btnDownload slideshowButton', true);
|
||||
}
|
||||
if (appHost.supports('sharing')) {
|
||||
if (appHost.supports(AppFeature.Sharing)) {
|
||||
html += getIcon('share', 'btnShare slideshowButton', true);
|
||||
}
|
||||
if (screenfull.isEnabled) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { appHost } from '../apphost';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import layoutManager from '../layoutManager';
|
||||
@@ -440,7 +442,7 @@ function showEditorInternal(itemId, serverId) {
|
||||
}
|
||||
|
||||
// Don't allow redirection to other websites from the TV layout
|
||||
if (layoutManager.tv || !appHost.supports('externallinks')) {
|
||||
if (layoutManager.tv || !appHost.supports(AppFeature.ExternalLinks)) {
|
||||
dlg.querySelector('.btnHelp').remove();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import globalize from '../../lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { appHost } from '../apphost';
|
||||
@@ -40,7 +41,7 @@ function getSubtitleAppearanceObject(context) {
|
||||
|
||||
function loadForm(context, user, userSettings, appearanceSettings, apiClient) {
|
||||
apiClient.getCultures().then(function (allCultures) {
|
||||
if (appHost.supports('subtitleburnsettings') && user.Policy.EnableVideoPlaybackTranscoding) {
|
||||
if (appHost.supports(AppFeature.SubtitleBurnIn) && user.Policy.EnableVideoPlaybackTranscoding) {
|
||||
context.querySelector('.fldBurnIn').classList.remove('hide');
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ function embed(options, self) {
|
||||
options.element.querySelector('.btnSave').classList.remove('hide');
|
||||
}
|
||||
|
||||
if (appHost.supports('subtitleappearancesettings')) {
|
||||
if (appHost.supports(AppFeature.SubtitleAppearance)) {
|
||||
options.element.querySelector('.subtitleAppearanceSection').classList.remove('hide');
|
||||
|
||||
self._fullPreview = options.element.querySelector('.subtitleappearance-fullpreview');
|
||||
|
||||
@@ -16,6 +16,7 @@ import React, { FC, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
|
||||
import globalize from 'lib/globalize';
|
||||
@@ -97,7 +98,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
{appHost.supports('clientsettings') && ([
|
||||
{appHost.supports(AppFeature.ClientSettings) && ([
|
||||
<Divider key='client-settings-divider' />,
|
||||
<MenuItem
|
||||
key='client-settings-button'
|
||||
@@ -156,7 +157,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{appHost.supports('multiserver') && (
|
||||
{appHost.supports(AppFeature.MultiServer) && (
|
||||
<MenuItem
|
||||
onClick={onSelectServerClick}
|
||||
>
|
||||
@@ -180,7 +181,7 @@ const AppUserMenu: FC<AppUserMenuProps> = ({
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
{appHost.supports('exitmenu') && ([
|
||||
{appHost.supports(AppFeature.ExitMenu) && ([
|
||||
<Divider key='exit-menu-divider' />,
|
||||
<MenuItem
|
||||
key='exit-menu-button'
|
||||
|
||||
57
src/constants/appFeature.ts
Normal file
57
src/constants/appFeature.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/** App feature flags */
|
||||
export enum AppFeature {
|
||||
/** The app supports changing the URL hash when the cast menu opens */
|
||||
CastMenuHashChange = 'castmenuhashchange',
|
||||
/** The app supports Chromecast */
|
||||
Chromecast = 'chromecast',
|
||||
/** The app supports showing client settings via a menu entry */
|
||||
ClientSettings = 'clientsettings',
|
||||
/** The app supports configuring the display language */
|
||||
DisplayLanguage = 'displaylanguage',
|
||||
/** The app supports configuring the display mode (TV, Desktop, etc.) */
|
||||
DisplayMode = 'displaymode',
|
||||
/** The app can exit via back navigation */
|
||||
Exit = 'exit',
|
||||
/** The app can be exited via a menu entry */
|
||||
ExitMenu = 'exitmenu',
|
||||
/** The app can open external URLs */
|
||||
ExternalLinks = 'externallinks',
|
||||
/** The app supports enabling external players */
|
||||
ExternalPlayerIntent = 'externalplayerintent',
|
||||
/** The app supports file downloads */
|
||||
FileDownload = 'filedownload',
|
||||
/** The app supports file input elements */
|
||||
FileInput = 'fileinput',
|
||||
/** The app supports enabling fullscreen media playback */
|
||||
Fullscreen = 'fullscreenchange',
|
||||
/** The app supports autoplay on the audio element */
|
||||
HtmlAudioAutoplay = 'htmlaudioautoplay',
|
||||
/** The app supports autoplay on the video element */
|
||||
HtmlVideoAutoplay = 'htmlvideoautoplay',
|
||||
/** The app supports switching servers */
|
||||
MultiServer = 'multiserver',
|
||||
/** The app supports playback of BluRay folders */
|
||||
NativeBluRayPlayback = 'nativeblurayplayback',
|
||||
/** The app supports playback of DVD folders */
|
||||
NativeDvdPlayback = 'nativedvdplayback',
|
||||
/** The app supports playback of ISO files */
|
||||
NativeIsoPlayback = 'nativeisoplayback',
|
||||
/** The app supports physical volume buttons */
|
||||
PhysicalVolumeControl = 'physicalvolumecontrol',
|
||||
/** The app supports playing remote audio */
|
||||
RemoteAudio = 'remoteaudio',
|
||||
/** The app supports the remote control (casting) feature */
|
||||
RemoteControl = 'remotecontrol',
|
||||
/** The app supports playing remote video */
|
||||
RemoteVideo = 'remotevideo',
|
||||
/** The app supports displaying a screensaver */
|
||||
Screensaver = 'screensaver',
|
||||
/** The app supports sharing content */
|
||||
Sharing = 'sharing',
|
||||
/** The app supports configuring subtitle appearance */
|
||||
SubtitleAppearance = 'subtitleappearancesettings',
|
||||
/** The app supports configuring subtitle burn-in */
|
||||
SubtitleBurnIn = 'subtitleburnsettings',
|
||||
/** The app can open URLs in a blank page. */
|
||||
TargetBlank = 'targetblank'
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import loading from 'components/loading/loading';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import itemShortcuts from 'components/shortcuts';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import browser from 'scripts/browser';
|
||||
@@ -636,7 +637,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
||||
|
||||
if (item.Type == 'Person' && item.ProductionLocations && item.ProductionLocations.length) {
|
||||
let location = item.ProductionLocations[0];
|
||||
if (!layoutManager.tv && appHost.supports('externallinks')) {
|
||||
if (!layoutManager.tv && appHost.supports(AppFeature.ExternalLinks)) {
|
||||
location = `<a is="emby-linkbutton" class="button-link textlink" target="_blank" href="https://www.openstreetmap.org/search?query=${encodeURIComponent(location)}">${escapeHtml(location)}</a>`;
|
||||
} else {
|
||||
location = escapeHtml(location);
|
||||
@@ -650,7 +651,7 @@ function reloadFromItem(instance, page, params, item, user) {
|
||||
setPeopleHeader(page, item);
|
||||
loading.hide();
|
||||
|
||||
if (item.Type === 'Book' && item.CanDownload && appHost.supports('filedownload')) {
|
||||
if (item.Type === 'Book' && item.CanDownload && appHost.supports(AppFeature.FileDownload)) {
|
||||
hideAll(page, 'btnDownload', true);
|
||||
}
|
||||
|
||||
@@ -1083,7 +1084,7 @@ function renderDetails(page, item, apiClient, context) {
|
||||
renderLyricsContainer(page, item, apiClient);
|
||||
|
||||
// Don't allow redirection to other websites from the TV layout
|
||||
if (!layoutManager.tv && appHost.supports('externallinks')) {
|
||||
if (!layoutManager.tv && appHost.supports(AppFeature.ExternalLinks)) {
|
||||
renderLinks(page, item);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
|
||||
import { EventType } from 'types/eventType';
|
||||
|
||||
import { playbackManager } from '../../../components/playback/playbackmanager';
|
||||
import browser from '../../../scripts/browser';
|
||||
import dom from '../../../scripts/dom';
|
||||
@@ -27,9 +33,6 @@ import LibraryMenu from '../../../scripts/libraryMenu';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
||||
import { pluginManager } from '../../../components/pluginManager';
|
||||
import { PluginType } from '../../../types/plugin.ts';
|
||||
import { EventType } from 'types/eventType';
|
||||
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
|
||||
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||
|
||||
function getOpenedDialog() {
|
||||
return document.querySelector('.dialogContainer .dialog.opened');
|
||||
@@ -872,7 +875,7 @@ export default function (view) {
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) {
|
||||
if (player.isLocalPlayer && appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||
showMuteButton = false;
|
||||
showVolumeSlider = false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import markdownIt from 'markdown-it';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
|
||||
import { appHost } from '../../../components/apphost';
|
||||
import appSettings from '../../../scripts/settings/appSettings';
|
||||
import dom from '../../../scripts/dom';
|
||||
@@ -15,7 +19,6 @@ import toast from '../../../components/toast/toast';
|
||||
import dialogHelper from '../../../components/dialogHelper/dialogHelper';
|
||||
import baseAlert from '../../../components/alert';
|
||||
import { getDefaultBackgroundClass } from '../../../components/cardbuilder/cardBuilderUtils';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
|
||||
import './login.scss';
|
||||
|
||||
@@ -263,7 +266,7 @@ export default function (view, params) {
|
||||
loading.show();
|
||||
libraryMenu.setTransparentMenu(true);
|
||||
|
||||
if (!appHost.supports('multiserver')) {
|
||||
if (!appHost.supports(AppFeature.MultiServer)) {
|
||||
view.querySelector('.btnSelectServer').classList.add('hide');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import layoutManager from '../../components/layoutManager';
|
||||
import shell from '../../scripts/shell';
|
||||
import { appRouter } from '../../components/router/appRouter';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import React, { AnchorHTMLAttributes, DetailedHTMLProps, MouseEvent, useCallback } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import shell from 'scripts/shell';
|
||||
|
||||
import './emby-button.scss';
|
||||
|
||||
interface LinkButtonProps extends DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
@@ -28,7 +31,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
|
||||
const url = href || '';
|
||||
if (url !== '#') {
|
||||
if (target) {
|
||||
if (!appHost.supports('targetblank')) {
|
||||
if (!appHost.supports(AppFeature.TargetBlank)) {
|
||||
e.preventDefault();
|
||||
shell.openUrl(url);
|
||||
}
|
||||
@@ -45,7 +48,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
|
||||
onClick?.(e);
|
||||
}, [ href, target, onClick ]);
|
||||
|
||||
if (isAutoHideEnabled === true && !appHost.supports('externallinks')) {
|
||||
if (isAutoHideEnabled === true && !appHost.supports(AppFeature.ExternalLinks)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'webcomponents.js/webcomponents-lite';
|
||||
import { removeEventListener, addEventListener } from '../../scripts/dom';
|
||||
import layoutManager from '../../components/layoutManager';
|
||||
import shell from '../../scripts/shell';
|
||||
import { appRouter } from '../../components/router/appRouter';
|
||||
import { appHost } from '../../components/apphost';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { removeEventListener, addEventListener } from 'scripts/dom';
|
||||
import shell from 'scripts/shell';
|
||||
|
||||
import './emby-button.scss';
|
||||
|
||||
const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
|
||||
@@ -13,7 +16,7 @@ function onAnchorClick(e) {
|
||||
const href = this.getAttribute('href') || '';
|
||||
if (href !== '#') {
|
||||
if (this.getAttribute('target')) {
|
||||
if (!appHost.supports('targetblank')) {
|
||||
if (!appHost.supports(AppFeature.TargetBlank)) {
|
||||
e.preventDefault();
|
||||
shell.openUrl(href);
|
||||
}
|
||||
@@ -46,7 +49,7 @@ EmbyButtonPrototype.attachedCallback = function () {
|
||||
addEventListener(this, 'click', onAnchorClick, {});
|
||||
|
||||
if (this.getAttribute('data-autohide') === 'true') {
|
||||
if (appHost.supports('externallinks')) {
|
||||
if (appHost.supports(AppFeature.ExternalLinks)) {
|
||||
this.classList.remove('hide');
|
||||
} else {
|
||||
this.classList.add('hide');
|
||||
|
||||
@@ -12,6 +12,7 @@ import autoFocuser from './components/autoFocuser';
|
||||
import loading from 'components/loading/loading';
|
||||
import { pluginManager } from './components/pluginManager';
|
||||
import { appRouter } from './components/router/appRouter';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import globalize from './lib/globalize';
|
||||
import { loadCoreDictionary } from 'lib/globalize/loader';
|
||||
import { initialize as initializeAutoCast } from 'scripts/autocast';
|
||||
@@ -136,7 +137,7 @@ async function loadPlugins() {
|
||||
console.dir(pluginManager);
|
||||
|
||||
let list = await getPlugins();
|
||||
if (!appHost.supports('remotecontrol')) {
|
||||
if (!appHost.supports(AppFeature.RemoteControl)) {
|
||||
// Disable remote player plugins if not supported
|
||||
list = list.filter(plugin => !plugin.startsWith('sessionPlayer')
|
||||
&& !plugin.startsWith('chromecastPlayer'));
|
||||
@@ -165,12 +166,12 @@ function loadPlatformFeatures() {
|
||||
import('./components/nowPlayingBar/nowPlayingBar');
|
||||
}
|
||||
|
||||
if (appHost.supports('remotecontrol')) {
|
||||
if (appHost.supports(AppFeature.RemoteControl)) {
|
||||
import('./components/playback/playerSelectionMenu');
|
||||
import('./components/playback/remotecontrolautoplay');
|
||||
}
|
||||
|
||||
if (!appHost.supports('physicalvolumecontrol') || browser.touch) {
|
||||
if (!appHost.supports(AppFeature.PhysicalVolumeControl) || browser.touch) {
|
||||
import('./components/playback/volumeosd');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
|
||||
import browser from '../../scripts/browser';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
|
||||
@@ -5,7 +8,6 @@ import profileBuilder from '../../scripts/browserDeviceProfile';
|
||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
|
||||
function getDefaultProfile() {
|
||||
return profileBuilder({});
|
||||
@@ -278,7 +280,7 @@ class HtmlAudioPlayer {
|
||||
}
|
||||
|
||||
// TODO: Move volume control to PlaybackManager. Player should just be a wrapper that translates commands into API calls.
|
||||
if (!appHost.supports('physicalvolumecontrol')) {
|
||||
if (!appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||
elem.volume = htmlMediaHelper.getSavedVolume();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import Screenfull from 'screenfull';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
|
||||
import browser from '../../scripts/browser';
|
||||
import appSettings from '../../scripts/settings/appSettings';
|
||||
@@ -27,9 +33,7 @@ import {
|
||||
getBufferedRanges
|
||||
} from '../../components/htmlMediaHelper';
|
||||
import itemHelper from '../../components/itemHelper';
|
||||
import Screenfull from 'screenfull';
|
||||
import globalize from '../../lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
|
||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||
@@ -37,8 +41,6 @@ import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { includesAny } from '../../utils/container.ts';
|
||||
import { isHls } from '../../utils/mediaSource.ts';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
|
||||
/**
|
||||
* Returns resolved URL.
|
||||
@@ -1667,7 +1669,7 @@ export class HtmlVideoPlayer {
|
||||
const cssClass = 'htmlvideoplayer';
|
||||
|
||||
// Can't autoplay in these browsers so we need to use the full controls, at least until playback starts
|
||||
if (!appHost.supports('htmlvideoautoplay')) {
|
||||
if (!appHost.supports(AppFeature.HtmlVideoAutoplay)) {
|
||||
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>';
|
||||
} else if (browser.web0s) {
|
||||
// in webOS, setting preload auto allows resuming videos
|
||||
@@ -1683,7 +1685,7 @@ export class HtmlVideoPlayer {
|
||||
const videoElement = playerDlg.querySelector('video');
|
||||
|
||||
// TODO: Move volume control to PlaybackManager. Player should just be a wrapper that translates commands into API calls.
|
||||
if (!appHost.supports('physicalvolumecontrol')) {
|
||||
if (!appHost.supports(AppFeature.PhysicalVolumeControl)) {
|
||||
videoElement.volume = getSavedVolume();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { appHost } from '../../../components/apphost';
|
||||
import { appHost } from 'components/apphost';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
|
||||
/**
|
||||
* Creates an audio element that plays a silent sound.
|
||||
@@ -35,7 +36,7 @@ class PlaybackPermissionManager {
|
||||
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
||||
*/
|
||||
check () {
|
||||
if (appHost.supports('htmlaudioautoplay')) {
|
||||
if (appHost.supports(AppFeature.HtmlAudioAutoplay)) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { playbackManager } from '../components/playback/playbackmanager';
|
||||
import focusManager from '../components/focusManager';
|
||||
import { appRouter } from '../components/router/appRouter';
|
||||
import dom from './dom';
|
||||
import { appHost } from '../components/apphost';
|
||||
import { appHost } from 'components/apphost';
|
||||
import focusManager from 'components/focusManager';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import dom from 'scripts/dom';
|
||||
|
||||
let lastInputTime = new Date().getTime();
|
||||
|
||||
@@ -111,7 +112,7 @@ export function handleCommand(commandName, options) {
|
||||
'back': () => {
|
||||
if (appRouter.canGoBack()) {
|
||||
appRouter.back();
|
||||
} else if (appHost.supports('exit')) {
|
||||
} else if (appHost.supports(AppFeature.Exit)) {
|
||||
appHost.exit();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,7 +4,10 @@ import Headroom from 'headroom.js';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { ApiClient } from 'jellyfin-apiclient';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { getUserViewsQuery } from 'hooks/useUserViews';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { EventType } from 'types/eventType';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
import { queryClient } from 'utils/query/queryClient';
|
||||
@@ -19,8 +22,6 @@ import { playbackManager } from '../components/playback/playbackmanager';
|
||||
import { pluginManager } from '../components/pluginManager';
|
||||
import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu';
|
||||
import browser from './browser';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import imageHelper from '../utils/image';
|
||||
import { getMenuLinks } from '../scripts/settings/webSettings';
|
||||
import Dashboard, { pageClassOn } from '../utils/dashboard';
|
||||
@@ -348,14 +349,14 @@ function refreshLibraryInfoInDrawer(user) {
|
||||
html += globalize.translate('HeaderUser');
|
||||
html += '</h3>';
|
||||
|
||||
if (appHost.supports('multiserver')) {
|
||||
if (appHost.supports(AppFeature.MultiServer)) {
|
||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSelectServer" data-itemid="selectserver" href="#"><span class="material-icons navMenuOptionIcon storage" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('SelectServer')}</span></a>`;
|
||||
}
|
||||
|
||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnSettings" data-itemid="settings" href="#"><span class="material-icons navMenuOptionIcon settings" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('Settings')}</span></a>`;
|
||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder btnLogout" data-itemid="logout" href="#"><span class="material-icons navMenuOptionIcon exit_to_app" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonSignOut')}</span></a>`;
|
||||
|
||||
if (appHost.supports('exitmenu')) {
|
||||
if (appHost.supports(AppFeature.ExitMenu)) {
|
||||
html += `<a is="emby-linkbutton" class="navMenuOption lnkMediaFolder exitApp" data-itemid="exitapp" href="#"><span class="material-icons navMenuOptionIcon close" aria-hidden="true"></span><span class="navMenuOptionText">${globalize.translate('ButtonExitApp')}</span></a>`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user