mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-09 17:19:59 +00:00
Misc fixes/improvements from WIP branches
This commit is contained in:
@@ -1,76 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
import { Channel, Client } from "castv2";
|
||||
import { Channel } from "castv2";
|
||||
|
||||
import { sendMessage } from "../../lib/nativeMessaging";
|
||||
|
||||
import { ReceiverDevice } from "../../types";
|
||||
import { ReceiverApplication, ReceiverMessage, SenderMessage } from "./types";
|
||||
|
||||
export const NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection";
|
||||
export const NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat";
|
||||
export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver";
|
||||
|
||||
const HEARTBEAT_INTERVAL = 5000;
|
||||
|
||||
class CastClient {
|
||||
protected client = new Client();
|
||||
|
||||
protected connectionChannel?: Channel;
|
||||
protected heartbeatChannel?: Channel;
|
||||
protected heartbeatIntervalId?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
protected sourceId = "sender-0",
|
||||
protected destinationId = "receiver-0"
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a channel on the client connection with a given
|
||||
* namespace.
|
||||
*/
|
||||
createChannel(
|
||||
namespace: string,
|
||||
sourceId = this.sourceId,
|
||||
destinationId = this.destinationId
|
||||
) {
|
||||
return this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
namespace,
|
||||
"JSON"
|
||||
);
|
||||
}
|
||||
|
||||
connect(host: string, port: number, onHeartbeat?: () => void) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Handle errors
|
||||
this.client.on("error", reject);
|
||||
this.client.on("close", () => {
|
||||
if (this.heartbeatChannel && this.heartbeatIntervalId) {
|
||||
clearInterval(this.heartbeatIntervalId);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.connect({ host, port }, () => {
|
||||
this.connectionChannel = this.createChannel(NS_CONNECTION);
|
||||
this.heartbeatChannel = this.createChannel(NS_HEARTBEAT);
|
||||
|
||||
this.connectionChannel.send({ type: "CONNECT" });
|
||||
this.heartbeatChannel.send({ type: "PING" });
|
||||
|
||||
this.heartbeatIntervalId = setInterval(() => {
|
||||
this.heartbeatChannel?.send({ type: "PING" });
|
||||
if (onHeartbeat) {
|
||||
onHeartbeat();
|
||||
}
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
import CastClient, { NS_CONNECTION, NS_HEARTBEAT, NS_RECEIVER } from "./client";
|
||||
|
||||
type OnSessionCreatedCallback = (sessionId: string) => void;
|
||||
|
||||
@@ -96,8 +33,6 @@ export default class Session extends CastClient {
|
||||
*/
|
||||
private launchRequestId?: number;
|
||||
|
||||
private onSessionCreated?: OnSessionCreatedCallback;
|
||||
|
||||
private establishAppConnection(transportId: string) {
|
||||
this.transportConnection = this.createChannel(
|
||||
NS_CONNECTION,
|
||||
@@ -236,7 +171,11 @@ export default class Session extends CastClient {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
constructor(public appId: string, public receiverDevice: ReceiverDevice) {
|
||||
constructor(
|
||||
private appId: string,
|
||||
private receiverDevice: ReceiverDevice,
|
||||
private onSessionCreated?: OnSessionCreatedCallback
|
||||
) {
|
||||
super();
|
||||
|
||||
this.client.on("close", () => {
|
||||
@@ -247,27 +186,19 @@ export default class Session extends CastClient {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async connect(
|
||||
host: string,
|
||||
port: number,
|
||||
onSessionCreated?: OnSessionCreatedCallback
|
||||
) {
|
||||
if (onSessionCreated) {
|
||||
this.onSessionCreated = onSessionCreated;
|
||||
}
|
||||
|
||||
await super.connect(host, port, () => {
|
||||
// Include transport heartbeat with platform heartbeat
|
||||
if (this.transportHeartbeat) {
|
||||
this.transportHeartbeat.send({ type: "PING" });
|
||||
super.connect(receiverDevice.host, {
|
||||
onHeartbeat: () => {
|
||||
// Include transport heartbeat with platform heartbeat
|
||||
if (this.transportHeartbeat) {
|
||||
this.transportHeartbeat.send({ type: "PING" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.launchRequestId = this.sendReceiverMessage({
|
||||
type: "LAUNCH",
|
||||
appId: this.appId
|
||||
}).then(() => {
|
||||
this.launchRequestId = this.sendReceiverMessage({
|
||||
type: "LAUNCH",
|
||||
appId: this.appId
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
90
app/src/bridge/components/cast/client.ts
Normal file
90
app/src/bridge/components/cast/client.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
|
||||
import { Channel, Client } from "castv2";
|
||||
|
||||
export const NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection";
|
||||
export const NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat";
|
||||
export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver";
|
||||
|
||||
const DEFAULT_PORT = 8009;
|
||||
const HEARTBEAT_INTERVAL_MS = 5000;
|
||||
|
||||
interface CastClientConnectOptions {
|
||||
port?: number;
|
||||
onHeartbeat?: () => void;
|
||||
}
|
||||
|
||||
export default class CastClient {
|
||||
protected client = new Client();
|
||||
|
||||
protected connectionChannel?: Channel;
|
||||
protected heartbeatChannel?: Channel;
|
||||
protected heartbeatIntervalId?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
protected sourceId = "sender-0",
|
||||
protected destinationId = "receiver-0"
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a channel on the client connection with a given
|
||||
* namespace.
|
||||
*/
|
||||
protected createChannel(
|
||||
namespace: string,
|
||||
sourceId = this.sourceId,
|
||||
destinationId = this.destinationId
|
||||
) {
|
||||
return this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
namespace,
|
||||
"JSON"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a cast receiver at a given host, returning a
|
||||
* promise that resolves once the client is connected.
|
||||
*/
|
||||
connect(host: string, options?: CastClientConnectOptions) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Handle errors
|
||||
this.client.on("error", reject);
|
||||
this.client.on("close", () => {
|
||||
if (this.heartbeatChannel && this.heartbeatIntervalId) {
|
||||
clearInterval(this.heartbeatIntervalId);
|
||||
}
|
||||
});
|
||||
|
||||
const connectOpts = {
|
||||
host,
|
||||
port: options?.port ?? DEFAULT_PORT
|
||||
};
|
||||
|
||||
this.client.connect(connectOpts, () => {
|
||||
this.connectionChannel = this.createChannel(NS_CONNECTION);
|
||||
this.heartbeatChannel = this.createChannel(NS_HEARTBEAT);
|
||||
|
||||
this.connectionChannel.send({ type: "CONNECT" });
|
||||
this.heartbeatChannel.send({ type: "PING" });
|
||||
|
||||
this.heartbeatIntervalId = setInterval(() => {
|
||||
this.heartbeatChannel?.send({ type: "PING" });
|
||||
options?.onHeartbeat?.();
|
||||
}, HEARTBEAT_INTERVAL_MS);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.heartbeatIntervalId) {
|
||||
clearInterval(this.heartbeatIntervalId);
|
||||
}
|
||||
|
||||
this.connectionChannel?.send({ type: "CLOSE" });
|
||||
this.client.close();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import castv2 from "castv2";
|
||||
import { sendMessage } from "../../lib/nativeMessaging";
|
||||
import { Message } from "../../messaging";
|
||||
|
||||
import Session, { NS_CONNECTION, NS_RECEIVER } from "./Session";
|
||||
import Session from "./Session";
|
||||
import { NS_CONNECTION, NS_RECEIVER } from "./client";
|
||||
|
||||
|
||||
const sessions = new Map<string, Session>();
|
||||
|
||||
@@ -15,14 +17,9 @@ export function handleCastMessage(message: Message) {
|
||||
const { appId, receiverDevice } = message.data;
|
||||
|
||||
// Connect and store with returned ID
|
||||
const session = new Session(appId, receiverDevice);
|
||||
session.connect(
|
||||
receiverDevice.host,
|
||||
receiverDevice.port,
|
||||
sessionId => {
|
||||
sessions.set(sessionId, session);
|
||||
}
|
||||
);
|
||||
const session = new Session(appId, receiverDevice, sessionId => {
|
||||
sessions.set(sessionId, session);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
117
app/src/bridge/components/cast/remote.ts
Normal file
117
app/src/bridge/components/cast/remote.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
"use strict";
|
||||
|
||||
import CastClient, { NS_RECEIVER } from "./client";
|
||||
|
||||
import {
|
||||
MediaStatus,
|
||||
ReceiverMessage,
|
||||
ReceiverMediaMessage,
|
||||
ReceiverStatus,
|
||||
SenderMediaMessage
|
||||
} from "./types";
|
||||
|
||||
const NS_MEDIA = "urn:x-cast:com.google.cast.media";
|
||||
|
||||
interface CastRemoteOptions {
|
||||
onApplicationFound?: () => void;
|
||||
onApplicationClose?: () => void;
|
||||
onReceiverStatusUpdate?: (status: ReceiverStatus) => void;
|
||||
onMediaStatusUpdate?: (status?: MediaStatus) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* castv2 client for receiver tracking.
|
||||
*/
|
||||
export default class Remote extends CastClient {
|
||||
private transportClient?: RemoteTransport;
|
||||
|
||||
constructor(private host: string, private options?: CastRemoteOptions) {
|
||||
super();
|
||||
this.connect(host).then(() => {
|
||||
// Request receiver status
|
||||
const receiverChannel = this.createChannel(NS_RECEIVER);
|
||||
receiverChannel.on("message", message => {
|
||||
this.onReceiverMessage(message);
|
||||
});
|
||||
receiverChannel.send({ type: "GET_STATUS", requestId: 1 });
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
this.transportClient?.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `NS_RECEIVER` messages from the receiver device.
|
||||
* On initial connection, a `GET_STATUS` message is sent that
|
||||
* results in a `RECEIVER_STATUS` response. If an application
|
||||
* is running, get the transport ID and make a connection to
|
||||
* fetch media status updates.
|
||||
*/
|
||||
private onReceiverMessage(message: ReceiverMessage) {
|
||||
if (message.type !== "RECEIVER_STATUS") {
|
||||
return;
|
||||
}
|
||||
|
||||
const application = message.status.applications?.[0];
|
||||
if (!application || application.isIdleScreen) {
|
||||
// Handle app close
|
||||
if (this.transportClient) {
|
||||
this.transportClient = undefined;
|
||||
this.options?.onApplicationClose?.();
|
||||
}
|
||||
}
|
||||
|
||||
// Update status before possible transport init
|
||||
this.options?.onReceiverStatusUpdate?.(message.status);
|
||||
|
||||
// Handle app creation/discovery
|
||||
if (application && !this.transportClient) {
|
||||
this.transportClient = new RemoteTransport(
|
||||
application.transportId,
|
||||
message => this.onMediaMessage(message)
|
||||
);
|
||||
|
||||
this.transportClient.connect(this.host).then(() => {
|
||||
this.transportClient?.sendMediaMessage({
|
||||
type: "GET_STATUS"
|
||||
});
|
||||
});
|
||||
|
||||
this.options?.onApplicationFound?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `NS_MEDIA` messages from the receiver application.
|
||||
* On initial connection. a `GET_STATUS` message is sent that
|
||||
* results in a `MEDIA_STATUS` response.
|
||||
*/
|
||||
private onMediaMessage(message: ReceiverMediaMessage) {
|
||||
if (message.type !== "MEDIA_STATUS") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options?.onMediaStatusUpdate?.(message.status[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* castv2 client for receiver application tracking.
|
||||
*/
|
||||
class RemoteTransport extends CastClient {
|
||||
private mediaChannel = this.createChannel(NS_MEDIA);
|
||||
|
||||
constructor(
|
||||
transportId: string,
|
||||
onMediaMessage: (message: ReceiverMediaMessage) => void
|
||||
) {
|
||||
super(undefined, transportId);
|
||||
this.mediaChannel.on("message", message => onMediaMessage(message));
|
||||
}
|
||||
|
||||
sendMediaMessage(message: SenderMediaMessage) {
|
||||
this.mediaChannel.send(message);
|
||||
}
|
||||
}
|
||||
@@ -353,9 +353,19 @@ interface MediaReqBase extends ReqBase {
|
||||
export type SenderMediaMessage =
|
||||
| (MediaReqBase & { type: "PLAY" })
|
||||
| (MediaReqBase & { type: "PAUSE" })
|
||||
| (MediaReqBase & { type: "MEDIA_GET_STATUS" })
|
||||
| {
|
||||
type: "MEDIA_GET_STATUS";
|
||||
mediaSessionId?: number;
|
||||
customData?: unknown;
|
||||
}
|
||||
| {
|
||||
type: "GET_STATUS";
|
||||
mediaSessionId?: number;
|
||||
customData?: unknown;
|
||||
}
|
||||
| (MediaReqBase & { type: "STOP" })
|
||||
| (MediaReqBase & { type: "MEDIA_SET_VOLUME"; volume: Volume })
|
||||
| (MediaReqBase & { type: "SET_VOLUME"; volume: Volume })
|
||||
| (MediaReqBase & { type: "SET_PLAYBACK_RATE"; playbackRate: number })
|
||||
| (ReqBase & {
|
||||
type: "LOAD";
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { Channel, Client } from "castv2";
|
||||
|
||||
import mdns from "mdns";
|
||||
|
||||
import Remote from "./cast/remote";
|
||||
import { ReceiverDevice } from "../types";
|
||||
import { sendMessage } from "../lib/nativeMessaging";
|
||||
|
||||
import { ReceiverStatus } from "./cast/types";
|
||||
import { NS_CONNECTION, NS_HEARTBEAT, NS_RECEIVER } from "./cast/Session";
|
||||
|
||||
interface CastTxtRecord {
|
||||
/**
|
||||
* Chromecast TXT record
|
||||
*/
|
||||
interface CastRecord {
|
||||
// Device ID
|
||||
id: string;
|
||||
// Model name (e.g. Chromecast, Google Nest Mini, etc...)
|
||||
md: string;
|
||||
// Friendly name (user-visible)
|
||||
fn: string;
|
||||
// Version (?)
|
||||
ve: string;
|
||||
// Icon path (?)
|
||||
ic: string;
|
||||
|
||||
cd: string;
|
||||
rm: string;
|
||||
ve: string;
|
||||
md: string;
|
||||
ic: string;
|
||||
fn: string;
|
||||
ca: string;
|
||||
st: string;
|
||||
bs: string;
|
||||
@@ -37,89 +41,15 @@ const browser = mdns.createBrowser(mdns.tcp("googlecast"), {
|
||||
]
|
||||
});
|
||||
|
||||
function onBrowserServiceUp(service: mdns.Service) {
|
||||
// Ignore without txt record / name
|
||||
if (!service.txtRecord || !service.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const txtRecord = service.txtRecord as CastTxtRecord;
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceUp",
|
||||
data: {
|
||||
receiverDevice: {
|
||||
host: service.addresses[0],
|
||||
port: service.port,
|
||||
id: service.name,
|
||||
friendlyName: txtRecord.fn
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onBrowserServiceDown(service: mdns.Service) {
|
||||
// Ignore without name
|
||||
if (!service.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const txtRecord = service.txtRecord as CastTxtRecord;
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceDown",
|
||||
data: { receiverDeviceId: service.name }
|
||||
});
|
||||
}
|
||||
|
||||
browser.on("serviceUp", onBrowserServiceUp);
|
||||
browser.on("serviceDown", onBrowserServiceDown);
|
||||
|
||||
interface InitializeOptions {
|
||||
shouldWatchStatus?: boolean;
|
||||
}
|
||||
|
||||
let shouldWatchStatus: boolean;
|
||||
export function startDiscovery(options: InitializeOptions) {
|
||||
if (options.shouldWatchStatus) {
|
||||
browser.on("serviceUp", onStatusBrowserServiceUp);
|
||||
browser.on("serviceDown", onStatusBrowserServiceDown);
|
||||
}
|
||||
shouldWatchStatus = options.shouldWatchStatus ?? false;
|
||||
|
||||
browser.start();
|
||||
|
||||
// Receiver status listeners for status mode
|
||||
const statusListeners = new Map<string, StatusListener>();
|
||||
|
||||
function onStatusBrowserServiceUp(service: mdns.Service) {
|
||||
if (!service.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = new StatusListener(service.addresses[0], service.port);
|
||||
|
||||
listener.on("receiverStatus", (status: ReceiverStatus) => {
|
||||
if (!service.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceUpdated",
|
||||
data: {
|
||||
receiverDeviceId: service.name,
|
||||
status
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
statusListeners.set(service.name, listener);
|
||||
}
|
||||
|
||||
function onStatusBrowserServiceDown(service: mdns.Service) {
|
||||
if (!service.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = statusListeners.get(service.name);
|
||||
listener?.deregister();
|
||||
}
|
||||
}
|
||||
|
||||
export function stopDiscovery() {
|
||||
@@ -127,91 +57,73 @@ export function stopDiscovery() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection to a receiver device and forwards
|
||||
* RECEIVER_STATUS updates to the extension.
|
||||
* Map of device IDs to remote instances.
|
||||
*/
|
||||
export default class StatusListener extends EventEmitter {
|
||||
private client: Client;
|
||||
private clientReceiver?: Channel;
|
||||
private clientHeartbeatIntervalId?: NodeJS.Timeout;
|
||||
const remotes = new Map<string, Remote>();
|
||||
|
||||
constructor(host: string, port: number) {
|
||||
super();
|
||||
/**
|
||||
* When a service is found, gather device info from service object and
|
||||
* TXT record, then send a `main:receiverDeviceUp` message.
|
||||
*/
|
||||
browser.on("serviceUp", service => {
|
||||
// Filter invalid results
|
||||
if (!service.txtRecord || !service.name) return;
|
||||
|
||||
this.client = new Client();
|
||||
this.client.connect({ host, port }, this.onConnect.bind(this));
|
||||
const record = service.txtRecord as CastRecord;
|
||||
const device: ReceiverDevice = {
|
||||
host: service.addresses[0],
|
||||
port: service.port,
|
||||
id: service.name,
|
||||
friendlyName: record.fn
|
||||
};
|
||||
|
||||
this.client.on("close", () => {
|
||||
clearInterval(this.clientHeartbeatIntervalId!);
|
||||
});
|
||||
|
||||
this.client.on("error", () => {
|
||||
clearInterval(this.clientHeartbeatIntervalId!);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes status listener connection.
|
||||
*/
|
||||
public deregister(): void {
|
||||
try {
|
||||
this.clientReceiver?.send({ type: "CLOSE" });
|
||||
} catch (err) {
|
||||
// Supress
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceUp",
|
||||
data: {
|
||||
deviceId: service.name,
|
||||
deviceInfo: device
|
||||
}
|
||||
});
|
||||
|
||||
this.client.close();
|
||||
if (shouldWatchStatus) {
|
||||
remotes.set(
|
||||
service.name,
|
||||
new Remote(device.host, {
|
||||
// RECEIVER_STATUS
|
||||
onReceiverStatusUpdate(status) {
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceStatusUpdated",
|
||||
data: { deviceId: device.id, status }
|
||||
});
|
||||
},
|
||||
// MEDIA_STATUS
|
||||
onMediaStatusUpdate(status) {
|
||||
if (!status) return;
|
||||
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceMediaStatusUpdated",
|
||||
data: { deviceId: device.id, status }
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
private onConnect(): void {
|
||||
const sourceId = "sender-0";
|
||||
const destinationId = "receiver-0";
|
||||
/**
|
||||
* When a service is lost, send a `main:receiverDeviceDown` message with
|
||||
* the service name as the `deviceId`.
|
||||
*/
|
||||
browser.on("serviceDown", service => {
|
||||
// Filter invalid results
|
||||
if (!service.name) return;
|
||||
|
||||
const clientConnection = this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_CONNECTION,
|
||||
"JSON"
|
||||
);
|
||||
const clientHeartbeat = this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_HEARTBEAT,
|
||||
"JSON"
|
||||
);
|
||||
const clientReceiver = this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_RECEIVER,
|
||||
"JSON"
|
||||
);
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceDown",
|
||||
data: { deviceId: service.name }
|
||||
});
|
||||
|
||||
clientReceiver.on("message", data => {
|
||||
switch (data.type) {
|
||||
case "CLOSE": {
|
||||
this.client.close();
|
||||
break;
|
||||
}
|
||||
|
||||
case "RECEIVER_STATUS": {
|
||||
this.emit("receiverStatus", data.status);
|
||||
break;
|
||||
}
|
||||
case "MEDIA_STATUS": {
|
||||
this.emit("mediaStatus", data.status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clientConnection.send({ type: "CONNECT" });
|
||||
clientHeartbeat.send({ type: "PING" });
|
||||
clientReceiver.send({ type: "GET_STATUS", requestId: 1 });
|
||||
|
||||
this.clientReceiver = clientReceiver;
|
||||
|
||||
this.clientHeartbeatIntervalId = setInterval(() => {
|
||||
clientHeartbeat.send({ type: "PING" });
|
||||
}, 5000);
|
||||
if (shouldWatchStatus) {
|
||||
remotes.get(service.name)?.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
Image,
|
||||
MediaStatus,
|
||||
ReceiverStatus,
|
||||
SenderApplication,
|
||||
SenderMessage,
|
||||
@@ -30,6 +31,11 @@ interface CastSessionCreated extends CastSessionUpdated {
|
||||
transportId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages that cross the native messaging channel. MUST keep
|
||||
* in-sync with the extension's version at:
|
||||
* ext/src/messaging.ts > MessageDefinitions
|
||||
*/
|
||||
type MessageDefinitions = {
|
||||
"shim:castSessionCreated": CastSessionCreated;
|
||||
"shim:castSessionUpdated": CastSessionUpdated;
|
||||
@@ -98,12 +104,16 @@ type MessageDefinitions = {
|
||||
id: string;
|
||||
status: ReceiverStatus;
|
||||
};
|
||||
"main:receiverDeviceUp": { receiverDevice: ReceiverDevice };
|
||||
"main:receiverDeviceDown": { receiverDeviceId: string };
|
||||
"main:receiverDeviceUpdated": {
|
||||
receiverDeviceId: string;
|
||||
"main:receiverDeviceUp": { deviceId: string, deviceInfo: ReceiverDevice };
|
||||
"main:receiverDeviceDown": { deviceId: string };
|
||||
"main:receiverDeviceStatusUpdated": {
|
||||
deviceId: string;
|
||||
status: ReceiverStatus;
|
||||
};
|
||||
"main:receiverDeviceMediaStatusUpdated": {
|
||||
deviceId: string;
|
||||
status: MediaStatus;
|
||||
};
|
||||
};
|
||||
|
||||
interface MessageBase<K extends keyof MessageDefinitions> {
|
||||
@@ -116,8 +126,8 @@ type Messages = {
|
||||
};
|
||||
|
||||
/**
|
||||
* For better call semantics, make message data key optional if
|
||||
* specified as blank or with all-optional keys.
|
||||
* Make message data key optional if specified as blank or with
|
||||
* all-optional keys.
|
||||
*/
|
||||
type NarrowedMessage<L extends MessageBase<keyof MessageDefinitions>> =
|
||||
L extends any
|
||||
|
||||
Reference in New Issue
Block a user