orchard/src/main/plugins/chromecast.ts
coredev-uk 94e8cd460a chore: Prettified Code
[ci skip]
2022-09-16 17:05:40 +00:00

353 lines
12 KiB
TypeScript

import * as electron from "electron";
import * as os from "os";
import { resolve } from "path";
import * as CiderReceiver from "../base/castreceiver";
const MediaRendererClient = require("upnp-mediarenderer-client");
export default class ChromecastPlugin {
/**
* Private variables for interaction in plugins
*/
private _win: any;
private _app: any;
private _lastfm: any;
private _store: any;
private _timer: any;
private audioClient = require("castv2-client").Client;
private mdns = require("mdns-js");
private devices: any = [];
private castDevices: any = [];
// private GCRunning = false;
// private GCBuffer: any;
// private expectedConnections = 0;
// private currentConnections = 0;
private activeConnections: any = [];
// private requests = [];
// private GCstream = new Stream.PassThrough(),
private connectedHosts: any = {};
private connectedPlayer: any;
private ciderPort: any = 9000;
private scanCount: any = 0;
// private server = false;
// private bufcount = 0;
// private bufcount2 = 0;
// private headerSent = false;
private searchForGCDevices() {
try {
let browser = this.mdns.createBrowser(this.mdns.tcp("googlecast"));
browser.on("ready", browser.discover);
browser.on("update", (service: any) => {
if (service.addresses && service.fullname && service.fullname.includes("_googlecast._tcp")) {
let a = service.txt.filter((u: any) => String(u).startsWith("fn="));
let name = (a[0] ?? "").substring(3) != "" ? (a[0] ?? "").substring(3) : service.fullname.substring(0, service.fullname.indexOf("._googlecast"));
this.ondeviceup(service.addresses[0], name + " (" + (service.type[0]?.description ?? "") + ")", "", "googlecast");
}
});
const Client = require("node-ssdp").Client;
// also do a SSDP/UPnP search
let ssdpBrowser = new Client();
ssdpBrowser.on("response", (headers: any, statusCode: any, rinfo: any) => {
var location = getLocation(headers);
if (location != null) {
this.getServiceDescription(location, rinfo.address);
}
});
function getLocation(headers: any) {
let location = null;
if (headers["LOCATION"] != null) {
location = headers["LOCATION"];
} else if (headers["Location"] != null) {
location = headers["Location"];
}
return location;
}
ssdpBrowser.search("urn:dial-multiscreen-org:device:dial:1");
// actual upnp devices
let ssdpBrowser2 = new Client();
ssdpBrowser2.on("response", (headers: any, statusCode: any, rinfo: any) => {
var location = getLocation(headers);
if (location != null) {
this.getServiceDescription(location, rinfo.address);
}
});
ssdpBrowser2.search("urn:schemas-upnp-org:device:MediaRenderer:1");
} catch (e) {
console.log("Search GC err", e);
}
}
private getServiceDescription(url: any, address: any) {
const request = require("request");
request.get(url, (error: any, response: any, body: any) => {
if (!error && response.statusCode === 200) {
this.parseServiceDescription(body, address, url);
}
});
}
private ondeviceup(host: any, name: any, location: any, type: any) {
if (this.castDevices.findIndex((item: any) => item.host === host && item.name === name && item.location === location && item.type === type) === -1) {
this.castDevices.push({
name: name,
host: host,
location: location,
type: type,
});
if (this.devices.indexOf(host) === -1) {
this.devices.push(host);
}
if (name) {
this._win.webContents.executeJavaScript(`console.log('deviceFound','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
console.log("deviceFound", host, name);
}
} else {
this._win.webContents.executeJavaScript(`console.log('deviceFound (added)','ip: ${host} name:${name}')`).catch((err: any) => console.error(err));
console.log("deviceFound (added)", host, name);
}
}
private parseServiceDescription(body: any, address: any, url: any) {
const parseString = require("xml2js").parseString;
parseString(body, (err: any, result: any) => {
if (!err && result && result.root && result.root.device) {
const device = result.root.device[0];
console.log("device", device);
let devicetype = "googlecast";
console.log();
if (device.deviceType && device.deviceType.toString() === "urn:schemas-upnp-org:device:MediaRenderer:1") {
devicetype = "upnp";
}
this.ondeviceup(address, device.friendlyName.toString(), url, devicetype);
}
});
}
private loadMedia(client: any, song: any, artist: any, album: any, albumart: any, cb?: any) {
// const u = 'http://' + this.getIp() + ':' + server.address().port + '/';
// const DefaultMediaReceiver : any = require('castv2-client').DefaultMediaReceiver;
client.launch(CiderReceiver, (err: any, player: any) => {
if (err) {
console.log(err);
return;
}
let media = {
// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.
contentId: "http://" + this.getIp() + ":" + this.ciderPort + "/audio.wav",
contentType: "audio/wav",
streamType: "LIVE", // or LIVE
// Title and cover displayed while buffering
metadata: {
type: 0,
metadataType: 3,
title: song ?? "",
albumName: album ?? "",
artist: artist ?? "",
images: [{ url: albumart ?? "" }],
},
};
player.on("status", (status: any) => {
console.log("status broadcast playerState=%s", status);
});
console.log('app "%s" launched, loading media %s ...', player, media);
player.load(
media,
{
autoplay: true,
},
(err: any, status: any) => {
console.log("media loaded playerState=%s", status);
}
);
client.getStatus((x: any, status: any) => {
if (status && status.volume) {
client.volume = status.volume.level;
client.muted = status.volume.muted;
client.stepInterval = status.volume.stepInterval;
}
});
// send websocket ip
player.sendIp("ws://" + this.getIp() + ":26369");
electron.ipcMain.on("stopGCast", (_event) => {
player.kill();
});
electron.app.on("before-quit", (_event) => {
player.kill();
});
});
}
private getIp() {
let ip: string = "";
let ip2: any = [];
let alias = 0;
const ifaces: any = os.networkInterfaces();
for (let dev in ifaces) {
ifaces[dev].forEach((details: any) => {
if (details.family === "IPv4" && !details.internal) {
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ":" + alias : ""))) {
if (details.address.substring(0, 8) === "192.168." || details.address.substring(0, 7) === "172.16." || details.address.substring(0, 3) === "10.") {
if (!ip.startsWith("192.168.") || (ip2.startsWith("192.168.") && !ip.startsWith("192.168.") && ip2.startsWith("172.16.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.")) || (ip2.startsWith("10.") && !ip.startsWith("192.168.") && !ip.startsWith("172.16.") && !ip.startsWith("10."))) {
ip = details.address;
}
++alias;
}
}
}
});
}
return ip;
}
private stream(device: any, song: any, artist: any, album: any, albumart: any) {
let castMode = "googlecast";
let UPNPDesc = "";
castMode = device.type;
UPNPDesc = device.location;
let client;
if (castMode === "googlecast") {
let client = new this.audioClient();
client.volume = 100;
client.stepInterval = 0.5;
client.muted = false;
client.connect(device.host, () => {
// console.log('connected, launching app ...', 'http://' + this.getIp() + ':' + server.address().port + '/');
if (!this.connectedHosts[device.host]) {
this.connectedHosts[device.host] = client;
this.activeConnections.push(client);
}
this.loadMedia(client, song, artist, album, albumart);
});
client.on("close", () => {
console.info("Client Closed");
for (let i = this.activeConnections.length - 1; i >= 0; i--) {
if (this.activeConnections[i] === client) {
this.activeConnections.splice(i, 1);
return;
}
}
});
client.on("error", (err: any) => {
console.log("Error: %s", err.message);
client.close();
delete this.connectedHosts[device.host];
});
} else {
// upnp devices
try {
let client = new MediaRendererClient(UPNPDesc);
const options = {
autoplay: true,
contentType: "audio/x-wav",
dlnaFeatures: "DLNA.ORG_PN=-;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000",
metadata: {
title: "Cider",
creator: "Streaming ...",
type: "audio", // can be 'video', 'audio' or 'image'
// url: 'http://' + getIp() + ':' + server.address().port + '/',
// protocolInfo: 'DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000;
},
};
client.load("http://" + this.getIp() + ":" + this.ciderPort + "/audio.wav", options, function (err: any, _result: any) {
if (err) throw err;
console.log("playing ...");
});
if (!this.connectedHosts[device.host]) {
this.connectedHosts[device.host] = client;
this.activeConnections.push(client);
}
} catch (e) {}
}
}
private async setupGCServer() {
return "";
}
/**
* Base Plugin Details (Eventually implemented into a GUI in settings)
*/
public name: string = "Chromecast";
public description: string = "LastFM plugin for Cider";
public version: string = "0.0.1";
public author: string = "vapormusic / Cider Collective";
/**
* Runs on plugin load (Currently run on application start)
*/
constructor(utils: { getApp: () => any; getStore: () => any }) {
this._app = utils.getApp();
this._store = utils.getStore();
}
/**
* Runs on app ready
*/
onReady(win: any): void {
this._win = win;
electron.ipcMain.on("getKnownCastDevices", (event) => {
event.returnValue = this.castDevices;
});
electron.ipcMain.on("performGCCast", (event, device, song, artist, album, albumart) => {
// this.setupGCServer().then( () => {
this._win.webContents.setAudioMuted(true);
console.log(device);
this.stream(device, song, artist, album, albumart);
// })
});
electron.ipcMain.on("getChromeCastDevices", (_event, _data) => {
if (this.scanCount++ == 2) {
this.scanCount = 0;
this.castDevices = [];
}
this.searchForGCDevices();
});
electron.ipcMain.on("stopGCast", (_event) => {
this._win.webContents.setAudioMuted(false);
this.activeConnections.forEach((client: any) => {
try {
client.stop();
} catch (e) {}
});
this.activeConnections = [];
this.connectedHosts = {};
});
}
/**
* Runs on app stop
*/
onBeforeQuit(): void {}
/**
* Runs on song change
* @param attributes Music Attributes
*/
onNowPlayingItemDidChange(attributes: any): void {}
onRendererReady(): void {
this._win.webContents.executeJavaScript(`ipcRenderer.sendSync('get-port')`).then((result: any) => {
this.ciderPort = result;
});
}
}