Improve cast client module receiver messages handling

This commit is contained in:
hensm
2022-03-15 09:34:10 +00:00
parent 3030f75590
commit 3c175a740d
3 changed files with 72 additions and 57 deletions

View File

@@ -5,9 +5,9 @@ import { Channel } from "castv2";
import { sendMessage } from "../../lib/nativeMessaging"; import { sendMessage } from "../../lib/nativeMessaging";
import { ReceiverDevice } from "../../types"; import { ReceiverDevice } from "../../types";
import { ReceiverApplication, ReceiverMessage, SenderMessage } from "./types"; import { ReceiverMessage } from "./types";
import CastClient, { NS_CONNECTION, NS_HEARTBEAT, NS_RECEIVER } from "./client"; import CastClient, { NS_CONNECTION, NS_HEARTBEAT } from "./client";
type OnSessionCreatedCallback = (sessionId: string) => void; type OnSessionCreatedCallback = (sessionId: string) => void;
@@ -15,10 +15,6 @@ export default class Session extends CastClient {
// Assigned by the receiver once the session is established // Assigned by the receiver once the session is established
public sessionId?: string; public sessionId?: string;
// Platform messaging
private receiverChannel?: Channel;
private receiverRequestId = 0;
// Receiver app messaging // Receiver app messaging
private transportId?: string; private transportId?: string;
private transportConnection?: Channel; private transportConnection?: Channel;
@@ -64,7 +60,7 @@ export default class Session extends CastClient {
* request response. * request response.
*/ */
if (!this.sessionId) { if (!this.sessionId) {
// Launch message response only // Match request ID on the response to the launch request ID.
if (message.requestId !== this.launchRequestId) { if (message.requestId !== this.launchRequestId) {
break; break;
} }
@@ -159,18 +155,6 @@ export default class Session extends CastClient {
channel.send(message); channel.send(message);
} }
sendReceiverMessage(message: DistributiveOmit<SenderMessage, "requestId">) {
if (!this.receiverChannel) {
this.receiverChannel = this.createChannel(NS_RECEIVER);
this.receiverChannel.on("message", this.onReceiverMessage);
}
const requestId = this.receiverRequestId++;
this.receiverChannel?.send({ ...message, requestId });
return requestId;
}
constructor( constructor(
private appId: string, private appId: string,
private receiverDevice: ReceiverDevice, private receiverDevice: ReceiverDevice,
@@ -178,6 +162,25 @@ export default class Session extends CastClient {
) { ) {
super(); super();
super.connect(receiverDevice.host, {
onHeartbeat: () => {
// Include transport heartbeat with platform heartbeat
if (this.transportHeartbeat) {
this.transportHeartbeat.send({ type: "PING" });
}
},
onReceiverMessage: message => {
this.onReceiverMessage(message);
}
}).then(() => {
// Send a launch request and store the request ID for reference
this.launchRequestId = this.sendReceiverMessage({
type: "LAUNCH",
appId: this.appId
});
});
// Handle client connection closed
this.client.on("close", () => { this.client.on("close", () => {
if (this.sessionId) { if (this.sessionId) {
sendMessage({ sendMessage({
@@ -186,19 +189,5 @@ export default class Session extends CastClient {
}); });
} }
}); });
super.connect(receiverDevice.host, {
onHeartbeat: () => {
// Include transport heartbeat with platform heartbeat
if (this.transportHeartbeat) {
this.transportHeartbeat.send({ type: "PING" });
}
}
}).then(() => {
this.launchRequestId = this.sendReceiverMessage({
type: "LAUNCH",
appId: this.appId
});
});
} }
} }

View File

@@ -2,6 +2,8 @@
import { Channel, Client } from "castv2"; import { Channel, Client } from "castv2";
import { ReceiverMessage, SenderMessage } from "./types";
export const NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection"; 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_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat";
export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver"; export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver";
@@ -11,6 +13,7 @@ const HEARTBEAT_INTERVAL_MS = 5000;
interface CastClientConnectOptions { interface CastClientConnectOptions {
port?: number; port?: number;
onReceiverMessage?: (message: ReceiverMessage) => void;
onHeartbeat?: () => void; onHeartbeat?: () => void;
} }
@@ -21,6 +24,10 @@ export default class CastClient {
protected heartbeatChannel?: Channel; protected heartbeatChannel?: Channel;
protected heartbeatIntervalId?: NodeJS.Timeout; protected heartbeatIntervalId?: NodeJS.Timeout;
// Platform messaging
private receiverChannel?: Channel;
private receiverRequestId = 0;
constructor( constructor(
protected sourceId = "sender-0", protected sourceId = "sender-0",
protected destinationId = "receiver-0" protected destinationId = "receiver-0"
@@ -43,6 +50,18 @@ export default class CastClient {
); );
} }
/**
* Sends a message on the receiver channel with the correct
* request ID.
*/
sendReceiverMessage(message: DistributiveOmit<SenderMessage, "requestId">) {
if (!this.receiverChannel) return;
const requestId = this.receiverRequestId++;
this.receiverChannel.send({ ...message, requestId });
return requestId;
}
/** /**
* Connects to a cast receiver at a given host, returning a * Connects to a cast receiver at a given host, returning a
* promise that resolves once the client is connected. * promise that resolves once the client is connected.
@@ -57,25 +76,33 @@ export default class CastClient {
} }
}); });
const connectOpts = { this.client.connect(
host, {
port: options?.port ?? DEFAULT_PORT host,
}; port: options?.port ?? DEFAULT_PORT
},
// On connection callback
() => {
this.connectionChannel = this.createChannel(NS_CONNECTION);
this.heartbeatChannel = this.createChannel(NS_HEARTBEAT);
this.client.connect(connectOpts, () => { // Handle receiver messages
this.connectionChannel = this.createChannel(NS_CONNECTION); this.receiverChannel = this.createChannel(NS_RECEIVER);
this.heartbeatChannel = this.createChannel(NS_HEARTBEAT); this.receiverChannel.on("message", message => {
options?.onReceiverMessage?.(message);
});
this.connectionChannel.send({ type: "CONNECT" }); this.connectionChannel.send({ type: "CONNECT" });
this.heartbeatChannel.send({ type: "PING" }); this.heartbeatChannel.send({ type: "PING" });
this.heartbeatIntervalId = setInterval(() => { this.heartbeatIntervalId = setInterval(() => {
this.heartbeatChannel?.send({ type: "PING" }); this.heartbeatChannel?.send({ type: "PING" });
options?.onHeartbeat?.(); options?.onHeartbeat?.();
}, HEARTBEAT_INTERVAL_MS); }, HEARTBEAT_INTERVAL_MS);
resolve(); resolve();
}); }
);
}); });
} }

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
import CastClient, { NS_RECEIVER } from "./client"; import CastClient from "./client";
import { import {
MediaStatus, MediaStatus,
@@ -27,13 +27,12 @@ export default class Remote extends CastClient {
constructor(private host: string, private options?: CastRemoteOptions) { constructor(private host: string, private options?: CastRemoteOptions) {
super(); super();
this.connect(host).then(() => { super.connect(host, {
// Request receiver status onReceiverMessage: message => {
const receiverChannel = this.createChannel(NS_RECEIVER);
receiverChannel.on("message", message => {
this.onReceiverMessage(message); this.onReceiverMessage(message);
}); }
receiverChannel.send({ type: "GET_STATUS", requestId: 1 }); }).then(() => {
this.sendReceiverMessage({ type: "GET_STATUS" });
}); });
} }
@@ -47,7 +46,7 @@ export default class Remote extends CastClient {
* On initial connection, a `GET_STATUS` message is sent that * On initial connection, a `GET_STATUS` message is sent that
* results in a `RECEIVER_STATUS` response. If an application * results in a `RECEIVER_STATUS` response. If an application
* is running, get the transport ID and make a connection to * is running, get the transport ID and make a connection to
* fetch media status updates. * receive media status updates.
*/ */
private onReceiverMessage(message: ReceiverMessage) { private onReceiverMessage(message: ReceiverMessage) {
if (message.type !== "RECEIVER_STATUS") { if (message.type !== "RECEIVER_STATUS") {
@@ -62,7 +61,7 @@ export default class Remote extends CastClient {
this.options?.onApplicationClose?.(); this.options?.onApplicationClose?.();
} }
} }
// Update status before possible transport init // Update status before possible transport init
this.options?.onReceiverStatusUpdate?.(message.status); this.options?.onReceiverStatusUpdate?.(message.status);