Files
fx_cast/ext/src/background/receiverSelector/ReceiverSelectorManager.ts

215 lines
6.3 KiB
TypeScript

"use strict";
import options from "../../lib/options";
import logger from "../../lib/logger";
import ShimManager from "../ShimManager";
import StatusManager from "../StatusManager";
import { getMediaTypesForPageUrl } from "../../lib/utils";
import { DEFAULT_MEDIA_RECEIVER_APP_ID } from "../../shim/cast/media/";
import { ReceiverSelector
, ReceiverSelectorType } from "./";
import { ReceiverSelection
, ReceiverSelectionActionType
, ReceiverSelectorMediaType } from "./ReceiverSelector";
import NativeReceiverSelector from "./NativeReceiverSelector";
import PopupReceiverSelector from "./PopupReceiverSelector";
import { Receiver } from "../../types";
async function createSelector () {
const type = await options.get("receiverSelectorType");
switch (type) {
case ReceiverSelectorType.Native: {
return new NativeReceiverSelector();
}
case ReceiverSelectorType.Popup: {
return new PopupReceiverSelector();
}
}
}
let sharedSelector: ReceiverSelector;
async function getSelector () {
if (!sharedSelector) {
try {
sharedSelector = await createSelector();
} 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
, withMediaSender = false)
: Promise<ReceiverSelection | null> {
return new Promise(async (resolve, reject) => {
let currentShim = ShimManager.getShim(
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 (currentShim?.requestedAppId ===
await options.get("mirroringAppId")) {
currentShim = undefined;
}
let defaultMediaType = ReceiverSelectorMediaType.Tab;
let availableMediaTypes;
try {
const { url } = await browser.webNavigation.getFrame({
tabId: contextTabId
, frameId: contextFrameId
});
availableMediaTypes = getMediaTypesForPageUrl(url);
} catch {
logger.error("Failed to locate frame, falling back to default available media types.");
availableMediaTypes = ReceiverSelectorMediaType.File;
}
// Enable app media type if initialized sender app is found
if (currentShim || withMediaSender) {
defaultMediaType = ReceiverSelectorMediaType.App;
availableMediaTypes |= ReceiverSelectorMediaType.App;
}
const opts = await options.getAll();
// Remove 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 = await createSelector();
let onSelected: any;
let onCancelled: any;
let onError: any;
let onStop: any;
type EvParamsType =
Parameters<typeof sharedSelector.addEventListener>[0];
function storeListener<T> (type: EvParamsType, fn: T) {
if (type === "selected") {
onSelected = fn;
} else if (type === "cancelled") {
onCancelled = fn;
} else if (type === "error") {
onError = fn;
} else if (type === "stop") {
onStop = fn;
}
return fn;
}
function removeListeners () {
sharedSelector.removeEventListener("selected", onSelected);
sharedSelector.removeEventListener("cancelled", onCancelled);
sharedSelector.removeEventListener("error", onError);
sharedSelector.removeEventListener("stop", onStop);
}
sharedSelector.addEventListener("selected"
, storeListener("selected", ev => {
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast
, receiver: ev.detail.receiver
, mediaType: ev.detail.mediaType
, filePath: ev.detail.filePath
});
removeListeners();
}));
sharedSelector.addEventListener("cancelled"
, storeListener("cancelled", () => {
logger.info("Cancelled receiver selection");
resolve(null);
removeListeners();
}));
sharedSelector.addEventListener("error"
, storeListener("error", () => {
reject();
removeListeners();
}));
sharedSelector.addEventListener("stop"
, storeListener("stop", ev => {
logger.info("Stopped receiver app", ev.detail);
StatusManager.init()
.then(() => StatusManager.stopReceiverApp(ev.detail.receiver))
.then(() => {
resolve({
actionType: ReceiverSelectionActionType.Stop
, receiver: ev.detail.receiver
});
removeListeners();
});
}));
// Ensure status manager is initialized
await StatusManager.init();
sharedSelector.open(
Array.from(StatusManager.getReceivers())
, defaultMediaType
, availableMediaTypes
, currentShim?.requestedAppId);
});
}
export default {
getSelection
, getSelector
};