mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-10-26 16:45:18 +00:00
Add still watching prompt
This commit is contained in:
1 parent
e1c7e99a1e
commit
8dcfc697ae
34 files changed
+206
-58
No files matched your search
@@ -8,9 +8,9 @@ import React, { useCallback, useState } from 'react';
|
||||
import { QUERY_KEY, useSyncPlayGroups } from 'apps/experimental/features/syncPlay/hooks/api/useSyncPlayGroups';
|
||||
import { useSyncPlay } from 'apps/experimental/features/syncPlay/hooks/useSyncPlay';
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import globalize from 'lib/globalize';
|
||||
import { PluginType } from 'types/plugin';
|
||||
import { queryClient } from 'utils/query/queryClient';
|
||||
|
||||
import AppSyncPlayMenu, { ID } from './menus/SyncPlayMenu';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { Plugin, PluginType } from 'types/plugin';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import type { Plugin } from 'types/plugin';
|
||||
import globalize from 'lib/globalize';
|
||||
|
||||
export function useScreensavers() {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ApiClient } from 'jellyfin-apiclient';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { PluginType } from 'types/plugin';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import Events, { type Event } from 'utils/events';
|
||||
|
||||
interface SyncPlayInstance {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { getUserQuery } from 'hooks/api/useUser';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
@@ -15,7 +16,6 @@ import datetime from '../../scripts/datetime';
|
||||
import globalize from '../../lib/globalize';
|
||||
import loading from '../loading/loading';
|
||||
import skinManager from '../../scripts/themeManager';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import toast from '../toast/toast';
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import globalize from '../../lib/globalize';
|
||||
import loading from '../loading/loading';
|
||||
import { appHost } from '../apphost';
|
||||
import alert from '../alert';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import { includesAny } from '../../utils/container.ts';
|
||||
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
|
||||
import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage';
|
||||
@@ -27,6 +26,7 @@ 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 { PluginType } from 'constants/pluginType';
|
||||
import { TICKS_PER_SECOND } from 'constants/time';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
|
||||
@@ -2364,23 +2364,21 @@ export class PlaybackManager {
|
||||
// Normalize defaults to simplfy checks throughout the process
|
||||
normalizePlayOptions(playOptions);
|
||||
|
||||
if (playOptions.isFirstItem) {
|
||||
playOptions.isFirstItem = false;
|
||||
} else {
|
||||
playOptions.isFirstItem = true;
|
||||
}
|
||||
playOptions.isFirstItem = playOptions.isFirstItem || !prevSource;
|
||||
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
|
||||
// TODO: This should be the media type requested, not the original media type
|
||||
const mediaType = item.MediaType;
|
||||
|
||||
if (playOptions.fullscreen) {
|
||||
loading.show();
|
||||
}
|
||||
|
||||
return runInterceptors(item, playOptions)
|
||||
.catch(onInterceptorRejection)
|
||||
.then(() => {
|
||||
if (playOptions.fullscreen) {
|
||||
loading.show();
|
||||
}
|
||||
})
|
||||
.then(() => detectBitrate(apiClient, item, mediaType))
|
||||
.then((bitrate) => {
|
||||
return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn, prevSource)
|
||||
@@ -2437,7 +2435,7 @@ export class PlaybackManager {
|
||||
const interceptors = pluginManager.ofType(PluginType.PreplayIntercept);
|
||||
|
||||
interceptors.sort(function (a, b) {
|
||||
return (a.order || 0) - (b.order || 0);
|
||||
return (a.priority || 0) - (b.priority || 0);
|
||||
});
|
||||
|
||||
if (!interceptors.length) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from 'types/plugin';
|
||||
import Events from 'utils/events';
|
||||
import { getReadableSize } from 'utils/file';
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api'
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import toast from 'components/toast/toast';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import dom from 'utils/dom';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
|
||||
import { PluginType } from 'types/plugin';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
import { isBlank } from 'utils/string';
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"logoScreensaver/plugin",
|
||||
"sessionPlayer/plugin",
|
||||
"chromecastPlayer/plugin",
|
||||
"stillWatching/plugin",
|
||||
"syncPlay/plugin"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/** The type of a web plugin. */
|
||||
export enum PluginType {
|
||||
MediaPlayer = 'mediaplayer',
|
||||
PreplayIntercept = 'preplayintercept',
|
||||
Screensaver = 'screensaver',
|
||||
SyncPlay = 'syncplay'
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
/** The number of milliseconds per second */
|
||||
export const MILLISECONDS_PER_SECOND = 1_000;
|
||||
|
||||
/** The number of milliseconds per minute */
|
||||
export const MILLISECONDS_PER_MINUTE = 60 * MILLISECONDS_PER_SECOND;
|
||||
|
||||
/** The number of ticks per millisecond */
|
||||
export const TICKS_PER_MILLISECOND = 10_000;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import escapeHtml from 'escape-html';
|
||||
|
||||
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
|
||||
import { EventType } from 'constants/eventType';
|
||||
|
||||
@@ -32,7 +33,6 @@ import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import LibraryMenu from '../../../scripts/libraryMenu';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
||||
import { pluginManager } from '../../../components/pluginManager';
|
||||
import { PluginType } from '../../../types/plugin.ts';
|
||||
|
||||
function getOpenedDialog() {
|
||||
return document.querySelector('.dialogContainer .dialog.opened');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import * as userSettings from 'scripts/settings/userSettings';
|
||||
import { PluginType } from 'types/plugin.ts';
|
||||
|
||||
class BackdropScreensaver {
|
||||
constructor() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||
import Screenfull from 'screenfull';
|
||||
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import browser from 'scripts/browser';
|
||||
import TouchHelper from 'scripts/touchHelper';
|
||||
@@ -13,7 +14,6 @@ import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import TableOfContents from './tableOfContents';
|
||||
import { translateHtml } from '../../lib/globalize';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
import 'material-design-icons-iconfont';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
|
||||
import appSettings from '../../scripts/settings/appSettings';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
@@ -5,7 +7,6 @@ import globalize from '../../lib/globalize';
|
||||
import CastSenderApi from './castSenderApi';
|
||||
import alert from '../../components/alert';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||
import { Archive } from 'libarchive.js';
|
||||
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
|
||||
@@ -9,7 +10,6 @@ import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import keyboardnavigation from '../../scripts/keyboardNavigation';
|
||||
import { appRouter } from '../../components/router/appRouter';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
|
||||
import globalize from '../../lib/globalize';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { appHost } from '../../components/apphost';
|
||||
import alert from '../../components/alert';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
|
||||
// TODO: Replace with date-fns
|
||||
// https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
|
||||
import browser from '../../scripts/browser';
|
||||
@@ -6,7 +7,6 @@ import { appHost } from '../../components/apphost';
|
||||
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
|
||||
import profileBuilder from '../../scripts/browserDeviceProfile';
|
||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
function getDefaultProfile() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Screenfull from 'screenfull';
|
||||
import { useCustomSubtitles } from 'apps/stable/features/playback/utils/subtitleStyles';
|
||||
import subtitleAppearanceHelper from 'components/subtitlesettings/subtitleappearancehelper';
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
|
||||
import { MediaError } from 'types/mediaError';
|
||||
@@ -40,7 +41,6 @@ import globalize from '../../lib/globalize';
|
||||
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
|
||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { includesAny } from '../../utils/container.ts';
|
||||
import { isHls } from '../../utils/mediaSource.ts';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import icon from '@jellyfin/ux-web/icon-transparent.png';
|
||||
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import { randomInt } from '../../utils/number.ts';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { randomInt } from 'utils/number';
|
||||
|
||||
export default function () {
|
||||
const self = this;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||
|
||||
import loading from '../../components/loading/loading';
|
||||
@@ -8,7 +9,6 @@ import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import dom from '../../utils/dom';
|
||||
import { appRouter } from '../../components/router/appRouter';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { PluginType } from 'types/plugin.ts';
|
||||
import * as userSettings from 'scripts/settings/userSettings';
|
||||
|
||||
export default class PhotoPlayer {
|
||||
constructor() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import alert from 'components/alert';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from 'types/plugin.ts';
|
||||
|
||||
function showErrorMessage() {
|
||||
return alert(globalize.translate('MessagePlayAccessRestricted'));
|
||||
@@ -12,7 +12,7 @@ class PlayAccessValidation {
|
||||
this.name = 'Playback validation';
|
||||
this.type = PluginType.PreplayIntercept;
|
||||
this.id = 'playaccessvalidation';
|
||||
this.order = -2;
|
||||
this.priority = -2;
|
||||
}
|
||||
|
||||
intercept(options) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { OutboundWebSocketMessageType, PeriodicListenerInterval } from '@jellyfin/sdk/lib/websocket';
|
||||
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import Events from 'utils/events';
|
||||
|
||||
function getActivePlayerId() {
|
||||
const info = playbackManager.getPlayerInfo();
|
||||
return info ? info.id : null;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
|
||||
import confirm from 'components/confirm/confirm';
|
||||
import { MILLISECONDS_PER_MINUTE } from 'constants/time';
|
||||
import globalize from 'lib/globalize';
|
||||
import inputManager from 'scripts/inputManager';
|
||||
import { type InterceptOptions, PreplayInterceptPlugin } from 'types/plugin';
|
||||
|
||||
/** List of item types that should display a still watching prompt. */
|
||||
const SUPPORTED_ITEM_TYPES: BaseItemKind[] = [
|
||||
BaseItemKind.Episode
|
||||
];
|
||||
|
||||
// TODO: These thresholds should be configurable by the user
|
||||
/** The minimum idle time required before showing the still watching prompt. */
|
||||
const MIN_IDLE_TIME = 90 * MILLISECONDS_PER_MINUTE; // 90 minutes
|
||||
/** The number of items that must be played before showing the still watching prompt. */
|
||||
const MIN_PLAY_COUNT = 3;
|
||||
|
||||
class StillWatchingPlugin extends PreplayInterceptPlugin {
|
||||
name = 'Still Watching';
|
||||
id = 'still-watching';
|
||||
priority = 10; // Run after plugins that validate access
|
||||
|
||||
/** The start time of the current play session or since the last reset. */
|
||||
private sessionStartTime?: Date;
|
||||
/** A set of item IDs that have been played in the current session or since the last reset. */
|
||||
private playedItems = new Set<string>();
|
||||
|
||||
/** Resets the play session, clearing the start time and played items. */
|
||||
private resetSession() {
|
||||
this.sessionStartTime = new Date();
|
||||
this.playedItems.clear();
|
||||
}
|
||||
|
||||
async intercept({
|
||||
isFirstItem,
|
||||
item
|
||||
}: InterceptOptions) {
|
||||
// Reset the session at the start of a new play session
|
||||
if (isFirstItem) this.resetSession();
|
||||
|
||||
// Bail if the item is not supported or if it lacks an ID
|
||||
if (!item.Id || !item.Type || !SUPPORTED_ITEM_TYPES.includes(item.Type)) return;
|
||||
|
||||
const id = item.Id;
|
||||
const idleTime = inputManager.idleTime();
|
||||
const sessionDuration = this.sessionStartTime ? new Date().getTime() - this.sessionStartTime.getTime() : 0;
|
||||
|
||||
if (
|
||||
// Check that the session has lasted at least the minimum idle time.
|
||||
// This prevents the prompt from appearing early for short episodes or skipping episodes.
|
||||
sessionDuration >= MIN_IDLE_TIME
|
||||
// Check that the user has been idle for at least the minimum idle time or has played at least the minimum
|
||||
// number of items.
|
||||
&& (idleTime >= MIN_IDLE_TIME || this.playedItems.size >= MIN_PLAY_COUNT)
|
||||
) {
|
||||
return confirm({
|
||||
text: globalize.translate('ConfirmStillWatching'),
|
||||
cancelText: globalize.translate('StopWatching'),
|
||||
confirmText: globalize.translate('ContinueWatching')
|
||||
}).then(() => {
|
||||
this.resetSession();
|
||||
this.playedItems.add(id);
|
||||
});
|
||||
}
|
||||
|
||||
this.playedItems.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
export default StillWatchingPlugin;
|
||||
@@ -1,11 +1,13 @@
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import Events from '../../utils/events';
|
||||
import type { Plugin } from 'types/plugin';
|
||||
import Events from 'utils/events';
|
||||
|
||||
import SyncPlay from './core';
|
||||
import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer';
|
||||
import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer';
|
||||
import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer';
|
||||
import { Plugin, PluginType } from '../../types/plugin';
|
||||
|
||||
class SyncPlayPlugin implements Plugin {
|
||||
name: string;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
|
||||
import SyncPlaySettingsEditor from './settings/SettingsEditor';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import toast from '../../../components/toast/toast';
|
||||
@@ -5,8 +8,6 @@ import actionsheet from '../../../components/actionSheet/actionSheet';
|
||||
import globalize from '../../../lib/globalize';
|
||||
import playbackPermissionManager from './playbackPermissionManager';
|
||||
import { pluginManager } from '../../../components/pluginManager';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from '../../../types/plugin.ts';
|
||||
import Events from '../../../utils/events.ts';
|
||||
|
||||
import './groupSelectionMenu.scss';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Module that displays an editor for changing SyncPlay settings.
|
||||
* @module components/syncPlay/settings/SettingsEditor
|
||||
*/
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
|
||||
import { setSetting } from '../../core/Settings';
|
||||
import dialogHelper from '../../../../components/dialogHelper/dialogHelper';
|
||||
@@ -10,7 +11,6 @@ import { pluginManager } from '../../../../components/pluginManager';
|
||||
import loading from '../../../../components/loading/loading';
|
||||
import toast from '../../../../components/toast/toast';
|
||||
import globalize from '../../../../lib/globalize';
|
||||
import { PluginType } from '../../../../types/plugin.ts';
|
||||
import Events from '../../../../utils/events.ts';
|
||||
|
||||
import 'material-design-icons-iconfont';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
|
||||
import browser from '../../scripts/browser';
|
||||
import { appRouter } from '../../components/router/appRouter';
|
||||
import loading from '../../components/loading/loading';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
/* globals YT */
|
||||
|
||||
@@ -2,6 +2,7 @@ import escapeHtml from 'escape-html';
|
||||
import Headroom from 'headroom.js';
|
||||
|
||||
import { AppFeature } from 'constants/appFeature';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { getUserViewsQuery } from 'hooks/api/useUserViews';
|
||||
import globalize from 'lib/globalize';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
@@ -22,7 +23,6 @@ import browser from './browser';
|
||||
import imageHelper from '../utils/image';
|
||||
import { getMenuLinks } from '../scripts/settings/webSettings';
|
||||
import Dashboard, { pageClassOn } from '../utils/dashboard';
|
||||
import { PluginType } from '../types/plugin.ts';
|
||||
import Events from '../utils/events.ts';
|
||||
import { getParameterByName } from '../utils/url.ts';
|
||||
import datetime from '../scripts/datetime';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import { PluginType } from 'types/plugin.ts';
|
||||
import Events from 'utils/events.ts';
|
||||
import Events from 'utils/events';
|
||||
|
||||
import inputManager from './inputManager';
|
||||
import * as userSettings from './settings/userSettings';
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
|
||||
|
||||
import alert from 'components/alert';
|
||||
import focusManager from 'components/focusManager';
|
||||
import { playbackManager } from 'components/playback/playbackmanager';
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import toast from 'components/toast/toast';
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
import { ServerConnections } from 'lib/jellyfin-apiclient';
|
||||
import inputManager from 'scripts/inputManager';
|
||||
import Events from 'utils/events.ts';
|
||||
import { PluginType } from 'types/plugin.ts';
|
||||
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
|
||||
import Events from 'utils/events';
|
||||
|
||||
const serverNotifications = {};
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@
|
||||
"ConfirmDeleteRepository": "Delete repository?",
|
||||
"ConfirmDeletion": "Confirm Deletion",
|
||||
"ConfirmEndPlayerSession": "Would you like to shut Jellyfin down on {0}?",
|
||||
"ConfirmStillWatching": "Are you still watching?",
|
||||
"Connect": "Connect",
|
||||
"ConnectAnyway": "Connect Anyway",
|
||||
"Console": "Console",
|
||||
@@ -1596,6 +1597,7 @@
|
||||
"StoryArc": "Story Arc",
|
||||
"StopPlayback": "Stop playback",
|
||||
"StopRecording": "Stop recording",
|
||||
"StopWatching": "Stop watching",
|
||||
"StorageType.CDRom": "CD-ROM",
|
||||
"StorageType.Fixed": "Fixed",
|
||||
"StorageType.Network": "Network",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
|
||||
export interface PlayOptions {
|
||||
/** The aspect ratio of the player */
|
||||
aspectRatio?: string
|
||||
/** The index of the audio stream to play */
|
||||
audioStreamIndex?: number
|
||||
/** Whether to play in fullscreen mode */
|
||||
fullscreen?: boolean
|
||||
/** Whether this is the first item in the play queue */
|
||||
isFirstItem?: boolean
|
||||
/** The list of items in the play queue */
|
||||
items?: BaseItemDto[] | null
|
||||
/** The media source ID to play for the current item */
|
||||
mediaSourceId?: string
|
||||
/** The index of the item to start playing from */
|
||||
startIndex?: number
|
||||
/** The position to start playing from */
|
||||
startPositionTicks?: number
|
||||
/** The index of the subtitle stream to play */
|
||||
subtitleStreamIndex?: number
|
||||
}
|
||||
+43
-8
@@ -1,17 +1,52 @@
|
||||
export enum PluginType {
|
||||
MediaPlayer = 'mediaplayer',
|
||||
PreplayIntercept = 'preplayintercept',
|
||||
Screensaver = 'screensaver',
|
||||
SyncPlay = 'syncplay'
|
||||
}
|
||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||
import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||
|
||||
import { PluginType } from 'constants/pluginType';
|
||||
|
||||
import type { PlayOptions } from './playOptions';
|
||||
|
||||
export interface Plugin {
|
||||
/** The name of the plugin */
|
||||
name: string
|
||||
/** The unique identifier for the plugin */
|
||||
id: string
|
||||
/** The type of the plugin */
|
||||
type: PluginType | string
|
||||
/** The priority of the plugin, used to determine the order of execution */
|
||||
priority?: number
|
||||
}
|
||||
|
||||
export interface PlayerPlugin extends Plugin {
|
||||
isLocalPlayer?: boolean
|
||||
export abstract class PlayerPlugin implements Plugin {
|
||||
type = PluginType.MediaPlayer;
|
||||
/** The name of the plugin */
|
||||
abstract name: string;
|
||||
/** The unique identifier for the plugin */
|
||||
abstract id: string;
|
||||
/** The priority of the plugin, used to determine the order of execution */
|
||||
priority?: number;
|
||||
/** Indicates if the player is a local player */
|
||||
isLocalPlayer?: boolean;
|
||||
}
|
||||
|
||||
export interface InterceptOptions extends PlayOptions {
|
||||
/** The item being played */
|
||||
item: BaseItemDto
|
||||
/** The media type of the item */
|
||||
mediaType?: MediaType
|
||||
}
|
||||
|
||||
export abstract class PreplayInterceptPlugin implements Plugin {
|
||||
type = PluginType.PreplayIntercept;
|
||||
/** The name of the plugin */
|
||||
abstract name: string;
|
||||
/** The unique identifier for the plugin */
|
||||
abstract id: string;
|
||||
/** The priority of the plugin, used to determine the order of execution */
|
||||
priority?: number;
|
||||
|
||||
/**
|
||||
* The function called prior to playback starting.
|
||||
* Returning a rejected promise will prevent playback from starting, while a resolved promise will allow it to proceed.
|
||||
*/
|
||||
abstract intercept(options: InterceptOptions): Promise<void>;
|
||||
}
|
||||
Reference in new issue
Block a user