diff --git a/examples/basic.js b/examples/basic.js index 67e2ce9..b960c82 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -1,20 +1,19 @@ -var MYPATH = "../index"; //castv2-client in production - -var Client = require(MYPATH).Client; -var DefaultMediaReceiver = require(MYPATH).DefaultMediaReceiver; -var scanner = require("./lib/scanner"); +var PlatformSender = require('../index').PlatformSender; +var DefaultMediaReceiver = require('../index').DefaultMediaReceiver; +var scanner = require("chromecast-scanner"); function ondeviceup(host, callback) { - - var client = new Client(); - - client.connect(host, function() { - console.log('connected, launching app ...'); - - client.launch(DefaultMediaReceiver, function(err, player) { - var media = { - + /** + * Client + * @type {PlatformSender} + */ + var client = new PlatformSender(); + console.log(host) + client.connect(host).then(() => { + console.log('connected, launching app ...') + client.launch(DefaultMediaReceiver).then((player) => { + const media = { // Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType. contentId: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4', contentType: 'video/mp4', @@ -29,50 +28,53 @@ function ondeviceup(host, callback) { { url: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg' } ] } - }; - - player.on('status', function(status) { - console.log('status broadcast playerState=%s', status.playerState); - }); - - console.log('app "%s" launched, loading media %s ...', player.session.displayName, media.contentId); - - player.load(media, { autoplay: true }, function(err, status) { + } + player.on('status', (status) => console.log('status broadcast playerState=%s', status.playerState)) + console.log('app "%s" launched, loading media %s ...', player.session.displayName, media.contentId) + player.load(media, { + autoplay: true + }).then((status) => { console.log('media loaded playerState=%s', status.playerState); - // Seek to 2 minutes after 5 seconds playing. setTimeout(function() { - console.log("seeking"); + console.log('seeking') player.seek(2*60, function(err, status) { // Stop after 2 seconds playing setTimeout(function() { - console.log("Stopping"); + console.log('Stopping') player.stop(function() { - console.log("Done!"); - callback(0); //Done - }); - }, 2000); - - }); - }, 5000); + console.log('Done!') + callback(0) + }) + }, 2000) + }) + }, 5000) + }).catch((err) => { + console.error('Media load error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Launch error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Connection error!', err) + callback(err) + }) - }); - - }); - - }); client.on('error', function(err) { - console.log('Error: %s', err.message); - client.close(); - callback(err); //Error + console.log('Error: %s', err.message) + client.close() + callback(err) }); } function findAndConnect(callback) { - scanner(function(ip, name, port){ - ondeviceup(ip, callback); + scanner(function(err, service) { + console.log('chromecast %s running on: %s', service.name, service.data); + ondeviceup('192.168.7.94', callback); }); } diff --git a/examples/queue.js b/examples/queue.js index 40ed43f..5e212e1 100644 --- a/examples/queue.js +++ b/examples/queue.js @@ -1,100 +1,76 @@ -var MYPATH = "../index"; //castv2-client in production - -var Client = require(MYPATH).Client; -var DefaultMediaReceiver = require(MYPATH).DefaultMediaReceiver; -var scanner = require("./lib/scanner"); - -var util = require("util"); - -function connectToDevice(host, callback) { - - var client = new Client(); - - client.connect(host, function() { - console.log('connected, launching app ...'); - - client.launch(DefaultMediaReceiver, function(err, player) { - - var mediaList = [ - { - autoplay : true, - preloadTime : 3, - startTime : 1, - activeTrackIds : [], - playbackDuration: 2, - media: { - contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/BigBuckBunnyAudio.mp4", - contentType: "audio/mpeg", - streamType: 'BUFFERED' - } - }, - { - autoplay : true, - preloadTime : 3, - startTime : 2, - activeTrackIds : [], - playbackDuration: 2, - media: { - contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ElephantsDreamAudio.mp4", - contentType: "audio/mpeg", - streamType: 'BUFFERED' +var PlatformSender = require('../index').PlatformSender; +var DefaultMediaReceiver = require('../index').DefaultMediaReceiver; +var scanner = require("chromecast-scanner"); + +var util = require("util"); + +function ondeviceup(host, callback) { + + var client = new PlatformSender(); + + client.connect(host).then(() => { + console.log('connected, launching app ...') + const mediaList = [ + { + autoplay: true, + preloadTime: 3, + startTime: 1, + activeTrackIds: [], + playbackDuration: 2, + media: { + contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/BigBuckBunnyAudio.mp4", + contentType: "audio/mpeg", + streamType: 'BUFFERED' + } + }, + { + autoplay: true, + preloadTime: 3, + startTime: 2, + activeTrackIds: [], + playbackDuration: 2, + media: { + contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ElephantsDreamAudio.mp4", + contentType: "audio/mpeg", + streamType: 'BUFFERED' + } + }, + { + autoplay: true, + preloadTime: 3, + startTime: 3, + activeTrackIds: [], + playbackDuration: 2, + media: { + contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ForBiggerBlazesAudio.mp4", + contentType: "audio/mpeg", + streamType: 'BUFFERED' + } } - }, - { - autoplay : true, - preloadTime : 3, - startTime : 3, - activeTrackIds : [], - playbackDuration: 2, - media: { - contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ForBiggerBlazesAudio.mp4", - contentType: "audio/mpeg", - streamType: 'BUFFERED' + ] + client.launch(DefaultMediaReceiver).then((player) => { + console.log('app "%s" launched, loading medias...', player.session.displayName) + function gotStatus(status) { + console.log('status broadcast = %s', util.inspect(status), " ") + if (isDone, status.idleReason == "FINISHED" && status.loadingItemId === undefined) { + console.log("Done!") + callback(0) + } } - } - ]; - - console.log('app "%s" launched, loading medias...', player.session.displayName); - - player.on('status', gotStatus); - - //Done re-ordering? - var isDone = false; - - function gotStatus(status) { - console.log('status broadcast = %s', util.inspect(status)," "); - if ( - isDone, - status.idleReason == "FINISHED" && - status.loadingItemId === undefined) - { - console.log("Done!"); - callback(0); - } - } - - // loads multiple items - player.queueLoad( - mediaList, - { - startIndex:1, - repeatMode: "REPEAT_OFF" - }, - function(err, status) { - console.log("Loaded QUEUE"); - if (err) { - console.log(util.inspect(err)); - callback(err); //Error - return; - }; - console.log(util.inspect(status)); - player.queueInsert( - [ + player.on('status', gotStatus) + let isDone = false + player.queueLoad(mediaList, { + startIndex: 1, + repeatMode: "REPEAT_OFF" + }).then((status) => { + console.log("Loaded QUEUE"); + console.log(util.inspect(status)); + player.queueInsert([ { - autoplay : true, - preloadTime : 4, - startTime : 8, - activeTrackIds : [], + autoplay: true, + preloadTime: 4, + startTime: 8, + activeTrackIds: [], playbackDuration: 2, media: { contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ForBiggerEscapesAudio.mp4", @@ -106,101 +82,98 @@ function connectToDevice(host, callback) { } } } - ], - function(err, status) { - console.log("Inserted in QUEUE"); - if (err) { - console.log(util.inspect(err)); - callback(err); //Error - return; - }; - console.log(util.inspect(status)); + ]).then((status) => { + console.log('Inserted in queue') + console.log(util.inspect(status)) //Get status before we modify the title bellow - player.getStatus(function (err, status){ - gotStatus(status); - }); - player.queueRemove([2],{currentItemId:0}, function(err, status) { - console.log("Removed from QUEUE"); - if (err) { - console.log(util.inspect(err)); - callback(err); //Error - return; - }; - console.log(util.inspect(status)); - player.queueUpdate( - [ - { - itemId: 4, - autoplay : true, - preloadTime : 4, - startTime : 4, - activeTrackIds : [], - playbackDuration: 2, - media: { - contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ForBiggerEscapesAudio.mp4", - contentType: "audio/mpeg", - streamType: 'BUFFERED', - metadata: { - metadataType: 3, - title: "Modified title" - } - } + player.getStatus().then(gotStatus).catch((err) => console.error(err)) + player.queueRemove([2], { currentItemId: 0 }).then((status) => { + console.log('Removed from queue') + console.log(util.inspect(status)) + player.queueUpdate([{ + itemId: 4, + autoplay: true, + preloadTime: 4, + startTime: 4, + activeTrackIds: [], + playbackDuration: 2, + media: { + contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/dash/ForBiggerEscapesAudio.mp4", + contentType: "audio/mpeg", + streamType: 'BUFFERED', + metadata: { + metadataType: 3, + title: "Modified title" } - ], - {currentItemId: 4}, - function(err, status) { - console.log("Updated Item"); - if (err) { - console.log(util.inspect(err)); - callback(err); //Error - return; - }; + } + }], { currentItemId: 4 }).then((status) => { + console.log('Updated item') + console.log(util.inspect(status)) + player.queueReorder([4, 3, 1], { currentItemId: 4 }).then((status) => { + console.log('Reordered queue'); console.log(util.inspect(status)); - player.queueReorder([4,3,1], {currentItemId:4}, function(err, status) { - console.log("Reordered QUEUE"); - if (err) { - console.log(util.inspect(err)); - callback(err); - return; - }; - console.log(util.inspect(status)); - //Get status - check that the title from itemId=4 has been modified - player.getStatus(function (err, status){ - gotStatus(status); - isDone = true; - }); - }); - }); - }); - }); - } - ); - }); - }); - - client.on('error', function(err) { + //Get status - check that the title from itemId=4 has been modified + player.getStatus().then((status) => { + gotStatus(status) + isDone = true; + }).catch((err) => { + console.error('Status get error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Queue reorder error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Queue update error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Queue removal error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Error!', err) + callback(err) + }) + }).catch((err) => { + console.log(util.inspect(err)) + callback(err) //Error + return + }) + }).catch((err) => { + console.error('Launch error!', err) + callback(err) + }) + }).catch((err) => { + console.error('Connect error!', err) + callback(err) + }) + + client.on('error', function (err) { console.log('Error: %s', err.message); client.close(); callback(err); //Error - }); + }) } function findAndConnect(callback) { - scanner(function(ip, name, port){ - connectToDevice(ip, callback); - }); + scanner(function(err, service) { + console.log('chromecast %s running on: %s', service.name, service.data) + ondeviceup(service.data, callback) + }) } //module for testcase module.exports = findAndConnect; //main -var main = function () { - findAndConnect(function(rc){ +var main = function () { + findAndConnect(function (rc) { process.exit(rc); }); -} -if (require.main === module) { - main(); +} +if (require.main === module) { + main(); } diff --git a/lib/controllers/connection.js b/lib/controllers/connection.js index b648b2c..eb135f1 100644 --- a/lib/controllers/connection.js +++ b/lib/controllers/connection.js @@ -2,34 +2,37 @@ var util = require('util'); var debug = require('debug')('castv2-client'); var JsonController = require('./json'); -function ConnectionController(client, sourceId, destinationId) { - JsonController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.google.cast.tp.connection'); - - this.on('message', onmessage); - this.once('close', onclose); - - var self = this; - - function onmessage(data, broadcast) { - if(data.type === 'CLOSE') { - self.emit('disconnect'); +class ConnectionController extends JsonController { + constructor(client, sourceId, destinationId) { + super(client, sourceId, destinationId, 'urn:x-cast:com.google.cast.tp.connection') + const self = this + function onMessage(data, broadcast) { + if (data.type === 'CLOSE') self.emit('disconnect') } + function onClose() { + self.removeListener('message', onMessage) + } + this.on('message', onMessage) + this.once('close', onClose) } - function onclose() { - self.removeListener('message', onmessage); + /** + * Connect + */ + connect() { + this.send({ + type: 'CONNECT' + }) + } + + /** + * Disconnect + */ + disconnect() { + this.send({ + type: 'CLOSE' + }) } - } -util.inherits(ConnectionController, JsonController); - -ConnectionController.prototype.connect = function() { - this.send({ type: 'CONNECT' }); -}; - -ConnectionController.prototype.disconnect = function() { - this.send({ type: 'CLOSE' }); -}; - module.exports = ConnectionController; \ No newline at end of file diff --git a/lib/controllers/controller.js b/lib/controllers/controller.js index afeb662..c29de77 100644 --- a/lib/controllers/controller.js +++ b/lib/controllers/controller.js @@ -1,35 +1,38 @@ -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var debug = require('debug')('castv2-client'); - -function Controller(client, sourceId, destinationId, namespace, encoding) { - EventEmitter.call(this); - - this.channel = client.createChannel(sourceId, destinationId, namespace, encoding); - - this.channel.on('message', onmessage); - this.channel.once('close', onclose); - - var self = this; +const debug = require('debug')('castv2-client'); +const EventEmitter = require('events').EventEmitter; +const util = require('util'); + +class Controller extends EventEmitter { + constructor(client, sourceId, destinationId, namespace, encoding) { + super() + this.channel = client.createChannel(sourceId, destinationId, namespace, encoding) + const self = this + function onMessage(data, broadcast) { + self.emit('message', data, broadcast) + } + function onClose() { + self.channel.removeListener('message', onMessage) + self.emit('close') + } + this.channel.on('message', onMessage) + this.channel.once('close', onClose) + } - function onmessage(data, broadcast) { - self.emit('message', data, broadcast); + /** + * Send data over the channel + * @param {any} data - Data to send + */ + send(data) { + debug('Sending', data) + this.channel.send(data) } - function onclose() { - self.channel.removeListener('message', onmessage); - self.emit('close'); + /** + * Close the channel + */ + close() { + this.channel.close() } } -util.inherits(Controller, EventEmitter); - -Controller.prototype.send = function(data) { - this.channel.send(data); -}; - -Controller.prototype.close = function() { - this.channel.close(); -}; - module.exports = Controller; \ No newline at end of file diff --git a/lib/controllers/heartbeat.js b/lib/controllers/heartbeat.js index 1bf0244..4b2caa7 100644 --- a/lib/controllers/heartbeat.js +++ b/lib/controllers/heartbeat.js @@ -1,80 +1,61 @@ -var util = require('util'); -var debug = require('debug')('castv2-client'); -var JsonController = require('./json'); - -var DEFAULT_INTERVAL = 5; // seconds -var TIMEOUT_FACTOR = 3; // timeouts after 3 intervals - -function HeartbeatController(client, sourceId, destinationId) { - JsonController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.google.cast.tp.heartbeat'); - - this.pingTimer = null; - this.timeout = null; - this.intervalValue = DEFAULT_INTERVAL; - - this.on('message', onmessage); - this.once('close', onclose); - - var self = this; - - function onmessage(data, broadcast) { - if(data.type === 'PONG') { - self.emit('pong'); +const JsonController = require('./json'); +const debug = require('debug')('castv2-client'); +const util = require('util'); + +const DEFAULT_INTERVAL = 5; // seconds +const TIMEOUT_FACTOR = 3; // timeouts after 3 intervals + +class HeartbeatController extends JsonController { + constructor(client, sourceId, destinationId) { + super(client, sourceId, destinationId, 'urn:x-cast:com.google.cast.tp.heartbeat') + this.pingTimer = null + this.timeout = null + this.intervalValue = DEFAULT_INTERVAL + const self = this + function onMessage(data, broadcast) { + if (data.type === 'PONG') self.emit('pong') } + function onClose() { + self.removeListener('message', onMessage) + self.stop() + } + this.on('message', onMessage) + this.once('close', onClose) } - function onclose() { - self.removeListener('message', onmessage); - self.stop(); - } - -} - -util.inherits(HeartbeatController, JsonController); - -HeartbeatController.prototype.ping = function() { - var self = this; - - if (this.timeout) { - // We already have a ping in progress. - return; + ping() { + debug('Received a .ping() before checking timeout') + if (this.timeout) return // We already have a ping in progress. + debug('We do not have a timeout, so we are continuing') + this.timeout = setTimeout(() => { + this.emit('timeout') + }, this.intervalValue * 1000 * TIMEOUT_FACTOR) + + this.once('pong', () => { + clearTimeout(this.timeout) + this.timeout = null + + this.pingTimer = setTimeout(() => { + this.pingTimer = null + this.ping() + }, this.intervalValue * 1000) + }) + + this.send({ + type: 'PING' + }) } - this.timeout = setTimeout(function ontimeout() { - self.emit('timeout'); - }, self.intervalValue * 1000 * TIMEOUT_FACTOR); - - this.once('pong', function onpong() { - clearTimeout(self.timeout); - self.timeout = null; - - self.pingTimer = setTimeout(function() { - self.pingTimer = null; - self.ping(); - }, self.intervalValue * 1000); - }); - - this.send({ type: 'PING' }); -}; - -HeartbeatController.prototype.start = function(intervalValue) { - var self = this; - - if(intervalValue) { - this.intervalValue = intervalValue; + start(intervalValue) { + this.intervalValue = intervalValue || this.intervalValue + this.ping() } - this.ping(); -}; - -HeartbeatController.prototype.stop = function() { - if (this.pingTimer) { - clearTimeout(this.pingTimer); + stop() { + if (this.pingTimer) clearTimeout(this.pingTimer) + if (this.timeout) clearTimeout(this.timeout) + this.removeAllListeners('pong') } - if (this.timeout) { - clearTimeout(this.timeout); - } - this.removeAllListeners('pong'); -}; +} module.exports = HeartbeatController; \ No newline at end of file diff --git a/lib/controllers/json.js b/lib/controllers/json.js index 154bcd8..92d46f8 100644 --- a/lib/controllers/json.js +++ b/lib/controllers/json.js @@ -1,11 +1,11 @@ -var util = require('util'); -var debug = require('debug')('castv2-client'); -var Controller = require('./controller'); +const Controller = require('./controller'); +const debug = require('debug')('castv2-client'); +const util = require('util'); -function JsonController(client, sourceId, destinationId, namespace) { - Controller.call(this, client, sourceId, destinationId, namespace, 'JSON'); +class JsonController extends Controller { + constructor(client, sourceId, destinationId, namespace) { + super(client, sourceId, destinationId, namespace, 'JSON') + } } -util.inherits(JsonController, Controller); - module.exports = JsonController; \ No newline at end of file diff --git a/lib/controllers/media.js b/lib/controllers/media.js index 5cf6c1e..1e07290 100644 --- a/lib/controllers/media.js +++ b/lib/controllers/media.js @@ -2,227 +2,233 @@ var util = require('util'); var debug = require('debug')('castv2-client'); var RequestResponseController = require('./request-response'); -function MediaController(client, sourceId, destinationId) { - RequestResponseController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.google.cast.media'); - - this.currentSession = null; - - this.on('message', onmessage); - this.once('close', onclose); - - var self = this; - - function onmessage(data, broadcast) { - if(data.type === 'MEDIA_STATUS' && broadcast) { - var status = data.status[0]; - // Sometimes an empty status array can come through; if so don't emit it - if (!status) return; - self.currentSession = status; - self.emit('status', status); +class MediaController extends RequestResponseController { + constructor(client, sourceId, destinationId) { + super(client, sourceId, destinationId, 'urn:x-cast:com.google.cast.media') + this.currentSession = null + const self = this + function onMessage(data, broadcast) { + if (data.type === 'MEDIA_STATUS' && broadcast) { + const status = data.status[0] + if (!status) return + self.currentSession = status + self.emit('status', status) + } + } + + function onClose() { + self.removeListener('message', onMessage) + self.stop() } + this.on('message', onMessage) + this.once('close', onClose) } - function onclose() { - self.removeListener('message', onmessage); - self.stop(); + + + /** + * Get the status + * @returns {Promise} + */ + getStatus() { + return new Promise((resolve, reject) => { + this.request({ + type: 'GET_STATUS' + }, (err, response) => { + if (err) return reject(err) + const status = response.status[0] + this.currentSession = status + return resolve(status) + }) + }) } -} - -util.inherits(MediaController, RequestResponseController); - -MediaController.prototype.getStatus = function(callback) { - var self = this; - - this.request({ type: 'GET_STATUS' }, function(err, response) { - if(err) return callback(err); - var status = response.status[0]; - self.currentSession = status; - callback(null, status); - }); -}; - -MediaController.prototype.load = function(media, options, callback) { - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + /** + * Load media + * @param {Object} media + * @param {Object} [options = {}] + * @returns {Promise} + */ + load(media, options = {}) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'LOAD', + media: media, + autoplay: false, + currentTime: 0, + activeTrackIds: [], + repeatMode: 'REPEAT_OFF' + }, options) + this.request(data).then((response) => { + if (response.type === 'LOAD_FAILED') return reject('Load failed') + if (response.type === 'LOAD_CANCELLED') return reject('Load cancelled') + const status = response.status[0] + resolve(status) + }).catch((err) => reject(err)) + }) } - var data = { type: 'LOAD' }; - - data.autoplay = (typeof options.autoplay !== 'undefined') - ? options.autoplay - : false; - - data.currentTime = (typeof options.currentTime !== 'undefined') - ? options.currentTime - : 0; - - data.activeTrackIds = (typeof options.activeTrackIds !== 'undefined') - ? options.activeTrackIds - : []; - - data.repeatMode = (typeof options.repeatMode === "string" && - typeof options.repeatMode !== 'undefined') - ? options.repeatMode - : "REPEAT_OFF"; - - data.media = media; - - this.request(data, function(err, response) { - if(err) return callback(err); - if(response.type === 'LOAD_FAILED') { - return callback(new Error('Load failed')); - } - if(response.type === 'LOAD_CANCELLED') { - return callback(new Error('Load cancelled')); - } - var status = response.status[0]; - callback(null, status); - }); -}; - -function noop() {} - -MediaController.prototype.sessionRequest = function(data, callback) { - data.mediaSessionId = this.currentSession.mediaSessionId; - callback = callback || noop; - - this.request(data, function(err, response) { - if(err) return callback(err); - var status = response.status[0]; - callback(null, status); - }); -}; - -MediaController.prototype.play = function(callback) { - this.sessionRequest({ type: 'PLAY' }, callback); -}; - -MediaController.prototype.pause = function(callback) { - this.sessionRequest({ type: 'PAUSE' }, callback); -}; - -MediaController.prototype.stop = function(callback) { - this.sessionRequest({ type: 'STOP' }, callback); -}; - -MediaController.prototype.seek = function(currentTime, callback) { - var data = { - type: 'SEEK', - currentTime: currentTime - }; - - this.sessionRequest(data, callback); -}; - -//Load a queue of items to play (playlist) -//See https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueLoadRequest -MediaController.prototype.queueLoad = function(items, options, callback) { - - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + /** + * Session request + * @param {Object} data + * @returns {Promise} + */ + sessionRequest(data) { + return new Promise((resolve, reject) => { + data.mediaSessionId = this.currentSession.mediaSessionId + this.request(data).then((response) => { + const status = response.status[0] + return resolve(status) + }).catch((err) => reject(err)) + }) } - var data = { type: 'QUEUE_LOAD' }; - - //REPEAT_OFF, REPEAT_ALL, REPEAT_SINGLE, REPEAT_ALL_AND_SHUFFLE - data.repeatMode = (typeof options.repeatMode === "string" && - typeof options.repeatMode !== 'undefined') - ? options.repeatMode - : "REPEAT_OFF"; - - data.currentTime = (typeof options.currentTime !== 'undefined') - ? options.currentTime - : 0; - - data.startIndex = (typeof options.startIndex !== 'undefined') - ? options.startIndex - : 0; - - data.items = items; - - this.request(data, function(err, response) { - if(err) return callback(err); - if(response.type === 'LOAD_FAILED') { - return callback(new Error('queueLoad failed')); - } - if(response.type === 'LOAD_CANCELLED') { - return callback(new Error('queueLoad cancelled')); - } - var status = response.status[0]; - callback(null, status); - }); -}; - -MediaController.prototype.queueInsert = function(items, options, callback) { - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + /** + * Play the media + * @returns {Promise} + */ + play() { + return new Promise((resolve, reject) => { + this.sessionRequest({ + type: 'PLAY' + }).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) } - - var data = { - type: 'QUEUE_INSERT', - currentItemId: options.currentItemId, //Item ID to play after this request or keep same item if undefined - currentItemIndex: options.currentItemIndex, //Item Index to play after this request or keep same item if undefined - currentTime: options.currentTime, //Seek in seconds for current stream - insertBefore: options.insertBefore, //ID or append if undefined - items: items - }; - - this.sessionRequest(data, callback); -}; - -MediaController.prototype.queueRemove = function(itemIds, options, callback) { - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + + /** + * Pause the media + * @returns {Promise} + */ + pause() { + return new Promise((resolve, reject) => { + this.sessionRequest({ + type: 'PAUSE' + }).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) } - var data = { - type: 'QUEUE_REMOVE', - currentItemId: options.currentItemId, - currentTime: options.currentTime, - itemIds: itemIds - }; - - this.sessionRequest(data, callback); -}; - -MediaController.prototype.queueReorder = function(itemIds, options, callback) { - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + /** + * Stop the media + * @returns {Promise} + */ + stop() { + return new Promise((resolve, reject) => { + this.sessionRequest({ + type: 'STOP' + }).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) } - var data = { - type: 'QUEUE_REORDER', - currentItemId: options.currentItemId, - currentTime: options.currentTime, - insertBefore: options.insertBefore, - itemIds: itemIds - }; + /** + * Seek through the media + * @param {Number} currentTime - Time to seek to + * @returns {Promise} + */ + seek(currentTime) { + return new Promise((resolve, reject) => { + this.sessionRequest({ + type: 'SEEK', + currentTime: currentTime + }).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) + } + + /** + * Load a queue of items to play (playlist) + * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueLoadRequest + * @param {Object[]} items - Items to load into the queue + * @param {Object} options - Options + * @returns {Promise} + */ + queueLoad(items, options = []) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'QUEUE_LOAD', + repeatMode: 'REPEAT_OFF', + startIndex: 0, + items: items + }, options) + + this.request(data).then((response) => { + if (response.type === 'LOAD_FAILED') return reject(new Error('queueLoad failed')) + if (response.type === 'LOAD_CANCELLED') return reject(new Error('queueLoad cancelled')) + const status = response.status[0] + resolve(status) + }) + }) + } - this.sessionRequest(data, callback); -}; + /** + * Insert items into the queue + * @param {Object[]} items - Items to insert + * @param {Object} options - Options + * @returns {Promise} + */ + queueInsert(items, options) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'QUEUE_INSERT', + items: items + }, options) + this.sessionRequest(data).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) + } -MediaController.prototype.queueUpdate = function(items, options, callback) { - if(typeof options === 'function' || typeof options === 'undefined') { - callback = options; - options = {}; + /** + * Remove items from the queue + * @param {String[]} itemIds - IDs to remove + * @param {Object} options - Options + * @returns {Promise} + */ + queueRemove(itemIds, options) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'QUEUE_REMOVE', + itemIds: itemIds + }, options) + this.sessionRequest(data).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) } - var data = { - type: 'QUEUE_UPDATE', - currentItemId: options.currentItemId, - currentTime: options.currentTime, - jump: options.jump, //Skip or go back (if negative) number of items - repeatMode: options.repeatMode, - items: items - }; + /** + * Reorder the queue + * @param {String[]} itemIds - IDs to reorder + * @param {Object} options - Options + * @returns {Promise} + */ + queueReorder(itemIds, options) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'QUEUE_REORDER', + itemIds: itemIds + }, options) + this.sessionRequest(data).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) + } - this.sessionRequest(data, callback); -}; + /** + * Update the queue + * @param {Object[]} items - Items + * @param {Object} options - Options + * @returns {Promise} + */ + queueUpdate(items, options) { + return new Promise((resolve, reject) => { + const data = Object.assign({ + type: 'QUEUE_UPDATE', + items: items + }, options) + this.sessionRequest(data).then((response) => resolve(response)) + .catch((err) => reject(err)) + }) + } +} module.exports = MediaController; diff --git a/lib/controllers/receiver.js b/lib/controllers/receiver.js index 01c40d0..5341da6 100644 --- a/lib/controllers/receiver.js +++ b/lib/controllers/receiver.js @@ -2,89 +2,126 @@ var util = require('util'); var debug = require('debug')('castv2-client'); var RequestResponseController = require('./request-response'); -function ReceiverController(client, sourceId, destinationId) { - RequestResponseController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.google.cast.receiver'); - - this.on('message', onmessage); - this.once('close', onclose); - - var self = this; - - function onmessage(data, broadcast) { - if(!broadcast) return; - if(data.type === 'RECEIVER_STATUS') { - self.emit('status', data.status); +class ReceiverController extends RequestResponseController { + constructor(client, sourceId, destinationId) { + super(client, sourceId, destinationId, 'urn:x-cast:com.google.cast.receiver') + const self = this + function onMessage(data, broadcast) { + if (!broadcast) return + if (data.type === 'RECEIVER_STATUS') self.emit('status', data.status) + } + + function onClose() { + self.removeListener('message', onMessage) } - } - function onclose() { - self.removeListener('message', onmessage); + this.on('message', onMessage) + this.once('close', onClose) } -} - -util.inherits(ReceiverController, RequestResponseController); - -ReceiverController.prototype.getStatus = function(callback) { - this.request({ type: 'GET_STATUS' }, function(err, response) { - if(err) return callback(err); - callback(null, response.status); - }); -}; - -ReceiverController.prototype.getAppAvailability = function(appId, callback) { - var data = { - type: 'GET_APP_AVAILABILITY', - appId: Array.isArray(appId) ? appId : [appId] - }; - - this.request(data, function(err, response) { - if(err) return callback(err); - callback(null, response.availability); - }); -}; + + + /** + * Get the status + * @returns {Promise} + */ + getStatus() { + return new Promise((resolve, reject) => { + this.request({ + type: 'GET_STATUS' + }).then((response) => resolve(response.status)) + .catch((err) => reject(err)) + }) + } -ReceiverController.prototype.launch = function(appId, callback) { - this.request({ type: 'LAUNCH', appId: appId }, function(err, response) { - if(err) return callback(err); - if(response.type === 'LAUNCH_ERROR') { - return callback(new Error('Launch failed. Reason: ' + response.reason)); - } - callback(null, response.status.applications || []); - }); -}; + /** + * Get app availability + * @param {String|Array} appId - App ID + * @returns {Promise} + */ + getAppAvailability(appId) { + return new Promise((resolve, reject) => { + const data = { + type: 'GET_APP_AVAILABILITY', + appId: Array.isArray(appId) ? appId : [appId] + } + this.request(data).then((response) => resolve(response.availability)) + .catch((err) => reject(err)) + }) + } -ReceiverController.prototype.stop = function(sessionId, callback) { - this.request({ type: 'STOP', sessionId: sessionId }, function(err, response) { - if(err) return callback(err); - callback(null, response.status.applications || []); - }); -}; + /** + * Launch an App with its ID + * @param {String} appId - App ID + * @returns {Promise} + */ + launch(appId) { + return new Promise((resolve, reject) => { + this.request({ + type: 'LAUNCH', + appId: appId + }).then((response) => { + if (response.type === 'LAUNCH_ERROR') return reject(new Error(`Launch failed. Reason: ${response.reason}`)) + resolve(response.status.applications || []) + }).catch((err) => reject(err)) + }) + } -ReceiverController.prototype.setVolume = function(options, callback) { - var data = { - type: 'SET_VOLUME', - volume: options // either `{ level: 0.5 }` or `{ muted: true }` - }; + /** + * Stop a session with its ID + * @param {String} sessionId - Session ID + * @returns {Promise} + */ + stop(sessionId) { + return new Promise((resolve, reject) => { + const data = { + type: 'STOP', + sessionId: sessionId + } + this.request(data).then((response) => resolve(response.status.applications || [])) + .catch((err) => reject(err)) + }) + } - this.request(data, function(err, response) { - if(err) return callback(err); - callback(null, response.status.volume); - }); -}; + /** + * Set the volume + * @param {Object} options - Options + * @returns {Promise} + */ + setVolume(options) { + return new Promise((resolve, reject) => { + const data = { + type: 'SET_VOLUME', + volume: options + } + this.request(data).then((response) => resolve(response.status.volume || [])) + .catch((err) => reject(err)) + }) + } -ReceiverController.prototype.getVolume = function(callback) { - this.getStatus(function(err, status) { - if(err) return callback(err); - callback(null, status.volume); - }); -}; + /** + * Get the volume + * @returns {Promise} + */ + getVolume() { + return new Promise((resolve, reject) => { + this.getStatus().then((status) => { + return resolve(status.volume) + }).catch((err) => reject(err)) + }) + } -ReceiverController.prototype.getSessions = function(callback) { - this.getStatus(function(err, status) { - if(err) return callback(err); - callback(null, status.applications || []); - }); -}; + /** + * Get the sessions + * @returns {Promise} + */ + getSessions() { + return new Promise((resolve, reject) => { + this.getStatus().then((status) => { + return resolve(status.applications || []) + }).catch((err) => reject(err)) + }) + } +} module.exports = ReceiverController; diff --git a/lib/controllers/request-response.js b/lib/controllers/request-response.js index 85ff476..8e2575a 100644 --- a/lib/controllers/request-response.js +++ b/lib/controllers/request-response.js @@ -2,36 +2,33 @@ var util = require('util'); var debug = require('debug')('castv2-client'); var JsonController = require('./json'); -function RequestResponseController(client, sourceId, destinationId, namespace) { - JsonController.call(this, client, sourceId, destinationId, namespace); - - this.lastRequestId = 0; -} - -util.inherits(RequestResponseController, JsonController); - -RequestResponseController.prototype.request = function(data, callback) { - var requestId = ++this.lastRequestId; - - var self = this; - - function onmessage(response, broadcast) { - if(response.requestId === requestId) { - self.removeListener('message', onmessage); +class RequestResponseController extends JsonController { + constructor(client, sourceId, destinationId, namespace) { + super(client, sourceId, destinationId, namespace) + this.lastRequestId = 0 + } - if(response.type === 'INVALID_REQUEST') { - return callback(new Error('Invalid request: ' + response.reason)); + /** + * Object to request + * @param {Object} data - data + */ + request(data) { + return new Promise((resolve, reject) => { + let requestId = ++this.lastRequestId + const self = this + function onMessage(response, broadcast) { + if (response.requestId === requestId) { + self.removeListener('message', onMessage) + if (response.type === 'INVALID_REQUEST') return reject(new Error(`Invalid request: ${response.reason}`)) + delete response.requestId + return resolve(response) + } } - - delete response.requestId; - callback(null, response); - } + this.on('message', onMessage) + data.requestId = requestId + this.send(data) + }) } - - this.on('message', onmessage); - - data.requestId = requestId; - this.send(data); -}; +} module.exports = RequestResponseController; \ No newline at end of file diff --git a/lib/senders/application.js b/lib/senders/application.js index 016aa4a..ec22275 100644 --- a/lib/senders/application.js +++ b/lib/senders/application.js @@ -1,45 +1,41 @@ -var util = require('util'); -var debug = require('debug')('castv2-client'); -var Sender = require('./sender'); -var ConnectionController = require('../controllers/connection'); - -function Application(client, session) { - Sender.call(this, client, randomSenderId(), session.transportId); - - this.session = session; - - this.connection = this.createController(ConnectionController); - this.connection.connect(); - - this.connection.on('disconnect', ondisconnect); - this.on('close', onclose); - - var self = this; - - function ondisconnect() { - self.emit('close'); +const ConnectionController = require('../controllers/connection'); +const debug = require('debug')('castv2-client'); +const Sender = require('./sender'); +const util = require('util'); + +class Application extends Sender { + constructor(client, session) { + super(client, randomSenderId(), session.transportId) + this.session = session + this.connection = this.createController(ConnectionController) + this.connection.connect() + const self = this + function onDisconnect() { + self.emit('close') + } + function onClose() { + self.connection.removeListener('disconnect', onDisconnect) + self.connection.close() + self.connection = undefined + self.session = undefined + self.superClose() + } + this.connection.on('disconnect', onDisconnect) + this.once('close', onClose) } - - function onclose() { - self.removeListener('close', onclose); - self.connection.removeListener('disconnect', ondisconnect); - self.connection.close(); - self.connection = null; - self.session = null; - Sender.prototype.close.call(this); + superClose() { + super.close() + } + close() { + this.connection.disconnect() + this.emit('close') } - } -util.inherits(Application, Sender); - -Application.prototype.close = function() { - this.connection.disconnect(); - this.emit('close'); -}; - function randomSenderId() { return 'client-' + Math.floor(Math.random() * 10e5); } +Application.APP_ID = 'CC1AD845'; + module.exports = Application; \ No newline at end of file diff --git a/lib/senders/default-media-receiver.js b/lib/senders/default-media-receiver.js index 544a385..9c65034 100644 --- a/lib/senders/default-media-receiver.js +++ b/lib/senders/default-media-receiver.js @@ -3,67 +3,136 @@ var debug = require('debug')('castv2-client'); var Application = require('./application'); var MediaController = require('../controllers/media'); -function DefaultMediaReceiver(client, session) { - Application.apply(this, arguments); - - this.media = this.createController(MediaController); - - this.media.on('status', onstatus); - - var self = this; - - function onstatus(status) { - self.emit('status', status); +class DefaultMediaReceiver extends Application { + constructor(client, session) { + super(client, session) + this.APP_ID = 'CC1AD845' + + /** + * Media controller + * @type {MediaController} + */ + this.media = this.createController(MediaController) + const self = this + function onDisconnect() { + self.emit('close') + } + function onStatus(status) { + self.emit('status', status) + } + function onClose() { + self.media.removeListener('disconnect', onDisconnect) + self.media.removeListener('status', onStatus) + self.media.close() + self.media = undefined + } + this.media.on('status', onStatus) + this.once('close', onClose) + this.media.on('disconnect', onDisconnect) } -} - -DefaultMediaReceiver.APP_ID = 'CC1AD845'; - -util.inherits(DefaultMediaReceiver, Application); - -DefaultMediaReceiver.prototype.getStatus = function(callback) { - this.media.getStatus.apply(this.media, arguments); -}; + /** + * Get the status + * @returns {Promise} + */ + getStatus() { + return this.media.getStatus() + } -DefaultMediaReceiver.prototype.load = function(media, options, callback) { - this.media.load.apply(this.media, arguments); -}; + /** + * Load a media object + * @param {Object} media - Media to load + * @param {Object} [options = {}] - Options + * @returns {Promise} + */ + load(media, options = {}) { + return this.media.load(media, options) + } + + /** + * Play the media + * @returns {Promise} + */ + play() { + return this.media.play() + } -DefaultMediaReceiver.prototype.play = function(callback) { - this.media.play.apply(this.media, arguments); -}; + /** + * Pause the media + * @returns {Promise} + */ + pause() { + return this.media.pause() + } -DefaultMediaReceiver.prototype.pause = function(callback) { - this.media.pause.apply(this.media, arguments); -}; + /** + * Stop the media + * @returns {Promise} + */ + stop() { + return this.media.stop() + } -DefaultMediaReceiver.prototype.stop = function(callback) { - this.media.stop.apply(this.media, arguments); -}; + /** + * Seek through the media + * @param {number} currentTime - Time to seek to + */ + seek(currentTime) { + return this.media.seek(currentTime) + } -DefaultMediaReceiver.prototype.seek = function(currentTime, callback) { - this.media.seek.apply(this.media, arguments); -}; + /** + * Load a queue of items to play (playlist) + * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueLoadRequest + * @param {Object[]} items - Items to load into the queue + * @param {Object} options - Options + * @returns {Promise} + */ + queueLoad(items, options = {}) { + return this.media.queueLoad(items, options) + } -DefaultMediaReceiver.prototype.queueLoad = function(items, options, callback) { - this.media.queueLoad.apply(this.media, arguments); -}; + /** + * Insert items into the queue + * @param {Object[]} items - Items to insert + * @param {Object} options - Options + * @returns {Promise} + */ + queueInsert(items, options = {}) { + return this.media.queueInsert(items, options) + } -DefaultMediaReceiver.prototype.queueInsert = function(items, options, callback) { - this.media.queueInsert.apply(this.media, arguments); -}; + /** + * Remove items from the queue + * @param {String[]} itemIds - IDs to remove + * @param {Object} options - Options + * @returns {Promise} + */ + queueRemove(itemIds, options = {}) { + return this.media.queueRemove(itemIds, options) + } -DefaultMediaReceiver.prototype.queueRemove = function(itemIds, options, callback) { - this.media.queueRemove.apply(this.media, arguments); -}; + /** + * Reorder the queue + * @param {String[]} itemIds - IDs to reorder + * @param {Object} options - Options + * @returns {Promise} + */ + queueReorder(itemIds, options = {}) { + return this.media.queueReorder(itemIds, options) + } -DefaultMediaReceiver.prototype.queueReorder = function(itemIds, options, callback) { - this.media.queueReorder.apply(this.media, arguments); -}; + /** + * Update the queue + * @param {Object[]} items - Items + * @param {Object} options - Options + * @returns {Promise} + */ + queueUpdate(items, options = {}) { + return this.media.queueUpdate(items, options) + } +} -DefaultMediaReceiver.prototype.queueUpdate = function(items, callback) { - this.media.queueUpdate.apply(this.media, arguments); -}; +DefaultMediaReceiver.APP_ID = 'CC1AD845'; module.exports = DefaultMediaReceiver; diff --git a/lib/senders/platform.js b/lib/senders/platform.js index 265f4c1..e79c7da 100644 --- a/lib/senders/platform.js +++ b/lib/senders/platform.js @@ -6,115 +6,166 @@ var ConnectionController = require('../controllers/connection'); var HeartbeatController = require('../controllers/heartbeat'); var ReceiverController = require('../controllers/receiver'); -function PlatformSender() { - Sender.call(this, new Client(), 'sender-0', 'receiver-0'); - - this.connection = null; - this.heartbeat = null; - this.receiver = null; -} - -util.inherits(PlatformSender, Sender); - -PlatformSender.prototype.connect = function(options, callback) { - var self = this; - - self.client.on('error', onerror); - function onerror(err) { - self.emit('error', err); +class PlatformSender extends Sender { + constructor() { + super(new Client(), 'sender-0', 'receiver-0') + /** + * ConnectionController + * @type {ConnectionController} + */ + this.connection = null + + /** + * HeartbeatController + * @type {HeartbeatController} + */ + this.heartbeat = null + + /** + * ReceiverController + * @type {ReceiverController} + */ + this.receiver = null } - this.client.connect(options, function() { - - self.connection = self.createController(ConnectionController); - self.heartbeat = self.createController(HeartbeatController); - self.receiver = self.createController(ReceiverController); - - self.receiver.on('status', onstatus); - - self.client.once('close', onclose); - - function onstatus(status) { - self.emit('status', status); - } - - function onclose() { - self.heartbeat.stop(); - self.receiver.removeListener('status', onstatus); - self.receiver.close(); - self.heartbeat.close(); - self.connection.close(); - self.receiver = null; - self.heartbeat = null; - self.connection = null; - Sender.prototype.close.call(self); - } - - self.heartbeat.once('timeout', ontimeout); - - function ontimeout() { - self.emit('error', new Error('Device timeout')); - } - - self.connection.connect(); - self.heartbeat.start(); - callback(); - }); -}; - -PlatformSender.prototype.close = function() { - this.client.close(); -}; - -PlatformSender.prototype.getStatus = function(callback) { - this.receiver.getStatus(callback); -}; - -PlatformSender.prototype.getSessions = function(callback) { - this.receiver.getSessions(callback); -}; - -PlatformSender.prototype.getAppAvailability = function(appId, callback) { - this.receiver.getAppAvailability(appId, function(err, availability) { - if(err) return callback(err); - for(key in availability) { - availability[key] = (availability[key] === 'APP_AVAILABLE'); - } - callback(err, availability); - }); -}; + /** + * Connect + * @param {Object} options - Options + * @returns {Promise} + */ + connect(options) { + const self = this + return new Promise((resolve, reject) => { + this.client.on('error', (err) => { + this.emit('error', err) + }) + + this.client.connect(options, () => { + this.connection = this.createController(ConnectionController) + this.heartbeat = this.createController(HeartbeatController) + this.receiver = this.createController(ReceiverController) + + function onStatus(status) { + self.emit('status', status) + } + this.receiver.on('status', onStatus) + this.client.once('close', () => { + this.heartbeat.stop() + this.receiver.removeListener('status', onStatus) + this.receiver.close() + this.heartbeat.close() + this.connection.close() + this.receiver = undefined + this.heartbeat = undefined + this.connection = undefined + super.close() + }) + this.heartbeat.once('timeout', () => { + this.emit('error', new Error('Device timeout')) + }) + this.connection.connect() + this.heartbeat.start() + resolve() + }) + }) + } + + /** + * Close + */ + close() { + this.client.close() + } -PlatformSender.prototype.join = function(session, Application, callback) { - callback(null, new Application(this.client, session)); -}; + /** + * Get the status + * @returns {Promise} + */ + getStatus() { + return this.receiver.getStatus() + } -PlatformSender.prototype.launch = function(Application, callback) { - var self = this; + /** + * Get the sessions + * @returns {Promise} + */ + getSessions() { + return this.receiver.getSessions() + } - this.receiver.launch(Application.APP_ID, function(err, sessions) { - if(err) return callback(err); + /** + * Get app availability + * @param {String} appId - App ID + * @returns {Promise} + */ + getAppAvailability(appId) { + return new Promise((resolve, reject) => { + this.receiver.getAppAvailability(appId).then((availability) => { + for(key in availability) { + availability[ky] = (availability[key] === 'APP_AVAILABLE') + } + resolve(availability) + }).catch((err) => reject(err)) + }) + } - var filtered = sessions.filter(function(session) { - return session.appId === Application.APP_ID; - }); - var session = filtered.shift(); + /** + * Join + * @param {String} session - Session + * @param {Application} application - Application + * @returns {Promise} + */ + join(session, application) { + return new Promise((resolve, reject) => { + process.nextTick(() => resolve(new application(this.client, session))) + }) + } - self.join(session, Application, callback); - }); -}; + /** + * Launch an application + * @param {Application} application - Application + * @returns {Promise} + */ + launch(application) { + return new Promise((resolve, reject) => { + this.receiver.launch(application.APP_ID).then((sessions) => { + const filtered = sessions.filter((session) => { + return session.appId === application.APP_ID + }) + const session = filtered.shift() + this.join(session, application).then((response) => resolve(response)) + .catch((err) => reject(err)) + }).catch((err) => reject(err)) + }) + } -PlatformSender.prototype.stop = function(application, callback) { - var session = application.session; - application.close(); - this.receiver.stop(session.sessionId, callback); -}; + /** + * Close an application and stop it + * @param {Application} application - Application to stop + * @returns {Promise} + */ + stop(application) { + const session = application.session + application.close() + return this.receiver.stop(session.sessionId) + } -PlatformSender.prototype.setVolume = function(volume, callback) { - this.receiver.setVolume(volume, callback); -}; + /** + * Set the volume + * @param {Object} volume - Volume + * @returns {Promise} + */ + setVolume(volume) { + return this.receiver.setVolume(volume) + } -PlatformSender.prototype.getVolume = function(callback) { - this.receiver.getVolume(callback); -}; + /** + * Get the volume + * @returns {Promise} + */ + getVolume() { + return this.receiver.getVolume() + } +} module.exports = PlatformSender; \ No newline at end of file diff --git a/lib/senders/sender.js b/lib/senders/sender.js index aa3c69a..7a6901f 100644 --- a/lib/senders/sender.js +++ b/lib/senders/sender.js @@ -1,35 +1,33 @@ -var EventEmitter = require('events').EventEmitter; -var util = require('util'); -var debug = require('debug')('castv2-client'); +const Controller = require('../controllers/controller') +const debug = require('debug')('castv2-client'); +const EventEmitter = require('events').EventEmitter; +const util = require('util'); -function Sender(client, senderId, receiverId) { - EventEmitter.call(this); - - this.client = client; - this.senderId = senderId; - this.receiverId = receiverId; -} - -util.inherits(Sender, EventEmitter); - -Sender.prototype.close = function() { - this.senderId = null; - this.receiverId = null; - this.client = null; -}; - -Sender.prototype.createController = function() { - var args = Array.prototype.slice.call(arguments); - var Controller = args.shift(); - return construct(Controller, [this.client, this.senderId, this.receiverId].concat(args)); -}; +class Sender extends EventEmitter { + constructor(client, senderId, receiverId) { + super() + this.client = client + this.senderId = senderId + this.receiverId = receiverId + } + + /** + * Close the Sender + */ + close() { + this.senderId = undefined + this.receiverId = undefined + this.client = undefined + } -function construct(contructor, args) { - function fn() { - return contructor.apply(this, args); + /** + * Create a controller using the Sender's properties + * @param {Controller} controller + * @param {*} args + */ + createController(controller, ...args) { + return new controller(this.client, this.senderId, this.receiverId, ...args) } - fn.prototype = contructor.prototype; - return new fn(); } module.exports = Sender; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7a8e19b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,450 @@ +{ + "name": "castv2-client", + "version": "1.2.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "array-find": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", + "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", + "dev": true + }, + "ascli": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-0.3.0.tgz", + "integrity": "sha1-XmYjDlIZ/j6JUqTvtPIPrllqgTo=", + "requires": { + "colour": "0.7.1", + "optjs": "3.2.2" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "bufferview": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz", + "integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0=" + }, + "bytebuffer": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-3.5.5.tgz", + "integrity": "sha1-em+vGhNRSwg/H8+VQcTJv75+f9M=", + "requires": { + "bufferview": "1.0.1", + "long": "2.4.0" + } + }, + "castv2": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/castv2/-/castv2-0.1.9.tgz", + "integrity": "sha1-0LD6sf0GsNnMpjaIZxbsEpOlkFo=", + "requires": { + "debug": "2.6.9", + "protobufjs": "3.8.2" + } + }, + "chromecast-scanner": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chromecast-scanner/-/chromecast-scanner-0.5.0.tgz", + "integrity": "sha1-ASlqPl0TDM40l061Ccu8fW943T0=", + "requires": { + "array-find": "0.1.1", + "multicast-dns": "4.0.1", + "xtend": "4.0.1" + }, + "dependencies": { + "array-find": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/array-find/-/array-find-0.1.1.tgz", + "integrity": "sha1-3IE4Ra1amvw1y5K3hsh42Btbgs4=" + }, + "multicast-dns": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-4.0.1.tgz", + "integrity": "sha1-q/Ai/IZnJwVangwryYCX9eutl6I=", + "requires": { + "thunky": "0.1.0" + } + } + } + }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.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 + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "dns-packet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.2.2.tgz", + "integrity": "sha512-kN+DjfGF7dJGUL7nWRktL9Z18t1rWP3aQlyZdY8XlpvU3Nc6GeFTQApftcjtWKxAZfiggZSGrCEoszNgvnpwDg==", + "dev": true, + "requires": { + "ip": "1.1.5", + "safe-buffer": "5.1.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "1.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "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.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "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.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" + }, + "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.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multicast-dns": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.1.tgz", + "integrity": "sha512-uV3/ckdsffHx9IrGQrx613mturMdMqQ06WTq+C09NsStJ9iNG6RcUWgPKs1Rfjy+idZT6tfQoXEusGNnEZhT3w==", + "dev": true, + "requires": { + "dns-packet": "1.2.2", + "thunky": "0.1.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, + "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 + }, + "protobufjs": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-3.8.2.tgz", + "integrity": "sha1-vIJuNMOvRpfo0K96Zp5NYSrtzRc=", + "requires": { + "ascli": "0.3.0", + "bytebuffer": "3.5.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + }, + "thunky": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz", + "integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } +} diff --git a/package.json b/package.json index c3e5f4e..a24422e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "main": "index.js", "dependencies": { "castv2": "~0.1.4", + "chromecast-scanner": "^0.5.0", "debug": "^2.2.0" }, "devDependencies": {