Move selectorManager getSelection to ReceiverSelector static method

This commit is contained in:
hensm
2022-08-25 22:34:54 +01:00
parent deb4c5154d
commit 7a60bb3278
5 changed files with 275 additions and 272 deletions

View File

@@ -3,9 +3,9 @@
import logger from "../lib/logger";
import messaging, { Port, Message } from "../messaging";
import options from "../lib/options";
import { TypedEventTarget } from "../lib/TypedEventTarget";
import { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
import { getMediaTypesForPageUrl } from "../lib/utils";
import {
ReceiverDevice,
ReceiverSelectionActionType,
@@ -13,6 +13,13 @@ import {
ReceiverSelectorPageInfo
} from "../types";
import deviceManager from "./deviceManager";
import castManager from "./castManager";
import { BaseConfig, baseConfigStorage, getAppTag } from "../cast/googleApi";
import type { SessionRequest } from "../cast/sdk/classes";
import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
export interface ReceiverSelectionCast {
@@ -45,6 +52,16 @@ interface ReceiverSelectorEvents {
mediaMessage: ReceiverSelectorMediaMessage;
}
let baseConfig: BaseConfig;
baseConfigStorage
.get("baseConfig")
.then(value => {
baseConfig = value.baseConfig;
})
.catch(() => {
logger.error("Failed to get Chromecast base config!");
});
/**
* Manages the receiver selector popup window and communication with the
* extension page hosted within.
@@ -316,4 +333,249 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
}
}
}
static shared = 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 getSelection(
contextTabId: number,
contextFrameId = 0,
selectionOpts?: {
sessionRequest?: SessionRequest;
withMediaSender?: boolean;
}
): Promise<ReceiverSelection | null> {
return new Promise(async (resolve, reject) => {
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?.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;
}
// Close an existing open selector
if (ReceiverSelector.shared && ReceiverSelector.shared.isOpen) {
ReceiverSelector.shared.close();
}
// Get a new selector for each selection
ReceiverSelector.shared = new ReceiverSelector();
function onReceiverChange() {
ReceiverSelector.shared.update(deviceManager.getDevices());
}
deviceManager.addEventListener(
"receiverDeviceUp",
onReceiverChange
);
deviceManager.addEventListener(
"receiverDeviceDown",
onReceiverChange
);
deviceManager.addEventListener(
"receiverDeviceUpdated",
onReceiverChange
);
deviceManager.addEventListener(
"receiverDeviceMediaUpdated",
onReceiverChange
);
function onSelectorSelected(
ev: CustomEvent<ReceiverSelectionCast>
) {
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast,
receiverDevice: ev.detail.receiverDevice,
mediaType: ev.detail.mediaType
});
}
function onSelectorStop(ev: CustomEvent<ReceiverSelectionStop>) {
logger.info("Stopping receiver app...", ev.detail);
deviceManager.stopReceiverApp(ev.detail.receiverDevice.id);
resolve({
actionType: ReceiverSelectionActionType.Stop,
receiverDevice: ev.detail.receiverDevice
});
}
function onSelectorCancelled() {
logger.info("Cancelled receiver selection");
resolve(null);
}
function onSelectorError(ev: CustomEvent<string>) {
reject(ev.detail);
}
function onReceiverMessage(
ev: CustomEvent<ReceiverSelectorReceiverMessage>
) {
deviceManager.sendReceiverMessage(
ev.detail.deviceId,
ev.detail.message
);
}
function onMediaMessage(
ev: CustomEvent<ReceiverSelectorMediaMessage>
) {
deviceManager.sendMediaMessage(
ev.detail.deviceId,
ev.detail.message
);
}
ReceiverSelector.shared.addEventListener(
"selected",
onSelectorSelected
);
ReceiverSelector.shared.addEventListener("stop", onSelectorStop);
ReceiverSelector.shared.addEventListener(
"cancelled",
onSelectorCancelled
);
ReceiverSelector.shared.addEventListener("error", onSelectorError);
ReceiverSelector.shared.addEventListener(
"receiverMessage",
onReceiverMessage
);
ReceiverSelector.shared.addEventListener(
"mediaMessage",
onMediaMessage
);
ReceiverSelector.shared.addEventListener("close", removeListeners);
function removeListeners() {
ReceiverSelector.shared.removeEventListener(
"selected",
onSelectorSelected
);
ReceiverSelector.shared.removeEventListener(
"stop",
onSelectorStop
);
ReceiverSelector.shared.removeEventListener(
"cancelled",
onSelectorCancelled
);
ReceiverSelector.shared.removeEventListener(
"error",
onSelectorError
);
ReceiverSelector.shared.removeEventListener(
"receiverMessage",
onReceiverMessage
);
ReceiverSelector.shared.removeEventListener(
"mediaMessage",
onMediaMessage
);
ReceiverSelector.shared.removeEventListener(
"close",
removeListeners
);
deviceManager.removeEventListener(
"receiverDeviceUp",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceDown",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceUpdated",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceMediaUpdated",
onReceiverChange
);
}
// Ensure status manager is initialized
await deviceManager.init();
let isRequestAppAudioCompatible: Optional<boolean>;
if (castInstance?.appId) {
const appTag = getAppTag(baseConfig, castInstance.appId);
isRequestAppAudioCompatible = appTag?.supports_audio_only;
}
ReceiverSelector.shared.open({
receiverDevices: deviceManager.getDevices(),
defaultMediaType,
availableMediaTypes,
appId: castInstance?.appId,
// Create page info
pageInfo: pageUrl
? {
url: pageUrl,
tabId: contextTabId,
frameId: contextFrameId,
sessionRequest: selectionOpts?.sessionRequest,
isRequestAppAudioCompatible
}
: undefined
});
});
}
}

View File

@@ -7,11 +7,11 @@ import bridge, { BridgeInfo } from "../lib/bridge";
import castManager from "./castManager";
import deviceManager from "./deviceManager";
import selectorManager from "./selectorManager";
import ReceiverSelector from "./ReceiverSelector";
import { initMenus } from "./menus";
import { initWhitelist } from "./whitelist";
import { baseConfigStorage, fetchBaseConfig } from "../cast/googleapi";
import { baseConfigStorage, fetchBaseConfig } from "../cast/googleApi";
const _ = browser.i18n.getMessage;
@@ -145,7 +145,7 @@ async function init() {
return;
}
const selection = await selectorManager.getSelection(tab.id);
const selection = await ReceiverSelector.getSelection(tab.id);
if (selection) {
castManager.loadSender({
tabId: tab.id,

View File

@@ -11,10 +11,8 @@ import {
ReceiverSelectorMediaType
} from "../types";
import { ReceiverSelection } from "./ReceiverSelector";
import deviceManager from "./deviceManager";
import selectorManager from "./selectorManager";
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
type AnyPort = Port | MessagePort;
@@ -225,7 +223,7 @@ export default new (class {
}
try {
const selection = await selectorManager.getSelection(
const selection = await ReceiverSelector.getSelection(
instance.contentTabId,
instance.contentFrameId,
{ sessionRequest: message.data.sessionRequest }
@@ -297,7 +295,7 @@ export default new (class {
* same one that caused the session creation.
*/
case "main:closeReceiverSelector": {
const selector = await selectorManager.getSelector();
const selector = ReceiverSelector.shared;
const shouldClose = await options.get(
"receiverSelectorWaitForConnection"
);

View File

@@ -9,9 +9,7 @@ import {
ReceiverSelectorMediaType
} from "../types";
import { ReceiverSelection } from "./ReceiverSelector";
import selectorManager from "./selectorManager";
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
import castManager from "./castManager";
const _ = browser.i18n.getMessage;
@@ -157,10 +155,12 @@ async function onMenuClicked(
let selection: Nullable<ReceiverSelection> = null;
try {
selection = await selectorManager.getSelection(
selection = await ReceiverSelector.getSelection(
tab.id,
info.frameId,
{ withMediaSender: castMediaMenuClicked }
{
withMediaSender: castMediaMenuClicked
}
);
} catch (err) {
logger.error("Failed to get receiver selection (cast menu)", err);

View File

@@ -1,257 +0,0 @@
"use strict";
import options from "../lib/options";
import logger from "../lib/logger";
import { getMediaTypesForPageUrl } from "../lib/utils";
import { BaseConfig, baseConfigStorage, getAppTag } from "../cast/googleapi";
import { SessionRequest } from "../cast/sdk/classes";
import castManager from "./castManager";
import deviceManager from "./deviceManager";
import ReceiverSelector, {
ReceiverSelection,
ReceiverSelectionCast,
ReceiverSelectionStop,
ReceiverSelectorMediaMessage,
ReceiverSelectorReceiverMessage
} from "./ReceiverSelector";
import {
ReceiverSelectionActionType,
ReceiverSelectorMediaType
} from "../types";
let baseConfig: BaseConfig;
baseConfigStorage
.get("baseConfig")
.then(value => {
baseConfig = value.baseConfig;
})
.catch(() => {
logger.error("Failed to get Chromecast base config!");
});
let sharedSelector: ReceiverSelector;
async function getSelector() {
if (!sharedSelector) {
try {
sharedSelector = new ReceiverSelector();
} catch (err) {
throw logger.error("Failed to create receiver selector.");
}
}
return sharedSelector;
}
/**
* 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.
*/
async function getSelection(
contextTabId: number,
contextFrameId = 0,
selectionOpts?: {
sessionRequest?: SessionRequest;
withMediaSender?: boolean;
}
): Promise<ReceiverSelection | null> {
return new Promise(async (resolve, reject) => {
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?.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;
}
// Close an existing open selector
if (sharedSelector && sharedSelector.isOpen) {
sharedSelector.close();
}
// Get a new selector for each selection
sharedSelector = new ReceiverSelector();
function onReceiverChange() {
sharedSelector.update(deviceManager.getDevices());
}
deviceManager.addEventListener("receiverDeviceUp", onReceiverChange);
deviceManager.addEventListener("receiverDeviceDown", onReceiverChange);
deviceManager.addEventListener(
"receiverDeviceUpdated",
onReceiverChange
);
deviceManager.addEventListener(
"receiverDeviceMediaUpdated",
onReceiverChange
);
function onSelectorSelected(ev: CustomEvent<ReceiverSelectionCast>) {
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast,
receiverDevice: ev.detail.receiverDevice,
mediaType: ev.detail.mediaType
});
}
function onSelectorStop(ev: CustomEvent<ReceiverSelectionStop>) {
logger.info("Stopping receiver app...", ev.detail);
deviceManager.stopReceiverApp(ev.detail.receiverDevice.id);
resolve({
actionType: ReceiverSelectionActionType.Stop,
receiverDevice: ev.detail.receiverDevice
});
}
function onSelectorCancelled() {
logger.info("Cancelled receiver selection");
resolve(null);
}
function onSelectorError(ev: CustomEvent<string>) {
reject(ev.detail);
}
function onReceiverMessage(
ev: CustomEvent<ReceiverSelectorReceiverMessage>
) {
deviceManager.sendReceiverMessage(
ev.detail.deviceId,
ev.detail.message
);
}
function onMediaMessage(ev: CustomEvent<ReceiverSelectorMediaMessage>) {
deviceManager.sendMediaMessage(
ev.detail.deviceId,
ev.detail.message
);
}
sharedSelector.addEventListener("selected", onSelectorSelected);
sharedSelector.addEventListener("stop", onSelectorStop);
sharedSelector.addEventListener("cancelled", onSelectorCancelled);
sharedSelector.addEventListener("error", onSelectorError);
sharedSelector.addEventListener("receiverMessage", onReceiverMessage);
sharedSelector.addEventListener("mediaMessage", onMediaMessage);
sharedSelector.addEventListener("close", removeListeners);
function removeListeners() {
sharedSelector.removeEventListener("selected", onSelectorSelected);
sharedSelector.removeEventListener("stop", onSelectorStop);
sharedSelector.removeEventListener(
"cancelled",
onSelectorCancelled
);
sharedSelector.removeEventListener("error", onSelectorError);
sharedSelector.removeEventListener(
"receiverMessage",
onReceiverMessage
);
sharedSelector.removeEventListener("mediaMessage", onMediaMessage);
sharedSelector.removeEventListener("close", removeListeners);
deviceManager.removeEventListener(
"receiverDeviceUp",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceDown",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceUpdated",
onReceiverChange
);
deviceManager.removeEventListener(
"receiverDeviceMediaUpdated",
onReceiverChange
);
}
// Ensure status manager is initialized
await deviceManager.init();
let isRequestAppAudioCompatible: Optional<boolean>;
if (castInstance?.appId) {
const appTag = getAppTag(baseConfig, castInstance.appId);
isRequestAppAudioCompatible = appTag?.supports_audio_only;
}
sharedSelector.open({
receiverDevices: deviceManager.getDevices(),
defaultMediaType,
availableMediaTypes,
appId: castInstance?.appId,
// Create page info
pageInfo: pageUrl
? {
url: pageUrl,
tabId: contextTabId,
frameId: contextFrameId,
sessionRequest: selectionOpts?.sessionRequest,
isRequestAppAudioCompatible
}
: undefined
});
});
}
export default {
getSelection,
getSelector
};