Rename shim -> cast

This commit is contained in:
hensm
2022-03-15 07:00:45 +00:00
parent d7592d5806
commit 9af43c2910
51 changed files with 182 additions and 177 deletions

View File

@@ -0,0 +1,438 @@
"use strict";
import { v1 as uuid } from "uuid";
import logger from "../../../lib/logger";
import { Volume, Error as _Error } from "../dataClasses";
import {
BreakStatus,
EditTracksInfoRequest,
GetStatusRequest,
LiveSeekableRange,
MediaInfo,
PauseRequest,
PlayRequest,
QueueData,
QueueJumpRequest,
QueueInsertItemsRequest,
QueueItem,
QueueSetPropertiesRequest,
QueueRemoveItemsRequest,
QueueReorderItemsRequest,
QueueUpdateItemsRequest,
SeekRequest,
StopRequest,
VideoInformation,
VolumeRequest
} from "./dataClasses";
import { PlayerState, RepeatMode } from "./enums";
import { ErrorCode } from "../enums";
import { ErrorCallback, SuccessCallback, UpdateListener } from "../../types";
import { SenderMediaMessage } from "../types";
export default class Media {
#id = uuid();
// Timestamp of last status update
_lastUpdateTime = 0;
_updateListeners = new Set<UpdateListener>();
activeTrackIds: Nullable<number[]> = null;
breakStatus?: BreakStatus;
currentTime = 0;
customData: any = null;
idleReason: Nullable<string> = null;
liveSeekableRange?: LiveSeekableRange;
media: Nullable<MediaInfo> = null;
playbackRate = 1;
playerState = PlayerState.IDLE;
repeatMode = RepeatMode.OFF;
supportedMediaCommands: string[] = [];
videoInfo?: VideoInformation;
volume: Volume = new Volume();
// Queues
items: Nullable<QueueItem[]> = null;
currentItemId: Nullable<number> = null;
loadingItemId: Nullable<number> = null;
preloadedItemId: Nullable<number> = null;
queueData?: QueueData;
constructor(
public sessionId: string,
public mediaSessionId: number,
public _sendMediaMessage: (
message: DistributiveOmit<SenderMediaMessage, "requestId">
) => Promise<void>
) {}
addUpdateListener(listener: UpdateListener) {
this._updateListeners.add(listener);
}
removeUpdateListener(listener: UpdateListener) {
this._updateListeners.delete(listener);
}
editTracksInfo(
editTracksInfoRequest: EditTracksInfoRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...editTracksInfoRequest,
type: "EDIT_TRACKS_INFO",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
getEstimatedBreakClipTime() {
logger.info("STUB :: Media#getEstimatedBreakClipTime");
}
getEstimatedBreakTime() {
logger.info("STUB :: Media#getEstimatedBreakTime");
}
getEstimatedLiveSeekableRange() {
logger.info("STUB :: Media#getEstimatedLiveSeekableRange");
}
/**
* Estimate the current playback position based on the last
* time reported by the receiver and the current playback
* rate.
*/
getEstimatedTime(): number {
if (this.playerState === PlayerState.PLAYING && this._lastUpdateTime) {
let estimatedTime =
this.currentTime + (Date.now() - this._lastUpdateTime) / 1000;
// Enforce valid range
if (estimatedTime < 0) {
estimatedTime = 0;
} else if (
this.media?.duration &&
estimatedTime > this.media.duration
) {
estimatedTime = this.media.duration;
}
return estimatedTime;
}
return this.currentTime;
}
/**
* Request media status from the receiver application. This
* will also trigger any added media update listeners.
*/
getStatus(
getStatusRequest = new GetStatusRequest(),
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...getStatusRequest,
type: "MEDIA_GET_STATUS",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
pause(
pauseRequest = new PauseRequest(),
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...pauseRequest,
type: "PAUSE",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
play(
playRequest = new PlayRequest(),
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...playRequest,
type: "PLAY",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueAppendItem(
item: QueueItem,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...new QueueInsertItemsRequest([item]),
type: "QUEUE_INSERT",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueInsertItems(
queueInsertItemsRequest: QueueInsertItemsRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...queueInsertItemsRequest,
type: "QUEUE_INSERT",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueJumpToItem(
itemId: number,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
if (this.items?.find(item => item.itemId === itemId)) {
const jumpRequest = new QueueJumpRequest();
jumpRequest.currentItemId = itemId;
this._sendMediaMessage({
...jumpRequest,
type: "QUEUE_UPDATE",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
}
queueMoveItemToNewIndex(
itemId: number,
newIndex: number,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
// Return early if not in queue
if (!this.items) {
return;
}
const itemIndex = this.items.findIndex(item => item.itemId === itemId);
if (itemIndex !== -1) {
// New index must not be negative
if (newIndex < 0) {
if (errorCallback) {
errorCallback(new _Error(ErrorCode.INVALID_PARAMETER));
}
} else if (newIndex == itemIndex) {
if (successCallback) {
successCallback();
}
}
} else {
if (newIndex > itemIndex) {
newIndex++;
}
const reorderItemsRequest = new QueueReorderItemsRequest([itemId]);
if (newIndex < this.items.length) {
const existingItem = this.items[newIndex];
reorderItemsRequest.insertBefore = existingItem.itemId;
}
this._sendMediaMessage({
...reorderItemsRequest,
type: "QUEUE_REORDER",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
}
queueNext(
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
const jumpRequest = new QueueJumpRequest();
jumpRequest.jump = 1;
this._sendMediaMessage({
...jumpRequest,
type: "QUEUE_UPDATE",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queuePrev(
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
const jumpRequest = new QueueJumpRequest();
jumpRequest.jump = -1;
this._sendMediaMessage({
...jumpRequest,
type: "QUEUE_UPDATE",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueRemoveItem(
itemId: number,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
const item = this.items?.find(item => item.itemId === itemId);
if (item) {
this.queueRemoveItems(
new QueueRemoveItemsRequest([itemId]),
successCallback,
errorCallback
);
}
}
queueRemoveItems(
queueRemoveItemsRequest: QueueRemoveItemsRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...queueRemoveItemsRequest,
mediaSessionId: this.mediaSessionId,
type: "QUEUE_REMOVE",
sessionId: this.sessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueReorderItems(
queueReorderItemsRequest: QueueReorderItemsRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...queueReorderItemsRequest,
mediaSessionId: this.mediaSessionId,
type: "QUEUE_REORDER",
sessionId: this.sessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueSetRepeatMode(
repeatMode: string,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
const setPropertiesRequest = new QueueSetPropertiesRequest();
setPropertiesRequest.repeatMode = repeatMode;
this._sendMediaMessage({
...setPropertiesRequest,
type: "QUEUE_UPDATE",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
queueUpdateItems(
queueUpdateItemsRequest: QueueUpdateItemsRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...queueUpdateItemsRequest,
type: "QUEUE_UPDATE",
sessionId: this.sessionId,
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
seek(
seekRequest: SeekRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...seekRequest,
type: "SEEK",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
setVolume(
volumeRequest: VolumeRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
this._sendMediaMessage({
...volumeRequest,
type: "MEDIA_SET_VOLUME",
mediaSessionId: this.mediaSessionId
})
.then(successCallback)
.catch(errorCallback);
}
stop(
stopRequest?: StopRequest,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
if (!stopRequest) {
stopRequest = new StopRequest();
}
this._sendMediaMessage({
...stopRequest,
type: "STOP",
mediaSessionId: this.mediaSessionId
})
.then(() => {
if (successCallback) {
successCallback();
}
})
.catch(errorCallback);
}
supportsCommand(command: string): boolean {
return this.supportedMediaCommands.includes(command);
}
}

View File

@@ -0,0 +1,380 @@
"use strict";
import { Image, Volume } from "../dataClasses";
import {
ContainerType,
HdrType,
HlsSegmentFormat,
HlsVideoSegmentFormat,
MetadataType,
RepeatMode,
ResumeState,
StreamType,
TrackType,
UserAction
} from "./enums";
export class AudiobookChapterMediaMetadata {
bookTitle?: string;
chapterNumber?: number;
chapterTitle?: string;
images?: Image[];
subtitle?: string;
title?: string;
type = MetadataType.AUDIOBOOK_CHAPTER;
}
export class AudiobookContainerMetadata {
authors?: string[];
narrators?: string[];
publisher?: string;
releaseDate?: string;
}
export class Break {
duration?: number;
isEmbedded?: boolean;
isWatched = false;
constructor(
public id: string,
public breakClipIds: string[],
public position: number
) {}
}
export class BreakClip {
clickThroughUrl?: string;
contentId?: string;
contentType?: string;
contentUrl?: string;
customData?: {};
duration?: number;
hlsSegmentFormat?: HlsSegmentFormat;
posterUrl?: string;
title?: string;
vastAdsRequest?: VastAdsRequest;
whenSkippable?: number;
constructor(public id: string) {}
}
export class BreakStatus {
breakClipId?: string;
breakId?: string;
currentBreakClipTime?: number;
currentBreakTime?: number;
whenSkippable?: number;
}
export class ContainerMetadata {
containerDuration?: number;
containerImages?: Image[];
sections?: MediaMetadata[];
title?: string;
constructor(
public containerType: ContainerType = ContainerType.GENERIC_CONTAINER
) {}
}
export class EditTracksInfoRequest {
requestId = 0;
constructor(
public activeTrackIds: Nullable<number[]> = null,
public textTrackStyle: Nullable<string> = null
) {}
}
export class GenericMediaMetadata {
images?: Image[];
metadataType = MetadataType.GENERIC;
releaseDate?: string;
releaseYear?: number;
subtitle?: string;
title?: string;
type = MetadataType.GENERIC;
}
export class GetStatusRequest {
customData: any = null;
}
export class LiveSeekableRange {
constructor(
public start?: number,
public end?: number,
public isMovingWindow?: boolean,
public isLiveDone?: boolean
) {}
}
export class LoadRequest {
activeTrackIds: Nullable<number[]> = null;
atvCredentials?: string;
atvCredentialsType?: string;
autoplay: Nullable<boolean> = true;
currentTime: Nullable<number> = null;
customData: any = null;
media: MediaInfo;
requestId = 0;
sessionId: Nullable<string> = null;
type: "LOAD" = "LOAD";
constructor(mediaInfo: MediaInfo) {
this.media = mediaInfo;
}
}
export type Metadata =
| GenericMediaMetadata
| MovieMediaMetadata
| MusicTrackMediaMetadata
| PhotoMediaMetadata
| TvShowMediaMetadata;
export class MediaInfo {
atvEntity?: string;
breakClips?: BreakClip[];
breaks?: Break[];
customData: any = null;
contentUrl?: string;
duration: Nullable<number> = null;
entity?: string;
hlsSegmentFormat?: HlsSegmentFormat;
hlsVideoSegmentFormat?: HlsVideoSegmentFormat;
metadata: Nullable<Metadata> = null;
startAbsoluteTime?: number;
streamType: string = StreamType.BUFFERED;
textTrackStyle: Nullable<TextTrackStyle> = null;
tracks: Nullable<Track[]> = null;
userActionStates?: UserActionState[];
vmapAdsRequest?: VastAdsRequest;
constructor(public contentId: string, public contentType: string) {}
}
export class MediaMetadata {
queueItemId?: number;
sectionDuration?: number;
sectionStartAbsoluteTime?: number;
sectionStartTimeInContainer?: number;
sectionStartTimeInMedia?: number;
type: MetadataType;
metadataType: MetadataType;
constructor(type: MetadataType) {
this.type = type;
this.metadataType = type;
}
}
export class MovieMediaMetadata {
images?: Image[];
metadataType = MetadataType.MOVIE;
releaseDate?: string;
releaseYear?: number;
studio?: string;
subtitle?: string;
title?: string;
type = MetadataType.MOVIE;
}
export class MusicTrackMediaMetadata {
albumArtist?: string;
albumName?: string;
artist?: string;
artistName?: string;
composer?: string;
discNumber?: number;
images?: Image[];
metadataType = MetadataType.MUSIC_TRACK;
releaseDate?: string;
releaseYear?: number;
songName?: string;
title?: string;
trackNumber?: number;
type = MetadataType.MUSIC_TRACK;
}
export class PauseRequest {
customData: any = null;
}
export class PhotoMediaMetadata {
artist?: string;
creationDateTime?: string;
height?: number;
images?: Image[];
latitude?: number;
location?: string;
longitude?: number;
metadataType = MetadataType.PHOTO;
title?: string;
type = MetadataType.PHOTO;
width?: number;
}
export class PlayRequest {
customData: any = null;
}
export class QueueData {
shuffle = false;
constructor(
public id?: string,
public name?: string,
public description?: string,
public repeatMode?: RepeatMode,
public items?: QueueItem[],
public startIndex?: number,
public startTime?: number
) {}
}
export class QueueInsertItemsRequest {
customData: any = null;
insertBefore: Nullable<number> = null;
requestId: Nullable<number> = null;
sessionId: Nullable<string> = null;
type = "QUEUE_INSERT";
constructor(public items: QueueItem[]) {}
}
export class QueueItem {
activeTrackIds: Nullable<number[]> = null;
autoplay = true;
customData: any = null;
itemId: Nullable<number> = null;
media: MediaInfo;
playbackDuration: Nullable<number> = null;
preloadTime = 0;
startTime = 0;
constructor(mediaInfo: MediaInfo) {
this.media = mediaInfo;
}
}
export class QueueJumpRequest {
type = "QUEUE_UPDATE";
jump: Nullable<number> = null;
currentItemId: Nullable<number> = null;
}
export class QueueLoadRequest {
type = "QUEUE_LOAD";
customData: any = null;
repeatMode: string = RepeatMode.OFF;
startIndex = 0;
constructor(public items: QueueItem[]) {}
}
export class QueueRemoveItemsRequest {
type = "QUEUE_REMOVE";
customData: any = null;
constructor(public itemIds: number[]) {}
}
export class QueueReorderItemsRequest {
customData: any = null;
insertBefore: Nullable<number> = null;
type = "QUEUE_REORDER";
constructor(public itemIds: number[]) {}
}
export class QueueSetPropertiesRequest {
type = "QUEUE_UPDATE";
customData: any = null;
repeatMode: Nullable<string> = null;
}
export class QueueUpdateItemsRequest {
type = "QUEUE_UPDATE";
customData: any = null;
constructor(public items: QueueItem[]) {}
}
export class SeekRequest {
currentTime: Nullable<number> = null;
customData: any = null;
resumeState: Nullable<ResumeState> = null;
}
export class StopRequest {
customData: any = null;
}
export class TextTrackStyle {
backgroundColor: Nullable<string> = null;
customData: any = null;
edgeColor: Nullable<string> = null;
edgeType: Nullable<string> = null;
fontFamily: Nullable<string> = null;
fontGenericFamily: Nullable<string> = null;
fontScale: Nullable<number> = null;
fontStyle: Nullable<string> = null;
foregroundColor: Nullable<string> = null;
windowColor: Nullable<string> = null;
windowRoundedCornerRadius: Nullable<number> = null;
windowType: Nullable<string> = null;
}
export class Track {
customData: any = null;
language: Nullable<string> = null;
name: Nullable<string> = null;
subtype: Nullable<string> = null;
trackContentId: Nullable<string> = null;
trackContentType: Nullable<string> = null;
constructor(public trackId: number, public type: TrackType) {}
}
export class TvShowMediaMetadata {
episode?: number;
episodeNumber?: number;
episodeTitle?: string;
images?: Image[];
metadataType: number = MetadataType.TV_SHOW;
originalAirdate?: string;
releaseYear?: number;
season?: number;
seasonNumber?: number;
seriesTitle?: string;
title?: string;
type = MetadataType.TV_SHOW;
}
export class UserActionState {
customData: any = null;
constructor(public userAction: UserAction) {}
}
export class VastAdsRequest {
adsResponse?: string;
adTagUrl?: string;
}
export class VideoInformation {
constructor(
public width: number,
public height: number,
public hdrType: HdrType
) {}
}
export class VolumeRequest {
customData: any = null;
constructor(public volume: Volume) {}
}

View File

@@ -0,0 +1,139 @@
"use strict";
export enum ContainerType {
GENERIC_CONTAINER,
AUDIOBOOK_CONTAINER
}
export enum HdrType {
SDR = "sdr",
HDR = "hdr",
DV = "dv"
}
export enum HlsSegmentFormat {
AAC = "aac",
AC3 = "ac3",
MP3 = "mp3",
TS = "ts",
TS_AAC = "ts_aac",
E_AC3 = "e_ac3",
FMP4 = "fmp4"
}
export enum HlsVideoSegmentFormat {
MPEG2_TS = "mpeg2_ts",
FMP4 = "fmp4"
}
export enum IdleReason {
CANCELLED = "CANCELLED",
INTERRUPTED = "INTERRUPTED",
FINISHED = "FINISHED",
ERROR = "ERROR"
}
export enum MediaCommand {
PAUSE = "pause",
SEEK = "seek",
STREAM_VOLUME = "stream_volume",
STREAM_MUTE = "stream_mute"
}
export enum MetadataType {
GENERIC,
MOVIE,
TV_SHOW,
MUSIC_TRACK,
PHOTO,
AUDIOBOOK_CHAPTER
}
export enum PlayerState {
IDLE = "IDLE",
PLAYING = "PLAYING",
PAUSED = "PAUSED",
BUFFERING = "BUFFERING"
}
export enum QueueType {
ALBUM = "ALBUM",
PLAYLIST = "PLAYLIST",
AUDIOBOOK = "AUDIOBOOK",
RADIO_STATION = "RADIO_STATION",
PODCAST_SERIES = "PODCAST_SERIES",
TV_SERIES = "TV_SERIES",
VIDEO_PLAYLIST = "VIDEO_PLAYLIST",
LIVE_TV = "LIVETV",
MOVIE = "MOVIE"
}
export enum RepeatMode {
OFF = "REPEAT_OFF",
ALL = "REPEAT_ALL",
SINGLE = "REPEAT_SINGLE",
ALL_AND_SHUFFLE = "REPEAT_ALL_AND_SHUFFLE"
}
export enum ResumeState {
PLAYBACK_START = "PLAYBACK_START",
PLAYBACK_PAUSE = "PLAYBACK_PAUSE"
}
export enum StreamType {
BUFFERED = "BUFFERED",
LIVE = "LIVE",
OTHER = "OTHER"
}
export enum TextTrackEdgeType {
NONE = "NONE",
OUTLINE = "OUTLINE",
DROP_SHADOW = "DROP_SHADOW",
RAISED = "RAISED",
DEPRESSED = "DEPRESSED"
}
export enum TextTrackFontGenericFamily {
SANS_SERIF = "SANS_SERIF",
MONOSPACED_SANS_SERIF = "MONOSPACED_SANS_SERIF",
SERIF = "SERIF",
MONOSPACED_SERIF = "MONOSPACED_SERIF",
CASUAL = "CASUAL",
CURSIVE = "CURSIVE",
SMALL_CAPITALS = "SMALL_CAPITALS"
}
export enum TextTrackFontStyle {
NORMAL = "NORMAL",
BOLD = "BOLD",
BOLD_ITALIC = "BOLD_ITALIC",
ITALIC = "ITALIC"
}
export enum TextTrackType {
SUBTITLES = "SUBTITLES",
CAPTIONS = "CAPTIONS",
DESCRIPTIONS = "DESCRIPTIONS",
CHAPTERS = "CHAPTERS",
METADATA = "METADATA"
}
export enum TextTrackWindowType {
NONE = "NONE",
NORMAL = "NORMAL",
ROUNDED_CORNERS = "ROUNDED_CORNERS"
}
export enum TrackType {
TEXT = "TEXT",
AUDIO = "AUDIO",
VIDEO = "VIDEO"
}
export enum UserAction {
LIKE = "LIKE",
DISLIKE = "DISLIKE",
FOLLOW = "FOLLOW",
UNFOLLOW = "UNFOLLOW"
}

View File

@@ -0,0 +1,22 @@
"use strict";
import Media from "./Media";
export { Media };
export * from "./dataClasses";
export * from "./enums";
export const timeout = {
editTracksInfo: 0,
getStatus: 0,
load: 0,
pause: 0,
play: 0,
queue: 0,
seek: 0,
setVolume: 0,
stop: 0
};
export const DEFAULT_MEDIA_RECEIVER_APP_ID = "CC1AD845";