Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
thibauts committed Jun 14, 2014
0 parents commit 7039dc6
Show file tree
Hide file tree
Showing 15 changed files with 643 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
test*
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
castv2-client
=============
### A Chromecast client based on the new (CASTV2) protocol

This module implements a Chromecast client over the new (CASTV2) protocol. A sender app for the `DefaultMediaPlayer` application is provided, as well as an `Application` base class and implementations of the basic protocols (see the `controllers` directory) that should make implementing custom senders a breeze.

For details about the protocol internals please see [https://github.com/thibauts/node-castv2](https://github.com/thibauts/node-castv2#protocol-description).

Installation
------------

``` bash
$ npm install castv2-client
```

Usage
-----

``` javascript
```
10 changes: 10 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports.Controller = require('./lib/controllers/controller');
module.exports.JsonController = require('./lib/controllers/json');
module.exports.RequestResponseController = require('./lib/controllers/request-response');
module.exports.ConnectionController = require('./lib/controllers/connection');
module.exports.HeartbeatController = require('./lib/controllers/heartbeat');
module.exports.ReceiverController = require('./lib/controllers/receiver');
module.exports.MediaController = require('./lib/controllers/media');

module.exports.Client = module.exports.PlatformSender = require('./lib/senders/platform');
module.exports.DefaultMediaReceiver = require('./lib/senders/default-media-receiver');
35 changes: 35 additions & 0 deletions lib/controllers/connection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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');
}
}

function onclose() {
self.removeListener('message', onmessage);
}

}

util.inherits(ConnectionController, JsonController);

ConnectionController.prototype.connect = function() {
this.send({ type: 'CONNECT' });
};

ConnectionController.prototype.disconnect = function() {
this.send({ type: 'CLOSE' });
};

module.exports = ConnectionController;
35 changes: 35 additions & 0 deletions lib/controllers/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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;

function onmessage(data, broadcast) {
self.emit('message', data, broadcast);
}

function onclose() {
self.channel.removeListener('message', onmessage);
self.emit('close');
}
}

util.inherits(Controller, EventEmitter);

Controller.prototype.send = function(data) {
this.channel.send(data);
};

Controller.prototype.close = function() {
this.channel.close();
};

module.exports = Controller;
51 changes: 51 additions & 0 deletions lib/controllers/heartbeat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
var util = require('util');
var debug = require('debug')('castv2-client');
var JsonController = require('./json');

var DEFAULT_INTERVAL = 5; // seconds

function HeartbeatController(client, sourceId, destinationId) {
JsonController.call(this, client, sourceId, destinationId, 'urn:x-cast:com.google.cast.tp.heartbeat');

this.interval = null;

this.on('message', onmessage);
this.once('close', onclose);

var self = this;

function onmessage(data, broadcast) {
if(data.type === 'PONG') {
self.emit('pong');
}
}

function onclose() {
self.removeListener('message', onmessage);
self.stop();
}

}

util.inherits(HeartbeatController, JsonController);

HeartbeatController.prototype.ping = function() {
this.send({ type: 'PING' });
};

HeartbeatController.prototype.start = function(intervalValue) {
var self = this;

function oninterval() {
self.ping();
}

this.interval = setInterval(oninterval, (intervalValue || DEFAULT_INTERVAL) * 1000);
this.ping();
};

HeartbeatController.prototype.stop = function() {
clearInterval(this.interval);
};

module.exports = HeartbeatController;
11 changes: 11 additions & 0 deletions lib/controllers/json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var util = require('util');
var debug = require('debug')('castv2-client');
var Controller = require('./controller');

function JsonController(client, sourceId, destinationId, namespace) {
Controller.call(this, client, sourceId, destinationId, namespace, 'JSON');
}

util.inherits(JsonController, Controller);

module.exports = JsonController;
93 changes: 93 additions & 0 deletions lib/controllers/media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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];
self.currentSession = status;
self.emit('status', status);
}
}

function onclose() {
self.removeListener('message', onmessage);
self.stop();
}

}

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 = {};
}

var data = { type: 'LOAD' };

data.autoplay = (typeof options.autoplay !== 'undefined')
? options.autoplay
: false;

data.currentTime = (typeof options.currentTime !== 'undefined')
? options.currentTime
: 0;

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'));
}
var status = response.status[0];
callback(null, status);
});
};

MediaController.prototype.sessionRequest = function(data, callback) {
data.mediaSessionId = this.currentSession.mediaSessionId;

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);
};

module.exports = MediaController;
87 changes: 87 additions & 0 deletions lib/controllers/receiver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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);
}
}

function onclose() {
self.removeListener('message', onmessage);
}

}

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);
});
};

ReceiverController.prototype.launch = function(appId, callback) {
this.request({ type: 'LAUNCH', appId: appId }, function(err, response) {
if(err) return callback(err);
callback(null, response.status.applications || []);
});
};

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 || []);
});
};

ReceiverController.prototype.setVolume = function(volume, callback) {
var data = {
type: 'SET_VOLUME',
volume: volume
};

this.request(data, function(err, response) {
if(err) return callback(err);
callback(null, response.status.volume);
});
};

ReceiverController.prototype.getVolume = function(callback) {
this.getStatus(function(err, status) {
if(err) return callback(err);
callback(null, status.volume);
});
};

ReceiverController.prototype.getSessions = function(callback) {
this.getStatus(function(err, status) {
if(err) return callback(err);
callback(null, status.applications || []);
});
};

module.exports = ReceiverController;
Loading

0 comments on commit 7039dc6

Please sign in to comment.