diff --git a/ext/src/cast/sdk/Session.ts b/ext/src/cast/sdk/Session.ts index 3d3e3e2..4401964 100644 --- a/ext/src/cast/sdk/Session.ts +++ b/ext/src/cast/sdk/Session.ts @@ -5,13 +5,13 @@ import { v4 as uuid } from "uuid"; import logger from "../../lib/logger"; import eventMessaging from "../pageMessenging"; +import { convertSupportedMediaCommandsFlags } from "../utils"; -import { +import type { MediaStatus, ReceiverMediaMessage, SenderMediaMessage, - SenderMessage, - _MediaCommand + SenderMessage } from "./types"; import { SessionStatus } from "./enums"; @@ -22,9 +22,13 @@ import type { SenderApplication } from "./classes"; -import { MediaCommand } from "./media/enums"; 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 @@ -32,7 +36,7 @@ import Media, { NS_MEDIA } from "./media/Media"; */ function updateMedia(media: Media, status: MediaStatus) { if (status.currentTime) { - media._lastUpdateTime = Date.now(); + MediaLastUpdateTimes.set(media, Date.now()); } // Copy basic props @@ -46,23 +50,9 @@ function updateMedia(media: Media, status: MediaStatus) { if (status.repeatMode) media.repeatMode = status.repeatMode; if (status.volume) media.volume = status.volume; - // Convert supportedMediaCommands bitflags to string array - const supportedMediaCommands: string[] = []; - 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; + media.supportedMediaCommands = convertSupportedMediaCommandsFlags( + status.supportedMediaCommands + ); // Update queue state if (status.items) { @@ -97,21 +87,57 @@ function updateMedia(media: Media, status: MediaStatus) { } } +export const SessionMessageListeners = new WeakMap< + Session, + Map> +>(); +export const SessionUpdateListeners = new WeakMap< + Session, + Set +>(); +export const SessionSendMessageCallbacks = new WeakMap< + Session, + Map +>(); + +/** Creates a Session object and initializes private data. */ +export function createSession( + sessionArgs: ConstructorParameters +) { + 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 UpdateListener = (isAlive: boolean) => void; +type SendMessageCallback = [(() => void)?, ((err: CastError) => void)?]; export default class Session { #loadMediaRequest?: LoadRequest; #loadMediaSuccessCallback?: (media: Media) => void; #loadMediaErrorCallback?: (err: CastError) => void; - _messageListeners = new Map>(); - _updateListeners = new Set(); - - _sendMessageCallbacks = new Map< - string, - [(() => void)?, ((err: CastError) => void)?] - >(); + get #messageListeners() { + const messageListeners = SessionMessageListeners.get(this); + if (!messageListeners) + throw logger.error("Missing session message listeners!"); + return messageListeners; + } + 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[] = []; namespaces: Array<{ name: string }> = []; @@ -129,6 +155,7 @@ export default class Session { ) { this.transportId = sessionId || ""; + SessionMessageListeners.set(this, new Map()); this.addMessageListener(NS_MEDIA, this.#mediaMessageListener); } @@ -147,9 +174,8 @@ export default class Session { // Handle Media creation if (!media) { - media = new Media( - this.sessionId, - mediaStatus.mediaSessionId, + media = createMedia( + [this.sessionId, mediaStatus.mediaSessionId], this.#sendMediaMessage ); @@ -158,8 +184,11 @@ export default class Session { this.#loadMediaSuccessCallback?.(media); } else { updateMedia(media, mediaStatus); - for (const listener of media._updateListeners) { - listener(true); + const updateListeners = MediaUpdateListeners.get(media); + 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) { - if (!this._messageListeners.has(namespace)) { - this._messageListeners.set(namespace, new Set()); + if (!this.#messageListeners.has(namespace)) { + this.#messageListeners.set(namespace, new Set()); } - this._messageListeners.get(namespace)?.add(listener); + this.#messageListeners.get(namespace)?.add(listener); } removeMessageListener(namespace: string, listener: MessageListener) { - this._messageListeners.get(namespace)?.delete(listener); + this.#messageListeners.get(namespace)?.delete(listener); } addUpdateListener(listener: UpdateListener) { - this._updateListeners.add(listener); + this.#updateListeners.add(listener); } removeUpdateListener(listener: UpdateListener) { - this._updateListeners.delete(listener); + this.#updateListeners.delete(listener); } leave( @@ -272,7 +301,7 @@ export default class Session { } }); - this._sendMessageCallbacks.set(messageId, [ + this.#sendMessageCallbacks.set(messageId, [ successCallback, errorCallback ]); diff --git a/ext/src/cast/sdk/classes.ts b/ext/src/cast/sdk/classes.ts index 15ad45f..866b1de 100644 --- a/ext/src/cast/sdk/classes.ts +++ b/ext/src/cast/sdk/classes.ts @@ -6,6 +6,7 @@ import { AutoJoinPolicy, Capability, DefaultActionPolicy, + ErrorCode, ReceiverAvailability, ReceiverType, VolumeControlType @@ -35,7 +36,7 @@ export class DialRequest { export class Error { constructor( - public code: string, + public code: ErrorCode, public description: Nullable = null, public details: unknown = null ) {} diff --git a/ext/src/cast/sdk/index.ts b/ext/src/cast/sdk/index.ts index 359a674..de79b0d 100644 --- a/ext/src/cast/sdk/index.ts +++ b/ext/src/cast/sdk/index.ts @@ -33,9 +33,14 @@ import { Volume } 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 = ( receiver: Receiver, @@ -49,6 +54,7 @@ export default class { #apiConfig?: ApiConfig; #sessionRequest?: SessionRequest; + /** Current receiver availability. */ #receiverAvailability = ReceiverAvailability.UNAVAILABLE; #initializeSuccessCallback?: () => void; @@ -87,7 +93,7 @@ export default class { Volume = Volume; Session = Session; - media = media; + media = { ...media }; VERSION = [1, 2]; isAvailable = false; @@ -101,10 +107,19 @@ export default class { switch (message.subject) { case "cast:initialized": this.isAvailable = true; - this.#initializeSuccessCallback?.(); 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; /** @@ -120,13 +135,13 @@ export default class { status.appImages ); - const session = new Session( + const session = createSession([ status.sessionId, status.appId, status.displayName, status.appImages, status.receiver - ); + ]); session.namespaces = status.namespaces; session.senderApps = status.senderApps; @@ -164,8 +179,11 @@ export default class { session.namespaces = status.namespaces; session.receiver.volume = status.volume; - for (const listener of session._updateListeners) { - listener(session.status !== SessionStatus.STOPPED); + const updateListeners = SessionUpdateListeners.get(session); + if (updateListeners) { + for (const listener of updateListeners) { + listener(session.status !== SessionStatus.STOPPED); + } } break; @@ -176,8 +194,12 @@ export default class { const session = this.#sessions.get(sessionId); if (session) { 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 session = this.#sessions.get(sessionId); if (session) { - const listeners = session._messageListeners.get(namespace); + const listeners = + SessionMessageListeners.get(session)?.get(namespace); if (listeners) { for (const listener of listeners) { listener(namespace, messageData); @@ -207,12 +230,16 @@ export default class { break; } - const callbacks = session._sendMessageCallbacks.get(messageId); - if (callbacks) { - const [successCallback, errorCallback] = callbacks; + const sendMessageCallback = + SessionSendMessageCallbacks.get(session)?.get(messageId); + if (sendMessageCallback) { + const [successCallback, errorCallback] = + sendMessageCallback; if (error) { - errorCallback?.(new CastError(error)); + errorCallback?.( + new CastError(ErrorCode.CHANNEL_ERROR, error) + ); return; } @@ -236,26 +263,11 @@ export default class { break; } - // Popup closed before session established - case "cast:sessionRequestCancelled": { - if (this.#sessionRequest) { - this.#sessionRequest = undefined; - - this.#requestSessionErrorCallback?.( - new CastError(ErrorCode.CANCEL) - ); - } - - break; - } - - case "cast:receiverAction": { + case "cast:receiverAction": for (const actionListener of this.#receiverActionListeners) { actionListener(message.data.receiver, message.data.action); } - break; - } } } diff --git a/ext/src/cast/sdk/media/Media.ts b/ext/src/cast/sdk/media/Media.ts index b1b8658..db568e2 100644 --- a/ext/src/cast/sdk/media/Media.ts +++ b/ext/src/cast/sdk/media/Media.ts @@ -2,9 +2,15 @@ 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 { ErrorCode } from "../enums"; + import { BreakStatus, EditTracksInfoRequest, @@ -26,23 +32,52 @@ import { VideoInformation, VolumeRequest } from "./classes"; - 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"; +type MediaMessageCallback = ( + message: DistributiveOmit +) => Promise; + +const MediaMessageCallbacks = new WeakMap(); +export const MediaUpdateListeners = new WeakMap>(); +export const MediaLastUpdateTimes = new WeakMap(); + +/** Creates a Media object and initializes private data. */ +export function createMedia( + mediaArgs: ConstructorParameters, + 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; export default class Media { #id = uuid(); - // Timestamp of last status update - _lastUpdateTime = 0; - _updateListeners = new Set(); + get #updateListeners() { + const updateListeners = MediaUpdateListeners.get(this); + 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 = null; breakStatus?: BreakStatus; @@ -65,19 +100,13 @@ export default class Media { preloadedItemId: Nullable = null; queueData?: QueueData; - constructor( - public sessionId: string, - public mediaSessionId: number, - public _sendMediaMessage: ( - message: DistributiveOmit - ) => Promise - ) {} + constructor(public sessionId: string, public mediaSessionId: number) {} addUpdateListener(listener: UpdateListener) { - this._updateListeners.add(listener); + this.#updateListeners?.add(listener); } removeUpdateListener(listener: UpdateListener) { - this._updateListeners.delete(listener); + this.#updateListeners?.delete(listener); } editTracksInfo( @@ -85,7 +114,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...editTracksInfoRequest, type: "EDIT_TRACKS_INFO", mediaSessionId: this.mediaSessionId @@ -108,7 +137,7 @@ export default class Media { return getEstimatedTime({ currentTime: this.breakStatus.currentBreakClipTime, - lastUpdateTime: this._lastUpdateTime, + lastUpdateTime: this.#lastUpdateTime, duration: currentBreakClip.duration }); } @@ -127,7 +156,7 @@ export default class Media { return getEstimatedTime({ currentTime: this.breakStatus.currentBreakTime, - lastUpdateTime: this._lastUpdateTime, + lastUpdateTime: this.#lastUpdateTime, duration: currentBreak.duration }); } @@ -144,7 +173,7 @@ export default class Media { if (this.playerState === PlayerState.PLAYING) { return getEstimatedTime({ currentTime: this.currentTime, - lastUpdateTime: this._lastUpdateTime, + lastUpdateTime: this.#lastUpdateTime, duration: this.media?.duration }); } @@ -161,7 +190,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...getStatusRequest, type: "MEDIA_GET_STATUS", mediaSessionId: this.mediaSessionId @@ -175,7 +204,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...pauseRequest, type: "PAUSE", mediaSessionId: this.mediaSessionId @@ -189,7 +218,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...playRequest, type: "PLAY", mediaSessionId: this.mediaSessionId @@ -203,7 +232,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...new QueueInsertItemsRequest([item]), type: "QUEUE_INSERT", sessionId: this.sessionId, @@ -218,7 +247,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...queueInsertItemsRequest, type: "QUEUE_INSERT", sessionId: this.sessionId, @@ -237,7 +266,7 @@ export default class Media { const jumpRequest = new QueueJumpRequest(); jumpRequest.currentItemId = itemId; - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...jumpRequest, type: "QUEUE_UPDATE", sessionId: this.sessionId, @@ -283,7 +312,7 @@ export default class Media { reorderItemsRequest.insertBefore = existingItem.itemId; } - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...reorderItemsRequest, type: "QUEUE_REORDER", sessionId: this.sessionId, @@ -301,7 +330,7 @@ export default class Media { const jumpRequest = new QueueJumpRequest(); jumpRequest.jump = 1; - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...jumpRequest, type: "QUEUE_UPDATE", sessionId: this.sessionId, @@ -318,7 +347,7 @@ export default class Media { const jumpRequest = new QueueJumpRequest(); jumpRequest.jump = -1; - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...jumpRequest, type: "QUEUE_UPDATE", sessionId: this.sessionId, @@ -348,7 +377,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...queueRemoveItemsRequest, mediaSessionId: this.mediaSessionId, @@ -364,7 +393,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...queueReorderItemsRequest, mediaSessionId: this.mediaSessionId, @@ -383,7 +412,7 @@ export default class Media { const setPropertiesRequest = new QueueSetPropertiesRequest(); setPropertiesRequest.repeatMode = repeatMode; - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...setPropertiesRequest, type: "QUEUE_UPDATE", sessionId: this.sessionId, @@ -398,7 +427,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...queueUpdateItemsRequest, type: "QUEUE_UPDATE", sessionId: this.sessionId, @@ -413,7 +442,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...seekRequest, type: "SEEK", mediaSessionId: this.mediaSessionId @@ -427,7 +456,7 @@ export default class Media { successCallback?: () => void, errorCallback?: (err: CastError) => void ) { - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...volumeRequest, type: "MEDIA_SET_VOLUME", mediaSessionId: this.mediaSessionId @@ -445,7 +474,7 @@ export default class Media { stopRequest = new StopRequest(); } - this._sendMediaMessage({ + this.#mediaMessageCallback?.({ ...stopRequest, type: "STOP", mediaSessionId: this.mediaSessionId diff --git a/ext/src/cast/sdk/media/index.ts b/ext/src/cast/sdk/media/index.ts index f2a0c5a..c584c0a 100644 --- a/ext/src/cast/sdk/media/index.ts +++ b/ext/src/cast/sdk/media/index.ts @@ -1,140 +1,20 @@ "use strict"; -import Media from "./Media"; +export * from "./enums"; +export * from "./classes"; -import { - ContainerType, - HdrType, - HlsSegmentFormat, - HlsVideoSegmentFormat, - IdleReason, - MediaCommand, - MetadataType, - PlayerState, - QueueType, - RepeatMode, - ResumeState, - StreamType, - TextTrackEdgeType, - TextTrackFontGenericFamily, - TextTrackFontStyle, - TextTrackType, - TextTrackWindowType, - TrackType, - UserAction -} from "./enums"; +export { default as Media } from "./Media"; -import { - 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 const DEFAULT_MEDIA_RECEIVER_APP_ID = "CC1AD845"; -export default { - DEFAULT_MEDIA_RECEIVER_APP_ID: "CC1AD845", - timeout: { - editTracksInfo: 0, - getStatus: 0, - load: 0, - pause: 0, - play: 0, - queue: 0, - seek: 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 +export const timeout = { + editTracksInfo: 0, + getStatus: 0, + load: 0, + pause: 0, + play: 0, + queue: 0, + seek: 0, + setVolume: 0, + stop: 0 }; diff --git a/ext/src/cast/senders/media.ts b/ext/src/cast/senders/media.ts index a11b48a..adba2cd 100644 --- a/ext/src/cast/senders/media.ts +++ b/ext/src/cast/senders/media.ts @@ -14,7 +14,7 @@ const logger = new Logger("fx_cast [media sender]"); interface MediaSenderOpts { mediaUrl: string; contextTabId?: number; - targetElementId?: number; + mediaElement?: HTMLMediaElement; } export default class MediaSender { @@ -23,6 +23,7 @@ export default class MediaSender { private mediaUrl: string; private contextTabId?: number; + /** Target media element if loaded as a content script. */ private mediaElement?: HTMLMediaElement; private isLocalMedia = false; @@ -34,12 +35,7 @@ export default class MediaSender { constructor(opts: MediaSenderOpts) { this.mediaUrl = opts.mediaUrl; this.contextTabId = opts.contextTabId; - - if (opts.targetElementId) { - this.mediaElement = browser.menus.getTargetElement( - opts.targetElementId - ) as HTMLMediaElement; - } + this.mediaElement = opts.mediaElement; this.init(); } @@ -79,7 +75,7 @@ export default class MediaSender { ), undefined, 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:") { const window_ = window as any; + + let mediaElement: Optional; + if (window_.targetElementId) { + mediaElement = browser.menus.getTargetElement( + window_.targetElementId + ) as HTMLMediaElement; + } + new MediaSender({ mediaUrl: window_.mediaUrl, - targetElementId: window_.targetElementId + mediaElement }); } diff --git a/ext/src/cast/utils.ts b/ext/src/cast/utils.ts index fc72e07..5a5987a 100644 --- a/ext/src/cast/utils.ts +++ b/ext/src/cast/utils.ts @@ -1,6 +1,8 @@ import { ReceiverDevice, ReceiverDeviceCapabilities } from "../types"; import { Receiver } from "./sdk/classes"; 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 @@ -29,8 +31,8 @@ export function hasRequiredCapabilities( }); } +/** Convert capabilities bitflags to string array. */ export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) { - // Convert capabilities bitflag to string array const capabilities: Capability[] = []; if (flags & ReceiverDeviceCapabilities.VIDEO_OUT) capabilities.push(Capability.VIDEO_OUT); @@ -47,6 +49,26 @@ export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) { 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 { currentTime: number; lastUpdateTime: number;