chromecast (very botchy)
This commit is contained in:
parent
9e52541ecf
commit
c893304b5d
9 changed files with 483 additions and 19 deletions
|
@ -38,6 +38,7 @@
|
||||||
"@sentry/electron": "^2.5.4",
|
"@sentry/electron": "^2.5.4",
|
||||||
"@sentry/integrations": "^6.17.4",
|
"@sentry/integrations": "^6.17.4",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
|
"castv2-client": "^1.2.0",
|
||||||
"discord-rpc": "^4.0.1",
|
"discord-rpc": "^4.0.1",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
"electron-fetch": "^1.7.4",
|
"electron-fetch": "^1.7.4",
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
"mpris-service": "^2.1.2",
|
"mpris-service": "^2.1.2",
|
||||||
"music-metadata": "^7.11.4",
|
"music-metadata": "^7.11.4",
|
||||||
"node-gyp": "^8.4.1",
|
"node-gyp": "^8.4.1",
|
||||||
|
"node-ssdp": "^4.0.1",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
|
|
@ -78,6 +78,7 @@ export class BrowserWindow {
|
||||||
"components/lyrics-view",
|
"components/lyrics-view",
|
||||||
"components/fullscreen",
|
"components/fullscreen",
|
||||||
"components/miniplayer",
|
"components/miniplayer",
|
||||||
|
"components/castmenu"
|
||||||
],
|
],
|
||||||
appRoutes: [
|
appRoutes: [
|
||||||
{
|
{
|
||||||
|
|
337
src/main/plugins/chromecast.ts
Normal file
337
src/main/plugins/chromecast.ts
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as os from 'os';
|
||||||
|
import {resolve} from 'path';
|
||||||
|
|
||||||
|
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 port = false;
|
||||||
|
// 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) {
|
||||||
|
this.ondeviceup(service.addresses[0], service.fullname.substring(0, service.fullname.indexOf("._googlecast")) + " " + (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
|
||||||
|
// if (app.cfg.get("audio.enableDLNA")) {
|
||||||
|
// let ssdpBrowser2 = new Client();
|
||||||
|
// ssdpBrowser2.on('response', (headers, statusCode, rinfo) => {
|
||||||
|
// 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(DefaultMediaReceiver, (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() + ':9000/audio.webm',
|
||||||
|
contentType: 'audio/webm',
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIp() {
|
||||||
|
let ip = false;
|
||||||
|
let alias = 0;
|
||||||
|
let ifaces: any = os.networkInterfaces();
|
||||||
|
for (var dev in ifaces) {
|
||||||
|
ifaces[dev].forEach((details:any) => {
|
||||||
|
if (details.family === 'IPv4') {
|
||||||
|
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.'
|
||||||
|
) {
|
||||||
|
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 {
|
||||||
|
// 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: 'Apple Music Electron',
|
||||||
|
// 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://' + getIp() + ':' + server.address().port + '/a.wav', options, function (err, _result) {
|
||||||
|
// if (err) throw err;
|
||||||
|
// console.log('playing ...');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// } 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(app: any, store: any) {
|
||||||
|
this._app = app;
|
||||||
|
this._store = store
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) => {
|
||||||
|
this.searchForGCDevices();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('stopGCast', (_event) => {
|
||||||
|
this._win.webContents.setAudioMuted(false);
|
||||||
|
this.activeConnections = [];
|
||||||
|
this.connectedHosts = {};
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: any): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,11 +9,13 @@ var CiderAudio = {
|
||||||
vibrantbassNode: null,
|
vibrantbassNode: null,
|
||||||
llpw: null,
|
llpw: null,
|
||||||
llpwEnabled: null,
|
llpwEnabled: null,
|
||||||
analogWarmth: null
|
analogWarmth: null,
|
||||||
},
|
},
|
||||||
|
ccON: false,
|
||||||
|
mediaRecorder: null,
|
||||||
init: function (cb = function () { }) {
|
init: function (cb = function () { }) {
|
||||||
//AudioOutputs.fInit = true;
|
//AudioOutputs.fInit = true;
|
||||||
searchInt = setInterval(function () {
|
let searchInt = setInterval(function () {
|
||||||
if (document.getElementById("apple-music-player")) {
|
if (document.getElementById("apple-music-player")) {
|
||||||
//AudioOutputs.eqReady = true;
|
//AudioOutputs.eqReady = true;
|
||||||
document.getElementById("apple-music-player").crossOrigin = "anonymous";
|
document.getElementById("apple-music-player").crossOrigin = "anonymous";
|
||||||
|
@ -138,19 +140,29 @@ var CiderAudio = {
|
||||||
CiderAudio.hierarchical_loading();
|
CiderAudio.hierarchical_loading();
|
||||||
},
|
},
|
||||||
sendAudio: function (){
|
sendAudio: function (){
|
||||||
var options = {
|
if (!CiderAudio.ccON) {
|
||||||
mimeType : 'audio/webm; codecs=opus'
|
CiderAudio.ccON = true
|
||||||
};
|
let searchInt = setInterval(function () {
|
||||||
var destnode = CiderAudio.context.createMediaStreamDestination();
|
if (CiderAudio.context != null && CiderAudio.audioNodes.gainNode != null) {
|
||||||
CiderAudio.audioNodes.gainNode.connect(destnode)
|
var options = {
|
||||||
var mediaRecorder = new MediaRecorder(destnode.stream,options);
|
mimeType: 'audio/webm; codecs=opus'
|
||||||
mediaRecorder.start(1);
|
};
|
||||||
mediaRecorder.ondataavailable = function(e) {
|
var destnode = CiderAudio.context.createMediaStreamDestination();
|
||||||
e.data.arrayBuffer().then(buffer => {
|
CiderAudio.audioNodes.gainNode.connect(destnode)
|
||||||
ipcRenderer.send('writeAudio',buffer)
|
var mediaRecorder = new MediaRecorder(destnode.stream, options);
|
||||||
}
|
mediaRecorder.start(1);
|
||||||
);
|
mediaRecorder.ondataavailable = function (e) {
|
||||||
|
e.data.arrayBuffer().then(buffer => {
|
||||||
|
ipcRenderer.send('writeAudio', buffer)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(searchInt);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
analogWarmth_h2_3: function (status, hierarchy){
|
analogWarmth_h2_3: function (status, hierarchy){
|
||||||
if (status === true) { // 23 Band Adjustment
|
if (status === true) { // 23 Band Adjustment
|
||||||
|
|
|
@ -253,6 +253,7 @@ const app = new Vue({
|
||||||
pluginMenu: false,
|
pluginMenu: false,
|
||||||
audioControls: false,
|
audioControls: false,
|
||||||
showPlaylist: false,
|
showPlaylist: false,
|
||||||
|
castMenu: false
|
||||||
},
|
},
|
||||||
socialBadges: {
|
socialBadges: {
|
||||||
badgeMap: {},
|
badgeMap: {},
|
||||||
|
@ -269,7 +270,8 @@ const app = new Vue({
|
||||||
headerItems: {}
|
headerItems: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pauseButtonTimer: null
|
pauseButtonTimer: null,
|
||||||
|
activeCasts: []
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
cfg: {
|
cfg: {
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
<transition name="modal">
|
<transition name="modal">
|
||||||
<audio-settings v-if="modals.audioSettings"></audio-settings>
|
<audio-settings v-if="modals.audioSettings"></audio-settings>
|
||||||
</transition>
|
</transition>
|
||||||
|
<transition name="modal">
|
||||||
|
<castmenu v-if="modals.castMenu"></castmenu>
|
||||||
|
</transition>
|
||||||
<transition name="modal">
|
<transition name="modal">
|
||||||
<plugin-menu v-if="modals.pluginMenu"></plugin-menu>
|
<plugin-menu v-if="modals.pluginMenu"></plugin-menu>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
|
@ -96,6 +96,10 @@
|
||||||
<span class="usermenu-item-icon"><%- include("../svg/smartphone.svg") %></span>
|
<span class="usermenu-item-icon"><%- include("../svg/smartphone.svg") %></span>
|
||||||
<span class="usermenu-item-name">{{$root.getLz('action.showWebRemoteQR')}}</span>
|
<span class="usermenu-item-name">{{$root.getLz('action.showWebRemoteQR')}}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="usermenu-item" @click="modals.castMenu = true">
|
||||||
|
<span class="usermenu-item-icon"><%- include("../svg/cast.svg") %></span>
|
||||||
|
<span class="usermenu-item-name">Cast</span>
|
||||||
|
</button>
|
||||||
<button class="usermenu-item" v-if="cfg.advanced.AudioContext"
|
<button class="usermenu-item" v-if="cfg.advanced.AudioContext"
|
||||||
@click="modals.audioSettings = true">
|
@click="modals.audioSettings = true">
|
||||||
<span class="usermenu-item-icon"><%- include("../svg/headphones.svg") %></span>
|
<span class="usermenu-item-icon"><%- include("../svg/headphones.svg") %></span>
|
||||||
|
|
106
src/renderer/views/components/castmenu.ejs
Normal file
106
src/renderer/views/components/castmenu.ejs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<script type="text/x-template" id="castmenu">
|
||||||
|
<div class="spatialproperties-panel castmenu modal-fullscreen" >
|
||||||
|
<div class="modal-window">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">Cast To Devices</div>
|
||||||
|
<button class="close-btn" @click="close()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content" style="overflow-y: overlay; padding: 3%">
|
||||||
|
<div class="md-labeltext">Chromecast</div>
|
||||||
|
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;">
|
||||||
|
<template v-if="!scanning">
|
||||||
|
<template v-for="(device) in devices.cast">
|
||||||
|
<div class="md-option-line" style="cursor: pointer" @click="setCast(device)">
|
||||||
|
<div class="md-option-segment">
|
||||||
|
{{ device.name }}
|
||||||
|
<br>
|
||||||
|
<small>{{ device.host }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment_auto" style="display: flex;justify-content: center;align-items: center" v-if="activeCasts.includes(device)">
|
||||||
|
Connected
|
||||||
|
</div>
|
||||||
|
<div class="md-option-segment_auto" v-else style="display: flex;justify-content: center;align-items: center">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 34 34" fill="#fff" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" class="castPlayIndicator"><path d="M28.228,18.327l-16.023,8.983c-0.99,0.555 -2.205,-0.17 -2.205,-1.318l0,-17.984c0,-1.146 1.215,-1.873 2.205,-1.317l16.023,8.982c1.029,0.577 1.029,2.077 0,2.654Z" style="fill-rule:nonzero"></path></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="md-option-line" style="cursor: pointer">
|
||||||
|
<div class="md-option-segment">
|
||||||
|
Scanning...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="md-labeltext" style="opacity:0.5;">AirPlay</div>
|
||||||
|
<div class="md-option-container" style="margin-top: 12px;margin-bottom: 12px;opacity:0.5;">
|
||||||
|
<div class="md-option-line">
|
||||||
|
<div class="md-option-segment">
|
||||||
|
AirPlay is still under development
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-footer">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" v-if="activeCasts.length != 0">
|
||||||
|
<button style="width:100%" @click="stopCasting()" class="md-btn md-btn-block md-btn-primary">Stop casting to all devices</button>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<button style="width:100%" class="md-btn md-btn-block" @click="scan()">Scan</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
Vue.component('castmenu', {
|
||||||
|
template: '#castmenu',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
devices: {
|
||||||
|
cast: [],
|
||||||
|
airplay: []
|
||||||
|
},
|
||||||
|
scanning: false,
|
||||||
|
activeCasts: this.$root.activeCasts,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.scan();
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
activeCasts: function (newVal, oldVal) {
|
||||||
|
this.$root.activeCasts = this.activeCasts;
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$root.modals.castMenu = false
|
||||||
|
},
|
||||||
|
scan() {
|
||||||
|
let self = this;
|
||||||
|
this.scanning = true;
|
||||||
|
ipcRenderer.send('getChromeCastDevices', '');
|
||||||
|
setTimeout(() => {
|
||||||
|
self.devices.cast = ipcRenderer.sendSync("getKnownCastDevices");
|
||||||
|
self.scanning = false;
|
||||||
|
}, 2000);
|
||||||
|
console.log(this.devices);
|
||||||
|
// vm.$forceUpdate();
|
||||||
|
},
|
||||||
|
setCast(device) {
|
||||||
|
CiderAudio.sendAudio();
|
||||||
|
this.activeCasts.push(device);
|
||||||
|
ipcRenderer.send('performGCCast', device, "Cider", "Playing ...", "Test build", '');
|
||||||
|
},
|
||||||
|
stopCasting() {
|
||||||
|
ipcRenderer.send('stopGCast', '');
|
||||||
|
this.activeCasts = [];
|
||||||
|
// vm.$forceUpdate();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,4 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 22" version="1.1" fill="#fff"
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cast"><path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/><line x1="2" y1="20" x2="2.01" y2="20"/></svg>
|
||||||
style="width: 100%; height: 100%; fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421">
|
|
||||||
<path d="M16.811,12.75c0.245,-0.355 0.389,-0.786 0.389,-1.25c0,-1.215 -0.985,-2.2 -2.2,-2.2c-1.215,0 -2.2,0.985 -2.2,2.2c0,0.466 0.145,0.898 0.392,1.254l-0.83,1.047c-0.537,-0.616 -0.862,-1.42 -0.862,-2.301c0,-1.933 1.567,-3.5 3.5,-3.5c1.933,0 3.5,1.567 3.5,3.5c0,0.879 -0.324,1.683 -0.859,2.297l-0.83,-1.047Zm1.271,1.604c0.694,-0.749 1.118,-1.752 1.118,-2.854c0,-2.32 -1.88,-4.2 -4.2,-4.2c-2.32,0 -4.2,1.88 -4.2,4.2c0,1.103 0.425,2.107 1.121,2.857l-0.814,1.028c-0.993,-0.995 -1.607,-2.368 -1.607,-3.885c0,-3.038 2.462,-5.5 5.5,-5.5c3.038,0 5.5,2.462 5.5,5.5c0,1.515 -0.613,2.887 -1.604,3.882l-0.814,-1.028Zm1.252,1.58c1.151,-1.126 1.866,-2.697 1.866,-4.434c0,-3.424 -2.776,-6.2 -6.2,-6.2c-3.424,0 -6.2,2.776 -6.2,6.2c0,1.739 0.716,3.311 1.869,4.437l-0.811,1.023c-1.452,-1.368 -2.358,-3.308 -2.358,-5.46c0,-4.142 3.358,-7.5 7.5,-7.5c4.142,0 7.5,3.358 7.5,7.5c0,2.15 -0.905,4.089 -2.355,5.457l-0.811,-1.023Zm-0.227,2.066l-8.219,0c-0.355,0 -0.515,-0.434 -0.27,-0.717l4.058,-5.12c0.178,-0.217 0.474,-0.217 0.652,0l4.058,5.12c0.237,0.283 0.085,0.717 -0.279,0.717Z" style="fill-rule:nonzero"></path>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 375 B |
Loading…
Add table
Add a link
Reference in a new issue