mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Move cast API code to cast/sdk/ and create wrapper class
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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";
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
export default class EventData {
|
|
||||||
constructor(public type: string) {}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
export default class RemotePlayerChangedEvent {
|
|
||||||
constructor(public type: string, public field: string, public value: any) {}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) {}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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
512
ext/src/cast/sdk/index.ts
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { Image, Volume } from "../dataClasses";
|
import { Image, Volume } from "../classes";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ContainerType,
|
ContainerType,
|
||||||
140
ext/src/cast/sdk/media/index.ts
Normal file
140
ext/src/cast/sdk/media/index.ts
Normal 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
|
||||||
|
};
|
||||||
@@ -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,
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user