mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-11 10:09:59 +00:00
Restructure background script (#70)
Splits some background script functionality into separate modules: - Receiver selector handling is moved to ./SelectorManager. - Status bridge handling is moved to ./StatusManager. - Menu creation and updates are handled in ./createMenus. - Shim creation is handled in ./createShim. TypedEventTarget allows EventTarget-derived classes to export typed events. Options type definition is moved to ./lib/options, module assumes more responsibility for update handling and provides a "changed" event. Private cast._requestSession method allows bypassing receiver selector.
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import EventEmitter from "events";
|
||||
import fs from "fs";
|
||||
import http from "http";
|
||||
import mime from "mime-types";
|
||||
|
||||
import { Message
|
||||
, SendMessageCallback } from "./types";
|
||||
|
||||
|
||||
export default class MediaServer extends EventEmitter {
|
||||
private httpServer: http.Server;
|
||||
|
||||
constructor (
|
||||
private filePath: string
|
||||
, private port: number) {
|
||||
|
||||
super();
|
||||
this.httpServer = http.createServer(this.requestListener.bind(this));
|
||||
}
|
||||
|
||||
public start () {
|
||||
this.httpServer.listen(this.port, () => {
|
||||
this.emit("started");
|
||||
});
|
||||
}
|
||||
|
||||
public stop () {
|
||||
if (this.httpServer && this.httpServer.listening) {
|
||||
this.httpServer.close(() => {
|
||||
this.emit("stopped");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private requestListener (
|
||||
req: http.IncomingMessage
|
||||
, res: http.ServerResponse) {
|
||||
|
||||
const { size: fileSize } = fs.statSync(this.filePath);
|
||||
const { range } = req.headers;
|
||||
|
||||
const contentType = mime.lookup(this.filePath) || "video/mp4";
|
||||
|
||||
// Partial content HTTP 206
|
||||
if (range) {
|
||||
const bounds = range.substring(6).split("-");
|
||||
|
||||
const start = parseInt(bounds[0]);
|
||||
const end = bounds[1] ? parseInt(bounds[1]) : fileSize - 1;
|
||||
|
||||
const chunkSize = (end - start) + 1;
|
||||
|
||||
res.writeHead(206, {
|
||||
"Accept-Ranges": "bytes"
|
||||
, "Content-Range": `bytes ${start}-${end}/${fileSize}`
|
||||
, "Content-Length": chunkSize
|
||||
, "Content-Type": contentType
|
||||
});
|
||||
|
||||
fs.createReadStream(this.filePath, { start, end }).pipe(res);
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
"Content-Length": fileSize
|
||||
, "Content-Type": contentType
|
||||
});
|
||||
|
||||
fs.createReadStream(this.filePath).pipe(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import mime from "mime-types";
|
||||
import path from "path";
|
||||
|
||||
import Media from "./Media";
|
||||
import MediaServer from "./MediaServer";
|
||||
import Session from "./Session";
|
||||
import StatusListener from "./StatusListener";
|
||||
|
||||
@@ -32,11 +31,11 @@ events.EventEmitter.defaultMaxListeners = 50;
|
||||
const browser = new dnssd.Browser(dnssd.tcp("googlecast"));
|
||||
|
||||
// Local media server
|
||||
let mediaServer: MediaServer;
|
||||
let mediaServer: http.Server;
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
if (mediaServer) {
|
||||
mediaServer.stop();
|
||||
if (mediaServer && mediaServer.listening) {
|
||||
mediaServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -47,18 +46,28 @@ const encodeTransform = new EncodeTransform();
|
||||
// stdin -> stdout
|
||||
process.stdin
|
||||
.pipe(decodeTransform)
|
||||
.pipe(new ResponseTransform(handleMessage))
|
||||
.pipe(encodeTransform)
|
||||
|
||||
decodeTransform.on("data", handleMessage);
|
||||
|
||||
encodeTransform
|
||||
.pipe(process.stdout);
|
||||
|
||||
/**
|
||||
* Encode and send a message to the extension.
|
||||
* Encode and send a message to the extension. If message is
|
||||
* a string, send that as the message subject, else send a
|
||||
* passed message object.
|
||||
*/
|
||||
function sendMessage (message: object) {
|
||||
function sendMessage (message: string | object) {
|
||||
try {
|
||||
encodeTransform.write(message);
|
||||
if (typeof message === "string") {
|
||||
encodeTransform.write({
|
||||
subject: message
|
||||
});
|
||||
} else {
|
||||
encodeTransform.write(message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to encode message");
|
||||
console.error("Failed to encode message", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +81,7 @@ const existingSessions: Map<string, Session> = new Map();
|
||||
const existingMedia: Map<string, Media> = new Map();
|
||||
|
||||
let receiverSelectorApp: child_process.ChildProcess;
|
||||
let receiverSelectorAppClosed = true;
|
||||
|
||||
/**
|
||||
* Handle incoming messages from the extension and forward
|
||||
@@ -130,10 +140,18 @@ async function handleMessage (message: Message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.subject.startsWith("bridge:/receiverSelector/")) {
|
||||
handleReceiverSelectorMessage(message);
|
||||
}
|
||||
|
||||
if (message.subject.startsWith("bridge:/mediaServer/")) {
|
||||
handleMediaServerMessage(message);
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
case "bridge:/getInfo": {
|
||||
const extensionVersion = message.data;
|
||||
return __applicationVersion;
|
||||
encodeTransform.write(__applicationVersion);
|
||||
}
|
||||
|
||||
case "bridge:/initialize": {
|
||||
@@ -142,8 +160,11 @@ async function handleMessage (message: Message) {
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleReceiverSelectorMessage (message: Message) {
|
||||
switch (message.subject) {
|
||||
case "bridge:/receiverSelector/open": {
|
||||
const receiverSelectorData = message.data;
|
||||
|
||||
@@ -167,6 +188,8 @@ async function handleMessage (message: Message) {
|
||||
path.join(process.cwd(), "selector")
|
||||
, [ receiverSelectorData ]);
|
||||
|
||||
receiverSelectorAppClosed = false;
|
||||
|
||||
receiverSelectorApp.stdout!.setEncoding("utf8");
|
||||
receiverSelectorApp.stdout!.on("data", data => {
|
||||
sendMessage({
|
||||
@@ -175,7 +198,7 @@ async function handleMessage (message: Message) {
|
||||
});
|
||||
});
|
||||
|
||||
receiverSelectorApp.addListener("error", err => {
|
||||
receiverSelectorApp.on("error", err => {
|
||||
sendMessage({
|
||||
subject: "main:/receiverSelector/error"
|
||||
, data: err.message
|
||||
@@ -183,9 +206,13 @@ async function handleMessage (message: Message) {
|
||||
});
|
||||
|
||||
receiverSelectorApp.on("close", () => {
|
||||
sendMessage({
|
||||
subject: "main:/receiverSelector/close"
|
||||
});
|
||||
if (!receiverSelectorAppClosed) {
|
||||
receiverSelectorAppClosed = true;
|
||||
|
||||
sendMessage({
|
||||
subject: "main:/receiverSelector/close"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -193,34 +220,78 @@ async function handleMessage (message: Message) {
|
||||
|
||||
case "bridge:/receiverSelector/close": {
|
||||
receiverSelectorApp.kill();
|
||||
receiverSelectorAppClosed = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleMediaServerMessage (message: Message) {
|
||||
switch (message.subject) {
|
||||
case "bridge:/mediaServer/start": {
|
||||
const { filePath, port } = message.data;
|
||||
const { filePath, port }
|
||||
: { filePath: string, port: number } = message.data;
|
||||
|
||||
mediaServer = new MediaServer(filePath, port);
|
||||
mediaServer.start();
|
||||
const contentType = mime.lookup(filePath);
|
||||
|
||||
mediaServer.on("started", () => {
|
||||
sendMessage({
|
||||
subject: "mediaCast:/mediaServer/started"
|
||||
});
|
||||
if (!contentType) {
|
||||
sendMessage("mediaCast:/mediaServer/error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (mediaServer && mediaServer.listening) {
|
||||
mediaServer.close();
|
||||
}
|
||||
|
||||
mediaServer = http.createServer((req, res) => {
|
||||
const { size: fileSize } = fs.statSync(filePath);
|
||||
const { range } = req.headers;
|
||||
|
||||
// Partial content HTTP 206
|
||||
if (range) {
|
||||
const bounds = range.substring(6).split("-");
|
||||
const start = parseInt(bounds[0]);
|
||||
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
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath, { start, end }).pipe(res);
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
"Content-Length": fileSize
|
||||
, "Content-Type": contentType
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
}
|
||||
});
|
||||
|
||||
mediaServer.on("stopped", () => {
|
||||
sendMessage({
|
||||
subject: "mediaCast:/mediaServer/stopped"
|
||||
});
|
||||
mediaServer.on("listening", () => {
|
||||
sendMessage("mediaCast:/mediaServer/started");
|
||||
});
|
||||
mediaServer.on("close", () => {
|
||||
console.error("mediaServer close");
|
||||
sendMessage("mediaCast:/mediaServer/stopped");
|
||||
});
|
||||
mediaServer.on("error", (a) => {
|
||||
console.error("mediaServer error", a);
|
||||
sendMessage("mediaCast:/mediaServer/error");
|
||||
});
|
||||
|
||||
mediaServer.listen(port);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "bridge:/mediaServer/stop": {
|
||||
if (mediaServer) {
|
||||
mediaServer.stop();
|
||||
if (mediaServer && mediaServer.listening) {
|
||||
mediaServer.close();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user