mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-10 17:49:58 +00:00
Fix longstanding architectural issues
This commit is contained in:
@@ -4,23 +4,15 @@ import logger from "../lib/logger";
|
||||
import messaging, { Port, Message } from "../messaging";
|
||||
import options from "../lib/options";
|
||||
import { TypedEventTarget } from "../lib/TypedEventTarget";
|
||||
import { getMediaTypesForPageUrl } from "../lib/utils";
|
||||
|
||||
import {
|
||||
import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
|
||||
import type {
|
||||
ReceiverDevice,
|
||||
ReceiverSelectorAppInfo,
|
||||
ReceiverSelectorMediaType,
|
||||
ReceiverSelectorPageInfo
|
||||
} from "../types";
|
||||
|
||||
import deviceManager from "./deviceManager";
|
||||
import castManager from "./castManager";
|
||||
|
||||
import { BaseConfig, baseConfigStorage, getAppTag } from "../cast/googleApi";
|
||||
import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
|
||||
import type { SessionRequest } from "../cast/sdk/classes";
|
||||
import { ReceiverAction } from "../cast/sdk/enums";
|
||||
import { createReceiver } from "../cast/utils";
|
||||
|
||||
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
|
||||
|
||||
export interface ReceiverSelection {
|
||||
@@ -47,8 +39,6 @@ interface ReceiverSelectorEvents {
|
||||
mediaMessage: ReceiverSelectorMediaMessage;
|
||||
}
|
||||
|
||||
let baseConfig: BaseConfig;
|
||||
|
||||
/**
|
||||
* Manages the receiver selector popup window and communication with the
|
||||
* extension page hosted within.
|
||||
@@ -68,8 +58,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
||||
|
||||
private wasReceiverSelected = false;
|
||||
|
||||
private appId?: string;
|
||||
|
||||
appInfo?: ReceiverSelectorAppInfo;
|
||||
pageInfo?: ReceiverSelectorPageInfo;
|
||||
|
||||
constructor() {
|
||||
@@ -102,10 +91,10 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
||||
receiverDevices: ReceiverDevice[];
|
||||
defaultMediaType: ReceiverSelectorMediaType;
|
||||
availableMediaTypes: ReceiverSelectorMediaType;
|
||||
appId?: string;
|
||||
appInfo?: ReceiverSelectorAppInfo;
|
||||
pageInfo?: ReceiverSelectorPageInfo;
|
||||
}) {
|
||||
this.appId = opts.appId;
|
||||
this.appInfo = opts.appInfo;
|
||||
this.pageInfo = opts.pageInfo;
|
||||
|
||||
// If popup already exists, close it
|
||||
@@ -178,8 +167,6 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
||||
await browser.windows.remove(this.windowId);
|
||||
}
|
||||
|
||||
this.appId = undefined;
|
||||
|
||||
if (this.messagePort && !this.messagePortDisconnected) {
|
||||
this.messagePort.disconnect();
|
||||
}
|
||||
@@ -220,7 +207,10 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
||||
|
||||
this.messagePort.postMessage({
|
||||
subject: "popup:init",
|
||||
data: { appId: this.appId, pageInfo: this.pageInfo }
|
||||
data: {
|
||||
appInfo: this.appInfo,
|
||||
pageInfo: this.pageInfo
|
||||
}
|
||||
});
|
||||
|
||||
this.messagePort.postMessage({
|
||||
@@ -303,226 +293,4 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static sharedInstance = new ReceiverSelector();
|
||||
|
||||
/**
|
||||
* Opens a receiver selector with the specified default/available media
|
||||
* types.
|
||||
*
|
||||
* Returns a promise that:
|
||||
* - Resolves to a ReceiverSelection object if selection is
|
||||
* successful.
|
||||
* - Resolves to null if the selection is cancelled.
|
||||
* - Rejects if the selection fails.
|
||||
*/
|
||||
static async getSelection(
|
||||
contextTabId: number,
|
||||
contextFrameId = 0,
|
||||
selectionOpts?: {
|
||||
sessionRequest?: SessionRequest;
|
||||
withMediaSender?: boolean;
|
||||
}
|
||||
): Promise<ReceiverSelection | null> {
|
||||
let castInstance = castManager.getInstance(
|
||||
contextTabId,
|
||||
contextFrameId
|
||||
);
|
||||
/**
|
||||
* If the current context is running the mirroring app, pretend
|
||||
* it doesn't exist because it shouldn't be launched like this.
|
||||
*/
|
||||
if (
|
||||
castInstance?.apiConfig?.sessionRequest.appId ===
|
||||
(await options.get("mirroringAppId"))
|
||||
) {
|
||||
castInstance = undefined;
|
||||
}
|
||||
|
||||
let defaultMediaType = ReceiverSelectorMediaType.Tab;
|
||||
let availableMediaTypes = ReceiverSelectorMediaType.None;
|
||||
|
||||
let pageUrl: string | undefined;
|
||||
try {
|
||||
pageUrl = (
|
||||
await browser.webNavigation.getFrame({
|
||||
tabId: contextTabId,
|
||||
frameId: contextFrameId
|
||||
})
|
||||
).url;
|
||||
|
||||
availableMediaTypes = getMediaTypesForPageUrl(pageUrl);
|
||||
} catch {
|
||||
logger.error(
|
||||
"Failed to locate frame, falling back to default available media types."
|
||||
);
|
||||
}
|
||||
|
||||
// Enable app media type if sender application is present
|
||||
if (castInstance || selectionOpts?.withMediaSender) {
|
||||
defaultMediaType = ReceiverSelectorMediaType.App;
|
||||
availableMediaTypes |= ReceiverSelectorMediaType.App;
|
||||
}
|
||||
|
||||
const opts = await options.getAll();
|
||||
|
||||
// Disable mirroring media types if mirroring is not enabled
|
||||
if (!opts.mirroringEnabled) {
|
||||
availableMediaTypes &= ~(
|
||||
ReceiverSelectorMediaType.Tab | ReceiverSelectorMediaType.Screen
|
||||
);
|
||||
}
|
||||
|
||||
// Remove file media type if local media is not enabled
|
||||
if (!opts.mediaEnabled || !opts.localMediaEnabled) {
|
||||
availableMediaTypes &= ~ReceiverSelectorMediaType.File;
|
||||
}
|
||||
|
||||
// Ensure status manager is initialized
|
||||
await deviceManager.init();
|
||||
|
||||
let isRequestAppAudioCompatible: Optional<boolean>;
|
||||
if (castInstance?.apiConfig?.sessionRequest.appId) {
|
||||
if (!baseConfig) {
|
||||
try {
|
||||
baseConfig = (await baseConfigStorage.get("baseConfig"))
|
||||
.baseConfig;
|
||||
} catch (err) {
|
||||
throw logger.error("Failed to get Chromecast base config!");
|
||||
}
|
||||
}
|
||||
|
||||
isRequestAppAudioCompatible = getAppTag(
|
||||
baseConfig,
|
||||
castInstance.apiConfig?.sessionRequest.appId
|
||||
)?.supports_audio_only;
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// Close an existing open selector
|
||||
if (ReceiverSelector.sharedInstance.isOpen) {
|
||||
await ReceiverSelector.sharedInstance.close();
|
||||
}
|
||||
|
||||
const selector = createSelector();
|
||||
ReceiverSelector.sharedInstance = selector;
|
||||
|
||||
// Handle selected return value
|
||||
const onSelected = (ev: CustomEvent<ReceiverSelection>) =>
|
||||
resolve(ev.detail);
|
||||
selector.addEventListener("selected", onSelected);
|
||||
|
||||
// Handle cancelled return value
|
||||
const onCancelled = () => resolve(null);
|
||||
selector.addEventListener("cancelled", onCancelled);
|
||||
|
||||
const onError = (ev: CustomEvent<string>) => reject(ev.detail);
|
||||
selector.addEventListener("error", onError);
|
||||
|
||||
// Cleanup listeners
|
||||
selector.addEventListener(
|
||||
"close",
|
||||
() => {
|
||||
selector.removeEventListener("selected", onSelected);
|
||||
selector.removeEventListener("cancelled", onCancelled);
|
||||
selector.removeEventListener("error", onError);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
selector.open({
|
||||
receiverDevices: deviceManager.getDevices(),
|
||||
defaultMediaType,
|
||||
availableMediaTypes,
|
||||
appId: castInstance?.apiConfig?.sessionRequest.appId,
|
||||
// Create page info
|
||||
pageInfo: pageUrl
|
||||
? {
|
||||
url: pageUrl,
|
||||
tabId: contextTabId,
|
||||
frameId: contextFrameId,
|
||||
sessionRequest: selectionOpts?.sessionRequest,
|
||||
isRequestAppAudioCompatible
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new ReceiverSelector object and adds listeners for
|
||||
* updates/messages.
|
||||
*/
|
||||
function createSelector() {
|
||||
// Get a new selector for each selection
|
||||
const selector = new ReceiverSelector();
|
||||
ReceiverSelector.sharedInstance = selector;
|
||||
|
||||
/**
|
||||
* Sends message to cast instance to trigger stopped receiver action
|
||||
* (if applicable).
|
||||
*/
|
||||
const onStop = (ev: CustomEvent<{ deviceId: string }>) => {
|
||||
const castInstance = castManager.getInstanceByDeviceId(
|
||||
ev.detail.deviceId
|
||||
);
|
||||
if (!castInstance) return;
|
||||
|
||||
const device = deviceManager.getDeviceById(ev.detail.deviceId);
|
||||
if (!device) return;
|
||||
|
||||
castInstance.contentPort.postMessage({
|
||||
subject: "cast:sendReceiverAction",
|
||||
data: {
|
||||
receiver: createReceiver(device),
|
||||
action: ReceiverAction.STOP
|
||||
}
|
||||
});
|
||||
};
|
||||
selector.addEventListener("stop", onStop);
|
||||
|
||||
// Forward receiver messages
|
||||
const onReceiverMessage = (
|
||||
ev: CustomEvent<ReceiverSelectorReceiverMessage>
|
||||
) =>
|
||||
deviceManager.sendReceiverMessage(
|
||||
ev.detail.deviceId,
|
||||
ev.detail.message
|
||||
);
|
||||
selector.addEventListener("receiverMessage", onReceiverMessage);
|
||||
|
||||
// Forward media messages
|
||||
const onMediaMessage = (ev: CustomEvent<ReceiverSelectorMediaMessage>) =>
|
||||
deviceManager.sendMediaMessage(ev.detail.deviceId, ev.detail.message);
|
||||
selector.addEventListener("mediaMessage", onMediaMessage);
|
||||
|
||||
// Update selector data whenever devices change/update
|
||||
const onDeviceChange = () => selector.update(deviceManager.getDevices());
|
||||
|
||||
deviceManager.addEventListener("deviceUp", onDeviceChange);
|
||||
deviceManager.addEventListener("deviceDown", onDeviceChange);
|
||||
deviceManager.addEventListener("deviceUpdated", onDeviceChange);
|
||||
deviceManager.addEventListener("deviceMediaUpdated", onDeviceChange);
|
||||
|
||||
// Cleanup listeners
|
||||
selector.addEventListener(
|
||||
"close",
|
||||
() => {
|
||||
deviceManager.removeEventListener("deviceUp", onDeviceChange);
|
||||
deviceManager.removeEventListener("deviceDown", onDeviceChange);
|
||||
deviceManager.removeEventListener("deviceUpdated", onDeviceChange);
|
||||
deviceManager.removeEventListener(
|
||||
"deviceMediaUpdated",
|
||||
onDeviceChange
|
||||
);
|
||||
|
||||
selector.removeEventListener("stop", onStop);
|
||||
selector.removeEventListener("receiverMessage", onReceiverMessage);
|
||||
selector.removeEventListener("mediaMessage", onMediaMessage);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user