Move cast API code to cast/sdk/ and create wrapper class

This commit is contained in:
hensm
2022-04-25 15:59:58 +01:00
parent 43f5d325f8
commit 4bccdecaa3
35 changed files with 707 additions and 1188 deletions

View File

@@ -1,493 +0,0 @@
"use strict";
import logger from "../../lib/logger";
import {
ReceiverDevice,
ReceiverDeviceCapabilities as ReceiverDeviceCapabilities
} from "../../types";
import { ErrorCallback, SuccessCallback } from "../types";
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
import {
AutoJoinPolicy,
Capability,
DefaultActionPolicy,
DialAppState,
ErrorCode,
ReceiverAction,
ReceiverAvailability,
ReceiverType,
SenderPlatform,
SessionStatus,
VolumeControlType
} from "./enums";
import {
ApiConfig,
CredentialsData,
DialRequest,
Error as Error_,
Image,
Receiver,
ReceiverDisplayStatus,
SenderApplication,
SessionRequest,
Timeout,
Volume
} from "./dataClasses";
import Session from "./Session";
type ReceiverActionListener = (
receiver: Receiver,
receiverAction: string
) => void;
type RequestSessionSuccessCallback = (session: Session) => void;
let apiConfig: Nullable<ApiConfig>;
let sessionRequest: Nullable<SessionRequest>;
let requestSessionSuccessCallback: Nullable<RequestSessionSuccessCallback>;
let requestSessionErrorCallback: Nullable<ErrorCallback>;
const receiverActionListeners = new Set<ReceiverActionListener>();
const receiverDevices = new Map<string, ReceiverDevice>();
const sessions = new Map<string, Session>();
export {
AutoJoinPolicy,
Capability,
DefaultActionPolicy,
DialAppState,
ErrorCode,
ReceiverAction,
ReceiverAvailability,
ReceiverType,
SenderPlatform,
SessionStatus,
VolumeControlType
};
export {
ApiConfig,
CredentialsData,
DialRequest,
Error_ as Error,
Image,
Receiver,
ReceiverDisplayStatus,
SenderApplication,
SessionRequest,
Timeout,
Volume,
Session
};
export const VERSION = [1, 2];
export let isAvailable = false;
export const timeout = new Timeout();
// chrome.cast.media namespace
export * as media from "./media";
/**
* Create `chrome.cast.Receiver` object from receiver device info.
*/
function createReceiver(device: ReceiverDevice) {
// Convert capabilities bitflag to string array
const capabilities: Capability[] = [];
if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_OUT) {
capabilities.push(Capability.VIDEO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_IN) {
capabilities.push(Capability.VIDEO_IN);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_OUT) {
capabilities.push(Capability.AUDIO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_IN) {
capabilities.push(Capability.AUDIO_IN);
} else if (
device.capabilities & ReceiverDeviceCapabilities.MULTIZONE_GROUP
) {
capabilities.push(Capability.MULTIZONE_GROUP);
}
const receiver = new Receiver(device.id, device.friendlyName, capabilities);
// Currently only supports CAST receivers
receiver.receiverType = ReceiverType.CAST;
return receiver;
}
function sendSessionRequest(
sessionRequest: SessionRequest,
receiverDevice: ReceiverDevice
) {
for (const listener of receiverActionListeners) {
listener(createReceiver(receiverDevice), ReceiverAction.CAST);
}
sendMessageResponse({
subject: "bridge:createCastSession",
data: {
appId: sessionRequest.appId,
receiverDevice: receiverDevice
}
});
}
export function initialize(
newApiConfig: ApiConfig,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
logger.info("cast.initialize");
// Already initialized
if (apiConfig) {
errorCallback?.(new Error_(ErrorCode.INVALID_PARAMETER));
return;
}
apiConfig = newApiConfig;
sendMessageResponse({
subject: "main:initializeCast",
data: { appId: apiConfig.sessionRequest.appId }
});
successCallback?.();
apiConfig.receiverListener(
receiverDevices.size
? ReceiverAvailability.AVAILABLE
: ReceiverAvailability.UNAVAILABLE
);
}
export function requestSession(
successCallback: RequestSessionSuccessCallback,
errorCallback: ErrorCallback,
newSessionRequest?: SessionRequest,
receiverDevice?: ReceiverDevice
) {
logger.info("cast.requestSession");
// Not yet initialized
if (!apiConfig) {
errorCallback?.(new Error_(ErrorCode.API_NOT_INITIALIZED));
return;
}
// Already requesting session
if (sessionRequest) {
errorCallback?.(
new Error_(
ErrorCode.INVALID_PARAMETER,
"Session request already in progress."
)
);
return;
}
// No receivers available
if (!receiverDevices.size) {
errorCallback?.(new Error_(ErrorCode.RECEIVER_UNAVAILABLE));
return;
}
/**
* Store session request for use in return message from
* receiver selection.
*/
sessionRequest = newSessionRequest ?? apiConfig.sessionRequest;
requestSessionSuccessCallback = successCallback;
requestSessionErrorCallback = errorCallback;
/**
* If a receiver was provided, skip the receiver selector
* process.
*/
if (receiverDevice) {
if (receiverDevice?.id && receiverDevices.has(receiverDevice.id)) {
sendSessionRequest(sessionRequest, receiverDevice);
}
} else {
// Open receiver selector UI
sendMessageResponse({
subject: "main:selectReceiver"
});
}
}
export function requestSessionById(_sessionId: string): void {
logger.info("STUB :: cast.requestSessionById");
}
export function setCustomReceivers(
_receivers: Receiver[],
_successCallback?: SuccessCallback,
_errorCallback?: ErrorCallback
): void {
logger.info("STUB :: cast.setCustomReceivers");
}
export function setPageContext(_win: Window): void {
logger.info("STUB :: cast.setPageContext");
}
export function setReceiverDisplayStatus(_sessionId: string): void {
logger.info("STUB :: cast.setReceiverDisplayStatus");
}
export function unescape(escaped: string): string {
return window.decodeURI(escaped);
}
export function addReceiverActionListener(listener: ReceiverActionListener) {
receiverActionListeners.add(listener);
}
export function removeReceiverActionListener(listener: ReceiverActionListener) {
receiverActionListeners.delete(listener);
}
export function logMessage(message: string) {
logger.info("cast.logMessage", message);
}
export function precache(_data: string) {
logger.info("STUB :: cast.precache");
}
onMessage(message => {
switch (message.subject) {
case "cast:initialized": {
isAvailable = true;
break;
}
/**
* Once the bridge detects a session creation, session info
* and data needed to create cast API objects is sent.
*/
case "cast:sessionCreated": {
// Notify background to close UI
sendMessageResponse({
subject: "main:closeReceiverSelector"
});
const status = message.data;
const receiverDevice = receiverDevices.get(status.receiverId);
if (!receiverDevice) {
logger.error(
`Could not find receiver device "${status.receiverFriendlyName}" (${status.receiverId})`
);
break;
}
const receiver = createReceiver(receiverDevice);
receiver.volume = status.volume;
receiver.displayStatus = new ReceiverDisplayStatus(
status.statusText,
status.appImages
);
const session = new Session(
status.sessionId, // sessionId
status.appId, // appId
status.displayName, // displayName
status.appImages, // appImages
receiver // receiver
);
session.senderApps = status.senderApps;
session.transportId = status.transportId;
sessions.set(session.sessionId, session);
}
// eslint-disable-next-line no-fallthrough
case "cast:sessionUpdated": {
const status = message.data;
const session = sessions.get(status.sessionId);
if (!session) {
logger.error(`Session not found (${status.sessionId})`);
return;
}
session.statusText = status.statusText;
session.namespaces = status.namespaces;
session.receiver.volume = status.volume;
/**
* If session created via requestSession, the success
* callback will be set, otherwise the session was created
* by the extension and the session listener should be
* called instead.
*/
if (requestSessionSuccessCallback) {
requestSessionSuccessCallback(session);
requestSessionSuccessCallback = null;
requestSessionErrorCallback = null;
} else {
apiConfig?.sessionListener(session);
}
break;
}
case "cast:sessionStopped": {
const { sessionId } = message.data;
const session = sessions.get(sessionId);
if (session) {
session.status = SessionStatus.STOPPED;
const updateListeners = session?._updateListeners;
if (updateListeners) {
for (const listener of updateListeners) {
listener(false);
}
}
}
break;
}
case "cast:receivedSessionMessage": {
const { sessionId, namespace, messageData } = message.data;
const session = sessions.get(sessionId);
if (session) {
const _messageListeners = session._messageListeners;
const listeners = _messageListeners.get(namespace);
if (listeners) {
for (const listener of listeners) {
listener(namespace, messageData);
}
}
}
break;
}
case "cast:impl_sendMessage": {
const { sessionId, messageId, error } = message.data;
const session = sessions.get(sessionId);
if (!session) {
break;
}
const callbacks = session._sendMessageCallbacks.get(messageId);
if (callbacks) {
const [successCallback, errorCallback] = callbacks;
if (error) {
errorCallback?.(new Error_(error));
return;
}
successCallback?.();
}
break;
}
case "cast:receiverDeviceUp": {
const { receiverDevice } = message.data;
if (receiverDevices.has(receiverDevice.id)) {
break;
}
receiverDevices.set(receiverDevice.id, receiverDevice);
if (apiConfig) {
// Notify listeners of new cast destination
apiConfig.receiverListener(ReceiverAvailability.AVAILABLE);
}
break;
}
case "cast:receiverDeviceDown": {
const { receiverDeviceId } = message.data;
receiverDevices.delete(receiverDeviceId);
if (receiverDevices.size === 0) {
if (apiConfig) {
apiConfig.receiverListener(
ReceiverAvailability.UNAVAILABLE
);
}
}
break;
}
case "cast:selectReceiver/selected": {
logger.info("Selected receiver");
if (sessionRequest) {
sendSessionRequest(sessionRequest, message.data.receiverDevice);
sessionRequest = null;
}
break;
}
case "cast:selectReceiver/stopped": {
const { receiverDevice } = message.data;
logger.info("Stopped receiver");
if (sessionRequest) {
sessionRequest = null;
for (const listener of receiverActionListeners) {
listener(
// TODO: Use existing receiver object?
createReceiver(receiverDevice),
ReceiverAction.STOP
);
}
}
break;
}
// Popup closed before session established
case "cast:selectReceiver/cancelled": {
if (sessionRequest) {
sessionRequest = null;
requestSessionErrorCallback?.(new Error_(ErrorCode.CANCEL));
}
break;
}
// Session request initiated via receiver selector
case "cast:launchApp": {
if (sessionRequest) {
logger.error("Session request already in progress.");
break;
}
if (!apiConfig?.sessionRequest) {
logger.error("Session request not found!");
break;
}
sendSessionRequest(
apiConfig.sessionRequest,
message.data.receiverDevice
);
break;
}
}
});

View File

@@ -1,22 +0,0 @@
"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";

View File

@@ -1,6 +1,5 @@
"use strict"; "use strict";
import * as cast from "./api";
import { Message } from "../messaging"; import { Message } from "../messaging";
import { BridgeInfo } from "../lib/bridge"; import { BridgeInfo } from "../lib/bridge";
@@ -12,6 +11,8 @@ import {
sendMessage sendMessage
} from "./eventMessageChannel"; } from "./eventMessageChannel";
import CastSDK from "./sdk";
let initializedBridgeInfo: BridgeInfo; let initializedBridgeInfo: BridgeInfo;
let initializedBackgroundPort: MessagePort; let initializedBackgroundPort: MessagePort;
@@ -103,4 +104,4 @@ export function ensureInit(): Promise<TypedMessagePort<Message>> {
}); });
} }
export default cast; export default new CastSDK();

View File

@@ -1,86 +0,0 @@
"use strict";
import logger from "../../lib/logger";
/**
* Custom element for a cast button used by sites that injects
* a cast icon and manages visibility state and event handling.
*/
export default class GoogleCastLauncher extends HTMLElement {
constructor() {
super();
this.style.display = "none";
const style = document.createElement("style");
style.textContent = `
.cast_caf_state_c {
fill: var(--connected-color, #4285f4);
}
.cast_caf_state_d {
fill: var(--disconnected-color, #7d7d7d);
}
.cast_caf_state_h {
opacity: 0;
}
`;
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
const icon = document.createElementNS(SVG_NAMESPACE, "svg");
const iconArch0 = document.createElementNS(SVG_NAMESPACE, "path");
const iconArch1 = document.createElementNS(SVG_NAMESPACE, "path");
const iconArch2 = document.createElementNS(SVG_NAMESPACE, "path");
const iconBox = document.createElementNS(SVG_NAMESPACE, "path");
const iconBoxFill = document.createElementNS(SVG_NAMESPACE, "path");
// Set SVG attributes
icon.setAttribute("x", "0");
icon.setAttribute("y", "0");
icon.setAttribute("width", "100%");
icon.setAttribute("height", "100%");
icon.setAttribute("viewBox", "0 0 24 24");
iconArch0.classList.add("cast_caf_state_d");
iconArch0.setAttribute("id", "cast_caf_icon_arch0");
iconArch0.setAttribute("d", "M1 18v3h3c0-1.7-1.34-3-3-3z");
iconArch1.classList.add("cast_caf_state_d");
iconArch1.setAttribute("id", "cast_caf_icon_arch1");
iconArch1.setAttribute(
"d",
"M1 14v2c2.76 0 5 2.2 5 5h2c0-3.87-3.13-7-7-7z"
);
iconArch2.classList.add("cast_caf_state_d");
iconArch2.setAttribute("id", "cast_caf_icon_arch2");
iconArch2.setAttribute(
"d",
"M1 10v2c4.97 0 9 4 9 9h2c0-6.08-4.93-11-11-11z"
);
iconBox.classList.add("cast_caf_state_d");
iconBox.setAttribute("id", "cast_caf_icon_box");
iconBox.setAttribute(
"d",
"M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
);
iconBoxFill.classList.add("cast_caf_state_h");
iconBoxFill.setAttribute("id", "cast_caf_icon_boxfill");
iconBoxFill.setAttribute(
"d",
"M5 7v1.63C8 8.6 13.37 14 13.37 17H19V7z"
);
// Add icon paths to SVG
icon.append(iconArch0, iconArch1, iconArch2, iconBox, iconBoxFill);
const shadow = this.attachShadow({ mode: "open" });
shadow.append(icon, style);
this.addEventListener("click", () => {
logger.info("<google-cast-launcher> onClick");
});
}
}

View File

@@ -1,11 +0,0 @@
"use strict";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ActiveInputStateEventData extends EventData {
constructor(public activeInputState: number) {
super(SessionEventType.ACTIVE_INPUT_STATE_CHANGED);
}
}

View File

@@ -1,21 +0,0 @@
"use strict";
import * as cast from "../../api";
export default class ApplicationMetadata {
public applicationId: string;
public images: cast.Image[];
public name: string;
public namespaces: string[];
constructor(sessionObj: cast.Session) {
this.applicationId = sessionObj.appId;
this.images = sessionObj.appImages;
this.name = sessionObj.displayName;
// Convert [{ name: <ns> }, ...] to [ <ns>, ... ]
this.namespaces = sessionObj.namespaces.map(
namespaceObj => namespaceObj.name
);
}
}

View File

@@ -1,12 +0,0 @@
"use strict";
import ApplicationMetadata from "./ApplicationMetadata";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationMetadataEventData extends EventData {
constructor(public metadata: ApplicationMetadata) {
super(SessionEventType.APPLICATION_METADATA_CHANGED);
}
}

View File

@@ -1,11 +0,0 @@
"use strict";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class ApplicationStatusEventData extends EventData {
constructor(public status: string) {
super(SessionEventType.APPLICATION_STATUS_CHANGED);
}
}

View File

@@ -1,38 +0,0 @@
"use strict";
import logger from "../../../lib/logger";
import CastOptions from "./CastOptions";
import CastSession from "./CastSession";
export default class CastContext extends EventTarget {
public endCurrentSession(_stopCasting: boolean): void {
logger.info("STUB :: CastContext#endCurrentSession");
}
// @ts-ignore
public getCastState(): string {
logger.info("STUB :: CastContext#getCastState");
}
// @ts-ignore
public getCurrentSession(): CastSession {
logger.info("STUB :: CastContext#getCurrentSession");
}
// @ts-ignore
public getSessionState(): string {
logger.info("STUB :: CastContext#getSessionState");
}
// @ts-ignore
public requestSession(): Promise<string> {
logger.info("STUB :: CastContext#requestSession");
}
public setOptions(_options: CastOptions): void {
logger.info("STUB :: CastContext#setOptions");
}
}
export const instance = new CastContext();

View File

@@ -1,25 +0,0 @@
"use strict";
import * as cast from "../../api";
export default class CastOptions {
public autoJoinPolicy: string = cast.AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED;
public language: string | null = null;
public receiverApplicationId: string | null = null;
public resumeSavedSession = true;
constructor(options: CastOptions = {} as CastOptions) {
if (options.autoJoinPolicy) {
this.autoJoinPolicy = options.autoJoinPolicy;
}
if (options.language) {
this.language = options.language;
}
if (options.receiverApplicationId) {
this.receiverApplicationId = options.receiverApplicationId;
}
if (options.resumeSavedSession) {
this.resumeSavedSession = options.resumeSavedSession;
}
}
}

View File

@@ -1,107 +0,0 @@
"use strict";
import logger from "../../../lib/logger";
import * as cast from "../../api";
import ApplicationMetadata from "./ApplicationMetadata";
type MessageListener = (namespace: string, message: string) => void;
export default class CastSession extends EventTarget {
constructor(_sessionObj: cast.Session, _state: string) {
super();
logger.info("STUB :: CastSession#constructor");
}
public addMessageListener(
_namespace: string,
_listener: MessageListener
): void {
logger.info("STUB :: CastSession#addMessageListener");
}
public endSession(_stopCasting: boolean): void {
logger.info("STUB :: CastSession#endSession");
}
// @ts-ignore
public getActiveInputState(): number {
logger.info("STUB :: CastSession#getActiveInputState");
}
// @ts-ignore
public getApplicationMetadata(): ApplicationMetadata {
logger.info("STUB :: CastSession#getApplicationMetadata");
}
// @ts-ignore
public getApplicationStatus(): string {
logger.info("STUB :: CastSession#getApplicationStatus");
}
// @ts-ignore
public getCastDevice(): cast.Receiver {
logger.info("STUB :: CastSession#getCastDevice");
}
// @ts-ignore
public getMediaSession(): cast.media.Media {
logger.info("STUB :: CastSession#getMediaSession");
}
// @ts-ignore
public getSessionId(): string {
logger.info("STUB :: CastSession#getSessionId");
}
// @ts-ignore
public getSessionObj(): cast.Session {
logger.info("STUB :: CastSession#getSessionObj");
}
// @ts-ignore
public getSessionState(): string {
logger.info("STUB :: CastSession#getSessionState");
}
// @ts-ignore
public getVolume(): number {
logger.info("STUB :: CastSession#getVolume");
}
// @ts-ignore
public isMute(): boolean {
logger.info("STUB :: CastSession#isMute");
}
// @ts-ignore
public loadMedia(_loadRequest: cast.media.LoadRequest): Promise<string> {
logger.info("STUB :: CastSession#loadMedia");
}
public removeMessageListener(
_namespace: string,
_listener: MessageListener
): void {
logger.info("STUB :: CastSession#removeMessageListener");
}
public sendMessage(
_namespace: string,
// @ts-ignore
_data: any
): Promise<void> {
logger.info("STUB :: CastSession#sendMessage");
}
// @ts-ignore
public setMute(_isMute: boolean): Promise<void> {
logger.info("STUB :: CastSession#setMute");
}
// @ts-ignore
public setVolume(_volume: number): Promise<void> {
logger.info("STUB :: CastSession#setVolume");
}
}

View File

@@ -1,11 +0,0 @@
"use strict";
import EventData from "./EventData";
import { CastContextEventType } from "../enums";
export default class CastStateEventData extends EventData {
constructor(public castState: string) {
super(CastContextEventType.CAST_STATE_CHANGED);
}
}

View File

@@ -1,5 +0,0 @@
"use strict";
export default class EventData {
constructor(public type: string) {}
}

View File

@@ -1,13 +0,0 @@
"use strict";
import * as cast from "../../api";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class MediaSessionEventData extends EventData {
constructor(public mediaSession: cast.media.Media) {
super(SessionEventType.MEDIA_SESSION);
}
}

View File

@@ -1,33 +0,0 @@
"use strict";
import * as cast from "../../api";
import RemotePlayerController from "./RemotePlayerController";
interface SavedPlayerState {
mediaInfo: string;
currentTime: number;
isPaused: boolean;
}
export default class RemotePlayer {
public canControlVolume = false;
public canPause = false;
public canSeek = false;
public controller: RemotePlayerController | null = null;
public currentTime = 0;
public displayName = "";
public displayStatus = "";
public duration = 0;
public imageUrl: string | null = null;
public isConnected = false;
public isMediaLoaded = false;
public isMuted = false;
public isPaused = false;
public mediaInfo: cast.media.MediaInfo | null = null;
public playerState: string | null = null;
public savedPlayerState: SavedPlayerState | null = null;
public statusText = "";
public title = "";
public volumeLevel = 1;
}

View File

@@ -1,5 +0,0 @@
"use strict";
export default class RemotePlayerChangedEvent {
constructor(public type: string, public field: string, public value: any) {}
}

View File

@@ -1,50 +0,0 @@
"use strict";
import logger from "../../../lib/logger";
import RemotePlayer from "./RemotePlayer";
export default class RemotePlayerController extends EventTarget {
constructor(_player: RemotePlayer) {
super();
logger.info("STUB :: RemotePlayerController#constructor");
}
public getFormattedTime(timeInSec: number): string {
const hours = Math.floor(timeInSec / 3600) % 24;
const minutes = Math.floor(timeInSec / 60) % 60;
const seconds = timeInSec % 60;
return [hours, minutes, seconds]
.map(c => c.toString().padStart(2, "0"))
.join(":");
}
public getSeekPosition(currentTime: number, duration: number) {
return (currentTime / duration) * 100;
}
public getSeekTime(currentPosition: number, duration: number) {
return (duration / 100) * currentPosition;
}
public muteOrUnmute(): void {
logger.info("STUB :: RemotePlayerController#muteOrUnmute");
}
public playOrPause(): void {
logger.info("STUB :: RemotePlayerController#playOrPause");
}
public seek(): void {
logger.info("STUB :: RemotePlayerController#seek");
}
public setVolumeLevel(): void {
logger.info("STUB :: RemotePlayerController#setVolumeLevel");
}
public stop(): void {
logger.info("STUB :: RemotePlayerController#stop");
}
}

View File

@@ -1,16 +0,0 @@
"use strict";
import CastSession from "./CastSession";
import EventData from "./EventData";
import { SessionEventType } from "../enums";
export default class SessionStateEventData extends EventData {
constructor(
public session: CastSession,
public sessionState: string,
public errorCode: string | null = null
) {
super(SessionEventType.APPLICATION_STATUS_CHANGED);
}
}

View File

@@ -1,9 +0,0 @@
"use strict";
import { SessionEventType } from "../enums";
export default class VolumeEventData {
public type = SessionEventType.VOLUME_CHANGED;
constructor(public volume: number, public isMute: boolean) {}
}

View File

@@ -1,64 +0,0 @@
"use strict";
export enum ActiveInputState {
ACTIVE_INPUT_STATE_UNKNOWN = -1,
ACTIVE_INPUT_STATE_NO = 0,
ACTIVE_INPUT_YES = 1
}
export enum CastContextEventType {
CAST_STATE_CHANGED = "caststatechanged",
SESSION_STATE_CHANGED = "sessionstatechanged"
}
export enum CastState {
NO_DEVICES_AVAILABLE = "NO_DEVICES_AVAILABLE",
NOT_CONNECTED = "NOT_CONNECTED",
CONNECTING = "CONNECTING",
CONNECTED = "CONNECTED"
}
export enum LoggerLevel {
DEBUG = 0,
INFO = 800,
WARNING = 900,
ERROR = 1000,
NONE = 1500
}
export enum RemotePlayerEventType {
ANY_CHANGE = "anyChanged",
IS_CONNECTED_CHANGE = "isConnectedChanged",
IS_MEDIA_LOADED_CHANGED = "isMediaLoadedChanged",
DURATION_CHANGED = "durationChanged",
CURRENT_TIME_CHANGED = "currentTimeChanged",
IS_PAUSED_CHANGED = "isPausedChanged",
VOLUME_LEVEL_CHANGED = "volumeLevelChanged",
CAN_CONTROL_VOLUME_CHANGED = "canControlVolumeChanged",
IS_MUTED_CHANGED = "isMutedChanged",
CAN_PAUSE_CHANGED = "canPauseChanged",
CAN_SEEK_CHANGED = "canSeekChanged",
DISPLAY_NAME_CHANGED = "displayNameChanged",
STATUS_TEXT_CHANGED = "statusTextChanged",
MEDIA_INFO_CHANGED = "mediaInfoChanged",
IMAGE_URL_CHANGED = "imageUrlChanged",
PLAYER_STATE_CHANGED = "playerStateChanged"
}
export enum SessionEventType {
APPLICATION_STATUS_CHANGED = "applicationstatuschanged",
APPLICATION_METADATA_CHANGED = "applicationmetadatachanged",
ACTIVE_INPUT_STATE_CHANGED = "activeinputstatechanged",
VOLUME_CHANGED = "volumechanged",
MEDIA_SESSION = "mediasession"
}
export enum SessionState {
NO_SESSION = "NO_SESSION",
SESSION_STARTING = "SESSION_STARTING",
SESSION_STARTED = "SESSION_STARTED",
SESSION_START_FAILED = "SESSION_START_FAILED",
SESSION_ENDING = "SESSION_ENDING",
SESSION_ENDED = "SESSION_ENDED",
SESSION_RESUMED = "SESSION_RESUMED"
}

View File

@@ -1,91 +0,0 @@
"use strict";
import logger from "../../lib/logger";
import ActiveInputStateEventData from "./classes/ActiveInputStateEventData";
import ApplicationMetadata from "./classes/ApplicationMetadata";
import ApplicationMetadataEventData from "./classes/ApplicationMetadataEventData";
import ApplicationStatusEventData from "./classes/ApplicationStatusEventData";
import CastContext, { instance } from "./classes/CastContext";
import CastOptions from "./classes/CastOptions";
import CastSession from "./classes/CastSession";
import CastStateEventData from "./classes/CastStateEventData";
import EventData from "./classes/EventData";
import MediaSessionEventData from "./classes/MediaSessionEventData";
import RemotePlayer from "./classes/RemotePlayer";
import RemotePlayerChangedEvent from "./classes/RemotePlayerChangedEvent";
import RemotePlayerController from "./classes/RemotePlayerController";
import SessionStateEventData from "./classes/SessionStateEventData";
import VolumeEventData from "./classes/VolumeEventData";
import {
ActiveInputState,
CastContextEventType,
CastState,
LoggerLevel,
RemotePlayerEventType,
SessionEventType,
SessionState
} from "./enums";
import GoogleCastLauncher from "./GoogleCastLauncher";
export default {
// Enums
ActiveInputState,
CastContextEventType,
CastState,
LoggerLevel,
RemotePlayerEventType,
SessionEventType,
SessionState,
// Classes
ActiveInputStateEventData,
ApplicationMetadata,
ApplicationMetadataEventData,
ApplicationStatusEventData,
CastOptions,
CastSession,
CastStateEventData,
EventData,
MediaSessionEventData,
RemotePlayer,
RemotePlayerChangedEvent,
RemotePlayerController,
SessionStateEventData,
VolumeEventData,
/**
* CastContext class with an extra getInstance method used to
* instantiate and fetch a singleton instance.
*/
CastContext: {
...CastContext,
getInstance() {
return instance;
}
},
VERSION: "1.0.07",
setLoggerLevel(_level: number) {
logger.info("STUB :: cast.framework.setLoggerLevel");
}
};
/**
* The Framework API defines a <google-cast-launcher> element
* and a <button is="google-cast-button"> element extension,
* both of which produce the same result.
*
* Chrome allowed custom elements to extend <button> elements
* via Element#createShadowRoot, but the standard
* Element#attachShadow method supported in Firefox specifies a
* limited whitelist of elements that are extendable.
*
* It's not officially advertised in the cast docs, so it
* shouldn't be much of a compatibility issue to ignore it.
*/
customElements.define("google-cast-launcher", GoogleCastLauncher);

View File

@@ -1,12 +1,13 @@
"use strict"; "use strict";
import * as cast from "./api";
import logger from "../lib/logger"; import logger from "../lib/logger";
import { loadScript } from "../lib/utils"; import { loadScript } from "../lib/utils";
import { CAST_FRAMEWORK_SCRIPT_URL } from "./endpoints"; import { CAST_FRAMEWORK_SCRIPT_URL } from "./endpoints";
import { onMessage } from "./eventMessageChannel"; import { onMessage } from "./eventMessageChannel";
import CastSDK from "./sdk";
const _window = window as any; const _window = window as any;
if (!_window.chrome) { if (!_window.chrome) {
@@ -14,7 +15,7 @@ if (!_window.chrome) {
} }
// Create page-accessible API object // Create page-accessible API object
_window.chrome.cast = cast; _window.chrome.cast = new CastSDK();
let bridgeInfo: any; let bridgeInfo: any;
let frameworkScriptPromise: Promise<HTMLScriptElement>; let frameworkScriptPromise: Promise<HTMLScriptElement>;

View File

@@ -22,17 +22,12 @@ import {
SenderMessage SenderMessage
} from "./types"; } from "./types";
import { Image, Receiver, SenderApplication } from "./dataClasses";
import { SessionStatus } from "./enums"; import { SessionStatus } from "./enums";
import { import { Image, Receiver, SenderApplication } from "./classes";
Media,
LoadRequest,
QueueLoadRequest,
QueueItem,
MediaCommand
} from "./media";
const NS_MEDIA = "urn:x-cast:com.google.cast.media"; import { MediaCommand } from "./media/enums";
import { LoadRequest, QueueLoadRequest, QueueItem } from "./media/classes";
import Media, { NS_MEDIA } from "./media/Media";
/** supportedMediaCommands bitflag returned in MEDIA_STATUS messages */ /** supportedMediaCommands bitflag returned in MEDIA_STATUS messages */
enum _MediaCommand { enum _MediaCommand {
@@ -53,20 +48,18 @@ function updateMedia(media: Media, status: MediaStatus) {
media._lastUpdateTime = Date.now(); media._lastUpdateTime = Date.now();
} }
// Copy props // Copy basic props
for (const prop in status) { if (status.currentTime) media.currentTime = status.currentTime;
switch (prop) { if (status.customData) media.customData = status.customData;
case "items": if (status.idleReason) media.idleReason = status.idleReason;
case "supportedMediaCommands": if (status.media) media.media = status.media;
continue; if (status.mediaSessionId) media.mediaSessionId = status.mediaSessionId;
} if (status.playbackRate) media.playbackRate = status.playbackRate;
if (status.playerState) media.playerState = status.playerState;
if (status.repeatMode) media.repeatMode = status.repeatMode;
if (status.volume) media.volume = status.volume;
if (status.hasOwnProperty(prop)) { // Convert supportedMediaCommands bitflags to string array
(media as any)[prop] = (status as any)[prop];
}
}
// Convert supportedMediaCommands bitflag to string array
const supportedMediaCommands: string[] = []; const supportedMediaCommands: string[] = [];
if (status.supportedMediaCommands & _MediaCommand.PAUSE) { if (status.supportedMediaCommands & _MediaCommand.PAUSE) {
supportedMediaCommands.push(MediaCommand.PAUSE); supportedMediaCommands.push(MediaCommand.PAUSE);
@@ -118,10 +111,6 @@ function updateMedia(media: Media, status: MediaStatus) {
} }
export default class Session { export default class Session {
#id = uuid();
#isConnected = false;
#loadMediaSuccessCallback?: (media: Media) => void; #loadMediaSuccessCallback?: (media: Media) => void;
#loadMediaErrorCallback?: ErrorCallback; #loadMediaErrorCallback?: ErrorCallback;
#loadMediaRequest?: LoadRequest; #loadMediaRequest?: LoadRequest;
@@ -134,9 +123,25 @@ export default class Session {
[SuccessCallback?, ErrorCallback?] [SuccessCallback?, ErrorCallback?]
>(); >();
/** media: Media[] = [];
* namespaces: Array<{ name: string }> = [];
*/ senderApps: SenderApplication[] = [];
status = SessionStatus.CONNECTED;
statusText: Nullable<string> = null;
transportId: string;
constructor(
public sessionId: string,
public appId: string,
public displayName: string,
public appImages: Image[],
public receiver: Receiver
) {
this.transportId = sessionId || "";
this.addMessageListener(NS_MEDIA, this.#mediaMessageListener);
}
#mediaMessageListener = (namespace: string, messageString: string) => { #mediaMessageListener = (namespace: string, messageString: string) => {
if (namespace !== NS_MEDIA) return; if (namespace !== NS_MEDIA) return;
@@ -176,14 +181,13 @@ export default class Session {
/** /**
* Sends a media message to the app receiver. * Sends a media message to the app receiver.
* `urn:x-cast:com.google.cast.media`
*/ */
#sendMediaMessage = ( #sendMediaMessage = (
message: DistributiveOmit<SenderMediaMessage, "requestId"> message: DistributiveOmit<SenderMediaMessage, "requestId">
) => { ) => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
this.sendMessage( this.sendMessage(
"urn:x-cast:com.google.cast.media", NS_MEDIA,
{ ...message, requestId: 0 }, { ...message, requestId: 0 },
resolve, resolve,
reject reject
@@ -210,25 +214,6 @@ export default class Session {
}); });
}; };
media: Media[] = [];
namespaces: Array<{ name: string }> = [];
senderApps: SenderApplication[] = [];
status = SessionStatus.CONNECTED;
statusText: Nullable<string> = null;
transportId: string;
constructor(
public sessionId: string,
public appId: string,
public displayName: string,
public appImages: Image[],
public receiver: Receiver
) {
this.transportId = sessionId || "";
this.addMessageListener(NS_MEDIA, this.#mediaMessageListener);
}
addMediaListener(_mediaListener: MediaListener) { addMediaListener(_mediaListener: MediaListener) {
logger.info("STUB :: Session#addMediaListener"); logger.info("STUB :: Session#addMediaListener");
} }
@@ -265,7 +250,6 @@ export default class Session {
) { ) {
this.#loadMediaSuccessCallback = successCallback; this.#loadMediaSuccessCallback = successCallback;
this.#loadMediaErrorCallback = errorCallback; this.#loadMediaErrorCallback = errorCallback;
this.#loadMediaRequest = loadRequest;
loadRequest.sessionId = this.sessionId; loadRequest.sessionId = this.sessionId;
this.#sendMediaMessage(loadRequest).catch(errorCallback); this.#sendMediaMessage(loadRequest).catch(errorCallback);

View File

@@ -78,7 +78,7 @@ export class SessionRequest {
constructor( constructor(
public appId: string, public appId: string,
public capabilities = [], public capabilities: Capability[] = [],
public requestSessionTimeout = new Timeout().requestSession, public requestSessionTimeout = new Timeout().requestSession,
public androidReceiverCompatible = false, public androidReceiverCompatible = false,
public credentialsData: Nullable<CredentialsData> = null public credentialsData: Nullable<CredentialsData> = null

512
ext/src/cast/sdk/index.ts Normal file
View File

@@ -0,0 +1,512 @@
"use strict";
import logger from "../../lib/logger";
import {
ReceiverDevice,
ReceiverDeviceCapabilities as ReceiverDeviceCapabilities
} from "../../types";
import { ErrorCallback, SuccessCallback } from "../types";
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
import {
AutoJoinPolicy,
Capability,
DefaultActionPolicy,
DialAppState,
ErrorCode,
ReceiverAction,
ReceiverAvailability,
ReceiverType,
SenderPlatform,
SessionStatus,
VolumeControlType
} from "./enums";
import {
ApiConfig,
CredentialsData,
DialRequest,
Error as Error_,
Image,
Receiver,
ReceiverDisplayStatus,
SenderApplication,
SessionRequest,
Timeout,
Volume
} from "./classes";
import Session from "./Session";
import media from "./media";
import { Message } from "../../messaging";
type ReceiverActionListener = (
receiver: Receiver,
receiverAction: string
) => void;
type RequestSessionSuccessCallback = (session: Session) => void;
/**
* Create `chrome.cast.Receiver` object from receiver device info.
*/
function createReceiver(device: ReceiverDevice) {
// Convert capabilities bitflag to string array
const capabilities: Capability[] = [];
if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_OUT) {
capabilities.push(Capability.VIDEO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.VIDEO_IN) {
capabilities.push(Capability.VIDEO_IN);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_OUT) {
capabilities.push(Capability.AUDIO_OUT);
} else if (device.capabilities & ReceiverDeviceCapabilities.AUDIO_IN) {
capabilities.push(Capability.AUDIO_IN);
} else if (
device.capabilities & ReceiverDeviceCapabilities.MULTIZONE_GROUP
) {
capabilities.push(Capability.MULTIZONE_GROUP);
}
const receiver = new Receiver(device.id, device.friendlyName, capabilities);
// Currently only supports CAST receivers
receiver.receiverType = ReceiverType.CAST;
return receiver;
}
/** Cast SDK root class */
export default class {
#receiverDevices = new Map<string, ReceiverDevice>();
#apiConfig?: ApiConfig;
#sessionRequest?: SessionRequest;
#requestSessionSuccessCallback?: RequestSessionSuccessCallback;
#requestSessionErrorCallback?: ErrorCallback;
#sessions = new Map<string, Session>();
#receiverActionListeners = new Set<ReceiverActionListener>();
// Enums
AutoJoinPolicy = AutoJoinPolicy;
Capability = Capability;
DefaultActionPolicy = DefaultActionPolicy;
DialAppState = DialAppState;
ErrorCode = ErrorCode;
ReceiverAction = ReceiverAction;
ReceiverAvailability = ReceiverAvailability;
ReceiverType = ReceiverType;
SenderPlatform = SenderPlatform;
SessionStatus = SessionStatus;
VolumeControlType = VolumeControlType;
// Classes
ApiConfig = ApiConfig;
CredentialsData = CredentialsData;
DialRequest = DialRequest;
Error = Error_;
Image = Image;
Receiver = Receiver;
ReceiverDisplayStatus = ReceiverDisplayStatus;
SenderApplication = SenderApplication;
SessionRequest = SessionRequest;
Timeout = Timeout;
Volume = Volume;
Session = Session;
media = media;
VERSION = [1, 2];
isAvailable = false;
timeout = new Timeout();
constructor() {
onMessage(this.#onMessage.bind(this));
}
#sendSessionRequest(
sessionRequest: SessionRequest,
receiverDevice: ReceiverDevice
) {
for (const listener of this.#receiverActionListeners) {
listener(createReceiver(receiverDevice), ReceiverAction.CAST);
}
sendMessageResponse({
subject: "bridge:createCastSession",
data: {
appId: sessionRequest.appId,
receiverDevice: receiverDevice
}
});
}
#onMessage(message: Message) {
switch (message.subject) {
case "cast:initialized": {
this.isAvailable = true;
break;
}
/**
* Once the bridge detects a session creation, session info
* and data needed to create cast API objects is sent.
*/
case "cast:sessionCreated": {
// Notify background to close UI
sendMessageResponse({
subject: "main:closeReceiverSelector"
});
const status = message.data;
const receiverDevice = this.#receiverDevices.get(
status.receiverId
);
if (!receiverDevice) {
logger.error(
`Could not find receiver device "${status.receiverFriendlyName}" (${status.receiverId})`
);
break;
}
const receiver = createReceiver(receiverDevice);
receiver.volume = status.volume;
receiver.displayStatus = new ReceiverDisplayStatus(
status.statusText,
status.appImages
);
const session = new Session(
status.sessionId, // sessionId
status.appId, // appId
status.displayName, // displayName
status.appImages, // appImages
receiver // receiver
);
session.senderApps = status.senderApps;
session.transportId = status.transportId;
this.#sessions.set(session.sessionId, session);
}
// eslint-disable-next-line no-fallthrough
case "cast:sessionUpdated": {
const status = message.data;
const session = this.#sessions.get(status.sessionId);
if (!session) {
logger.error(`Session not found (${status.sessionId})`);
return;
}
session.statusText = status.statusText;
session.namespaces = status.namespaces;
session.receiver.volume = status.volume;
/**
* If session created via requestSession, the success
* callback will be set, otherwise the session was created
* by the extension and the session listener should be
* called instead.
*/
if (this.#requestSessionSuccessCallback) {
this.#requestSessionSuccessCallback(session);
this.#requestSessionSuccessCallback = undefined;
this.#requestSessionErrorCallback = undefined;
} else {
this.#apiConfig?.sessionListener(session);
}
break;
}
case "cast:sessionStopped": {
const { sessionId } = message.data;
const session = this.#sessions.get(sessionId);
if (session) {
session.status = SessionStatus.STOPPED;
const updateListeners = session?._updateListeners;
if (updateListeners) {
for (const listener of updateListeners) {
listener(false);
}
}
}
break;
}
case "cast:receivedSessionMessage": {
const { sessionId, namespace, messageData } = message.data;
const session = this.#sessions.get(sessionId);
if (session) {
const _messageListeners = session._messageListeners;
const listeners = _messageListeners.get(namespace);
if (listeners) {
for (const listener of listeners) {
listener(namespace, messageData);
}
}
}
break;
}
case "cast:impl_sendMessage": {
const { sessionId, messageId, error } = message.data;
const session = this.#sessions.get(sessionId);
if (!session) {
break;
}
const callbacks = session._sendMessageCallbacks.get(messageId);
if (callbacks) {
const [successCallback, errorCallback] = callbacks;
if (error) {
errorCallback?.(new Error_(error));
return;
}
successCallback?.();
}
break;
}
case "cast:receiverDeviceUp": {
const { receiverDevice } = message.data;
if (this.#receiverDevices.has(receiverDevice.id)) {
break;
}
this.#receiverDevices.set(receiverDevice.id, receiverDevice);
if (this.#apiConfig) {
// Notify listeners of new cast destination
this.#apiConfig.receiverListener(
ReceiverAvailability.AVAILABLE
);
}
break;
}
case "cast:receiverDeviceDown": {
const { receiverDeviceId } = message.data;
this.#receiverDevices.delete(receiverDeviceId);
if (this.#receiverDevices.size === 0) {
if (this.#apiConfig) {
this.#apiConfig.receiverListener(
ReceiverAvailability.UNAVAILABLE
);
}
}
break;
}
case "cast:selectReceiver/selected": {
logger.info("Selected receiver");
if (this.#sessionRequest) {
this.#sendSessionRequest(
this.#sessionRequest,
message.data.receiverDevice
);
this.#sessionRequest = undefined;
}
break;
}
case "cast:selectReceiver/stopped": {
const { receiverDevice } = message.data;
logger.info("Stopped receiver");
if (this.#sessionRequest) {
this.#sessionRequest = undefined;
for (const listener of this.#receiverActionListeners) {
listener(
// TODO: Use existing receiver object?
createReceiver(receiverDevice),
ReceiverAction.STOP
);
}
}
break;
}
// Popup closed before session established
case "cast:selectReceiver/cancelled": {
if (this.#sessionRequest) {
this.#sessionRequest = undefined;
this.#requestSessionErrorCallback?.(
new Error_(ErrorCode.CANCEL)
);
}
break;
}
// Session request initiated via receiver selector
case "cast:launchApp": {
if (this.#sessionRequest) {
logger.error("Session request already in progress.");
break;
}
if (!this.#apiConfig?.sessionRequest) {
logger.error("Session request not found!");
break;
}
this.#sendSessionRequest(
this.#apiConfig.sessionRequest,
message.data.receiverDevice
);
break;
}
}
}
initialize(
apiConfig: ApiConfig,
successCallback?: SuccessCallback,
errorCallback?: ErrorCallback
) {
logger.info("cast.initialize");
// Already initialized
if (this.#apiConfig) {
errorCallback?.(new Error_(ErrorCode.INVALID_PARAMETER));
return;
}
this.#apiConfig = apiConfig;
sendMessageResponse({
subject: "main:initializeCast",
data: { appId: this.#apiConfig.sessionRequest.appId }
});
successCallback?.();
this.#apiConfig.receiverListener(
this.#receiverDevices.size
? ReceiverAvailability.AVAILABLE
: ReceiverAvailability.UNAVAILABLE
);
}
requestSession(
successCallback: RequestSessionSuccessCallback,
errorCallback: ErrorCallback,
newSessionRequest?: SessionRequest,
receiverDevice?: ReceiverDevice
) {
logger.info("cast.requestSession");
// Not yet initialized
if (!this.#apiConfig) {
errorCallback?.(new Error_(ErrorCode.API_NOT_INITIALIZED));
return;
}
// Already requesting session
if (this.#sessionRequest) {
errorCallback?.(
new Error_(
ErrorCode.INVALID_PARAMETER,
"Session request already in progress."
)
);
return;
}
// No receivers available
if (!this.#receiverDevices.size) {
errorCallback?.(new Error_(ErrorCode.RECEIVER_UNAVAILABLE));
return;
}
/**
* Store session request for use in return message from
* receiver selection.
*/
this.#sessionRequest =
newSessionRequest ?? this.#apiConfig.sessionRequest;
this.#requestSessionSuccessCallback = successCallback;
this.#requestSessionErrorCallback = errorCallback;
/**
* If a receiver was provided, skip the receiver selector
* process.
*/
if (receiverDevice) {
if (
receiverDevice?.id &&
this.#receiverDevices.has(receiverDevice.id)
) {
this.#sendSessionRequest(this.#sessionRequest, receiverDevice);
}
} else {
// Open receiver selector UI
sendMessageResponse({
subject: "main:selectReceiver"
});
}
}
requestSessionById(_sessionId: string): void {
logger.info("STUB :: cast.requestSessionById");
}
setCustomReceivers(
_receivers: Receiver[],
_successCallback?: SuccessCallback,
_errorCallback?: ErrorCallback
): void {
logger.info("STUB :: cast.setCustomReceivers");
}
setPageContext(_win: Window): void {
logger.info("STUB :: cast.setPageContext");
}
setReceiverDisplayStatus(_sessionId: string): void {
logger.info("STUB :: cast.setReceiverDisplayStatus");
}
unescape(escaped: string): string {
return window.decodeURI(escaped);
}
addReceiverActionListener(listener: ReceiverActionListener) {
this.#receiverActionListeners.add(listener);
}
removeReceiverActionListener(listener: ReceiverActionListener) {
this.#receiverActionListeners.delete(listener);
}
logMessage(message: string) {
logger.info("cast.logMessage", message);
}
precache(_data: string) {
logger.info("STUB :: cast.precache");
}
}

View File

@@ -4,7 +4,7 @@ import { v1 as uuid } from "uuid";
import logger from "../../../lib/logger"; import logger from "../../../lib/logger";
import { Volume, Error as _Error } from "../dataClasses"; import { Volume, Error as _Error } from "../classes";
import { import {
BreakStatus, BreakStatus,
EditTracksInfoRequest, EditTracksInfoRequest,
@@ -25,7 +25,7 @@ import {
StopRequest, StopRequest,
VideoInformation, VideoInformation,
VolumeRequest VolumeRequest
} from "./dataClasses"; } from "./classes";
import { PlayerState, RepeatMode } from "./enums"; import { PlayerState, RepeatMode } from "./enums";
import { ErrorCode } from "../enums"; import { ErrorCode } from "../enums";
@@ -33,6 +33,8 @@ import { ErrorCode } from "../enums";
import { ErrorCallback, SuccessCallback, UpdateListener } from "../../types"; import { ErrorCallback, SuccessCallback, UpdateListener } from "../../types";
import { SenderMediaMessage } from "../types"; import { SenderMediaMessage } from "../types";
export const NS_MEDIA = "urn:x-cast:com.google.cast.media";
export default class Media { export default class Media {
#id = uuid(); #id = uuid();

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
import { Image, Volume } from "../dataClasses"; import { Image, Volume } from "../classes";
import { import {
ContainerType, ContainerType,

View File

@@ -0,0 +1,140 @@
"use strict";
import Media from "./Media";
import {
ContainerType,
HdrType,
HlsSegmentFormat,
HlsVideoSegmentFormat,
IdleReason,
MediaCommand,
MetadataType,
PlayerState,
QueueType,
RepeatMode,
ResumeState,
StreamType,
TextTrackEdgeType,
TextTrackFontGenericFamily,
TextTrackFontStyle,
TextTrackType,
TextTrackWindowType,
TrackType,
UserAction
} from "./enums";
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 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
};

View File

@@ -5,8 +5,8 @@
* app/src/bridge/components/cast/types.ts * app/src/bridge/components/cast/types.ts
*/ */
import { SenderApplication, Volume, Image } from "./dataClasses"; import { SenderApplication, Volume, Image } from "./classes";
import { MediaInfo, QueueItem } from "./media/dataClasses"; import { MediaInfo, QueueItem } from "./media/classes";
import { import {
IdleReason, IdleReason,
PlayerState, PlayerState,

View File

@@ -1,7 +1,7 @@
"use strict"; "use strict";
import { Error as Error_ } from "./api/dataClasses"; import { Error as Error_ } from "./sdk/classes";
import { Media } from "./api/media"; import Media from "./sdk/media/Media";
export type SuccessCallback = () => void; export type SuccessCallback = () => void;
export type ErrorCallback = (err: Error_) => void; export type ErrorCallback = (err: Error_) => void;

View File

@@ -16,7 +16,7 @@ import {
MediaStatus, MediaStatus,
ReceiverStatus, ReceiverStatus,
SenderMessage SenderMessage
} from "./cast/api/types"; } from "./cast/sdk/types";
import { ReceiverDevice } from "./types"; import { ReceiverDevice } from "./types";

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
import { ReceiverStatus } from "./cast/api/types"; import { ReceiverStatus } from "./cast/sdk/types";
export enum ReceiverDeviceCapabilities { export enum ReceiverDeviceCapabilities {
NONE = 0, NONE = 0,

View File

@@ -10,7 +10,9 @@ import options from "../../lib/options";
import messaging, { Message, Port } from "../../messaging"; import messaging, { Message, Port } from "../../messaging";
import { getNextEllipsis } from "../../lib/utils"; import { getNextEllipsis } from "../../lib/utils";
import { RemoteMatchPattern } from "../../lib/matchPattern"; import { RemoteMatchPattern } from "../../lib/matchPattern";
import { ReceiverDevice } from "../../types"; import { ReceiverDevice } from "../../types";
import { Capability } from "../../cast/sdk/enums";
import { import {
ReceiverSelectionActionType, ReceiverSelectionActionType,