diff --git a/app/src/bridge/components/cast/Session.ts b/app/src/bridge/components/cast/Session.ts index 7954dfb..faa6b4d 100644 --- a/app/src/bridge/components/cast/Session.ts +++ b/app/src/bridge/components/cast/Session.ts @@ -5,9 +5,9 @@ import { Channel } from "castv2"; import { sendMessage } from "../../lib/nativeMessaging"; 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; @@ -15,10 +15,6 @@ export default class Session extends CastClient { // Assigned by the receiver once the session is established public sessionId?: string; - // Platform messaging - private receiverChannel?: Channel; - private receiverRequestId = 0; - // Receiver app messaging private transportId?: string; private transportConnection?: Channel; @@ -64,7 +60,7 @@ export default class Session extends CastClient { * request response. */ if (!this.sessionId) { - // Launch message response only + // Match request ID on the response to the launch request ID. if (message.requestId !== this.launchRequestId) { break; } @@ -159,18 +155,6 @@ export default class Session extends CastClient { channel.send(message); } - sendReceiverMessage(message: DistributiveOmit) { - 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( private appId: string, private receiverDevice: ReceiverDevice, @@ -178,6 +162,25 @@ export default class Session extends CastClient { ) { 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", () => { if (this.sessionId) { 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 - }); - }); } } diff --git a/app/src/bridge/components/cast/client.ts b/app/src/bridge/components/cast/client.ts index 8e1a09c..ad32e59 100644 --- a/app/src/bridge/components/cast/client.ts +++ b/app/src/bridge/components/cast/client.ts @@ -2,6 +2,8 @@ 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_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat"; export const NS_RECEIVER = "urn:x-cast:com.google.cast.receiver"; @@ -11,6 +13,7 @@ const HEARTBEAT_INTERVAL_MS = 5000; interface CastClientConnectOptions { port?: number; + onReceiverMessage?: (message: ReceiverMessage) => void; onHeartbeat?: () => void; } @@ -21,6 +24,10 @@ export default class CastClient { protected heartbeatChannel?: Channel; protected heartbeatIntervalId?: NodeJS.Timeout; + // Platform messaging + private receiverChannel?: Channel; + private receiverRequestId = 0; + constructor( protected sourceId = "sender-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) { + 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 * promise that resolves once the client is connected. @@ -57,25 +76,33 @@ export default class CastClient { } }); - const connectOpts = { - host, - port: options?.port ?? DEFAULT_PORT - }; + this.client.connect( + { + 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, () => { - this.connectionChannel = this.createChannel(NS_CONNECTION); - this.heartbeatChannel = this.createChannel(NS_HEARTBEAT); + // Handle receiver messages + this.receiverChannel = this.createChannel(NS_RECEIVER); + this.receiverChannel.on("message", message => { + options?.onReceiverMessage?.(message); + }); - this.connectionChannel.send({ type: "CONNECT" }); - this.heartbeatChannel.send({ type: "PING" }); + this.connectionChannel.send({ type: "CONNECT" }); + this.heartbeatChannel.send({ type: "PING" }); - this.heartbeatIntervalId = setInterval(() => { - this.heartbeatChannel?.send({ type: "PING" }); - options?.onHeartbeat?.(); - }, HEARTBEAT_INTERVAL_MS); + this.heartbeatIntervalId = setInterval(() => { + this.heartbeatChannel?.send({ type: "PING" }); + options?.onHeartbeat?.(); + }, HEARTBEAT_INTERVAL_MS); - resolve(); - }); + resolve(); + } + ); }); } diff --git a/app/src/bridge/components/cast/remote.ts b/app/src/bridge/components/cast/remote.ts index 66bd7bb..bf35860 100644 --- a/app/src/bridge/components/cast/remote.ts +++ b/app/src/bridge/components/cast/remote.ts @@ -1,6 +1,6 @@ "use strict"; -import CastClient, { NS_RECEIVER } from "./client"; +import CastClient from "./client"; import { MediaStatus, @@ -27,13 +27,12 @@ export default class Remote extends CastClient { 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 => { + super.connect(host, { + 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 * results in a `RECEIVER_STATUS` response. If an application * is running, get the transport ID and make a connection to - * fetch media status updates. + * receive media status updates. */ private onReceiverMessage(message: ReceiverMessage) { if (message.type !== "RECEIVER_STATUS") { @@ -62,7 +61,7 @@ export default class Remote extends CastClient { this.options?.onApplicationClose?.(); } } - + // Update status before possible transport init this.options?.onReceiverStatusUpdate?.(message.status);