mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-10 09:39:58 +00:00
prettier: Re-format .ts files
This commit is contained in:
@@ -14,7 +14,6 @@ import fetch, { Headers } from "node-fetch";
|
||||
import nacl from "tweetnacl";
|
||||
import bplist from "./bplist";
|
||||
|
||||
|
||||
const AIRPLAY_PORT = 7000;
|
||||
const MIMETYPE_BPLIST = "application/x-apple-binary-plist";
|
||||
|
||||
@@ -27,10 +26,10 @@ export class AirPlayAuthCredentials {
|
||||
public clientPk: Uint8Array;
|
||||
|
||||
constructor(
|
||||
clientId?: string
|
||||
, clientSk?: Uint8Array
|
||||
, clientPk?: Uint8Array) {
|
||||
|
||||
clientId?: string,
|
||||
clientSk?: Uint8Array,
|
||||
clientPk?: Uint8Array
|
||||
) {
|
||||
if (clientId && clientSk && clientPk) {
|
||||
this.clientId = clientId;
|
||||
this.clientSk = clientSk;
|
||||
@@ -74,8 +73,7 @@ export class AirPlayAuth {
|
||||
*/
|
||||
public async finishPairing(pin: string) {
|
||||
// Stage 1 response
|
||||
const { pk: serverPk
|
||||
, salt: serverSalt } = await this.pairSetupPin1();
|
||||
const { pk: serverPk, salt: serverSalt } = await this.pairSetupPin1();
|
||||
|
||||
// SRP params must 2048-bit SHA1
|
||||
const srpParams = srp6a.params[2048];
|
||||
@@ -83,19 +81,21 @@ export class AirPlayAuth {
|
||||
|
||||
// Create SRP client
|
||||
const srpClient = new srp6a.Client(
|
||||
srpParams // Params
|
||||
, serverSalt // Receiver salt
|
||||
, Buffer.from(this.credentials.clientId) // Username
|
||||
, Buffer.from(pin) // Password (receiver pin)
|
||||
, Buffer.from(this.credentials.clientSk)); // Client secret key
|
||||
srpParams, // Params
|
||||
serverSalt, // Receiver salt
|
||||
Buffer.from(this.credentials.clientId), // Username
|
||||
Buffer.from(pin), // Password (receiver pin)
|
||||
Buffer.from(this.credentials.clientSk) // Client secret key
|
||||
);
|
||||
|
||||
// Add receiver's public key
|
||||
srpClient.setB(serverPk);
|
||||
|
||||
// Stage 2 response
|
||||
await this.pairSetupPin2(
|
||||
srpClient.computeA() // SRP public key
|
||||
, srpClient.computeM1()); // SRP proof
|
||||
srpClient.computeA(), // SRP public key
|
||||
srpClient.computeM1() // SRP proof
|
||||
);
|
||||
|
||||
// Stage 3 response
|
||||
await this.pairSetupPin3(srpClient.computeK());
|
||||
@@ -108,12 +108,10 @@ export class AirPlayAuth {
|
||||
* its public key / salt.
|
||||
*/
|
||||
public async pairSetupPin1(): Promise<any> {
|
||||
const [ response ] = await this.sendPostRequestBplist(
|
||||
"/pair-setup-pin"
|
||||
, {
|
||||
method: "pin"
|
||||
, user: this.credentials.clientId
|
||||
});
|
||||
const [response] = await this.sendPostRequestBplist("/pair-setup-pin", {
|
||||
method: "pin",
|
||||
user: this.credentials.clientId
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -125,13 +123,11 @@ export class AirPlayAuth {
|
||||
* public keys, sending them to the receiver and receiving its
|
||||
* proof.
|
||||
*/
|
||||
public async pairSetupPin2(
|
||||
pk: Buffer
|
||||
, proof: Buffer): Promise<any> {
|
||||
|
||||
const [ response ] = await this.sendPostRequestBplist(
|
||||
"/pair-setup-pin"
|
||||
, { pk, proof });
|
||||
public async pairSetupPin2(pk: Buffer, proof: Buffer): Promise<any> {
|
||||
const [response] = await this.sendPostRequestBplist("/pair-setup-pin", {
|
||||
pk,
|
||||
proof
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -144,17 +140,19 @@ export class AirPlayAuth {
|
||||
* responds confirming the pairing is complete.
|
||||
*/
|
||||
public async pairSetupPin3(
|
||||
sharedSecretHash: crypto.BinaryLike): Promise<any> {
|
||||
|
||||
sharedSecretHash: crypto.BinaryLike
|
||||
): Promise<any> {
|
||||
// Create AES key
|
||||
const aesKey = crypto.createHash("sha512")
|
||||
const aesKey = crypto
|
||||
.createHash("sha512")
|
||||
.update("Pair-Setup-AES-Key")
|
||||
.update(sharedSecretHash)
|
||||
.digest()
|
||||
.slice(0, 16);
|
||||
|
||||
// Create AES IV
|
||||
const aesIv = crypto.createHash("sha512")
|
||||
const aesIv = crypto
|
||||
.createHash("sha512")
|
||||
.update("Pair-Setup-AES-IV")
|
||||
.update(sharedSecretHash)
|
||||
.digest()
|
||||
@@ -162,7 +160,6 @@ export class AirPlayAuth {
|
||||
|
||||
aesIv[15]++;
|
||||
|
||||
|
||||
const cipher = crypto.createCipheriv("aes-128-gcm", aesKey, aesIv);
|
||||
|
||||
// Encode client public key
|
||||
@@ -170,23 +167,23 @@ export class AirPlayAuth {
|
||||
cipher.final();
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
const [ response ] = await this.sendPostRequestBplist(
|
||||
"/pair-setup-pin"
|
||||
, { epk, authTag });
|
||||
const [response] = await this.sendPostRequestBplist("/pair-setup-pin", {
|
||||
epk,
|
||||
authTag
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a POST request to receiver and returns the
|
||||
* response.
|
||||
*/
|
||||
public async sendPostRequest(
|
||||
path: string
|
||||
, contentType?: string
|
||||
, data?: Buffer | string): Promise<any> {
|
||||
|
||||
path: string,
|
||||
contentType?: string,
|
||||
data?: Buffer | string
|
||||
): Promise<any> {
|
||||
// Create URL from base receiver URL and path
|
||||
const requestUrl = new URL(path, this.baseUrl);
|
||||
|
||||
@@ -200,9 +197,9 @@ export class AirPlayAuth {
|
||||
}
|
||||
|
||||
const response = await fetch(requestUrl.href, {
|
||||
method: "POST"
|
||||
, headers: requestHeaders
|
||||
, body: data
|
||||
method: "POST",
|
||||
headers: requestHeaders,
|
||||
body: data
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -217,16 +214,17 @@ export class AirPlayAuth {
|
||||
* receiver, then decodes and returns the response.
|
||||
*/
|
||||
public async sendPostRequestBplist(
|
||||
path: string
|
||||
, data?: object): Promise<any> {
|
||||
|
||||
path: string,
|
||||
data?: object
|
||||
): Promise<any> {
|
||||
// Convert data to compatible type
|
||||
const requestBody = data
|
||||
? bplist.create(data)
|
||||
: undefined;
|
||||
const requestBody = data ? bplist.create(data) : undefined;
|
||||
|
||||
const response = await this.sendPostRequest(
|
||||
path, MIMETYPE_BPLIST, requestBody);
|
||||
path,
|
||||
MIMETYPE_BPLIST,
|
||||
requestBody
|
||||
);
|
||||
|
||||
// Convert response data to Buffer for bplist-parser
|
||||
return bplist.parse.parseBuffer(response);
|
||||
|
||||
@@ -7,14 +7,12 @@ 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();
|
||||
|
||||
@@ -22,18 +20,26 @@ class CastClient {
|
||||
protected heartbeatChannel?: Channel;
|
||||
protected heartbeatIntervalId?: NodeJS.Timeout;
|
||||
|
||||
constructor(protected sourceId = "sender-0"
|
||||
, protected destinationId = "receiver-0") {}
|
||||
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");
|
||||
createChannel(
|
||||
namespace: string,
|
||||
sourceId = this.sourceId,
|
||||
destinationId = this.destinationId
|
||||
) {
|
||||
return this.client.createChannel(
|
||||
sourceId,
|
||||
destinationId,
|
||||
namespace,
|
||||
"JSON"
|
||||
);
|
||||
}
|
||||
|
||||
connect(host: string, port: number, onHeartbeat?: () => void) {
|
||||
@@ -49,10 +55,10 @@ class CastClient {
|
||||
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) {
|
||||
@@ -66,7 +72,6 @@ class CastClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type OnSessionCreatedCallback = (sessionId: string) => void;
|
||||
|
||||
export default class Session extends CastClient {
|
||||
@@ -81,7 +86,7 @@ export default class Session extends CastClient {
|
||||
private transportId?: string;
|
||||
private transportConnection?: Channel;
|
||||
private transportHeartbeat?: Channel;
|
||||
|
||||
|
||||
// Channels created by `sendCastSessionMessage` messages
|
||||
private namespaceChannelMap = new Map<string, Channel>();
|
||||
|
||||
@@ -93,12 +98,17 @@ export default class Session extends CastClient {
|
||||
|
||||
private onSessionCreated?: OnSessionCreatedCallback;
|
||||
|
||||
|
||||
private establishAppConnection(transportId: string) {
|
||||
this.transportConnection = this.createChannel(
|
||||
NS_CONNECTION, this.sourceId, transportId);
|
||||
NS_CONNECTION,
|
||||
this.sourceId,
|
||||
transportId
|
||||
);
|
||||
this.transportHeartbeat = this.createChannel(
|
||||
NS_HEARTBEAT, this.sourceId, transportId);
|
||||
NS_HEARTBEAT,
|
||||
this.sourceId,
|
||||
transportId
|
||||
);
|
||||
|
||||
this.transportConnection.send({ type: "CONNECT" });
|
||||
}
|
||||
@@ -111,7 +121,8 @@ export default class Session extends CastClient {
|
||||
case "RECEIVER_STATUS": {
|
||||
const { status } = message;
|
||||
const application = status.applications?.find(
|
||||
app => app.appId === this.appId);
|
||||
app => app.appId === this.appId
|
||||
);
|
||||
|
||||
/**
|
||||
* If application isn't set, still waiting on the launch
|
||||
@@ -133,20 +144,20 @@ export default class Session extends CastClient {
|
||||
const { friendlyName } = this.receiverDevice;
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:castSessionCreated"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, statusText: application.statusText
|
||||
, namespaces: application.namespaces
|
||||
, volume: status.volume
|
||||
, appId: application.appId
|
||||
, displayName: application.displayName
|
||||
, receiverFriendlyName: friendlyName
|
||||
, transportId: this.sessionId
|
||||
subject: "shim:castSessionCreated",
|
||||
data: {
|
||||
sessionId: this.sessionId,
|
||||
statusText: application.statusText,
|
||||
namespaces: application.namespaces,
|
||||
volume: status.volume,
|
||||
appId: application.appId,
|
||||
displayName: application.displayName,
|
||||
receiverFriendlyName: friendlyName,
|
||||
transportId: this.sessionId,
|
||||
|
||||
// TODO: Fix this
|
||||
, senderApps: []
|
||||
, appImages: []
|
||||
// TODO: Fix this
|
||||
senderApps: [],
|
||||
appImages: []
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -161,15 +172,15 @@ export default class Session extends CastClient {
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:castSessionUpdated"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, statusText: application.statusText
|
||||
, namespaces: application.namespaces
|
||||
, volume: message.status.volume
|
||||
subject: "shim:castSessionUpdated",
|
||||
data: {
|
||||
sessionId: this.sessionId,
|
||||
statusText: application.statusText,
|
||||
namespaces: application.namespaces,
|
||||
volume: message.status.volume
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -179,13 +190,16 @@ export default class Session extends CastClient {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sendMessage(namespace: string, message: unknown) {
|
||||
let channel = this.namespaceChannelMap.get(namespace);
|
||||
if (!channel) {
|
||||
channel = this.createChannel(
|
||||
namespace, this.sourceId, this.transportId);
|
||||
namespace,
|
||||
this.sourceId,
|
||||
this.transportId
|
||||
);
|
||||
|
||||
channel.on("message", messageData => {
|
||||
if (!this.sessionId) {
|
||||
@@ -195,11 +209,11 @@ export default class Session extends CastClient {
|
||||
messageData = JSON.stringify(messageData);
|
||||
|
||||
sendMessage({
|
||||
subject: "shim:receivedCastSessionMessage"
|
||||
, data: {
|
||||
sessionId: this.sessionId
|
||||
, namespace
|
||||
, messageData
|
||||
subject: "shim:receivedCastSessionMessage",
|
||||
data: {
|
||||
sessionId: this.sessionId,
|
||||
namespace,
|
||||
messageData
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -222,25 +236,24 @@ export default class Session extends CastClient {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
constructor(public appId: string
|
||||
, public receiverDevice: ReceiverDevice) {
|
||||
|
||||
constructor(public appId: string, public receiverDevice: ReceiverDevice) {
|
||||
super();
|
||||
|
||||
this.client.on("close", () => {
|
||||
if (this.sessionId) {
|
||||
sendMessage({
|
||||
subject: "shim:castSessionStopped"
|
||||
, data: { sessionId: this.sessionId }
|
||||
subject: "shim:castSessionStopped",
|
||||
data: { sessionId: this.sessionId }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async connect(host: string
|
||||
, port: number
|
||||
, onSessionCreated?: OnSessionCreatedCallback) {
|
||||
|
||||
async connect(
|
||||
host: string,
|
||||
port: number,
|
||||
onSessionCreated?: OnSessionCreatedCallback
|
||||
) {
|
||||
if (onSessionCreated) {
|
||||
this.onSessionCreated = onSessionCreated;
|
||||
}
|
||||
@@ -253,8 +266,8 @@ export default class Session extends CastClient {
|
||||
});
|
||||
|
||||
this.launchRequestId = this.sendReceiverMessage({
|
||||
type: "LAUNCH"
|
||||
, appId: this.appId
|
||||
type: "LAUNCH",
|
||||
appId: this.appId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import castv2 from "castv2";
|
||||
import { sendMessage } from "../../lib/nativeMessaging";
|
||||
import { Message } from "../../messaging";
|
||||
|
||||
import Session, { NS_CONNECTION
|
||||
, NS_RECEIVER } from "./Session";
|
||||
|
||||
import Session, { NS_CONNECTION, NS_RECEIVER } from "./Session";
|
||||
|
||||
const sessions = new Map<string, Session>();
|
||||
|
||||
@@ -19,9 +17,12 @@ export function handleCastMessage(message: Message) {
|
||||
// Connect and store with returned ID
|
||||
const session = new Session(appId, receiverDevice);
|
||||
session.connect(
|
||||
receiverDevice.host, receiverDevice.port, sessionId => {
|
||||
sessions.set(sessionId, session);
|
||||
});
|
||||
receiverDevice.host,
|
||||
receiverDevice.port,
|
||||
sessionId => {
|
||||
sessions.set(sessionId, session);
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -32,10 +33,11 @@ export function handleCastMessage(message: Message) {
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: "Session does not exist"
|
||||
, sessionId, messageId
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: {
|
||||
error: "Session does not exist",
|
||||
sessionId,
|
||||
messageId
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,10 +48,11 @@ export function handleCastMessage(message: Message) {
|
||||
session.sendReceiverMessage(messageData);
|
||||
} catch (err) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: `Failed to send message (${err})`
|
||||
, sessionId, messageId
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: {
|
||||
error: `Failed to send message (${err})`,
|
||||
sessionId,
|
||||
messageId
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,8 +61,8 @@ export function handleCastMessage(message: Message) {
|
||||
|
||||
// Success
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: { sessionId, messageId }
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: { sessionId, messageId }
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -71,10 +74,11 @@ export function handleCastMessage(message: Message) {
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: "Session does not exist"
|
||||
, sessionId, messageId
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: {
|
||||
error: "Session does not exist",
|
||||
sessionId,
|
||||
messageId
|
||||
}
|
||||
});
|
||||
|
||||
@@ -91,10 +95,11 @@ export function handleCastMessage(message: Message) {
|
||||
session.sendMessage(namespace, messageData);
|
||||
} catch (err) {
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: {
|
||||
error: `Failed to send message (${err})`
|
||||
, sessionId, messageId
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: {
|
||||
error: `Failed to send message (${err})`,
|
||||
sessionId,
|
||||
messageId
|
||||
}
|
||||
});
|
||||
|
||||
@@ -103,8 +108,8 @@ export function handleCastMessage(message: Message) {
|
||||
|
||||
// Success
|
||||
sendMessage({
|
||||
subject: "shim:impl_sendCastMessage"
|
||||
, data: { sessionId, messageId }
|
||||
subject: "shim:impl_sendCastMessage",
|
||||
data: { sessionId, messageId }
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -117,12 +122,20 @@ export function handleCastMessage(message: Message) {
|
||||
client.connect({ host, port }, () => {
|
||||
const sourceId = "sender-0";
|
||||
const destinationId = "receiver-0";
|
||||
|
||||
|
||||
const clientConnection = client.createChannel(
|
||||
sourceId, destinationId, NS_CONNECTION, "JSON");
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_CONNECTION,
|
||||
"JSON"
|
||||
);
|
||||
const clientReceiver = client.createChannel(
|
||||
sourceId, destinationId, NS_RECEIVER, "JSON");
|
||||
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_RECEIVER,
|
||||
"JSON"
|
||||
);
|
||||
|
||||
clientConnection.send({ type: "CONNECT" });
|
||||
clientReceiver.send({ type: "STOP", requestId: 1 });
|
||||
});
|
||||
|
||||
@@ -7,18 +7,18 @@ export interface Image {
|
||||
}
|
||||
|
||||
enum Capability {
|
||||
VIDEO_OUT = "video_out"
|
||||
, AUDIO_OUT = "audio_out"
|
||||
, VIDEO_IN = "video_in"
|
||||
, AUDIO_IN = "audio_in"
|
||||
, MULTIZONE_GROUP = "multizone_group"
|
||||
VIDEO_OUT = "video_out",
|
||||
AUDIO_OUT = "audio_out",
|
||||
VIDEO_IN = "video_in",
|
||||
AUDIO_IN = "audio_in",
|
||||
MULTIZONE_GROUP = "multizone_group"
|
||||
}
|
||||
|
||||
enum ReceiverType {
|
||||
CAST = "cast"
|
||||
, DIAL = "dial"
|
||||
, HANGOUT = "hangout"
|
||||
, CUSTOM = "custom"
|
||||
CAST = "cast",
|
||||
DIAL = "dial",
|
||||
HANGOUT = "hangout",
|
||||
CUSTOM = "custom"
|
||||
}
|
||||
|
||||
export interface SenderApplication {
|
||||
@@ -28,9 +28,9 @@ export interface SenderApplication {
|
||||
}
|
||||
|
||||
enum VolumeControlType {
|
||||
ATTENUATION = "attenuation"
|
||||
, FIXED = "fixed"
|
||||
, MASTER = "master"
|
||||
ATTENUATION = "attenuation",
|
||||
FIXED = "fixed",
|
||||
MASTER = "master"
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
@@ -43,75 +43,74 @@ export interface Volume {
|
||||
// Media
|
||||
|
||||
enum IdleReason {
|
||||
CANCELLED = "CANCELLED"
|
||||
, INTERRUPTED = "INTERRUPTED"
|
||||
, FINISHED = "FINISHED"
|
||||
, ERROR = "ERROR"
|
||||
CANCELLED = "CANCELLED",
|
||||
INTERRUPTED = "INTERRUPTED",
|
||||
FINISHED = "FINISHED",
|
||||
ERROR = "ERROR"
|
||||
}
|
||||
|
||||
enum HlsSegmentFormat {
|
||||
AAC = "aac"
|
||||
, AC3 = "ac3"
|
||||
, MP3 = "mp3"
|
||||
, TS = "ts"
|
||||
, TS_AAC = "ts_aac"
|
||||
, E_AC3 = "e_ac3"
|
||||
, FMP4 = "fmp4"
|
||||
AAC = "aac",
|
||||
AC3 = "ac3",
|
||||
MP3 = "mp3",
|
||||
TS = "ts",
|
||||
TS_AAC = "ts_aac",
|
||||
E_AC3 = "e_ac3",
|
||||
FMP4 = "fmp4"
|
||||
}
|
||||
|
||||
export enum HlsVideoSegmentFormat {
|
||||
MPEG2_TS = "mpeg2_ts"
|
||||
, FMP4 = "fmp4"
|
||||
MPEG2_TS = "mpeg2_ts",
|
||||
FMP4 = "fmp4"
|
||||
}
|
||||
|
||||
enum MetadataType {
|
||||
GENERIC
|
||||
, MOVIE
|
||||
, TV_SHOW
|
||||
, MUSIC_TRACK
|
||||
, PHOTO
|
||||
, AUDIOBOOK_CHAPTER
|
||||
GENERIC,
|
||||
MOVIE,
|
||||
TV_SHOW,
|
||||
MUSIC_TRACK,
|
||||
PHOTO,
|
||||
AUDIOBOOK_CHAPTER
|
||||
}
|
||||
|
||||
enum PlayerState {
|
||||
IDLE = "IDLE"
|
||||
, PLAYING = "PLAYING"
|
||||
, PAUSED = "PAUSED"
|
||||
, BUFFERING = "BUFFERING"
|
||||
IDLE = "IDLE",
|
||||
PLAYING = "PLAYING",
|
||||
PAUSED = "PAUSED",
|
||||
BUFFERING = "BUFFERING"
|
||||
}
|
||||
|
||||
enum RepeatMode {
|
||||
OFF = "REPEAT_OFF"
|
||||
, ALL = "REPEAT_ALL"
|
||||
, SINGLE = "REPEAT_SINGLE"
|
||||
, ALL_AND_SHUFFLE = "REPEAT_ALL_AND_SHUFFLE"
|
||||
OFF = "REPEAT_OFF",
|
||||
ALL = "REPEAT_ALL",
|
||||
SINGLE = "REPEAT_SINGLE",
|
||||
ALL_AND_SHUFFLE = "REPEAT_ALL_AND_SHUFFLE"
|
||||
}
|
||||
|
||||
enum ResumeState {
|
||||
PLAYBACK_START = "PLAYBACK_START"
|
||||
, PLAYBACK_PAUSE = "PLAYBACK_PAUSE"
|
||||
PLAYBACK_START = "PLAYBACK_START",
|
||||
PLAYBACK_PAUSE = "PLAYBACK_PAUSE"
|
||||
}
|
||||
|
||||
enum StreamType {
|
||||
BUFFERED = "BUFFERED"
|
||||
, LIVE = "LIVE"
|
||||
, OTHER = "OTHER"
|
||||
BUFFERED = "BUFFERED",
|
||||
LIVE = "LIVE",
|
||||
OTHER = "OTHER"
|
||||
}
|
||||
|
||||
enum TrackType {
|
||||
TEXT = "TEXT"
|
||||
, AUDIO = "AUDIO"
|
||||
, VIDEO = "VIDEO"
|
||||
TEXT = "TEXT",
|
||||
AUDIO = "AUDIO",
|
||||
VIDEO = "VIDEO"
|
||||
}
|
||||
|
||||
export enum UserAction {
|
||||
LIKE = "LIKE"
|
||||
, DISLIKE = "DISLIKE"
|
||||
, FOLLOW = "FOLLOW"
|
||||
, UNFOLLOW = "UNFOLLOW"
|
||||
LIKE = "LIKE",
|
||||
DISLIKE = "DISLIKE",
|
||||
FOLLOW = "FOLLOW",
|
||||
UNFOLLOW = "UNFOLLOW"
|
||||
}
|
||||
|
||||
|
||||
interface Break {
|
||||
breakClipIds: string[];
|
||||
duration?: number;
|
||||
@@ -173,11 +172,11 @@ interface VastAdsRequest {
|
||||
}
|
||||
|
||||
type Metadata =
|
||||
GenericMediaMetadata
|
||||
| MovieMediaMetadata
|
||||
| MusicTrackMediaMetadata
|
||||
| PhotoMediaMetadata
|
||||
| TvShowMediaMetadata;
|
||||
| GenericMediaMetadata
|
||||
| MovieMediaMetadata
|
||||
| MusicTrackMediaMetadata
|
||||
| PhotoMediaMetadata
|
||||
| TvShowMediaMetadata;
|
||||
|
||||
interface MediaInformation {
|
||||
atvEntity?: string;
|
||||
@@ -284,11 +283,11 @@ export interface MediaStatus {
|
||||
playbackRate: number;
|
||||
playerState: PlayerState;
|
||||
idleReason?: IdleReason;
|
||||
items?: QueueItem[]
|
||||
items?: QueueItem[];
|
||||
currentTime: number;
|
||||
supportedMediaCommands: number;
|
||||
repeatMode: RepeatMode;
|
||||
volume: Volume
|
||||
volume: Volume;
|
||||
customData: unknown;
|
||||
}
|
||||
|
||||
@@ -329,23 +328,21 @@ export interface ReceiverStatus {
|
||||
volume: Volume;
|
||||
}
|
||||
|
||||
|
||||
interface ReqBase {
|
||||
requestId: number;
|
||||
}
|
||||
|
||||
// NS: urn:x-cast:com.google.cast.receiver
|
||||
export type SenderMessage =
|
||||
ReqBase & { type: "LAUNCH", appId: string }
|
||||
| ReqBase & { type: "STOP", sessionId: string }
|
||||
| ReqBase & { type: "GET_STATUS" }
|
||||
| ReqBase & { type: "GET_APP_AVAILABILITY", appId: string[] }
|
||||
| ReqBase & { type: "SET_VOLUME", volume: Volume };
|
||||
| (ReqBase & { type: "LAUNCH"; appId: string })
|
||||
| (ReqBase & { type: "STOP"; sessionId: string })
|
||||
| (ReqBase & { type: "GET_STATUS" })
|
||||
| (ReqBase & { type: "GET_APP_AVAILABILITY"; appId: string[] })
|
||||
| (ReqBase & { type: "SET_VOLUME"; volume: Volume });
|
||||
|
||||
export type ReceiverMessage =
|
||||
ReqBase & { type: "RECEIVER_STATUS", status: ReceiverStatus }
|
||||
| ReqBase & { type: "LAUNCH_ERROR", reason: string }
|
||||
|
||||
| (ReqBase & { type: "RECEIVER_STATUS"; status: ReceiverStatus })
|
||||
| (ReqBase & { type: "LAUNCH_ERROR"; reason: string });
|
||||
|
||||
interface MediaReqBase extends ReqBase {
|
||||
mediaSessionId: number;
|
||||
@@ -354,84 +351,84 @@ interface MediaReqBase extends ReqBase {
|
||||
|
||||
// NS: urn:x-cast:com.google.cast.media
|
||||
export type SenderMediaMessage =
|
||||
| MediaReqBase & { type: "PLAY" }
|
||||
| MediaReqBase & { type: "PAUSE" }
|
||||
| MediaReqBase & { type: "MEDIA_GET_STATUS" }
|
||||
| MediaReqBase & { type: "STOP" }
|
||||
| MediaReqBase & { type: "MEDIA_SET_VOLUME", volume: Volume }
|
||||
| MediaReqBase & { type: "SET_PLAYBACK_RATE" , playbackRate: number }
|
||||
| ReqBase & {
|
||||
type: "LOAD"
|
||||
, activeTrackIds: Nullable<number[]>
|
||||
, atvCredentials?: string
|
||||
, atvCredentialsType?: string
|
||||
, autoplay: Nullable<boolean>
|
||||
, currentTime: Nullable<number>
|
||||
, customData?: unknown
|
||||
, media: MediaInformation
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
| MediaReqBase & {
|
||||
type: "SEEK"
|
||||
, resumeState: Nullable<ResumeState>
|
||||
, currentTime: Nullable<number>
|
||||
}
|
||||
| MediaReqBase & {
|
||||
type: "EDIT_TRACKS_INFO"
|
||||
, activeTrackIds: Nullable<number[]>
|
||||
, textTrackStyle: Nullable<string>
|
||||
}
|
||||
// QueueLoadRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_LOAD"
|
||||
, items: QueueItem[]
|
||||
, startIndex: number
|
||||
, repeatMode: string
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueInsertItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_INSERT"
|
||||
, items: QueueItem[]
|
||||
, insertBefore: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueUpdateItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, items: QueueItem[]
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueJumpRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, jump: Nullable<number>
|
||||
, currentItemId: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueRemoveItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_REMOVE"
|
||||
, itemIds: number[]
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueReorderItemsRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_REORDER"
|
||||
, itemIds: number[]
|
||||
, insertBefore: Nullable<number>
|
||||
, sessionId: Nullable<string>
|
||||
}
|
||||
// QueueSetPropertiesRequest
|
||||
| MediaReqBase & {
|
||||
type: "QUEUE_UPDATE"
|
||||
, repeatMode: Nullable<string>
|
||||
, sessionId: Nullable<string>
|
||||
};
|
||||
| (MediaReqBase & { type: "PLAY" })
|
||||
| (MediaReqBase & { type: "PAUSE" })
|
||||
| (MediaReqBase & { type: "MEDIA_GET_STATUS" })
|
||||
| (MediaReqBase & { type: "STOP" })
|
||||
| (MediaReqBase & { type: "MEDIA_SET_VOLUME"; volume: Volume })
|
||||
| (MediaReqBase & { type: "SET_PLAYBACK_RATE"; playbackRate: number })
|
||||
| (ReqBase & {
|
||||
type: "LOAD";
|
||||
activeTrackIds: Nullable<number[]>;
|
||||
atvCredentials?: string;
|
||||
atvCredentialsType?: string;
|
||||
autoplay: Nullable<boolean>;
|
||||
currentTime: Nullable<number>;
|
||||
customData?: unknown;
|
||||
media: MediaInformation;
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
| (MediaReqBase & {
|
||||
type: "SEEK";
|
||||
resumeState: Nullable<ResumeState>;
|
||||
currentTime: Nullable<number>;
|
||||
})
|
||||
| (MediaReqBase & {
|
||||
type: "EDIT_TRACKS_INFO";
|
||||
activeTrackIds: Nullable<number[]>;
|
||||
textTrackStyle: Nullable<string>;
|
||||
})
|
||||
// QueueLoadRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_LOAD";
|
||||
items: QueueItem[];
|
||||
startIndex: number;
|
||||
repeatMode: string;
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueInsertItemsRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_INSERT";
|
||||
items: QueueItem[];
|
||||
insertBefore: Nullable<number>;
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueUpdateItemsRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_UPDATE";
|
||||
items: QueueItem[];
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueJumpRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_UPDATE";
|
||||
jump: Nullable<number>;
|
||||
currentItemId: Nullable<number>;
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueRemoveItemsRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_REMOVE";
|
||||
itemIds: number[];
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueReorderItemsRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_REORDER";
|
||||
itemIds: number[];
|
||||
insertBefore: Nullable<number>;
|
||||
sessionId: Nullable<string>;
|
||||
})
|
||||
// QueueSetPropertiesRequest
|
||||
| (MediaReqBase & {
|
||||
type: "QUEUE_UPDATE";
|
||||
repeatMode: Nullable<string>;
|
||||
sessionId: Nullable<string>;
|
||||
});
|
||||
|
||||
export type ReceiverMediaMessage =
|
||||
MediaReqBase & { type: "MEDIA_STATUS", status: MediaStatus[] }
|
||||
| MediaReqBase & { type: "INVALID_PLAYER_STATE" }
|
||||
| MediaReqBase & { type: "LOAD_FAILED" }
|
||||
| MediaReqBase & { type: "LOAD_CANCELLED" }
|
||||
| MediaReqBase & { type: "INVALID_REQUEST" };
|
||||
| (MediaReqBase & { type: "MEDIA_STATUS"; status: MediaStatus[] })
|
||||
| (MediaReqBase & { type: "INVALID_PLAYER_STATE" })
|
||||
| (MediaReqBase & { type: "LOAD_FAILED" })
|
||||
| (MediaReqBase & { type: "LOAD_CANCELLED" })
|
||||
| (MediaReqBase & { type: "INVALID_REQUEST" });
|
||||
|
||||
@@ -9,25 +9,31 @@ import mdns from "mdns";
|
||||
import { sendMessage } from "../lib/nativeMessaging";
|
||||
|
||||
import { ReceiverStatus } from "./cast/types";
|
||||
import { NS_CONNECTION, NS_HEARTBEAT, NS_RECEIVER }
|
||||
from "./cast/Session";
|
||||
|
||||
import { NS_CONNECTION, NS_HEARTBEAT, NS_RECEIVER } from "./cast/Session";
|
||||
|
||||
interface CastTxtRecord {
|
||||
id: string; cd: string; rm: string;
|
||||
ve: string; md: string; ic: string;
|
||||
fn: string; ca: string; st: string;
|
||||
bs: string; nf: string; rs: string;
|
||||
id: string;
|
||||
cd: string;
|
||||
rm: string;
|
||||
ve: string;
|
||||
md: string;
|
||||
ic: string;
|
||||
fn: string;
|
||||
ca: string;
|
||||
st: string;
|
||||
bs: string;
|
||||
nf: string;
|
||||
rs: string;
|
||||
}
|
||||
|
||||
const browser = mdns.createBrowser(mdns.tcp("googlecast"), {
|
||||
resolverSequence: [
|
||||
mdns.rst.DNSServiceResolve()
|
||||
, "DNSServiceGetAddrInfo" in mdns.dns_sd
|
||||
mdns.rst.DNSServiceResolve(),
|
||||
"DNSServiceGetAddrInfo" in mdns.dns_sd
|
||||
? mdns.rst.DNSServiceGetAddrInfo()
|
||||
// Some issues on Linux with IPv6, so restrict to IPv4
|
||||
: mdns.rst.getaddrinfo({ families: [ 4 ]})
|
||||
, mdns.rst.makeAddressesUnique()
|
||||
: // Some issues on Linux with IPv6, so restrict to IPv4
|
||||
mdns.rst.getaddrinfo({ families: [4] }),
|
||||
mdns.rst.makeAddressesUnique()
|
||||
]
|
||||
});
|
||||
|
||||
@@ -39,13 +45,13 @@ function onBrowserServiceUp(service: mdns.Service) {
|
||||
|
||||
const txtRecord = service.txtRecord as CastTxtRecord;
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceUp"
|
||||
, data: {
|
||||
subject: "main:receiverDeviceUp",
|
||||
data: {
|
||||
receiverDevice: {
|
||||
host: service.addresses[0]
|
||||
, port: service.port
|
||||
, id: service.name
|
||||
, friendlyName: txtRecord.fn
|
||||
host: service.addresses[0],
|
||||
port: service.port,
|
||||
id: service.name,
|
||||
friendlyName: txtRecord.fn
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -59,15 +65,14 @@ function onBrowserServiceDown(service: mdns.Service) {
|
||||
|
||||
const txtRecord = service.txtRecord as CastTxtRecord;
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceDown"
|
||||
, data: { receiverDeviceId: service.name }
|
||||
subject: "main:receiverDeviceDown",
|
||||
data: { receiverDeviceId: service.name }
|
||||
});
|
||||
}
|
||||
|
||||
browser.on("serviceUp", onBrowserServiceUp);
|
||||
browser.on("serviceDown", onBrowserServiceDown);
|
||||
|
||||
|
||||
interface InitializeOptions {
|
||||
shouldWatchStatus?: boolean;
|
||||
}
|
||||
@@ -88,8 +93,7 @@ export function startDiscovery(options: InitializeOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = new StatusListener(
|
||||
service.addresses[0], service.port);
|
||||
const listener = new StatusListener(service.addresses[0], service.port);
|
||||
|
||||
listener.on("receiverStatus", (status: ReceiverStatus) => {
|
||||
if (!service.name) {
|
||||
@@ -97,10 +101,10 @@ export function startDiscovery(options: InitializeOptions) {
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "main:receiverDeviceUpdated"
|
||||
, data: {
|
||||
receiverDeviceId: service.name
|
||||
, status
|
||||
subject: "main:receiverDeviceUpdated",
|
||||
data: {
|
||||
receiverDeviceId: service.name,
|
||||
status
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -122,12 +126,11 @@ export function stopDiscovery() {
|
||||
browser.stop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a connection to a receiver device and forwards
|
||||
* RECEIVER_STATUS updates to the extension.
|
||||
*/
|
||||
export default class StatusListener extends EventEmitter {
|
||||
export default class StatusListener extends EventEmitter {
|
||||
private client: Client;
|
||||
private clientReceiver?: Channel;
|
||||
private clientHeartbeatIntervalId?: NodeJS.Timeout;
|
||||
@@ -160,17 +163,28 @@ export function stopDiscovery() {
|
||||
this.client.close();
|
||||
}
|
||||
|
||||
|
||||
private onConnect(): void {
|
||||
const sourceId = "sender-0";
|
||||
const destinationId = "receiver-0";
|
||||
|
||||
const clientConnection = this.client.createChannel(
|
||||
sourceId, destinationId, NS_CONNECTION, "JSON");
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_CONNECTION,
|
||||
"JSON"
|
||||
);
|
||||
const clientHeartbeat = this.client.createChannel(
|
||||
sourceId, destinationId, NS_HEARTBEAT, "JSON");
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_HEARTBEAT,
|
||||
"JSON"
|
||||
);
|
||||
const clientReceiver = this.client.createChannel(
|
||||
sourceId, destinationId, NS_RECEIVER, "JSON");
|
||||
sourceId,
|
||||
destinationId,
|
||||
NS_RECEIVER,
|
||||
"JSON"
|
||||
);
|
||||
|
||||
clientReceiver.on("message", data => {
|
||||
switch (data.type) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import mime from "mime-types";
|
||||
import { sendMessage } from "../lib/nativeMessaging";
|
||||
import { convertSrtToVtt } from "../lib/subtitles";
|
||||
|
||||
|
||||
export let mediaServer: http.Server | undefined;
|
||||
|
||||
export async function startMediaServer(filePath: string, port: number) {
|
||||
@@ -41,7 +40,7 @@ export async function startMediaServer(filePath: string, port: number) {
|
||||
} catch (err) {
|
||||
console.error("Error: Failed to find media path.");
|
||||
sendMessage({
|
||||
subject: "mediaCast:mediaServerError"
|
||||
subject: "mediaCast:mediaServerError"
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -63,15 +62,19 @@ export async function startMediaServer(filePath: string, port: number) {
|
||||
*/
|
||||
const subtitles = new Map<string, string>();
|
||||
try {
|
||||
const dirEntries = await fs.promises.readdir(
|
||||
fileDir, { withFileTypes: true });
|
||||
const dirEntries = await fs.promises.readdir(fileDir, {
|
||||
withFileTypes: true
|
||||
});
|
||||
|
||||
for (const dirEntry of dirEntries) {
|
||||
if (dirEntry.isFile()
|
||||
&& mime.lookup(dirEntry.name) === "application/x-subrip") {
|
||||
|
||||
subtitles.set(dirEntry.name, await convertSrtToVtt(
|
||||
path.join(fileDir, dirEntry.name)));
|
||||
if (
|
||||
dirEntry.isFile() &&
|
||||
mime.lookup(dirEntry.name) === "application/x-subrip"
|
||||
) {
|
||||
subtitles.set(
|
||||
dirEntry.name,
|
||||
await convertSrtToVtt(path.join(fileDir, dirEntry.name))
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -96,22 +99,20 @@ export async function startMediaServer(filePath: string, port: number) {
|
||||
if (range) {
|
||||
const bounds = range.substring(6).split("-");
|
||||
const start = parseInt(bounds[0]);
|
||||
const end = bounds[1]
|
||||
? parseInt(bounds[1]) : fileSize - 1;
|
||||
const end = bounds[1] ? parseInt(bounds[1]) : fileSize - 1;
|
||||
|
||||
res.writeHead(206, {
|
||||
"Accept-Ranges": "bytes"
|
||||
, "Content-Range": `bytes ${start}-${end}/${fileSize}`
|
||||
, "Content-Length": (end - start) + 1
|
||||
, "Content-Type": contentType
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
|
||||
"Content-Length": end - start + 1,
|
||||
"Content-Type": contentType
|
||||
});
|
||||
|
||||
fs.createReadStream(
|
||||
filePath, { start, end }).pipe(res);
|
||||
fs.createReadStream(filePath, { start, end }).pipe(res);
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
"Content-Length": fileSize
|
||||
, "Content-Type": contentType
|
||||
"Content-Length": fileSize,
|
||||
"Content-Type": contentType
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
@@ -139,7 +140,8 @@ export async function startMediaServer(filePath: string, port: number) {
|
||||
const ifaces = Object.values(os.networkInterfaces());
|
||||
for (const iface of ifaces) {
|
||||
const matchingIface = iface?.find(
|
||||
details => details.family === "IPv4" && !details.internal);
|
||||
details => details.family === "IPv4" && !details.internal
|
||||
);
|
||||
if (matchingIface) {
|
||||
localAddress = matchingIface.address;
|
||||
}
|
||||
@@ -155,21 +157,25 @@ export async function startMediaServer(filePath: string, port: number) {
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "mediaCast:mediaServerStarted"
|
||||
, data: {
|
||||
mediaPath: fileName
|
||||
, subtitlePaths: Array.from(subtitles.keys())
|
||||
, localAddress
|
||||
subject: "mediaCast:mediaServerStarted",
|
||||
data: {
|
||||
mediaPath: fileName,
|
||||
subtitlePaths: Array.from(subtitles.keys()),
|
||||
localAddress
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mediaServer.on("close", () => sendMessage({
|
||||
subject: "mediaCast:mediaServerStopped"
|
||||
}));
|
||||
mediaServer.on("error", () => sendMessage({
|
||||
subject: "mediaCast:mediaServerError"
|
||||
}));
|
||||
mediaServer.on("close", () =>
|
||||
sendMessage({
|
||||
subject: "mediaCast:mediaServerStopped"
|
||||
})
|
||||
);
|
||||
mediaServer.on("error", () =>
|
||||
sendMessage({
|
||||
subject: "mediaCast:mediaServerError"
|
||||
})
|
||||
);
|
||||
|
||||
mediaServer.listen(port);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import path from "path";
|
||||
|
||||
import { sendMessage } from "../lib/nativeMessaging";
|
||||
|
||||
|
||||
function fatal(message: string) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
let selectorApp: child_process.ChildProcess | undefined;
|
||||
let selectorAppOpen = false;
|
||||
|
||||
@@ -35,37 +33,39 @@ export function startReceiverSelector(data: string) {
|
||||
selectorAppOpen = false;
|
||||
}
|
||||
|
||||
const selectorPath = path.join(process.cwd()
|
||||
, "fx_cast_selector.app/Contents/MacOS/fx_cast_selector");
|
||||
|
||||
selectorApp = child_process.spawn(selectorPath, [ data ]);
|
||||
const selectorPath = path.join(
|
||||
process.cwd(),
|
||||
"fx_cast_selector.app/Contents/MacOS/fx_cast_selector"
|
||||
);
|
||||
|
||||
selectorApp = child_process.spawn(selectorPath, [data]);
|
||||
selectorAppOpen = true;
|
||||
|
||||
if (selectorApp.stdout) {
|
||||
selectorApp.stdout.setEncoding("utf-8");
|
||||
selectorApp.stdout.setEncoding("utf-8");
|
||||
selectorApp.stdout.on("data", data => {
|
||||
const jsonData = JSON.parse(data);
|
||||
|
||||
if (!jsonData.mediaType) {
|
||||
sendMessage({
|
||||
subject: "main:receiverSelector/stopped"
|
||||
, data: jsonData
|
||||
subject: "main:receiverSelector/stopped",
|
||||
data: jsonData
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
subject: "main:receiverSelector/selected"
|
||||
, data: jsonData
|
||||
subject: "main:receiverSelector/selected",
|
||||
data: jsonData
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selectorApp.on("error", err => {
|
||||
sendMessage({
|
||||
subject: "main:receiverSelector/error"
|
||||
, data: err.message
|
||||
subject: "main:receiverSelector/error",
|
||||
data: err.message
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user