Add still watching prompt

This commit is contained in:
Bill Thornton committed 2026-06-01 17:31:41 -04:00
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';
+9 -11
View File
@@ -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 -1
View File
@@ -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';
+1
View File
@@ -45,6 +45,7 @@
"logoScreensaver/plugin",
"sessionPlayer/plugin",
"chromecastPlayer/plugin",
"stillWatching/plugin",
"syncPlay/plugin"
]
}
+7
View File
@@ -0,0 +1,7 @@
/** The type of a web plugin. */
export enum PluginType {
MediaPlayer = 'mediaplayer',
PreplayIntercept = 'preplayintercept',
Screensaver = 'screensaver',
SyncPlay = 'syncplay'
}
+3
View File
@@ -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;
+1 -1
View File
@@ -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 -2
View File
@@ -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 -1
View File
@@ -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';
+2 -1
View File
@@ -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 -1
View File
@@ -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';
+2 -1
View File
@@ -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 -1
View File
@@ -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() {
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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 -1
View File
@@ -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';
+2 -2
View File
@@ -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() {
+2 -2
View File
@@ -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) {
+5 -4
View File
@@ -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;
+72
View File
@@ -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;
+5 -3
View File
@@ -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';
+2 -1
View File
@@ -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 */
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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';
+4 -3
View File
@@ -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 = {};
+2
View File
@@ -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",
+22
View File
@@ -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
View File
@@ -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>;
}