"use strict"; import options from "../../lib/options"; import logger from "../../lib/logger"; import CastManager from "../../cast/CastManager"; import { SessionRequest } from "../../cast/sdk/classes"; import receiverDevices from "../receiverDevices"; import { getMediaTypesForPageUrl } from "../../lib/utils"; import { ReceiverSelection, ReceiverSelectionActionType, ReceiverSelectorMediaType } from "./index"; import ReceiverSelector from "./ReceiverSelector"; async function createSelector() { return new ReceiverSelector(); } 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, selectionOpts?: { sessionRequest: SessionRequest; withMediaSender?: boolean; } ): Promise { 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; 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." ); availableMediaTypes = ReceiverSelectorMediaType.File; } // Enable app media type if initialized sender app is found if (castInstance || selectionOpts?.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(); function onReceiverChange() { sharedSelector.update(receiverDevices.getDevices()); } receiverDevices.addEventListener("receiverDeviceUp", onReceiverChange); receiverDevices.addEventListener( "receiverDeviceDown", onReceiverChange ); receiverDevices.addEventListener( "receiverDeviceUpdated", onReceiverChange ); let onSelected: any; let onCancelled: any; let onError: any; let onStop: any; type EvParamsType = Parameters< typeof sharedSelector.addEventListener >[0]; function storeListener(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); receiverDevices.removeEventListener( "receiverDeviceUp", onReceiverChange ); receiverDevices.removeEventListener( "receiverDeviceDown", onReceiverChange ); receiverDevices.removeEventListener( "receiverDeviceUpdated", onReceiverChange ); } sharedSelector.addEventListener( "selected", storeListener("selected", ev => { logger.info("Selected receiver", ev.detail); resolve({ actionType: ReceiverSelectionActionType.Cast, receiverDevice: ev.detail.receiverDevice, 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", ev => { reject(ev.detail); removeListeners(); }) ); sharedSelector.addEventListener( "stop", storeListener("stop", async ev => { logger.info("Stopping receiver app...", ev.detail); receiverDevices.stopReceiverApp(ev.detail.receiverDevice.id); resolve({ actionType: ReceiverSelectionActionType.Stop, receiverDevice: ev.detail.receiverDevice }); removeListeners(); }) ); // Ensure status manager is initialized await receiverDevices.init(); const pageInfo = pageUrl ? { url: pageUrl, tabId: contextTabId, frameId: contextFrameId, sessionRequest: selectionOpts?.sessionRequest } : undefined; sharedSelector.open( receiverDevices.getDevices(), defaultMediaType, availableMediaTypes, castInstance?.appId, pageInfo ); }); } export default { getSelection, getSelector };