Hide Session/Media private data and preserve Media constructor signature

This commit is contained in:
hensm
2022-09-01 04:36:42 +01:00
committed by Matt Hensman
parent 7a35da2ba1
commit 6562294586
7 changed files with 237 additions and 257 deletions

View File

@@ -5,13 +5,13 @@ import { v4 as uuid } from "uuid";
import logger from "../../lib/logger"; import logger from "../../lib/logger";
import eventMessaging from "../pageMessenging"; import eventMessaging from "../pageMessenging";
import { convertSupportedMediaCommandsFlags } from "../utils";
import { import type {
MediaStatus, MediaStatus,
ReceiverMediaMessage, ReceiverMediaMessage,
SenderMediaMessage, SenderMediaMessage,
SenderMessage, SenderMessage
_MediaCommand
} from "./types"; } from "./types";
import { SessionStatus } from "./enums"; import { SessionStatus } from "./enums";
@@ -22,9 +22,13 @@ import type {
SenderApplication SenderApplication
} from "./classes"; } from "./classes";
import { MediaCommand } from "./media/enums";
import type { LoadRequest, QueueLoadRequest, QueueItem } from "./media/classes"; import type { LoadRequest, QueueLoadRequest, QueueItem } from "./media/classes";
import Media, { NS_MEDIA } from "./media/Media"; import Media, {
createMedia,
MediaLastUpdateTimes,
MediaUpdateListeners,
NS_MEDIA
} from "./media/Media";
/** /**
* Takes a media object and a media status object and merges the status * Takes a media object and a media status object and merges the status
@@ -32,7 +36,7 @@ import Media, { NS_MEDIA } from "./media/Media";
*/ */
function updateMedia(media: Media, status: MediaStatus) { function updateMedia(media: Media, status: MediaStatus) {
if (status.currentTime) { if (status.currentTime) {
media._lastUpdateTime = Date.now(); MediaLastUpdateTimes.set(media, Date.now());
} }
// Copy basic props // Copy basic props
@@ -46,23 +50,9 @@ function updateMedia(media: Media, status: MediaStatus) {
if (status.repeatMode) media.repeatMode = status.repeatMode; if (status.repeatMode) media.repeatMode = status.repeatMode;
if (status.volume) media.volume = status.volume; if (status.volume) media.volume = status.volume;
// Convert supportedMediaCommands bitflags to string array media.supportedMediaCommands = convertSupportedMediaCommandsFlags(
const supportedMediaCommands: string[] = []; status.supportedMediaCommands
if (status.supportedMediaCommands & _MediaCommand.PAUSE) { );
supportedMediaCommands.push(MediaCommand.PAUSE);
} else if (status.supportedMediaCommands & _MediaCommand.SEEK) {
supportedMediaCommands.push(MediaCommand.SEEK);
} else if (status.supportedMediaCommands & _MediaCommand.STREAM_VOLUME) {
supportedMediaCommands.push(MediaCommand.STREAM_VOLUME);
} else if (status.supportedMediaCommands & _MediaCommand.STREAM_MUTE) {
supportedMediaCommands.push(MediaCommand.STREAM_MUTE);
} else if (status.supportedMediaCommands & _MediaCommand.QUEUE_NEXT) {
supportedMediaCommands.push("queue_next");
} else if (status.supportedMediaCommands & _MediaCommand.QUEUE_PREV) {
supportedMediaCommands.push("queue_prev");
}
media.supportedMediaCommands = supportedMediaCommands;
// Update queue state // Update queue state
if (status.items) { if (status.items) {
@@ -97,21 +87,57 @@ function updateMedia(media: Media, status: MediaStatus) {
} }
} }
export const SessionMessageListeners = new WeakMap<
Session,
Map<string, Set<MessageListener>>
>();
export const SessionUpdateListeners = new WeakMap<
Session,
Set<UpdateListener>
>();
export const SessionSendMessageCallbacks = new WeakMap<
Session,
Map<string, SendMessageCallback>
>();
/** Creates a Session object and initializes private data. */
export function createSession(
sessionArgs: ConstructorParameters<typeof Session>
) {
const session = new Session(...sessionArgs);
SessionUpdateListeners.set(session, new Set());
SessionSendMessageCallbacks.set(session, new Map());
return session;
}
type MessageListener = (namespace: string, message: string) => void; type MessageListener = (namespace: string, message: string) => void;
type UpdateListener = (isAlive: boolean) => void; type UpdateListener = (isAlive: boolean) => void;
type SendMessageCallback = [(() => void)?, ((err: CastError) => void)?];
export default class Session { export default class Session {
#loadMediaRequest?: LoadRequest; #loadMediaRequest?: LoadRequest;
#loadMediaSuccessCallback?: (media: Media) => void; #loadMediaSuccessCallback?: (media: Media) => void;
#loadMediaErrorCallback?: (err: CastError) => void; #loadMediaErrorCallback?: (err: CastError) => void;
_messageListeners = new Map<string, Set<MessageListener>>(); get #messageListeners() {
_updateListeners = new Set<UpdateListener>(); const messageListeners = SessionMessageListeners.get(this);
if (!messageListeners)
_sendMessageCallbacks = new Map< throw logger.error("Missing session message listeners!");
string, return messageListeners;
[(() => void)?, ((err: CastError) => void)?] }
>(); get #updateListeners() {
const updateListeners = SessionUpdateListeners.get(this);
if (!updateListeners)
throw logger.error("Missing session update listeners!");
return updateListeners;
}
get #sendMessageCallbacks() {
const sendMessageCallback = SessionSendMessageCallbacks.get(this);
if (!sendMessageCallback)
throw logger.error("Missing session sendMessage callback!");
return sendMessageCallback;
}
media: Media[] = []; media: Media[] = [];
namespaces: Array<{ name: string }> = []; namespaces: Array<{ name: string }> = [];
@@ -129,6 +155,7 @@ export default class Session {
) { ) {
this.transportId = sessionId || ""; this.transportId = sessionId || "";
SessionMessageListeners.set(this, new Map());
this.addMessageListener(NS_MEDIA, this.#mediaMessageListener); this.addMessageListener(NS_MEDIA, this.#mediaMessageListener);
} }
@@ -147,9 +174,8 @@ export default class Session {
// Handle Media creation // Handle Media creation
if (!media) { if (!media) {
media = new Media( media = createMedia(
this.sessionId, [this.sessionId, mediaStatus.mediaSessionId],
mediaStatus.mediaSessionId,
this.#sendMediaMessage this.#sendMediaMessage
); );
@@ -158,8 +184,11 @@ export default class Session {
this.#loadMediaSuccessCallback?.(media); this.#loadMediaSuccessCallback?.(media);
} else { } else {
updateMedia(media, mediaStatus); updateMedia(media, mediaStatus);
for (const listener of media._updateListeners) { const updateListeners = MediaUpdateListeners.get(media);
listener(true); if (updateListeners) {
for (const listener of updateListeners) {
listener(true);
}
} }
} }
} }
@@ -198,7 +227,7 @@ export default class Session {
} }
}); });
this._sendMessageCallbacks.set(messageId, [resolve, reject]); this.#sendMessageCallbacks.set(messageId, [resolve, reject]);
}); });
}; };
@@ -210,21 +239,21 @@ export default class Session {
} }
addMessageListener(namespace: string, listener: MessageListener) { addMessageListener(namespace: string, listener: MessageListener) {
if (!this._messageListeners.has(namespace)) { if (!this.#messageListeners.has(namespace)) {
this._messageListeners.set(namespace, new Set()); this.#messageListeners.set(namespace, new Set());
} }
this._messageListeners.get(namespace)?.add(listener); this.#messageListeners.get(namespace)?.add(listener);
} }
removeMessageListener(namespace: string, listener: MessageListener) { removeMessageListener(namespace: string, listener: MessageListener) {
this._messageListeners.get(namespace)?.delete(listener); this.#messageListeners.get(namespace)?.delete(listener);
} }
addUpdateListener(listener: UpdateListener) { addUpdateListener(listener: UpdateListener) {
this._updateListeners.add(listener); this.#updateListeners.add(listener);
} }
removeUpdateListener(listener: UpdateListener) { removeUpdateListener(listener: UpdateListener) {
this._updateListeners.delete(listener); this.#updateListeners.delete(listener);
} }
leave( leave(
@@ -272,7 +301,7 @@ export default class Session {
} }
}); });
this._sendMessageCallbacks.set(messageId, [ this.#sendMessageCallbacks.set(messageId, [
successCallback, successCallback,
errorCallback errorCallback
]); ]);

View File

@@ -6,6 +6,7 @@ import {
AutoJoinPolicy, AutoJoinPolicy,
Capability, Capability,
DefaultActionPolicy, DefaultActionPolicy,
ErrorCode,
ReceiverAvailability, ReceiverAvailability,
ReceiverType, ReceiverType,
VolumeControlType VolumeControlType
@@ -35,7 +36,7 @@ export class DialRequest {
export class Error { export class Error {
constructor( constructor(
public code: string, public code: ErrorCode,
public description: Nullable<string> = null, public description: Nullable<string> = null,
public details: unknown = null public details: unknown = null
) {} ) {}

View File

@@ -33,9 +33,14 @@ import {
Volume Volume
} from "./classes"; } from "./classes";
import Session from "./Session"; import Session, {
createSession,
SessionMessageListeners,
SessionSendMessageCallbacks,
SessionUpdateListeners
} from "./Session";
import media from "./media"; import * as media from "./media";
type ReceiverActionListener = ( type ReceiverActionListener = (
receiver: Receiver, receiver: Receiver,
@@ -49,6 +54,7 @@ export default class {
#apiConfig?: ApiConfig; #apiConfig?: ApiConfig;
#sessionRequest?: SessionRequest; #sessionRequest?: SessionRequest;
/** Current receiver availability. */
#receiverAvailability = ReceiverAvailability.UNAVAILABLE; #receiverAvailability = ReceiverAvailability.UNAVAILABLE;
#initializeSuccessCallback?: () => void; #initializeSuccessCallback?: () => void;
@@ -87,7 +93,7 @@ export default class {
Volume = Volume; Volume = Volume;
Session = Session; Session = Session;
media = media; media = { ...media };
VERSION = [1, 2]; VERSION = [1, 2];
isAvailable = false; isAvailable = false;
@@ -101,10 +107,19 @@ export default class {
switch (message.subject) { switch (message.subject) {
case "cast:initialized": case "cast:initialized":
this.isAvailable = true; this.isAvailable = true;
this.#initializeSuccessCallback?.(); this.#initializeSuccessCallback?.();
this.#apiConfig?.receiverListener(this.#receiverAvailability); this.#apiConfig?.receiverListener(this.#receiverAvailability);
break;
// Popup closed before session established
case "cast:sessionRequestCancelled":
if (this.#sessionRequest) {
this.#sessionRequest = undefined;
this.#requestSessionErrorCallback?.(
new CastError(ErrorCode.CANCEL)
);
}
break; break;
/** /**
@@ -120,13 +135,13 @@ export default class {
status.appImages status.appImages
); );
const session = new Session( const session = createSession([
status.sessionId, status.sessionId,
status.appId, status.appId,
status.displayName, status.displayName,
status.appImages, status.appImages,
status.receiver status.receiver
); ]);
session.namespaces = status.namespaces; session.namespaces = status.namespaces;
session.senderApps = status.senderApps; session.senderApps = status.senderApps;
@@ -164,8 +179,11 @@ export default class {
session.namespaces = status.namespaces; session.namespaces = status.namespaces;
session.receiver.volume = status.volume; session.receiver.volume = status.volume;
for (const listener of session._updateListeners) { const updateListeners = SessionUpdateListeners.get(session);
listener(session.status !== SessionStatus.STOPPED); if (updateListeners) {
for (const listener of updateListeners) {
listener(session.status !== SessionStatus.STOPPED);
}
} }
break; break;
@@ -176,8 +194,12 @@ export default class {
const session = this.#sessions.get(sessionId); const session = this.#sessions.get(sessionId);
if (session) { if (session) {
session.status = SessionStatus.STOPPED; session.status = SessionStatus.STOPPED;
for (const listener of session._updateListeners) {
listener(false); const updateListeners = SessionUpdateListeners.get(session);
if (updateListeners) {
for (const listener of updateListeners) {
listener(false);
}
} }
} }
@@ -188,7 +210,8 @@ export default class {
const { sessionId, namespace, messageData } = message.data; const { sessionId, namespace, messageData } = message.data;
const session = this.#sessions.get(sessionId); const session = this.#sessions.get(sessionId);
if (session) { if (session) {
const listeners = session._messageListeners.get(namespace); const listeners =
SessionMessageListeners.get(session)?.get(namespace);
if (listeners) { if (listeners) {
for (const listener of listeners) { for (const listener of listeners) {
listener(namespace, messageData); listener(namespace, messageData);
@@ -207,12 +230,16 @@ export default class {
break; break;
} }
const callbacks = session._sendMessageCallbacks.get(messageId); const sendMessageCallback =
if (callbacks) { SessionSendMessageCallbacks.get(session)?.get(messageId);
const [successCallback, errorCallback] = callbacks; if (sendMessageCallback) {
const [successCallback, errorCallback] =
sendMessageCallback;
if (error) { if (error) {
errorCallback?.(new CastError(error)); errorCallback?.(
new CastError(ErrorCode.CHANNEL_ERROR, error)
);
return; return;
} }
@@ -236,26 +263,11 @@ export default class {
break; break;
} }
// Popup closed before session established case "cast:receiverAction":
case "cast:sessionRequestCancelled": {
if (this.#sessionRequest) {
this.#sessionRequest = undefined;
this.#requestSessionErrorCallback?.(
new CastError(ErrorCode.CANCEL)
);
}
break;
}
case "cast:receiverAction": {
for (const actionListener of this.#receiverActionListeners) { for (const actionListener of this.#receiverActionListeners) {
actionListener(message.data.receiver, message.data.action); actionListener(message.data.receiver, message.data.action);
} }
break; break;
}
} }
} }

View File

@@ -2,9 +2,15 @@
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import logger from "../../../lib/logger"; import { Logger } from "../../../lib/logger";
const logger = new Logger("fx_cast [sdk :: cast.Media]");
import { getEstimatedTime } from "../../utils";
import type { SenderMediaMessage } from "../types";
import { Volume, Error as CastError } from "../classes"; import { Volume, Error as CastError } from "../classes";
import { ErrorCode } from "../enums";
import { import {
BreakStatus, BreakStatus,
EditTracksInfoRequest, EditTracksInfoRequest,
@@ -26,23 +32,52 @@ import {
VideoInformation, VideoInformation,
VolumeRequest VolumeRequest
} from "./classes"; } from "./classes";
import { PlayerState, RepeatMode } from "./enums"; import { PlayerState, RepeatMode } from "./enums";
import { ErrorCode } from "../enums";
import type { SenderMediaMessage } from "../types";
import { getEstimatedTime } from "../../utils";
export const NS_MEDIA = "urn:x-cast:com.google.cast.media"; export const NS_MEDIA = "urn:x-cast:com.google.cast.media";
type MediaMessageCallback = (
message: DistributiveOmit<SenderMediaMessage, "requestId">
) => Promise<void>;
const MediaMessageCallbacks = new WeakMap<Media, MediaMessageCallback>();
export const MediaUpdateListeners = new WeakMap<Media, Set<UpdateListener>>();
export const MediaLastUpdateTimes = new WeakMap<Media, number>();
/** Creates a Media object and initializes private data. */
export function createMedia(
mediaArgs: ConstructorParameters<typeof Media>,
mediaMessageCallback: MediaMessageCallback
) {
const media = new Media(...mediaArgs);
MediaMessageCallbacks.set(media, mediaMessageCallback);
MediaUpdateListeners.set(media, new Set());
MediaLastUpdateTimes.set(media, 0);
return media;
}
type UpdateListener = (isAlive: boolean) => void; type UpdateListener = (isAlive: boolean) => void;
export default class Media { export default class Media {
#id = uuid(); #id = uuid();
// Timestamp of last status update get #updateListeners() {
_lastUpdateTime = 0; const updateListeners = MediaUpdateListeners.get(this);
_updateListeners = new Set<UpdateListener>(); if (!updateListeners)
throw logger.error("Missing media update listeners!");
return updateListeners;
}
get #mediaMessageCallback() {
const callback = MediaMessageCallbacks.get(this);
if (!callback) throw logger.error("Missing media message callback!");
return callback;
}
get #lastUpdateTime() {
const lastUpdateTime = MediaLastUpdateTimes.get(this);
if (!lastUpdateTime) throw logger.error("Missing last update time!");
return lastUpdateTime;
}
activeTrackIds: Nullable<number[]> = null; activeTrackIds: Nullable<number[]> = null;
breakStatus?: BreakStatus; breakStatus?: BreakStatus;
@@ -65,19 +100,13 @@ export default class Media {
preloadedItemId: Nullable<number> = null; preloadedItemId: Nullable<number> = null;
queueData?: QueueData; queueData?: QueueData;
constructor( constructor(public sessionId: string, public mediaSessionId: number) {}
public sessionId: string,
public mediaSessionId: number,
public _sendMediaMessage: (
message: DistributiveOmit<SenderMediaMessage, "requestId">
) => Promise<void>
) {}
addUpdateListener(listener: UpdateListener) { addUpdateListener(listener: UpdateListener) {
this._updateListeners.add(listener); this.#updateListeners?.add(listener);
} }
removeUpdateListener(listener: UpdateListener) { removeUpdateListener(listener: UpdateListener) {
this._updateListeners.delete(listener); this.#updateListeners?.delete(listener);
} }
editTracksInfo( editTracksInfo(
@@ -85,7 +114,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...editTracksInfoRequest, ...editTracksInfoRequest,
type: "EDIT_TRACKS_INFO", type: "EDIT_TRACKS_INFO",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -108,7 +137,7 @@ export default class Media {
return getEstimatedTime({ return getEstimatedTime({
currentTime: this.breakStatus.currentBreakClipTime, currentTime: this.breakStatus.currentBreakClipTime,
lastUpdateTime: this._lastUpdateTime, lastUpdateTime: this.#lastUpdateTime,
duration: currentBreakClip.duration duration: currentBreakClip.duration
}); });
} }
@@ -127,7 +156,7 @@ export default class Media {
return getEstimatedTime({ return getEstimatedTime({
currentTime: this.breakStatus.currentBreakTime, currentTime: this.breakStatus.currentBreakTime,
lastUpdateTime: this._lastUpdateTime, lastUpdateTime: this.#lastUpdateTime,
duration: currentBreak.duration duration: currentBreak.duration
}); });
} }
@@ -144,7 +173,7 @@ export default class Media {
if (this.playerState === PlayerState.PLAYING) { if (this.playerState === PlayerState.PLAYING) {
return getEstimatedTime({ return getEstimatedTime({
currentTime: this.currentTime, currentTime: this.currentTime,
lastUpdateTime: this._lastUpdateTime, lastUpdateTime: this.#lastUpdateTime,
duration: this.media?.duration duration: this.media?.duration
}); });
} }
@@ -161,7 +190,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...getStatusRequest, ...getStatusRequest,
type: "MEDIA_GET_STATUS", type: "MEDIA_GET_STATUS",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -175,7 +204,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...pauseRequest, ...pauseRequest,
type: "PAUSE", type: "PAUSE",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -189,7 +218,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...playRequest, ...playRequest,
type: "PLAY", type: "PLAY",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -203,7 +232,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...new QueueInsertItemsRequest([item]), ...new QueueInsertItemsRequest([item]),
type: "QUEUE_INSERT", type: "QUEUE_INSERT",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -218,7 +247,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...queueInsertItemsRequest, ...queueInsertItemsRequest,
type: "QUEUE_INSERT", type: "QUEUE_INSERT",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -237,7 +266,7 @@ export default class Media {
const jumpRequest = new QueueJumpRequest(); const jumpRequest = new QueueJumpRequest();
jumpRequest.currentItemId = itemId; jumpRequest.currentItemId = itemId;
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...jumpRequest, ...jumpRequest,
type: "QUEUE_UPDATE", type: "QUEUE_UPDATE",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -283,7 +312,7 @@ export default class Media {
reorderItemsRequest.insertBefore = existingItem.itemId; reorderItemsRequest.insertBefore = existingItem.itemId;
} }
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...reorderItemsRequest, ...reorderItemsRequest,
type: "QUEUE_REORDER", type: "QUEUE_REORDER",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -301,7 +330,7 @@ export default class Media {
const jumpRequest = new QueueJumpRequest(); const jumpRequest = new QueueJumpRequest();
jumpRequest.jump = 1; jumpRequest.jump = 1;
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...jumpRequest, ...jumpRequest,
type: "QUEUE_UPDATE", type: "QUEUE_UPDATE",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -318,7 +347,7 @@ export default class Media {
const jumpRequest = new QueueJumpRequest(); const jumpRequest = new QueueJumpRequest();
jumpRequest.jump = -1; jumpRequest.jump = -1;
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...jumpRequest, ...jumpRequest,
type: "QUEUE_UPDATE", type: "QUEUE_UPDATE",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -348,7 +377,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...queueRemoveItemsRequest, ...queueRemoveItemsRequest,
mediaSessionId: this.mediaSessionId, mediaSessionId: this.mediaSessionId,
@@ -364,7 +393,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...queueReorderItemsRequest, ...queueReorderItemsRequest,
mediaSessionId: this.mediaSessionId, mediaSessionId: this.mediaSessionId,
@@ -383,7 +412,7 @@ export default class Media {
const setPropertiesRequest = new QueueSetPropertiesRequest(); const setPropertiesRequest = new QueueSetPropertiesRequest();
setPropertiesRequest.repeatMode = repeatMode; setPropertiesRequest.repeatMode = repeatMode;
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...setPropertiesRequest, ...setPropertiesRequest,
type: "QUEUE_UPDATE", type: "QUEUE_UPDATE",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -398,7 +427,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...queueUpdateItemsRequest, ...queueUpdateItemsRequest,
type: "QUEUE_UPDATE", type: "QUEUE_UPDATE",
sessionId: this.sessionId, sessionId: this.sessionId,
@@ -413,7 +442,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...seekRequest, ...seekRequest,
type: "SEEK", type: "SEEK",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -427,7 +456,7 @@ export default class Media {
successCallback?: () => void, successCallback?: () => void,
errorCallback?: (err: CastError) => void errorCallback?: (err: CastError) => void
) { ) {
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...volumeRequest, ...volumeRequest,
type: "MEDIA_SET_VOLUME", type: "MEDIA_SET_VOLUME",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId
@@ -445,7 +474,7 @@ export default class Media {
stopRequest = new StopRequest(); stopRequest = new StopRequest();
} }
this._sendMediaMessage({ this.#mediaMessageCallback?.({
...stopRequest, ...stopRequest,
type: "STOP", type: "STOP",
mediaSessionId: this.mediaSessionId mediaSessionId: this.mediaSessionId

View File

@@ -1,140 +1,20 @@
"use strict"; "use strict";
import Media from "./Media"; export * from "./enums";
export * from "./classes";
import { export { default as Media } from "./Media";
ContainerType,
HdrType,
HlsSegmentFormat,
HlsVideoSegmentFormat,
IdleReason,
MediaCommand,
MetadataType,
PlayerState,
QueueType,
RepeatMode,
ResumeState,
StreamType,
TextTrackEdgeType,
TextTrackFontGenericFamily,
TextTrackFontStyle,
TextTrackType,
TextTrackWindowType,
TrackType,
UserAction
} from "./enums";
import { export const DEFAULT_MEDIA_RECEIVER_APP_ID = "CC1AD845";
AudiobookChapterMediaMetadata,
AudiobookContainerMetadata,
Break,
BreakClip,
BreakStatus,
ContainerMetadata,
EditTracksInfoRequest,
GenericMediaMetadata,
GetStatusRequest,
LiveSeekableRange,
LoadRequest,
MediaInfo,
MediaMetadata,
MovieMediaMetadata,
MusicTrackMediaMetadata,
PauseRequest,
PhotoMediaMetadata,
PlayRequest,
QueueData,
QueueInsertItemsRequest,
QueueItem,
QueueJumpRequest,
QueueLoadRequest,
QueueRemoveItemsRequest,
QueueReorderItemsRequest,
QueueSetPropertiesRequest,
QueueUpdateItemsRequest,
SeekRequest,
StopRequest,
TextTrackStyle,
Track,
TvShowMediaMetadata,
UserActionState,
VastAdsRequest,
VideoInformation,
VolumeRequest
} from "./classes";
export default { export const timeout = {
DEFAULT_MEDIA_RECEIVER_APP_ID: "CC1AD845", editTracksInfo: 0,
timeout: { getStatus: 0,
editTracksInfo: 0, load: 0,
getStatus: 0, pause: 0,
load: 0, play: 0,
pause: 0, queue: 0,
play: 0, seek: 0,
queue: 0, setVolume: 0,
seek: 0, stop: 0
setVolume: 0,
stop: 0
},
Media,
// Enums
ContainerType,
HdrType,
HlsSegmentFormat,
HlsVideoSegmentFormat,
IdleReason,
MediaCommand,
MetadataType,
PlayerState,
QueueType,
RepeatMode,
ResumeState,
StreamType,
TextTrackEdgeType,
TextTrackFontGenericFamily,
TextTrackFontStyle,
TextTrackType,
TextTrackWindowType,
TrackType,
UserAction,
// Classes
AudiobookChapterMediaMetadata,
AudiobookContainerMetadata,
Break,
BreakClip,
BreakStatus,
ContainerMetadata,
EditTracksInfoRequest,
GenericMediaMetadata,
GetStatusRequest,
LiveSeekableRange,
LoadRequest,
MediaInfo,
MediaMetadata,
MovieMediaMetadata,
MusicTrackMediaMetadata,
PauseRequest,
PhotoMediaMetadata,
PlayRequest,
QueueData,
QueueInsertItemsRequest,
QueueItem,
QueueJumpRequest,
QueueLoadRequest,
QueueRemoveItemsRequest,
QueueReorderItemsRequest,
QueueSetPropertiesRequest,
QueueUpdateItemsRequest,
SeekRequest,
StopRequest,
TextTrackStyle,
Track,
TvShowMediaMetadata,
UserActionState,
VastAdsRequest,
VideoInformation,
VolumeRequest
}; };

View File

@@ -14,7 +14,7 @@ const logger = new Logger("fx_cast [media sender]");
interface MediaSenderOpts { interface MediaSenderOpts {
mediaUrl: string; mediaUrl: string;
contextTabId?: number; contextTabId?: number;
targetElementId?: number; mediaElement?: HTMLMediaElement;
} }
export default class MediaSender { export default class MediaSender {
@@ -23,6 +23,7 @@ export default class MediaSender {
private mediaUrl: string; private mediaUrl: string;
private contextTabId?: number; private contextTabId?: number;
/** Target media element if loaded as a content script. */
private mediaElement?: HTMLMediaElement; private mediaElement?: HTMLMediaElement;
private isLocalMedia = false; private isLocalMedia = false;
@@ -34,12 +35,7 @@ export default class MediaSender {
constructor(opts: MediaSenderOpts) { constructor(opts: MediaSenderOpts) {
this.mediaUrl = opts.mediaUrl; this.mediaUrl = opts.mediaUrl;
this.contextTabId = opts.contextTabId; this.contextTabId = opts.contextTabId;
this.mediaElement = opts.mediaElement;
if (opts.targetElementId) {
this.mediaElement = browser.menus.getTargetElement(
opts.targetElementId
) as HTMLMediaElement;
}
this.init(); this.init();
} }
@@ -79,7 +75,7 @@ export default class MediaSender {
), ),
undefined, undefined,
err => { err => {
logger.error("Failed to initialize cast API", err); logger.error("Failed to initialize cast SDK", err);
} }
); );
} }
@@ -270,10 +266,21 @@ export default class MediaSender {
} }
} }
/**
* If loaded as a content script, opts are stored on the window object.
*/
if (window.location.protocol !== "moz-extension:") { if (window.location.protocol !== "moz-extension:") {
const window_ = window as any; const window_ = window as any;
let mediaElement: Optional<HTMLMediaElement>;
if (window_.targetElementId) {
mediaElement = browser.menus.getTargetElement(
window_.targetElementId
) as HTMLMediaElement;
}
new MediaSender({ new MediaSender({
mediaUrl: window_.mediaUrl, mediaUrl: window_.mediaUrl,
targetElementId: window_.targetElementId mediaElement
}); });
} }

View File

@@ -1,6 +1,8 @@
import { ReceiverDevice, ReceiverDeviceCapabilities } from "../types"; import { ReceiverDevice, ReceiverDeviceCapabilities } from "../types";
import { Receiver } from "./sdk/classes"; import { Receiver } from "./sdk/classes";
import { Capability, ReceiverType } from "./sdk/enums"; import { Capability, ReceiverType } from "./sdk/enums";
import { MediaCommand } from "./sdk/media/enums";
import { _MediaCommand } from "./sdk/types";
/** /**
* Check receiver device capabilities bitflags against array of * Check receiver device capabilities bitflags against array of
@@ -29,8 +31,8 @@ export function hasRequiredCapabilities(
}); });
} }
/** Convert capabilities bitflags to string array. */
export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) { export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) {
// Convert capabilities bitflag to string array
const capabilities: Capability[] = []; const capabilities: Capability[] = [];
if (flags & ReceiverDeviceCapabilities.VIDEO_OUT) if (flags & ReceiverDeviceCapabilities.VIDEO_OUT)
capabilities.push(Capability.VIDEO_OUT); capabilities.push(Capability.VIDEO_OUT);
@@ -47,6 +49,26 @@ export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) {
return capabilities; return capabilities;
} }
/** Convert media commands bitflags to string array. */
export function convertSupportedMediaCommandsFlags(flags: _MediaCommand) {
const supportedMediaCommands: string[] = [];
if (flags & _MediaCommand.PAUSE) {
supportedMediaCommands.push(MediaCommand.PAUSE);
} else if (flags & _MediaCommand.SEEK) {
supportedMediaCommands.push(MediaCommand.SEEK);
} else if (flags & _MediaCommand.STREAM_VOLUME) {
supportedMediaCommands.push(MediaCommand.STREAM_VOLUME);
} else if (flags & _MediaCommand.STREAM_MUTE) {
supportedMediaCommands.push(MediaCommand.STREAM_MUTE);
} else if (flags & _MediaCommand.QUEUE_NEXT) {
supportedMediaCommands.push("queue_next");
} else if (flags & _MediaCommand.QUEUE_PREV) {
supportedMediaCommands.push("queue_prev");
}
return supportedMediaCommands;
}
interface GetEstimatedTimeOpts { interface GetEstimatedTimeOpts {
currentTime: number; currentTime: number;
lastUpdateTime: number; lastUpdateTime: number;