mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Implement local media casting
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
const { Client } = require("castv2");
|
||||
const mdns = require("mdns-js");
|
||||
const mdns = require("mdns-js");
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
|
||||
const transforms = require("./transforms");
|
||||
|
||||
|
||||
const browser = mdns.createBrowser(mdns.tcp("googlecast"));
|
||||
|
||||
// Local media server
|
||||
let httpServer;
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
if (httpServer) httpServer.close();
|
||||
});
|
||||
|
||||
// Increase listener limit
|
||||
require('events').EventEmitter.defaultMaxListeners = 50;
|
||||
require("events").EventEmitter.defaultMaxListeners = 50;
|
||||
|
||||
// stdin -> stdout
|
||||
process.stdin
|
||||
@@ -45,10 +54,61 @@ async function handleMessage (message) {
|
||||
case "bridge:discover":
|
||||
browser.discover();
|
||||
break;
|
||||
|
||||
case "bridge:startHttpServer": {
|
||||
const { filePath, port } = message.data;
|
||||
|
||||
httpServer = 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;
|
||||
|
||||
const chunkSize = (end - start) + 1;
|
||||
|
||||
res.writeHead(206, {
|
||||
"Accept-Ranges": "bytes"
|
||||
, "Content-Range": `bytes ${start}-${end}/${fileSize}`
|
||||
, "Content-Length": chunkSize
|
||||
, "Content-Type": "video/mp4"
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath, { start, end }).pipe(res);
|
||||
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
"Content-Length": fileSize
|
||||
, "Content-Type": "video/mp4"
|
||||
});
|
||||
|
||||
fs.createReadStream(filePath).pipe(res)
|
||||
}
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
sendMessage({
|
||||
subject: "mediaCast:httpServerStarted"
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
};
|
||||
|
||||
case "bridge:stopHttpServer":
|
||||
if (httpServer) httpServer.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
browser.on("update", service => {
|
||||
if (!service.txt) return;
|
||||
|
||||
@@ -420,7 +480,7 @@ class Media {
|
||||
this.channel = this.session.channelMap.get(namespace);
|
||||
|
||||
this.channel.on("message", data => {
|
||||
if (data && data.type === 'MEDIA_STATUS'
|
||||
if (data && data.type === "MEDIA_STATUS"
|
||||
&& data.status && data.status.length > 0) {
|
||||
|
||||
const status = data.status[0];
|
||||
|
||||
10702
ext/package-lock.json
generated
10702
ext/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -80,10 +80,19 @@ browser.menus.create({
|
||||
, targetUrlPatterns: [
|
||||
"http://*/*"
|
||||
, "https://*/*"
|
||||
, "file://*/*"
|
||||
]
|
||||
, title: _("context_media_cast")
|
||||
});
|
||||
|
||||
|
||||
let mediaCastTabId;
|
||||
let mediaCastFrameId;
|
||||
|
||||
let mirrorCastTabId;
|
||||
let mirrorCastFrameId;
|
||||
|
||||
|
||||
browser.menus.onClicked.addListener(async (info, tab) => {
|
||||
const { frameId } = info;
|
||||
|
||||
@@ -95,6 +104,9 @@ browser.menus.onClicked.addListener(async (info, tab) => {
|
||||
|
||||
switch (info.menuItemId) {
|
||||
case "contextCast":
|
||||
mirrorCastTabId = tab.id;
|
||||
mirrorCastFrameId = frameId;
|
||||
|
||||
await browser.tabs.executeScript(tab.id, {
|
||||
code: `const selectedMedia = "${info.pageUrl ? "tab" : "screen"}";`
|
||||
, frameId
|
||||
@@ -108,6 +120,9 @@ browser.menus.onClicked.addListener(async (info, tab) => {
|
||||
break;
|
||||
|
||||
case "contextCastMedia":
|
||||
mediaCastTabId = tab.id;
|
||||
mediaCastFrameId = frameId;
|
||||
|
||||
// Pass media URL to media sender app
|
||||
await browser.tabs.executeScript(tab.id, {
|
||||
code: `const srcUrl = "${info.srcUrl}";`
|
||||
@@ -145,6 +160,8 @@ function initBridge (tabId, frameId) {
|
||||
// TODO: Integrate into messageRouter
|
||||
if (message.subject.startsWith("shim:")) {
|
||||
browser.tabs.sendMessage(tabId, message, { frameId });
|
||||
} else {
|
||||
messageRouter.handleMessage(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -220,6 +237,7 @@ messageRouter.register("main", async (message, sender) => {
|
||||
});
|
||||
|
||||
messageRouter.register("bridge", (message, sender) => {
|
||||
console.log(message);
|
||||
bridgeMap.get(sender.tab.id).postMessage(message);
|
||||
});
|
||||
|
||||
@@ -238,6 +256,16 @@ messageRouter.register("popup", (message, sender) => {
|
||||
}
|
||||
});
|
||||
|
||||
messageRouter.register("mirrorCast", message => {
|
||||
browser.tabs.sendMessage(mirrorCastTabId, message
|
||||
, { frameId: mirrorCastFrameId });
|
||||
});
|
||||
messageRouter.register("mediaCast", message => {
|
||||
browser.tabs.sendMessage(mediaCastTabId, message
|
||||
, { frameId: mediaCastFrameId });
|
||||
});
|
||||
|
||||
|
||||
browser.runtime.onMessage.addListener((message, sender) => {
|
||||
messageRouter.handleMessage(message, sender);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,35 @@ let logMessage;
|
||||
let session;
|
||||
let currentMedia;
|
||||
|
||||
let mediaElement = document.querySelector(`[src="${srcUrl}"]`);
|
||||
|
||||
const isLocalFile = window.location.protocol === "file:";
|
||||
|
||||
const mediaElement = isLocalFile
|
||||
? document.querySelector("video, audio")
|
||||
: document.querySelector(`[src="${srcUrl}"]`);
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
browser.runtime.sendMessage({
|
||||
subject: "bridge:stopHttpServer"
|
||||
});
|
||||
});
|
||||
|
||||
function getLocalAddress () {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel(null);
|
||||
pc.createOffer().then(pc.setLocalDescription.bind(pc));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pc.addEventListener("icecandidate", ev => {
|
||||
if (ev.candidate) {
|
||||
resolve(ev.candidate.candidate.split(" ")[4]);
|
||||
}
|
||||
});
|
||||
pc.addEventListener("error", ev => {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Fix this broken mess
|
||||
let ignoreMediaEvents = false;
|
||||
@@ -88,12 +116,39 @@ mediaElement.addEventListener("volumechange", () => {
|
||||
});
|
||||
|
||||
|
||||
function onRequestSessionSuccess (session_) {
|
||||
async function onRequestSessionSuccess (session_) {
|
||||
logMessage("onRequestSessionSuccess");
|
||||
|
||||
session = session_;
|
||||
|
||||
const mediaUrl = new URL(srcUrl);
|
||||
let mediaUrl = new URL(srcUrl);
|
||||
|
||||
// TODO: Get from extension settings
|
||||
const port = 9555;
|
||||
|
||||
|
||||
if (isLocalFile) {
|
||||
await new Promise((resolve, reject) => {
|
||||
browser.runtime.sendMessage({
|
||||
subject: "bridge:startHttpServer"
|
||||
, data: {
|
||||
filePath: decodeURI(mediaUrl.pathname)
|
||||
, port
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener(function onMessage (message) {
|
||||
if (message.subject === "mediaCast:httpServerStarted") {
|
||||
browser.runtime.onMessage.removeListener(onMessage);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Address of local HTTP server
|
||||
mediaUrl = new URL(`http://${await getLocalAddress()}:${port}/`);
|
||||
}
|
||||
|
||||
const mediaInfo = new chrome.cast.media.MediaInfo(mediaUrl.href);
|
||||
|
||||
// Media metadata (title/poster)
|
||||
|
||||
258
package-lock.json
generated
258
package-lock.json
generated
@@ -1,258 +0,0 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
|
||||
"integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
|
||||
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.1.0.tgz",
|
||||
"integrity": "sha1-K9Wf1+xuwOistk4J9Fpo7SrRlSo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.6",
|
||||
"jasmine-core": "~3.1.0"
|
||||
}
|
||||
},
|
||||
"jasmine-core": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.1.0.tgz",
|
||||
"integrity": "sha1-pHheE11d9lAk38kiSVPfWFvSdmw=",
|
||||
"dev": true
|
||||
},
|
||||
"jszip": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz",
|
||||
"integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "~2.3.0",
|
||||
"es6-promise": "~3.0.2",
|
||||
"lie": "~3.1.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.0.6"
|
||||
}
|
||||
},
|
||||
"lie": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
|
||||
"integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
|
||||
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~1.0.6",
|
||||
"string_decoder": "~0.10.x",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.5"
|
||||
}
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||
"dev": true
|
||||
},
|
||||
"selenium-webdriver": {
|
||||
"version": "4.0.0-alpha.1",
|
||||
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.1.tgz",
|
||||
"integrity": "sha512-z88rdjHAv3jmTZ7KSGUkTvo4rGzcDGMq0oXWHNIDK96Gs31JKVdu9+FMtT4KBrVoibg8dUicJDok6GnqqttO5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jszip": "^3.1.3",
|
||||
"rimraf": "^2.5.4",
|
||||
"tmp": "0.0.30",
|
||||
"xml2js": "^0.4.17"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz",
|
||||
"integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~9.0.1"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user