mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Improve handling of receiver actions
This commit is contained in:
@@ -8,7 +8,6 @@ import { getMediaTypesForPageUrl } from "../lib/utils";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ReceiverDevice,
|
ReceiverDevice,
|
||||||
ReceiverSelectionActionType,
|
|
||||||
ReceiverSelectorMediaType,
|
ReceiverSelectorMediaType,
|
||||||
ReceiverSelectorPageInfo
|
ReceiverSelectorPageInfo
|
||||||
} from "../types";
|
} from "../types";
|
||||||
@@ -22,16 +21,10 @@ import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
|
|||||||
|
|
||||||
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
|
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
|
||||||
|
|
||||||
export interface ReceiverSelectionCast {
|
export interface ReceiverSelection {
|
||||||
actionType: ReceiverSelectionActionType.Cast;
|
|
||||||
receiverDevice: ReceiverDevice;
|
receiverDevice: ReceiverDevice;
|
||||||
mediaType: ReceiverSelectorMediaType;
|
mediaType: ReceiverSelectorMediaType;
|
||||||
}
|
}
|
||||||
export interface ReceiverSelectionStop {
|
|
||||||
actionType: ReceiverSelectionActionType.Stop;
|
|
||||||
receiverDevice: ReceiverDevice;
|
|
||||||
}
|
|
||||||
export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;
|
|
||||||
|
|
||||||
export interface ReceiverSelectorReceiverMessage {
|
export interface ReceiverSelectorReceiverMessage {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
@@ -43,24 +36,16 @@ export interface ReceiverSelectorMediaMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ReceiverSelectorEvents {
|
interface ReceiverSelectorEvents {
|
||||||
selected: ReceiverSelectionCast;
|
selected: ReceiverSelection;
|
||||||
error: string;
|
|
||||||
cancelled: void;
|
cancelled: void;
|
||||||
stop: ReceiverSelectionStop;
|
stop: { deviceId: string };
|
||||||
|
error: string;
|
||||||
close: void;
|
close: void;
|
||||||
receiverMessage: ReceiverSelectorReceiverMessage;
|
receiverMessage: ReceiverSelectorReceiverMessage;
|
||||||
mediaMessage: ReceiverSelectorMediaMessage;
|
mediaMessage: ReceiverSelectorMediaMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
let baseConfig: BaseConfig;
|
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
|
* Manages the receiver selector popup window and communication with the
|
||||||
@@ -248,39 +233,27 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
/** Handles messages from the popup extension page. */
|
/** Handles messages from the popup extension page. */
|
||||||
private onPopupMessage(message: Message) {
|
private onPopupMessage(message: Message) {
|
||||||
switch (message.subject) {
|
switch (message.subject) {
|
||||||
case "receiverSelector:selected": {
|
case "receiverSelector:selected":
|
||||||
this.wasReceiverSelected = true;
|
this.wasReceiverSelected = true;
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("selected", {
|
new CustomEvent("selected", { detail: message.data })
|
||||||
detail: message.data
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case "receiverSelector:stop": {
|
case "receiverSelector:stop":
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("stop", {
|
new CustomEvent("stop", { detail: message.data })
|
||||||
detail: message.data
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case "receiverSelector:receiverMessage":
|
case "receiverSelector:receiverMessage":
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("receiverMessage", {
|
new CustomEvent("receiverMessage", { detail: message.data })
|
||||||
detail: message.data
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "receiverSelector:mediaMessage":
|
case "receiverSelector:mediaMessage":
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("mediaMessage", {
|
new CustomEvent("mediaMessage", { detail: message.data })
|
||||||
detail: message.data
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -307,13 +280,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("close"));
|
this.dispatchEvent(new CustomEvent("close"));
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
delete this.windowId;
|
delete this.windowId;
|
||||||
delete this.messagePort;
|
|
||||||
delete this.receiverDevices;
|
|
||||||
delete this.defaultMediaType;
|
|
||||||
delete this.availableMediaTypes;
|
|
||||||
this.wasReceiverSelected = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,7 +301,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static shared = new ReceiverSelector();
|
static sharedInstance = new ReceiverSelector();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a receiver selector with the specified default/available media
|
* Opens a receiver selector with the specified default/available media
|
||||||
@@ -346,7 +313,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
* - Resolves to null if the selection is cancelled.
|
* - Resolves to null if the selection is cancelled.
|
||||||
* - Rejects if the selection fails.
|
* - Rejects if the selection fails.
|
||||||
*/
|
*/
|
||||||
static getSelection(
|
static async getSelection(
|
||||||
contextTabId: number,
|
contextTabId: number,
|
||||||
contextFrameId = 0,
|
contextFrameId = 0,
|
||||||
selectionOpts?: {
|
selectionOpts?: {
|
||||||
@@ -354,213 +321,110 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
withMediaSender?: boolean;
|
withMediaSender?: boolean;
|
||||||
}
|
}
|
||||||
): Promise<ReceiverSelection | null> {
|
): 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?.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?.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.appId
|
||||||
|
)?.supports_audio_only;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
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
|
// Close an existing open selector
|
||||||
if (ReceiverSelector.shared && ReceiverSelector.shared.isOpen) {
|
if (ReceiverSelector.sharedInstance.isOpen) {
|
||||||
ReceiverSelector.shared.close();
|
await ReceiverSelector.sharedInstance.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a new selector for each selection
|
const selector = createSelector();
|
||||||
ReceiverSelector.shared = new ReceiverSelector();
|
ReceiverSelector.sharedInstance = selector;
|
||||||
|
|
||||||
function onReceiverChange() {
|
// Handle selected return value
|
||||||
ReceiverSelector.shared.update(deviceManager.getDevices());
|
const onSelected = (ev: CustomEvent<ReceiverSelection>) =>
|
||||||
}
|
resolve(ev.detail);
|
||||||
|
selector.addEventListener("selected", onSelected);
|
||||||
|
|
||||||
deviceManager.addEventListener(
|
// Handle cancelled return value
|
||||||
"receiverDeviceUp",
|
const onCancelled = () => resolve(null);
|
||||||
onReceiverChange
|
selector.addEventListener("cancelled", onCancelled);
|
||||||
);
|
|
||||||
deviceManager.addEventListener(
|
const onError = (ev: CustomEvent<string>) => reject(ev.detail);
|
||||||
"receiverDeviceDown",
|
selector.addEventListener("error", onError);
|
||||||
onReceiverChange
|
|
||||||
);
|
// Cleanup listeners
|
||||||
deviceManager.addEventListener(
|
selector.addEventListener(
|
||||||
"receiverDeviceUpdated",
|
"close",
|
||||||
onReceiverChange
|
() => {
|
||||||
);
|
selector.removeEventListener("selected", onSelected);
|
||||||
deviceManager.addEventListener(
|
selector.removeEventListener("cancelled", onCancelled);
|
||||||
"receiverDeviceMediaUpdated",
|
selector.removeEventListener("error", onError);
|
||||||
onReceiverChange
|
},
|
||||||
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
function onSelectorSelected(
|
selector.open({
|
||||||
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(),
|
receiverDevices: deviceManager.getDevices(),
|
||||||
defaultMediaType,
|
defaultMediaType,
|
||||||
availableMediaTypes,
|
availableMediaTypes,
|
||||||
@@ -579,3 +443,74 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
castInstance.contentPort.postMessage({
|
||||||
|
subject: "cast:receiverStoppedAction",
|
||||||
|
data: { deviceId: ev.detail.deviceId }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import messaging, { Message, Port } from "../messaging";
|
|||||||
import options from "../lib/options";
|
import options from "../lib/options";
|
||||||
import { stringify } from "../lib/utils";
|
import { stringify } from "../lib/utils";
|
||||||
|
|
||||||
import {
|
import { ReceiverSelectorMediaType } from "../types";
|
||||||
ReceiverSelectionActionType,
|
|
||||||
ReceiverSelectorMediaType
|
|
||||||
} from "../types";
|
|
||||||
|
|
||||||
import deviceManager from "./deviceManager";
|
import deviceManager from "./deviceManager";
|
||||||
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
|
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
|
||||||
@@ -22,6 +19,12 @@ export interface CastInstance {
|
|||||||
contentTabId?: number;
|
contentTabId?: number;
|
||||||
contentFrameId?: number;
|
contentFrameId?: number;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
|
session?: CastSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CastSession {
|
||||||
|
sessionId: string;
|
||||||
|
deviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Keeps track of cast API instances and provides bridge messaging. */
|
/** Keeps track of cast API instances and provides bridge messaging. */
|
||||||
@@ -37,7 +40,7 @@ export default new (class {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Forward receiver eventes to cast instances
|
// Forward receiver eventes to cast instances
|
||||||
deviceManager.addEventListener("receiverDeviceUp", ev => {
|
deviceManager.addEventListener("deviceUp", ev => {
|
||||||
for (const instance of this.activeInstances) {
|
for (const instance of this.activeInstances) {
|
||||||
instance.contentPort.postMessage({
|
instance.contentPort.postMessage({
|
||||||
subject: "cast:receiverDeviceUp",
|
subject: "cast:receiverDeviceUp",
|
||||||
@@ -45,7 +48,7 @@ export default new (class {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
deviceManager.addEventListener("receiverDeviceDown", ev => {
|
deviceManager.addEventListener("deviceDown", ev => {
|
||||||
for (const instance of this.activeInstances) {
|
for (const instance of this.activeInstances) {
|
||||||
instance.contentPort.postMessage({
|
instance.contentPort.postMessage({
|
||||||
subject: "cast:receiverDeviceDown",
|
subject: "cast:receiverDeviceDown",
|
||||||
@@ -71,6 +74,12 @@ export default new (class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getInstanceByDeviceId(deviceId: string) {
|
||||||
|
for (const instance of this.activeInstances) {
|
||||||
|
if (instance.session?.deviceId === deviceId) return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a cast instance with a given port and connects messaging
|
* Creates a cast instance with a given port and connects messaging
|
||||||
* correctly depending on the type of port.
|
* correctly depending on the type of port.
|
||||||
@@ -106,7 +115,7 @@ export default new (class {
|
|||||||
|
|
||||||
// bridge -> content
|
// bridge -> content
|
||||||
instance.bridgePort.onMessage.addListener(message => {
|
instance.bridgePort.onMessage.addListener(message => {
|
||||||
contentPort.postMessage(message);
|
this.handleBridgeMessage(instance, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
// content -> (any)
|
// content -> (any)
|
||||||
@@ -160,7 +169,7 @@ export default new (class {
|
|||||||
};
|
};
|
||||||
// bridge -> content
|
// bridge -> content
|
||||||
const onBridgePortMessage = (message: Message) => {
|
const onBridgePortMessage = (message: Message) => {
|
||||||
contentPort.postMessage(message);
|
this.handleBridgeMessage(instance, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDisconnect = () => {
|
const onDisconnect = () => {
|
||||||
@@ -182,6 +191,23 @@ export default new (class {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleBridgeMessage(
|
||||||
|
instance: CastInstance,
|
||||||
|
message: Message
|
||||||
|
) {
|
||||||
|
// Intercept messages to store relevant info
|
||||||
|
switch (message.subject) {
|
||||||
|
case "cast:sessionCreated":
|
||||||
|
instance.session = {
|
||||||
|
deviceId: message.data.receiverId,
|
||||||
|
sessionId: message.data.sessionId
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.contentPort.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle content messages from the cast instance. These will either
|
* Handle content messages from the cast instance. These will either
|
||||||
* be handled here in the background script or forwarded to the
|
* be handled here in the background script or forwarded to the
|
||||||
@@ -238,48 +264,29 @@ export default new (class {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (selection.actionType) {
|
/**
|
||||||
case ReceiverSelectionActionType.Cast: {
|
* If the media type returned from the selector has
|
||||||
/**
|
* been changed, we need to cancel the current
|
||||||
* If the media type returned from the
|
* sender and switch it out for the right one.
|
||||||
* selector has been changed, we need to
|
*/
|
||||||
* cancel the current sender and switch it
|
if (selection.mediaType !== ReceiverSelectorMediaType.App) {
|
||||||
* out for the right one.
|
instance.contentPort.postMessage({
|
||||||
*/
|
subject: "cast:selectReceiver/cancelled"
|
||||||
if (
|
});
|
||||||
selection.mediaType !==
|
|
||||||
ReceiverSelectorMediaType.App
|
|
||||||
) {
|
|
||||||
instance.contentPort.postMessage({
|
|
||||||
subject: "cast:selectReceiver/cancelled"
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadSender({
|
this.loadSender({
|
||||||
tabId: instance.contentTabId,
|
tabId: instance.contentTabId,
|
||||||
frameId: instance.contentFrameId,
|
frameId: instance.contentFrameId,
|
||||||
selection
|
selection
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
instance.contentPort.postMessage({
|
|
||||||
subject: "cast:selectReceiver/selected",
|
|
||||||
data: selection
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ReceiverSelectionActionType.Stop: {
|
|
||||||
instance.contentPort.postMessage({
|
|
||||||
subject: "cast:selectReceiver/stopped",
|
|
||||||
data: selection
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance.contentPort.postMessage({
|
||||||
|
subject: "cast:selectReceiver/selected",
|
||||||
|
data: selection
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// TODO: Report errors properly
|
// TODO: Report errors properly
|
||||||
instance.contentPort.postMessage({
|
instance.contentPort.postMessage({
|
||||||
@@ -295,7 +302,7 @@ export default new (class {
|
|||||||
* same one that caused the session creation.
|
* same one that caused the session creation.
|
||||||
*/
|
*/
|
||||||
case "main:closeReceiverSelector": {
|
case "main:closeReceiverSelector": {
|
||||||
const selector = ReceiverSelector.shared;
|
const selector = ReceiverSelector.sharedInstance;
|
||||||
const shouldClose = await options.get(
|
const shouldClose = await options.get(
|
||||||
"receiverSelectorWaitForConnection"
|
"receiverSelectorWaitForConnection"
|
||||||
);
|
);
|
||||||
@@ -323,10 +330,6 @@ export default new (class {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.selection.actionType !== ReceiverSelectionActionType.Cast) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (opts.selection.mediaType) {
|
switch (opts.selection.mediaType) {
|
||||||
case ReceiverSelectorMediaType.App: {
|
case ReceiverSelectorMediaType.App: {
|
||||||
const instance = this.getInstance(opts.tabId, opts.frameId);
|
const instance = this.getInstance(opts.tabId, opts.frameId);
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import type {
|
|||||||
import { PlayerState } from "../cast/sdk/media/enums";
|
import { PlayerState } from "../cast/sdk/media/enums";
|
||||||
|
|
||||||
interface EventMap {
|
interface EventMap {
|
||||||
receiverDeviceUp: { deviceInfo: ReceiverDevice };
|
deviceUp: { deviceInfo: ReceiverDevice };
|
||||||
receiverDeviceDown: { deviceId: string };
|
deviceDown: { deviceId: string };
|
||||||
receiverDeviceUpdated: {
|
deviceUpdated: {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
status: ReceiverStatus;
|
status: ReceiverStatus;
|
||||||
};
|
};
|
||||||
receiverDeviceMediaUpdated: {
|
deviceMediaUpdated: {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
status: MediaStatus;
|
status: MediaStatus;
|
||||||
};
|
};
|
||||||
@@ -139,7 +139,7 @@ export default new (class extends TypedEventTarget<EventMap> {
|
|||||||
|
|
||||||
this.receiverDevices.set(deviceId, deviceInfo);
|
this.receiverDevices.set(deviceId, deviceInfo);
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("receiverDeviceUp", {
|
new CustomEvent("deviceUp", {
|
||||||
detail: { deviceInfo }
|
detail: { deviceInfo }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -154,7 +154,7 @@ export default new (class extends TypedEventTarget<EventMap> {
|
|||||||
this.receiverDevices.delete(deviceId);
|
this.receiverDevices.delete(deviceId);
|
||||||
}
|
}
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("receiverDeviceDown", {
|
new CustomEvent("deviceDown", {
|
||||||
detail: { deviceId: deviceId }
|
detail: { deviceId: deviceId }
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -176,7 +176,7 @@ export default new (class extends TypedEventTarget<EventMap> {
|
|||||||
device.status = status;
|
device.status = status;
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("receiverDeviceUpdated", {
|
new CustomEvent("deviceUpdated", {
|
||||||
detail: {
|
detail: {
|
||||||
deviceId,
|
deviceId,
|
||||||
status: device.status
|
status: device.status
|
||||||
@@ -202,7 +202,7 @@ export default new (class extends TypedEventTarget<EventMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("receiverDeviceMediaUpdated", {
|
new CustomEvent("deviceMediaUpdated", {
|
||||||
detail: {
|
detail: {
|
||||||
deviceId,
|
deviceId,
|
||||||
status: device.mediaStatus
|
status: device.mediaStatus
|
||||||
@@ -218,7 +218,7 @@ export default new (class extends TypedEventTarget<EventMap> {
|
|||||||
private onBridgeDisconnect = () => {
|
private onBridgeDisconnect = () => {
|
||||||
// Notify listeners of device availablility
|
// Notify listeners of device availablility
|
||||||
for (const [, receiverDevice] of this.receiverDevices) {
|
for (const [, receiverDevice] of this.receiverDevices) {
|
||||||
const event = new CustomEvent("receiverDeviceDown", {
|
const event = new CustomEvent("deviceDown", {
|
||||||
detail: { deviceId: receiverDevice.id }
|
detail: { deviceId: receiverDevice.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import logger from "../lib/logger";
|
|||||||
import options from "../lib/options";
|
import options from "../lib/options";
|
||||||
import { stringify } from "../lib/utils";
|
import { stringify } from "../lib/utils";
|
||||||
|
|
||||||
import {
|
import { ReceiverSelectorMediaType } from "../types";
|
||||||
ReceiverSelectionActionType,
|
|
||||||
ReceiverSelectorMediaType
|
|
||||||
} from "../types";
|
|
||||||
|
|
||||||
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
|
import ReceiverSelector, { ReceiverSelection } from "./ReceiverSelector";
|
||||||
import castManager from "./castManager";
|
import castManager from "./castManager";
|
||||||
@@ -167,12 +164,7 @@ async function onMenuClicked(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Invalid selection result
|
// Invalid selection result
|
||||||
if (
|
if (!selection) return;
|
||||||
!selection ||
|
|
||||||
selection.actionType !== ReceiverSelectionActionType.Cast
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (castMenuClicked) {
|
if (castMenuClicked) {
|
||||||
castManager.loadSender({
|
castManager.loadSender({
|
||||||
|
|||||||
@@ -316,26 +316,6 @@ export default class {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "cast:selectReceiver/stopped": {
|
|
||||||
const { receiverDevice } = message.data;
|
|
||||||
|
|
||||||
logger.info("Stopped receiver");
|
|
||||||
|
|
||||||
if (this.#sessionRequest) {
|
|
||||||
this.#sessionRequest = undefined;
|
|
||||||
|
|
||||||
for (const listener of this.#receiverActionListeners) {
|
|
||||||
listener(
|
|
||||||
// TODO: Use existing receiver object?
|
|
||||||
createReceiver(receiverDevice),
|
|
||||||
ReceiverAction.STOP
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popup closed before session established
|
// Popup closed before session established
|
||||||
case "cast:selectReceiver/cancelled": {
|
case "cast:selectReceiver/cancelled": {
|
||||||
if (this.#sessionRequest) {
|
if (this.#sessionRequest) {
|
||||||
@@ -349,6 +329,17 @@ export default class {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "cast:receiverStoppedAction": {
|
||||||
|
const device = this.#receiverDevices.get(message.data.deviceId);
|
||||||
|
if (!device) break;
|
||||||
|
|
||||||
|
for (const actionListener of this.#receiverActionListeners) {
|
||||||
|
actionListener(createReceiver(device), ReceiverAction.STOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Session request initiated via receiver selector
|
// Session request initiated via receiver selector
|
||||||
case "cast:launchApp": {
|
case "cast:launchApp": {
|
||||||
if (this.#sessionRequest) {
|
if (this.#sessionRequest) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ interface ReqBase {
|
|||||||
// NS: urn:x-cast:com.google.cast.receiver
|
// NS: urn:x-cast:com.google.cast.receiver
|
||||||
export type SenderMessage =
|
export type SenderMessage =
|
||||||
| (ReqBase & { type: "LAUNCH"; appId: string })
|
| (ReqBase & { type: "LAUNCH"; appId: string })
|
||||||
| (ReqBase & { type: "STOP"; sessionId: string })
|
| (ReqBase & { type: "STOP"; sessionId?: string })
|
||||||
| (ReqBase & { type: "GET_STATUS" })
|
| (ReqBase & { type: "GET_STATUS" })
|
||||||
| (ReqBase & { type: "GET_APP_AVAILABILITY"; appId: string[] })
|
| (ReqBase & { type: "GET_APP_AVAILABILITY"; appId: string[] })
|
||||||
| (ReqBase & { type: "SET_VOLUME"; volume: Partial<Volume> });
|
| (ReqBase & { type: "SET_VOLUME"; volume: Partial<Volume> });
|
||||||
|
|||||||
@@ -11,19 +11,21 @@ export class TypedEventTarget<T extends TypedEvents> extends EventTarget {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public addEventListener<K extends keyof T>(
|
public addEventListener<K extends keyof T>(
|
||||||
type: K,
|
type: K,
|
||||||
listener: (ev: CustomEvent<T[K]>) => void
|
listener: (ev: CustomEvent<T[K]>) => void,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
): void {
|
): void {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super.addEventListener(type as string, listener);
|
super.addEventListener(type as string, listener, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public removeEventListener<K extends keyof T>(
|
public removeEventListener<K extends keyof T>(
|
||||||
type: K,
|
type: K,
|
||||||
listener: (ev: CustomEvent<T[K]>) => void
|
listener: (ev: CustomEvent<T[K]>) => void,
|
||||||
|
options?: boolean | EventListenerOptions
|
||||||
): void {
|
): void {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super.removeEventListener(type as string, listener);
|
super.removeEventListener(type, listener, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispatchEvent<K extends keyof T>(ev: CustomEvent<T[K]>): boolean {
|
public dispatchEvent<K extends keyof T>(ev: CustomEvent<T[K]>): boolean {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import type { BridgeInfo } from "./lib/bridge";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ReceiverSelection,
|
ReceiverSelection,
|
||||||
ReceiverSelectionCast,
|
|
||||||
ReceiverSelectionStop,
|
|
||||||
ReceiverSelectorMediaMessage,
|
ReceiverSelectorMediaMessage,
|
||||||
ReceiverSelectorReceiverMessage
|
ReceiverSelectorReceiverMessage
|
||||||
} from "./background/receiverSelector";
|
} from "./background/receiverSelector";
|
||||||
@@ -57,16 +55,16 @@ type ExtMessageDefinitions = {
|
|||||||
"popup:close": undefined;
|
"popup:close": undefined;
|
||||||
|
|
||||||
"receiverSelector:selected": ReceiverSelection;
|
"receiverSelector:selected": ReceiverSelection;
|
||||||
"receiverSelector:stop": ReceiverSelection;
|
"receiverSelector:stop": { deviceId: string };
|
||||||
"receiverSelector:receiverMessage": ReceiverSelectorReceiverMessage;
|
"receiverSelector:receiverMessage": ReceiverSelectorReceiverMessage;
|
||||||
"receiverSelector:mediaMessage": ReceiverSelectorMediaMessage;
|
"receiverSelector:mediaMessage": ReceiverSelectorMediaMessage;
|
||||||
|
|
||||||
"main:selectReceiver": {
|
"main:selectReceiver": {
|
||||||
sessionRequest: SessionRequest;
|
sessionRequest: SessionRequest;
|
||||||
};
|
};
|
||||||
"cast:selectReceiver/selected": ReceiverSelectionCast;
|
"cast:selectReceiver/selected": ReceiverSelection;
|
||||||
"cast:selectReceiver/stopped": ReceiverSelectionStop;
|
|
||||||
"cast:selectReceiver/cancelled": undefined;
|
"cast:selectReceiver/cancelled": undefined;
|
||||||
|
"cast:receiverStoppedAction": { deviceId: string };
|
||||||
|
|
||||||
"main:closeReceiverSelector": undefined;
|
"main:closeReceiverSelector": undefined;
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ type ExtMessageDefinitions = {
|
|||||||
"cast:initialized": BridgeInfo;
|
"cast:initialized": BridgeInfo;
|
||||||
|
|
||||||
"cast:receiverDeviceUp": { receiverDevice: ReceiverDevice };
|
"cast:receiverDeviceUp": { receiverDevice: ReceiverDevice };
|
||||||
"cast:receiverDeviceDown": { receiverDeviceId: ReceiverDevice["id"] };
|
"cast:receiverDeviceDown": { receiverDeviceId: string };
|
||||||
"cast:launchApp": { receiverDevice: ReceiverDevice };
|
"cast:launchApp": { receiverDevice: ReceiverDevice };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ export enum ReceiverSelectorMediaType {
|
|||||||
Screen = 4,
|
Screen = 4,
|
||||||
File = 8
|
File = 8
|
||||||
}
|
}
|
||||||
export enum ReceiverSelectionActionType {
|
|
||||||
Cast = 1,
|
|
||||||
Stop = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Info about sender page context. */
|
/** Info about sender page context. */
|
||||||
export interface ReceiverSelectorPageInfo {
|
export interface ReceiverSelectorPageInfo {
|
||||||
url: string;
|
url: string;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
import {
|
import {
|
||||||
ReceiverDevice,
|
ReceiverDevice,
|
||||||
ReceiverDeviceCapabilities,
|
ReceiverDeviceCapabilities,
|
||||||
ReceiverSelectionActionType,
|
|
||||||
ReceiverSelectorMediaType,
|
ReceiverSelectorMediaType,
|
||||||
ReceiverSelectorPageInfo
|
ReceiverSelectorPageInfo
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
@@ -347,7 +346,6 @@
|
|||||||
subject: "receiverSelector:selected",
|
subject: "receiverSelector:selected",
|
||||||
data: {
|
data: {
|
||||||
receiverDevice,
|
receiverDevice,
|
||||||
actionType: ReceiverSelectionActionType.Cast,
|
|
||||||
mediaType
|
mediaType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -355,12 +353,17 @@
|
|||||||
|
|
||||||
function onReceiverStop(receiverDevice: ReceiverDevice) {
|
function onReceiverStop(receiverDevice: ReceiverDevice) {
|
||||||
port?.postMessage({
|
port?.postMessage({
|
||||||
subject: "receiverSelector:stop",
|
subject: "receiverSelector:receiverMessage",
|
||||||
data: {
|
data: {
|
||||||
receiverDevice,
|
deviceId: receiverDevice.id,
|
||||||
actionType: ReceiverSelectionActionType.Stop
|
message: { requestId: 0, type: "STOP" }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
port?.postMessage({
|
||||||
|
subject: "receiverSelector:stop",
|
||||||
|
data: { deviceId: receiverDevice.id }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user