UNPKG

cast-web-api

Version:

Web API for Google Cast enabled devices.

296 lines (256 loc) 8.44 kB
const events = require('events'); const https = require("https"); const http = require("http"); const url = require("url"); const logger = require('../../log/logger'); const Callback = require('./callback'); const CastReceiver = require('./connection/cast-receiver'); const CastMedia = require('./connection/cast-media'); const CastDefaultMediaReceiver = require('./connection/cast-default-media-receiver.js'); const CastGroup = require('./connection/cast-group'); const ReconnectionManager = require('./connection/reconnection-manager'); const configuration = require("../../config/config"); class CastDevice extends events { constructor(id, address, name) { super(); this.id = id; this._address = address; this._name = name; this._link = 'disconnected'; this._castReceiver = new CastReceiver(this._address, this.id); this._castMedia = new CastMedia(this._address, this.id); this.groups = new Map(); this.members = new CastGroup(this); this.callback = null; this.reconnect = new ReconnectionManager(this); this._dmr = new CastDefaultMediaReceiver(this._address, this.id); this._status = { groupPlayback: {on: false, from: null}, volume: 0, muted: false, application: '', status: '', title: '', subtitle: '', image: '' }; this.setConnectionListeners(); if (configuration.autoConnect) { logger.log('info', 'CastDevice.constructor()', 'autoConnect', this.id ); this.connect(); } } connect() { logger.log('info', 'CastDevice.connect()', 'connecting', this.id ); this._castReceiver.connect(); } disconnect() { this._castReceiver.disconnect(); this._castMedia.disconnect(); } remove() { this.groups.forEach(group => { group.members.removeMember(this); //This is important for garbage collection }); this.disconnect(); this.reconnect.stop(); this.removeAllListeners(); //TODO: maybe also delete all the objects, especially the connection stuff, just in case } setConnectionListeners() { this._castReceiver.on('linkChanged', link => { this.link = link; }); this._castReceiver.on('statusChanged', status => { this.status = status; }); this._castReceiver.on('sessionIdChanged', sessionId => { console.log("sessionIdChanged: "+sessionId); if (sessionId) { this._castMedia.connect(sessionId); } else { this._castMedia.disconnect(); } }); this._castReceiver.on('error', error => { logger.log("error", "CastDevice._castReceiver", "error: "+error, this.id); //TODO: }); this._castMedia.on('linkChanged', link => { // }); this._castMedia.on('statusChanged', status => { this.status = status; }); this._castMedia.on('error', error => { logger.log("error", "CastDevice._castMedia", "error: "+error, this.id); //TODO: }); } toObject() { return { id: this.id, name: this._name, connection: this.link, address: this.address, sessionId: this.sessionId, status: this.status, // groups: this.getGroups(), groups: this.getGroups(), members: this.members.members } } volume(targetLevel) { logger.log('info', 'CastDevice.volume()', targetLevel, this.id); return this._castReceiver.volume(targetLevel); } volumeGroup(targetLevel) { logger.log('info', 'CastDevice.volumeGroup()', targetLevel, this.id); return this.members.volume(targetLevel); } muted(isMuted) { logger.log('info', 'CastDevice.muted()', isMuted, this.id); return this._castReceiver.muted(isMuted); } play() { logger.log('info', 'CastDevice.play()', '', this.id); return this._castMedia.play(); } pause() { logger.log('info', 'CastDevice.pause()', '', this.id); return this._castMedia.pause(); } stop() { logger.log('info', 'CastDevice.stop()', '', this.id); return this._castReceiver.stop(); } seek(to) { logger.log('info', 'CastDevice.seek()', 'to: '+to, this.id); return this._castMedia.seek(to); } playMedia(media) { this._dmr.play(media); } subscribe(url) { if(this.callback != null) { this.unsubscribe(); } this.callback = new Callback(url, this); } unsubscribe() { if (this.callback != null) { this.callback.stop(); this.callback = null; //TODO: required } } get address() { return { port: this._address.port, host: this._address.host }; } set address(value) { logger.log('info', 'CastDevice.set address(value)', 'value: ' + JSON.stringify(value), this.id ); let reconnect = true; //TODO: this destroys autoConnect setting, but theres no other way of telling wether a device is down due to incorrect address or because it was never supposed to up if (this._address != null && this._link !== 'disconnected') { reconnect = true; this.disconnect(); } if (this._address.host !== value.host || this._address.port !== value.port) { this._address = value; this._castReceiver.address = value; this._castMedia.address = value; logger.log('info', 'CastDevice.set address(value)', 'changed _address: ' + JSON.stringify(this._address)+ JSON.stringify(this._castReceiver._address)+ JSON.stringify(this._castMedia._address), this.id ); if (reconnect) { this.connect(); } } } get status() { return { groupPlayback: this._status.groupPlayback.on, volume: this._status.volume, muted: this._status.muted, application: this._status.application, status: this._status.status, title: this._status.title, subtitle: this._status.subtitle, image: this._status.image }; } set status(status) { let changed = false; for (let key in status) { let value = status[key]; if (this._status[key] !== value && !this.isGroupPlaybackLocked(status, key)) { this._status[key] = value; changed = true; } } if (changed) { this.emit('statusChanged', this._status); } } isGroupPlaybackLocked(status, key) { //TODO: does not lock if groupPlayback is set, let locked = false; if (key === 'groupPlayback' || key === 'application' || key === 'status'|| key === 'title' || key === 'subtitle' || key === 'image') { logger.log('info', 'CastDevice.isGroupPlaybackLocked()', 'is media key: '+key, this.id); if (this._status.groupPlayback.on !== false) { //groupPlayBack set locked = true; if (status.hasOwnProperty('groupPlayback')) { logger.log('info', 'CastDevice.isGroupPlaybackLocked()', 'status.hasOwnProp: '+status.hasOwnProperty('groupPlayback')+' this._status.groupPlayback: '+JSON.stringify(this._status.groupPlayback), this.id); if (status.groupPlayback.from === this._status.groupPlayback.from) { logger.log('info', 'CastDevice.isGroupPlaybackLocked()', 'status.groupPlayback.from: '+status.groupPlayback.from+'===this._status.groupPlayback.from: '+this._status.groupPlayback.from, this.id); locked = false; } else { logger.log('error', 'CastDevice.isGroupPlaybackLocked()', 'status.groupPlayback.from: '+status.groupPlayback.from+'!==this._status.groupPlayback.from: '+this._status.groupPlayback.from, this.id); } } } else { //no groupPlayBack set logger.log('info', 'CastDevice.isGroupPlaybackLocked()', 'ELSE own prop: '+status.hasOwnProperty('groupPlayback')+' this._status.groupPlayback: '+this._status.groupPlayback, this.id); } } logger.log('info', 'CastDevice.isGroupPlaybackLocked()', 'key: '+key+', status[key]: '+status[key]+', lock: '+locked, this.id); //TODO: sometimes: key: groupPlayback, locked: false and still the new groupPlayback is not written return locked; } set link(value) { if (value !== this.link) { this._link = value; this.emit('linkChanged', this.link); } } get link() { return this._link; } get sessionId() { return this._castReceiver.sessionId; } getGroups() { let groups = []; this.groups.forEach(group => { groups.push(group.id); }); return groups; } downloadImage() { return new Promise((resolve, reject) => { let query = url.parse("https://lh3.googleusercontent.com/LB5CRdhftEGo2emsHOyHz6NWSfLVD5NC45y6auOqYoyrv7BC5mdDm66vPDCEAJjcDA=w360"); if (this.status.image) { try { query = url.parse(this.status.image) } catch (e) {} } let protocol = query.protocol === 'https:' ? https : http; let options = { hostname: query.hostname, path: query.path }; protocol.request(options, res => { if (res.statusCode === 200) resolve(res); else reject(res.statusCode); }).end(); }); } } module.exports = CastDevice;