mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-12 18:39:58 +00:00
Move http media server to separate class
This commit is contained in:
@@ -39,6 +39,10 @@ extension AppDelegate: NSApplicationDelegate {
|
|||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applicationDidResignActive(_ aNotification: Notification) {
|
||||||
|
self.mainWindow?.performClose(aNotification)
|
||||||
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {}
|
func applicationWillTerminate(_ aNotification: Notification) {}
|
||||||
|
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(_ app: NSApplication) -> Bool {
|
func applicationShouldTerminateAfterLastWindowClosed(_ app: NSApplication) -> Bool {
|
||||||
|
|||||||
73
app/src/MediaServer.ts
Normal file
73
app/src/MediaServer.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import http from "http";
|
||||||
|
import mime from "mime-types";
|
||||||
|
import EventEmitter from "events";
|
||||||
|
|
||||||
|
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,6 +8,7 @@ import mime from "mime-types";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import Media from "./Media";
|
import Media from "./Media";
|
||||||
|
import MediaServer from "./MediaServer";
|
||||||
import Session from "./Session";
|
import Session from "./Session";
|
||||||
import StatusListener from "./StatusListener";
|
import StatusListener from "./StatusListener";
|
||||||
import * as transforms from "./transforms";
|
import * as transforms from "./transforms";
|
||||||
@@ -27,11 +28,11 @@ events.EventEmitter.defaultMaxListeners = 50;
|
|||||||
const browser = new dnssd.Browser(dnssd.tcp("googlecast"));
|
const browser = new dnssd.Browser(dnssd.tcp("googlecast"));
|
||||||
|
|
||||||
// Local media server
|
// Local media server
|
||||||
let httpServer: http.Server;
|
let mediaServer: MediaServer;
|
||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
if (httpServer) {
|
if (mediaServer) {
|
||||||
httpServer.close();
|
mediaServer.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,58 +183,32 @@ async function handleMessage (message: Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case "bridge:/startHttpServer": {
|
case "bridge:/mediaServer/start": {
|
||||||
const { filePath, port } = message.data;
|
const { filePath, port } = message.data;
|
||||||
|
|
||||||
httpServer = http.createServer((req, res) => {
|
mediaServer = new MediaServer(filePath, port);
|
||||||
const { size: fileSize } = fs.statSync(filePath);
|
mediaServer.start();
|
||||||
const { range } = req.headers;
|
|
||||||
|
|
||||||
const contentType = mime.lookup(filePath) || "video/mp4";
|
mediaServer.on("started", () => {
|
||||||
|
|
||||||
// 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(filePath, { start, end }).pipe(res);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res.writeHead(200, {
|
|
||||||
"Content-Length": fileSize
|
|
||||||
, "Content-Type": contentType
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.createReadStream(filePath).pipe(res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
httpServer.listen(port, () => {
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
subject: "mediaCast:/httpServerStarted"
|
subject: "mediaCast:/mediaServer/started"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mediaServer.on("stopped", () => {
|
||||||
|
sendMessage({
|
||||||
|
subject: "mediaCast:/mediaServer/stopped"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "bridge:/stopHttpServer": {
|
case "bridge:/mediaServer/stop": {
|
||||||
if (httpServer) {
|
if (mediaServer) {
|
||||||
httpServer.close();
|
mediaServer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
subject: string;
|
subject: string;
|
||||||
data: any;
|
data?: any;
|
||||||
_id?: string;
|
_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
"message": "HTML5 video/audio media casting."
|
"message": "HTML5 video/audio media casting."
|
||||||
}
|
}
|
||||||
, "optionsMediaEnabled": {
|
, "optionsMediaEnabled": {
|
||||||
"message": "Media casting enabled"
|
"message": "Enable media casting"
|
||||||
}
|
}
|
||||||
, "optionsMediaSyncElement": {
|
, "optionsMediaSyncElement": {
|
||||||
"message": "Sync receiver state with media element"
|
"message": "Sync receiver state with media element"
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
"message": "HTTP server started by the bridge app to stream local media files to the cast receiver."
|
"message": "HTTP server started by the bridge app to stream local media files to the cast receiver."
|
||||||
}
|
}
|
||||||
, "optionsLocalMediaEnabled": {
|
, "optionsLocalMediaEnabled": {
|
||||||
"message": "Local media casting enabled"
|
"message": "Enable local media casting"
|
||||||
}
|
}
|
||||||
, "optionsLocalMediaServerPort": {
|
, "optionsLocalMediaServerPort": {
|
||||||
"message": "HTTP server port:"
|
"message": "HTTP server port:"
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
"message": "Sites for which to replace the user agent with a Chrome version for compatibility. Must be valid match patterns."
|
"message": "Sites for which to replace the user agent with a Chrome version for compatibility. Must be valid match patterns."
|
||||||
}
|
}
|
||||||
, "optionsUserAgentWhitelistEnabled": {
|
, "optionsUserAgentWhitelistEnabled": {
|
||||||
"message": "Site whitelist enabled"
|
"message": "Enable site whitelist"
|
||||||
}
|
}
|
||||||
, "optionsUserAgentWhitelistContent": {
|
, "optionsUserAgentWhitelistContent": {
|
||||||
"message": "Match patterns (newline-separated):"
|
"message": "Match patterns (newline-separated):"
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
"message": "Screen/Tab mirroring to a Chromecast receiver app."
|
"message": "Screen/Tab mirroring to a Chromecast receiver app."
|
||||||
}
|
}
|
||||||
, "optionsMirroringEnabled": {
|
, "optionsMirroringEnabled": {
|
||||||
"message": "Screen mirroring enabled"
|
"message": "Enable screen mirroring"
|
||||||
}
|
}
|
||||||
, "optionsMirroringAppId": {
|
, "optionsMirroringAppId": {
|
||||||
"message": "Receiver app ID:"
|
"message": "Receiver app ID:"
|
||||||
|
|||||||
@@ -373,8 +373,6 @@ const statusBridge = browser.runtime.connectNative(APPLICATION_NAME);
|
|||||||
const statusBridgeReceivers = new Map<string, Receiver>();
|
const statusBridgeReceivers = new Map<string, Receiver>();
|
||||||
|
|
||||||
statusBridge.onMessage.addListener(async (message: Message) => {
|
statusBridge.onMessage.addListener(async (message: Message) => {
|
||||||
console.log(message);
|
|
||||||
|
|
||||||
switch (message.subject) {
|
switch (message.subject) {
|
||||||
case "shim:/serviceUp": {
|
case "shim:/serviceUp": {
|
||||||
const receiver = (message as ServiceUpMessage).data;
|
const receiver = (message as ServiceUpMessage).data;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const mediaElement = browser.menus.getTargetElement(targetElementId);
|
|||||||
|
|
||||||
window.addEventListener("beforeunload", () => {
|
window.addEventListener("beforeunload", () => {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
subject: "bridge:/stopHttpServer"
|
subject: "bridge:/mediaServer/stop"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.mediaStopOnUnload) {
|
if (options.mediaStopOnUnload) {
|
||||||
@@ -58,7 +58,7 @@ async function onRequestSessionSuccess (session_) {
|
|||||||
if (isLocalFile) {
|
if (isLocalFile) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
browser.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
subject: "bridge:/startHttpServer"
|
subject: "bridge:/mediaServer/start"
|
||||||
, data: {
|
, data: {
|
||||||
filePath: decodeURI(mediaUrl.pathname)
|
filePath: decodeURI(mediaUrl.pathname)
|
||||||
, port
|
, port
|
||||||
@@ -66,7 +66,7 @@ async function onRequestSessionSuccess (session_) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener(function onMessage (message) {
|
browser.runtime.onMessage.addListener(function onMessage (message) {
|
||||||
if (message.subject === "mediaCast:/httpServerStarted") {
|
if (message.subject === "mediaCast:/mediaServer/started") {
|
||||||
browser.runtime.onMessage.removeListener(onMessage);
|
browser.runtime.onMessage.removeListener(onMessage);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user