mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add playback subscriber abstraction
This commit is contained in:
parent
2442dc6b52
commit
26f7f281cd
7 changed files with 209 additions and 1 deletions
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Events triggered by PlaybackManager.
|
||||||
|
*/
|
||||||
|
export enum PlaybackManagerEvent {
|
||||||
|
Pairing = 'pairing',
|
||||||
|
Paired = 'paired',
|
||||||
|
PairError = 'pairerror',
|
||||||
|
PlaybackCancelled = 'playbackcancelled',
|
||||||
|
PlaybackError = 'playbackerror',
|
||||||
|
PlaybackStart = 'playbackstart',
|
||||||
|
PlaybackStop = 'playbackstop',
|
||||||
|
PlayerChange = 'playerchange',
|
||||||
|
ReportPlayback = 'reportplayback'
|
||||||
|
}
|
23
src/apps/stable/features/playback/constants/playerEvent.ts
Normal file
23
src/apps/stable/features/playback/constants/playerEvent.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Events triggered by media player plugins.
|
||||||
|
* NOTE: This list is incomplete
|
||||||
|
*/
|
||||||
|
export enum PlayerEvent {
|
||||||
|
Error = 'error',
|
||||||
|
FullscreenChange = 'fullscreenchange',
|
||||||
|
ItemStarted = 'itemstarted',
|
||||||
|
ItemStopped = 'itemstopped',
|
||||||
|
MediaStreamsChange = 'mediastreamschange',
|
||||||
|
Pause = 'pause',
|
||||||
|
PlaybackStart = 'playbackstart',
|
||||||
|
PlaybackStop = 'playbackstop',
|
||||||
|
PlaylistItemAdd = 'playlistitemadd',
|
||||||
|
PlaylistItemMove = 'playlistitemmove',
|
||||||
|
PlaylistItemRemove = 'playlistitemremove',
|
||||||
|
RepeatModeChange = 'repeatmodechange',
|
||||||
|
ShuffleModeChange = 'shufflequeuemodechange',
|
||||||
|
Stopped = 'stopped',
|
||||||
|
TimeUpdate = 'timeupdate',
|
||||||
|
Unpause = 'unpause',
|
||||||
|
VolumeChange = 'volumechange'
|
||||||
|
}
|
33
src/apps/stable/features/playback/types/callbacks.ts
Normal file
33
src/apps/stable/features/playback/types/callbacks.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||||
|
import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info';
|
||||||
|
import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
|
|
||||||
|
import type { StreamInfo } from './streamInfo';
|
||||||
|
|
||||||
|
export interface ManagedPlayerStopInfo {
|
||||||
|
item: BaseItemDto
|
||||||
|
mediaSource: MediaSourceInfo
|
||||||
|
nextItem?: BaseItemDto | null
|
||||||
|
nextMediaType?: MediaType | null
|
||||||
|
positionMs?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MovedItem {
|
||||||
|
newIndex: number
|
||||||
|
playlistItemId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlayerErrorCode = string;
|
||||||
|
|
||||||
|
export interface PlayerStopInfo {
|
||||||
|
src?: URL | BaseItemDto
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerError {
|
||||||
|
streamInfo?: StreamInfo
|
||||||
|
type: MediaError | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemovedItems {
|
||||||
|
playlistItemIds: string[]
|
||||||
|
}
|
34
src/apps/stable/features/playback/types/streamInfo.ts
Normal file
34
src/apps/stable/features/playback/types/streamInfo.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||||
|
import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info';
|
||||||
|
import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
|
import type { PlayMethod } from '@jellyfin/sdk/lib/generated-client/models/play-method';
|
||||||
|
|
||||||
|
export interface StreamInfo {
|
||||||
|
ended?: boolean
|
||||||
|
fullscreen?: boolean
|
||||||
|
item?: BaseItemDto
|
||||||
|
lastMediaInfoQuery?: number
|
||||||
|
liveStreamId?: string
|
||||||
|
mediaSource?: MediaSourceInfo
|
||||||
|
mediaType?: MediaType
|
||||||
|
mimeType?: string
|
||||||
|
playMethod?: PlayMethod
|
||||||
|
playSessionId?: string
|
||||||
|
playbackStartTimeTicks?: number
|
||||||
|
playerStartPositionTicks?: number
|
||||||
|
resetSubtitleOffset?: boolean
|
||||||
|
started?: boolean
|
||||||
|
textTracks?: TrackInfo[]
|
||||||
|
title?: string
|
||||||
|
tracks?: TrackInfo[]
|
||||||
|
transcodingOffsetTicks?: number
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrackInfo {
|
||||||
|
url: string
|
||||||
|
language: string
|
||||||
|
isDefault: boolean
|
||||||
|
index: number
|
||||||
|
format: string
|
||||||
|
}
|
101
src/apps/stable/features/playback/utils/playbackSubscriber.ts
Normal file
101
src/apps/stable/features/playback/utils/playbackSubscriber.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto';
|
||||||
|
import type { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client/models/media-source-info';
|
||||||
|
|
||||||
|
import type { PlaybackManager } from 'components/playback/playbackmanager';
|
||||||
|
import type { MediaError } from 'types/mediaError';
|
||||||
|
import type { PlayTarget } from 'types/playTarget';
|
||||||
|
import type { PlaybackStopInfo, PlayerState } from 'types/playbackStopInfo';
|
||||||
|
import type { Plugin } from 'types/plugin';
|
||||||
|
import Events, { type Event } from 'utils/events';
|
||||||
|
|
||||||
|
import { PlaybackManagerEvent } from '../constants/playbackManagerEvent';
|
||||||
|
import { PlayerEvent } from '../constants/playerEvent';
|
||||||
|
import type { ManagedPlayerStopInfo, MovedItem, PlayerError, PlayerErrorCode, PlayerStopInfo, RemovedItems } from '../types/callbacks';
|
||||||
|
|
||||||
|
export interface PlaybackSubscriber {
|
||||||
|
onPlaybackCancelled?(e: Event): void
|
||||||
|
onPlaybackError?(e: Event, errorType: MediaError): void
|
||||||
|
onPlaybackStart?(e: Event, player: Plugin, state: PlayerState): void
|
||||||
|
onPlaybackStop?(e: Event, info: PlaybackStopInfo): void
|
||||||
|
onPlayerChange?(e: Event, player: Plugin, target: PlayTarget, previousPlayer: Plugin): void
|
||||||
|
onPlayerError?(e: Event, error: PlayerError): void
|
||||||
|
onPlayerFullscreenChange?(e: Event): void
|
||||||
|
onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void
|
||||||
|
onPlayerItemStopped?(e: Event, info: ManagedPlayerStopInfo): void
|
||||||
|
onPlayerMediaStreamsChange?(e: Event): void
|
||||||
|
onPlayerPause?(e: Event): void
|
||||||
|
onPlayerPlaybackStart?(e: Event, state: PlayerState): void
|
||||||
|
onPlayerPlaybackStop?(e: Event, state: PlayerState): void
|
||||||
|
onPlayerPlaylistItemAdd?(e: Event): void
|
||||||
|
onPlayerPlaylistItemMove?(e: Event, item: MovedItem): void
|
||||||
|
onPlayerPlaylistItemRemove?(e: Event, items?: RemovedItems): void
|
||||||
|
onPlayerRepeatModeChange?(e: Event): void
|
||||||
|
onPlayerShuffleModeChange?(e: Event): void
|
||||||
|
onPlayerStopped?(e: Event, info?: PlayerStopInfo | PlayerErrorCode): void
|
||||||
|
onPlayerTimeUpdate?(e: Event): void
|
||||||
|
onPlayerUnpause?(e: Event): void
|
||||||
|
onPlayerVolumeChange?(e: Event): void
|
||||||
|
onReportPlayback?(e: Event, isServerItem: boolean): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PlaybackSubscriber {
|
||||||
|
private player: Plugin | undefined;
|
||||||
|
|
||||||
|
private playbackManagerEvents = {
|
||||||
|
[PlaybackManagerEvent.PlaybackCancelled]: this.onPlaybackCancelled,
|
||||||
|
[PlaybackManagerEvent.PlaybackError]: this.onPlaybackError,
|
||||||
|
[PlaybackManagerEvent.PlaybackStart]: this.onPlaybackStart,
|
||||||
|
[PlaybackManagerEvent.PlaybackStop]: this.onPlaybackStop,
|
||||||
|
[PlaybackManagerEvent.PlayerChange]: this.onPlayerChange,
|
||||||
|
[PlaybackManagerEvent.ReportPlayback]: this.onReportPlayback
|
||||||
|
};
|
||||||
|
|
||||||
|
private playerEvents = {
|
||||||
|
[PlayerEvent.Error]: this.onPlayerError,
|
||||||
|
[PlayerEvent.FullscreenChange]: this.onPlayerFullscreenChange,
|
||||||
|
[PlayerEvent.ItemStarted]: this.onPlayerItemStarted,
|
||||||
|
[PlayerEvent.ItemStopped]: this.onPlayerItemStopped,
|
||||||
|
[PlayerEvent.MediaStreamsChange]: this.onPlayerMediaStreamsChange,
|
||||||
|
[PlayerEvent.Pause]: this.onPlayerPause,
|
||||||
|
[PlayerEvent.PlaybackStart]: this.onPlayerPlaybackStart,
|
||||||
|
[PlayerEvent.PlaybackStop]: this.onPlayerPlaybackStop,
|
||||||
|
[PlayerEvent.PlaylistItemAdd]: this.onPlayerPlaylistItemAdd,
|
||||||
|
[PlayerEvent.PlaylistItemMove]: this.onPlayerPlaylistItemMove,
|
||||||
|
[PlayerEvent.PlaylistItemRemove]: this.onPlayerPlaylistItemRemove,
|
||||||
|
[PlayerEvent.RepeatModeChange]: this.onPlayerRepeatModeChange,
|
||||||
|
[PlayerEvent.ShuffleModeChange]: this.onPlayerShuffleModeChange,
|
||||||
|
[PlayerEvent.Stopped]: this.onPlayerStopped,
|
||||||
|
[PlayerEvent.TimeUpdate]: this.onPlayerTimeUpdate,
|
||||||
|
[PlayerEvent.Unpause]: this.onPlayerUnpause,
|
||||||
|
[PlayerEvent.VolumeChange]: this.onPlayerVolumeChange
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly playbackManager: PlaybackManager
|
||||||
|
) {
|
||||||
|
Object.entries(this.playbackManagerEvents).forEach(([event, handler]) => {
|
||||||
|
if (handler) Events.on(playbackManager, event, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bindPlayerEvents();
|
||||||
|
Events.on(playbackManager, PlaybackManagerEvent.PlayerChange, this.bindPlayerEvents.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindPlayerEvents() {
|
||||||
|
const newPlayer = this.playbackManager.getCurrentPlayer();
|
||||||
|
if (this.player === newPlayer) return;
|
||||||
|
|
||||||
|
if (this.player) {
|
||||||
|
Object.entries(this.playerEvents).forEach(([event, handler]) => {
|
||||||
|
if (handler) Events.off(this.player, event, handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player = newPlayer;
|
||||||
|
if (!this.player) return;
|
||||||
|
|
||||||
|
Object.entries(this.playerEvents).forEach(([event, handler]) => {
|
||||||
|
if (handler) Events.on(this.player, event, handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -687,7 +687,7 @@ function sortPlayerTargets(a, b) {
|
||||||
return aVal.localeCompare(bVal);
|
return aVal.localeCompare(bVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlaybackManager {
|
export class PlaybackManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||||
|
|
||||||
export interface PlayTarget {
|
export interface PlayTarget {
|
||||||
|
@ -7,5 +8,7 @@ export interface PlayTarget {
|
||||||
playerName?: string
|
playerName?: string
|
||||||
deviceType?: string
|
deviceType?: string
|
||||||
isLocalPlayer?: boolean
|
isLocalPlayer?: boolean
|
||||||
|
playableMediaTypes: MediaType[]
|
||||||
|
supportedCommands?: string[]
|
||||||
user?: UserDto
|
user?: UserDto
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue