mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-10 17:49:58 +00:00
Replace StatusManager
This commit is contained in:
@@ -12,7 +12,7 @@ import { ReceiverSelectionActionType
|
||||
import ReceiverSelectorManager
|
||||
from "./receiverSelector/ReceiverSelectorManager";
|
||||
|
||||
import StatusManager from "./StatusManager";
|
||||
import receiverDevices from "./receiverDevices";
|
||||
|
||||
|
||||
type AnyPort = Port | MessagePort;
|
||||
@@ -37,20 +37,20 @@ export default new class ShimManager {
|
||||
}
|
||||
});
|
||||
|
||||
StatusManager.addEventListener("serviceUp", ev => {
|
||||
receiverDevices.addEventListener("receiverDeviceUp", ev => {
|
||||
for (const shim of this.activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:serviceUp"
|
||||
, data: { id: ev.detail.id }
|
||||
, data: { id: ev.detail.receiverDevice.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
StatusManager.addEventListener("serviceDown", ev => {
|
||||
receiverDevices.addEventListener("receiverDeviceDown", ev => {
|
||||
for (const shim of this.activeShims) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:serviceDown"
|
||||
, data: { id: ev.detail.id }
|
||||
, data: { id: ev.detail.receiverDeviceId }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -170,10 +170,10 @@ export default new class ShimManager {
|
||||
case "main:shimReady": {
|
||||
shim.appId = message.data.appId;
|
||||
|
||||
for (const receiver of StatusManager.getReceivers()) {
|
||||
for (const receiverDevice of receiverDevices.getDevices()) {
|
||||
shim.contentPort.postMessage({
|
||||
subject: "shim:serviceUp"
|
||||
, data: { id: receiver.id }
|
||||
, data: { id: receiverDevice.id }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import bridge from "../lib/bridge";
|
||||
import logger from "../lib/logger";
|
||||
import { Message, Port } from "../messaging";
|
||||
|
||||
import { TypedEventTarget } from "../lib/TypedEventTarget";
|
||||
import { Receiver, ReceiverStatus } from "../types";
|
||||
|
||||
|
||||
interface EventMap {
|
||||
"serviceUp": Receiver;
|
||||
"serviceDown": { id: string };
|
||||
"statusUpdate": { id: string, status: ReceiverStatus };
|
||||
}
|
||||
|
||||
export default new class StatusManager
|
||||
extends TypedEventTarget<EventMap> {
|
||||
|
||||
private bridgePort: (Port | null) = null;
|
||||
private receivers = new Map<string, Receiver>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Bind listeners
|
||||
this.onBridgePortMessage = this.onBridgePortMessage.bind(this);
|
||||
this.onBridgePortDisconnect = this.onBridgePortDisconnect.bind(this);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
if (!this.bridgePort) {
|
||||
this.bridgePort = await this.createBridgePort();
|
||||
}
|
||||
}
|
||||
|
||||
public *getReceivers() {
|
||||
for (const [ , receiver ] of this.receivers) {
|
||||
if (receiver.status && receiver.status.application
|
||||
&& receiver.status.volume) {
|
||||
yield receiver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async stopReceiverApp(receiver: Receiver) {
|
||||
if (!this.bridgePort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bridgePort.postMessage({
|
||||
subject: "bridge:stopReceiverApp"
|
||||
, data: { receiver }
|
||||
});
|
||||
}
|
||||
|
||||
private async createBridgePort() {
|
||||
const bridgePort = await bridge.connect();
|
||||
bridgePort.onMessage.addListener(this.onBridgePortMessage);
|
||||
bridgePort.onDisconnect.addListener(this.onBridgePortDisconnect);
|
||||
|
||||
bridgePort.postMessage({
|
||||
subject: "bridge:initialize"
|
||||
, data: {
|
||||
shouldWatchStatus: true
|
||||
}
|
||||
});
|
||||
|
||||
return bridgePort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming bridge status messages, manages the
|
||||
* receiver list, and dispatches events.
|
||||
*/
|
||||
private onBridgePortMessage(message: Message) {
|
||||
switch (message.subject) {
|
||||
case "main:serviceUp": {
|
||||
const { data: receiver } = message;
|
||||
this.receivers.set(receiver.id, receiver);
|
||||
|
||||
this.dispatchEvent(new CustomEvent("serviceUp", {
|
||||
detail: receiver
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:serviceDown": {
|
||||
const { data: { id }} = message;
|
||||
|
||||
if (this.receivers.has(id)) {
|
||||
this.receivers.delete(id);
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent("serviceDown", {
|
||||
detail: { id }
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:updateReceiverStatus": {
|
||||
const { data: { id, status }} = message;
|
||||
const receiver = this.receivers.get(id);
|
||||
|
||||
if (!receiver) {
|
||||
throw logger.error(`Could not find receiver (${id}) specified in status message.`);
|
||||
}
|
||||
|
||||
// Merge with existing
|
||||
this.receivers.set(id, {
|
||||
...receiver
|
||||
, status: {
|
||||
...receiver.status
|
||||
, ...status
|
||||
}
|
||||
});
|
||||
|
||||
this.dispatchEvent(new CustomEvent("statusUpdate", {
|
||||
detail: { id, status }
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs once the status bridge has disconnected. Sends
|
||||
* serviceDown messages for all receivers to all shims to
|
||||
* update receiver availability, then clears the receiver
|
||||
* list.
|
||||
*
|
||||
* Attempts to reinitialize the status bridge after 10
|
||||
* seconds. If it fails immediately, this handler will be
|
||||
* triggered again and the timer is reset for another 10
|
||||
* seconds.
|
||||
*/
|
||||
private onBridgePortDisconnect() {
|
||||
for (const [ , receiver ] of this.receivers) {
|
||||
const serviceDownEvent = new CustomEvent("serviceDown", {
|
||||
detail: { id: receiver.id }
|
||||
});
|
||||
|
||||
this.dispatchEvent(serviceDownEvent);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
this.receivers.clear();
|
||||
|
||||
if (this.bridgePort) {
|
||||
this.bridgePort.onDisconnect.removeListener(
|
||||
this.onBridgePortDisconnect);
|
||||
this.bridgePort.onMessage.removeListener(
|
||||
this.onBridgePortMessage);
|
||||
|
||||
this.bridgePort = null;
|
||||
}
|
||||
|
||||
window.setTimeout(async () => {
|
||||
this.bridgePort = await this.createBridgePort();
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
@@ -11,7 +11,8 @@ import ReceiverSelectorManager
|
||||
from "./receiverSelector/ReceiverSelectorManager";
|
||||
|
||||
import ShimManager from "./ShimManager";
|
||||
import StatusManager from "./StatusManager";
|
||||
|
||||
import receiverDevices from "./receiverDevices";
|
||||
|
||||
import { initMenus } from "./menus";
|
||||
import { initWhitelist } from "./whitelist";
|
||||
@@ -155,7 +156,7 @@ async function init() {
|
||||
|
||||
await notifyBridgeCompat();
|
||||
|
||||
await StatusManager.init();
|
||||
await receiverDevices.init();
|
||||
await ShimManager.init();
|
||||
|
||||
await initMenus();
|
||||
|
||||
167
ext/src/background/receiverDevices.ts
Normal file
167
ext/src/background/receiverDevices.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
"use strict";
|
||||
|
||||
import bridge from "../lib/bridge";
|
||||
import logger from "../lib/logger";
|
||||
import { TypedEventTarget } from "../lib/TypedEventTarget";
|
||||
|
||||
import messaging, { Message, Port } from "../messaging";
|
||||
import { ReceiverDevice, ReceiverStatus } from "../types";
|
||||
|
||||
|
||||
interface EventMap {
|
||||
"receiverDeviceUp": { receiverDevice: ReceiverDevice }
|
||||
, "receiverDeviceDown": { receiverDeviceId: string }
|
||||
, "receiverDeviceUpdated": {
|
||||
receiverDeviceId: string
|
||||
, status: ReceiverStatus
|
||||
}
|
||||
}
|
||||
|
||||
export default new class extends TypedEventTarget<EventMap> {
|
||||
/**
|
||||
* Map of receiver device IDs to devices. Updated as
|
||||
* receiverDevice messages are received from the bridge.
|
||||
*/
|
||||
private receiverDevices = new Map<string, ReceiverDevice>();
|
||||
|
||||
private bridgePort?: Port;
|
||||
|
||||
|
||||
async init() {
|
||||
if (!this.bridgePort) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize (or re-initialize) a bridge connection to
|
||||
* start dispatching events.
|
||||
*/
|
||||
async refresh() {
|
||||
this.bridgePort?.disconnect();
|
||||
|
||||
const port = await bridge.connect();
|
||||
|
||||
port.onMessage.addListener(this.onBridgeMessage);
|
||||
port.onDisconnect.addListener(this.onBridgeDisconnect);
|
||||
|
||||
port.postMessage({
|
||||
subject: "bridge:initialize"
|
||||
, data: {
|
||||
// Also send back status messages
|
||||
shouldWatchStatus: true
|
||||
}
|
||||
});
|
||||
|
||||
this.bridgePort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of receiver devices
|
||||
*/
|
||||
getDevices() {
|
||||
return Array.from(this.receiverDevices.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a receiver app running on a given device.
|
||||
*/
|
||||
stopReceiverApp(receiverDeviceId: string) {
|
||||
if (!this.bridgePort) {
|
||||
logger.error("Failed to stop receiver device, no bridge connection");
|
||||
return;
|
||||
}
|
||||
|
||||
const receiverDevice = this.receiverDevices.get(receiverDeviceId);
|
||||
if (receiverDevice) {
|
||||
this.bridgePort.postMessage({
|
||||
subject: "bridge:stopReceiverApp"
|
||||
, data: { receiverDevice }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onBridgeMessage = (message: Message) => {
|
||||
switch (message.subject) {
|
||||
case "main:receiverDeviceUp": {
|
||||
const { receiverDevice } = message.data;
|
||||
|
||||
this.receiverDevices.set(receiverDevice.id, receiverDevice);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("receiverDeviceUp"
|
||||
, {
|
||||
detail: { receiverDevice }
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:receiverDeviceDown": {
|
||||
const { receiverDeviceId } = message.data;
|
||||
|
||||
if (this.receiverDevices.has(receiverDeviceId)) {
|
||||
this.receiverDevices.delete(receiverDeviceId);
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("receiverDeviceDown"
|
||||
, {
|
||||
detail: { receiverDeviceId }
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "main:receiverDeviceUpdated": {
|
||||
const { receiverDeviceId, status } = message.data;
|
||||
const receiverDevice =
|
||||
this.receiverDevices.get(receiverDeviceId);
|
||||
|
||||
if (!receiverDevice) {
|
||||
logger.error(`Receiver ID \`${receiverDeviceId}\` not found!`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (receiverDevice.status) {
|
||||
receiverDevice.status.isActiveInput = status.isActiveInput;
|
||||
receiverDevice.status.isStandBy = status.isStandBy;
|
||||
receiverDevice.status.userEq = status.userEq;
|
||||
receiverDevice.status.volume = status.volume;
|
||||
|
||||
if (status.applications) {
|
||||
receiverDevice.status.applications =
|
||||
status.applications;
|
||||
}
|
||||
} else {
|
||||
receiverDevice.status = status;
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("receiverDeviceUpdated"
|
||||
, {
|
||||
detail: {
|
||||
receiverDeviceId
|
||||
, status: receiverDevice.status
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onBridgeDisconnect = () => {
|
||||
// Notify listeners of device availablility
|
||||
for (const [ , receiverDevice ] of this.receiverDevices) {
|
||||
const event = new CustomEvent("receiverDeviceDown", {
|
||||
detail: { receiverDeviceId: receiverDevice.id }
|
||||
});
|
||||
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
this.receiverDevices.clear();
|
||||
|
||||
// Re-initialize after 10 seconds
|
||||
window.setTimeout(() => {
|
||||
this.refresh();
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import options from "../../lib/options";
|
||||
|
||||
import { TypedEventTarget } from "../../lib/TypedEventTarget";
|
||||
import { getWindowCenteredProps } from "../../lib/utils";
|
||||
import { Receiver } from "../../types";
|
||||
import { ReceiverDevice } from "../../types";
|
||||
|
||||
import ReceiverSelector, {
|
||||
ReceiverSelection
|
||||
@@ -34,7 +34,7 @@ export default class NativeReceiverSelector extends ReceiverSelector {
|
||||
}
|
||||
|
||||
public async open(
|
||||
receivers: Receiver[]
|
||||
receivers: ReceiverDevice[]
|
||||
, defaultMediaType: ReceiverSelectorMediaType
|
||||
, availableMediaTypes: ReceiverSelectorMediaType
|
||||
, appId?: string): Promise<void> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import options from "../../lib/options";
|
||||
|
||||
import { TypedEventTarget } from "../../lib/TypedEventTarget";
|
||||
import { getWindowCenteredProps, WindowCenteredProps } from "../../lib/utils";
|
||||
import { Receiver } from "../../types";
|
||||
import { ReceiverDevice } from "../../types";
|
||||
|
||||
|
||||
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
|
||||
@@ -20,7 +20,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
|
||||
private messagePort?: Port;
|
||||
private messagePortDisconnected?: boolean;
|
||||
|
||||
private receivers?: Receiver[];
|
||||
private receivers?: ReceiverDevice[];
|
||||
private defaultMediaType?: ReceiverSelectorMediaType;
|
||||
private availableMediaTypes?: ReceiverSelectorMediaType;
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
|
||||
}
|
||||
|
||||
public async open(
|
||||
receivers: Receiver[]
|
||||
receivers: ReceiverDevice[]
|
||||
, defaultMediaType: ReceiverSelectorMediaType
|
||||
, availableMediaTypes: ReceiverSelectorMediaType
|
||||
, appId?: string): Promise<void> {
|
||||
@@ -115,7 +115,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
|
||||
}
|
||||
}
|
||||
|
||||
public update(receivers: Receiver[]) {
|
||||
public update(receivers: ReceiverDevice[]) {
|
||||
this.receivers = receivers;
|
||||
this.messagePort?.postMessage({
|
||||
subject: "popup:update"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import { TypedEventTarget } from "../../lib/TypedEventTarget";
|
||||
import { Receiver } from "../../types";
|
||||
import { ReceiverDevice } from "../../types";
|
||||
|
||||
|
||||
export enum ReceiverSelectorMediaType {
|
||||
@@ -18,13 +18,13 @@ export enum ReceiverSelectionActionType {
|
||||
|
||||
export interface ReceiverSelectionCast {
|
||||
actionType: ReceiverSelectionActionType.Cast;
|
||||
receiver: Receiver;
|
||||
receiver: ReceiverDevice;
|
||||
mediaType: ReceiverSelectorMediaType;
|
||||
filePath?: string;
|
||||
}
|
||||
export interface ReceiverSelectionStop {
|
||||
actionType: ReceiverSelectionActionType.Stop;
|
||||
receiver: Receiver;
|
||||
receiver: ReceiverDevice;
|
||||
}
|
||||
|
||||
export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;
|
||||
@@ -43,12 +43,12 @@ export default abstract class ReceiverSelector
|
||||
abstract readonly isOpen: boolean;
|
||||
|
||||
abstract open (
|
||||
receivers: Receiver[]
|
||||
receivers: ReceiverDevice[]
|
||||
, defaultMediaType: ReceiverSelectorMediaType
|
||||
, availableMediaTypes: ReceiverSelectorMediaType
|
||||
, appId?: string): void;
|
||||
|
||||
abstract update (receivers: Receiver[]): void;
|
||||
abstract update (receivers: ReceiverDevice[]): void;
|
||||
|
||||
abstract close (): void;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import options from "../../lib/options";
|
||||
import logger from "../../lib/logger";
|
||||
|
||||
import ShimManager from "../ShimManager";
|
||||
import StatusManager from "../StatusManager";
|
||||
import receiverDevices from "../receiverDevices";
|
||||
|
||||
import { getMediaTypesForPageUrl } from "../../lib/utils";
|
||||
|
||||
@@ -120,12 +120,15 @@ async function getSelection(
|
||||
|
||||
|
||||
function onReceiverChange() {
|
||||
sharedSelector.update(Array.from(StatusManager.getReceivers()));
|
||||
sharedSelector.update(receiverDevices.getDevices());
|
||||
}
|
||||
|
||||
StatusManager.addEventListener("serviceUp", onReceiverChange);
|
||||
StatusManager.addEventListener("serviceDown", onReceiverChange);
|
||||
StatusManager.addEventListener("statusUpdate", onReceiverChange);
|
||||
receiverDevices.addEventListener(
|
||||
"receiverDeviceUp", onReceiverChange);
|
||||
receiverDevices.addEventListener(
|
||||
"receiverDeviceDown", onReceiverChange);
|
||||
receiverDevices.addEventListener(
|
||||
"receiverDeviceUpdated", onReceiverChange);
|
||||
|
||||
|
||||
let onSelected: any;
|
||||
@@ -156,9 +159,12 @@ async function getSelection(
|
||||
sharedSelector.removeEventListener("error", onError);
|
||||
sharedSelector.removeEventListener("stop", onStop);
|
||||
|
||||
StatusManager.removeEventListener("serviceUp", onReceiverChange);
|
||||
StatusManager.removeEventListener("serviceDown", onReceiverChange);
|
||||
StatusManager.removeEventListener("statusUpdate", onReceiverChange);
|
||||
receiverDevices.removeEventListener(
|
||||
"receiverDeviceUp", onReceiverChange);
|
||||
receiverDevices.removeEventListener(
|
||||
"receiverDeviceDown", onReceiverChange);
|
||||
receiverDevices.removeEventListener(
|
||||
"receiverDeviceUpdated", onReceiverChange);
|
||||
}
|
||||
|
||||
sharedSelector.addEventListener("selected"
|
||||
@@ -191,10 +197,9 @@ async function getSelection(
|
||||
sharedSelector.addEventListener("stop"
|
||||
, storeListener("stop", async ev => {
|
||||
|
||||
logger.info("Stopped receiver app", ev.detail);
|
||||
logger.info("Stopping receiver app...", ev.detail);
|
||||
|
||||
await StatusManager.init();
|
||||
await StatusManager.stopReceiverApp(ev.detail.receiver);
|
||||
receiverDevices.stopReceiverApp(ev.detail.receiver.id);
|
||||
|
||||
resolve({
|
||||
actionType: ReceiverSelectionActionType.Stop
|
||||
@@ -205,10 +210,10 @@ async function getSelection(
|
||||
|
||||
|
||||
// Ensure status manager is initialized
|
||||
await StatusManager.init();
|
||||
await receiverDevices.init();
|
||||
|
||||
sharedSelector.open(
|
||||
Array.from(StatusManager.getReceivers())
|
||||
receiverDevices.getDevices()
|
||||
, defaultMediaType
|
||||
, availableMediaTypes
|
||||
, currentShim?.appId);
|
||||
|
||||
Reference in New Issue
Block a user