mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-11 18:19:58 +00:00
Handle audio device support checking
This commit is contained in:
60
ext/src/cast/googleApi.ts
Normal file
60
ext/src/cast/googleApi.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import logger from "../lib/logger";
|
||||
import { TypedStorageArea } from "../lib/TypedStorageArea";
|
||||
|
||||
const ENDPOINT = "https://clients3.google.com/cast/chromecast/device";
|
||||
|
||||
export interface BaseConfig {
|
||||
app_tags: Array<{
|
||||
supports_audio_only: boolean;
|
||||
suports_video: boolean;
|
||||
app_id: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const baseConfigStorage = new TypedStorageArea<{
|
||||
baseConfig: BaseConfig;
|
||||
baseConfigUpdated: number;
|
||||
}>(browser.storage.local);
|
||||
|
||||
/**
|
||||
* Fetches Chromecast base config data subset.
|
||||
*/
|
||||
export async function fetchBaseConfig(): Promise<BaseConfig | null> {
|
||||
try {
|
||||
const res = await fetch(`${ENDPOINT}/baseconfig`);
|
||||
const baseConfig = JSON.parse((await res.text()).slice(4));
|
||||
|
||||
// Strip other properties
|
||||
return { app_tags: baseConfig.app_tags };
|
||||
} catch (err) {
|
||||
logger.error("Failed to fetch Chromecast base config!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app tag from base config.
|
||||
* @param baseConfig Base config data.
|
||||
* @param appId Chromecast app ID.
|
||||
*/
|
||||
export function getAppTag(baseConfig: BaseConfig, appId: string) {
|
||||
// App tag IDs are represented as 32-bit signed integers
|
||||
const signedAppId = (parseInt(appId, 16) << 32) >> 32;
|
||||
return baseConfig.app_tags.find(tag => tag.app_id === signedAppId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Chromecast app config.
|
||||
*
|
||||
* @param appId Chromecast app ID
|
||||
* @returns
|
||||
*/
|
||||
export async function fetchAppConfig(appId: string) {
|
||||
try {
|
||||
const res = await fetch(`${ENDPOINT}/app?a=${appId}`);
|
||||
return JSON.parse((await res.text()).slice(4));
|
||||
} catch (err) {
|
||||
logger.error("Failed to fetch Chromecast app config!", { appId });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import logger from "../../lib/logger";
|
||||
|
||||
import { ReceiverDevice, ReceiverDeviceCapabilities } from "../../types";
|
||||
import { ErrorCallback, SuccessCallback } from "../types";
|
||||
|
||||
import { Message } from "../../messaging";
|
||||
import eventMessaging from "../eventMessaging";
|
||||
|
||||
import { ReceiverDevice } from "../../types";
|
||||
import { ErrorCallback, SuccessCallback } from "../types";
|
||||
|
||||
import {
|
||||
AutoJoinPolicy,
|
||||
Capability,
|
||||
@@ -38,7 +39,7 @@ import {
|
||||
import Session from "./Session";
|
||||
|
||||
import media from "./media";
|
||||
import { Message } from "../../messaging";
|
||||
import { convertCapabilitiesFlags } from "../utils";
|
||||
|
||||
type ReceiverActionListener = (
|
||||
receiver: Receiver,
|
||||
@@ -51,23 +52,11 @@ 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);
|
||||
const receiver = new Receiver(
|
||||
device.id,
|
||||
device.friendlyName,
|
||||
convertCapabilitiesFlags(device.capabilities)
|
||||
);
|
||||
|
||||
// Currently only supports CAST receivers
|
||||
receiver.receiverType = ReceiverType.CAST;
|
||||
@@ -178,11 +167,11 @@ export default class {
|
||||
);
|
||||
|
||||
const session = new Session(
|
||||
status.sessionId, // sessionId
|
||||
status.appId, // appId
|
||||
status.displayName, // displayName
|
||||
status.appImages, // appImages
|
||||
receiver // receiver
|
||||
status.sessionId,
|
||||
status.appId,
|
||||
status.displayName,
|
||||
status.appImages,
|
||||
receiver
|
||||
);
|
||||
|
||||
session.namespaces = status.namespaces;
|
||||
@@ -221,11 +210,8 @@ export default class {
|
||||
session.namespaces = status.namespaces;
|
||||
session.receiver.volume = status.volume;
|
||||
|
||||
const updateListeners = session?._updateListeners;
|
||||
if (updateListeners) {
|
||||
for (const listener of updateListeners) {
|
||||
listener(session.status !== SessionStatus.STOPPED);
|
||||
}
|
||||
for (const listener of session._updateListeners) {
|
||||
listener(session.status !== SessionStatus.STOPPED);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -236,12 +222,8 @@ export default class {
|
||||
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);
|
||||
}
|
||||
for (const listener of session._updateListeners) {
|
||||
listener(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,9 +234,7 @@ export default class {
|
||||
const { sessionId, namespace, messageData } = message.data;
|
||||
const session = this.#sessions.get(sessionId);
|
||||
if (session) {
|
||||
const _messageListeners = session._messageListeners;
|
||||
const listeners = _messageListeners.get(namespace);
|
||||
|
||||
const listeners = session._messageListeners.get(namespace);
|
||||
if (listeners) {
|
||||
for (const listener of listeners) {
|
||||
listener(namespace, messageData);
|
||||
|
||||
@@ -140,7 +140,7 @@ export default class Media {
|
||||
* information reported by the receiver.
|
||||
*/
|
||||
getEstimatedTime(): number {
|
||||
if (this.playerState === PlayerState.PLAYING && this._lastUpdateTime) {
|
||||
if (this.playerState === PlayerState.PLAYING) {
|
||||
return getEstimatedTime({
|
||||
currentTime: this.currentTime,
|
||||
lastUpdateTime: this._lastUpdateTime,
|
||||
|
||||
@@ -28,6 +28,24 @@ export function hasRequiredCapabilities(
|
||||
});
|
||||
}
|
||||
|
||||
export function convertCapabilitiesFlags(flags: ReceiverDeviceCapabilities) {
|
||||
// Convert capabilities bitflag to string array
|
||||
const capabilities: Capability[] = [];
|
||||
if (flags & ReceiverDeviceCapabilities.VIDEO_OUT)
|
||||
capabilities.push(Capability.VIDEO_OUT);
|
||||
if (flags & ReceiverDeviceCapabilities.VIDEO_IN)
|
||||
capabilities.push(Capability.VIDEO_IN);
|
||||
if (flags & ReceiverDeviceCapabilities.AUDIO_OUT)
|
||||
capabilities.push(Capability.AUDIO_OUT);
|
||||
if (flags & ReceiverDeviceCapabilities.AUDIO_IN)
|
||||
capabilities.push(Capability.AUDIO_IN);
|
||||
|
||||
if (flags & ReceiverDeviceCapabilities.MULTIZONE_GROUP)
|
||||
capabilities.push(Capability.MULTIZONE_GROUP);
|
||||
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
interface GetEstimatedTimeOpts {
|
||||
currentTime: number;
|
||||
lastUpdateTime: number;
|
||||
|
||||
Reference in New Issue
Block a user