Skip to content
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"adm-zip": "0.4.7",
"airplay-js": "git+https://github.com/guerrerocarlos/node-airplay-js.git",
"async": "1.5.x",
"chromecast-js": "git+https://github.com/captainyarr/chromecast-js.git",
"chromecasts": "git+https://github.com/mafintosh/chromecasts.git",
"dlnacasts": "^0.0.3",
"gitlab": "1.4.1",
"defer-request": "0.0.2",
"i18n": "0.x.x",
Expand All @@ -55,10 +56,9 @@
"moment": ">=2.11.2",
"mv": "2.x.x",
"nedb": "1.4.0",
"node-captions": "0.4.6",
"node-captions": "git+https://github.com/brai4u/node-captions.git",
"node-tvdb": "1.6.x",
"node-webkit-fdialogs": "latest",
"nodecast-js": "^1.0.1",
"opensubtitles-api": "^2.3.x",
"os-name": "1.x.x",
"peerflix": "^0.32.1",
Expand All @@ -77,7 +77,6 @@
"trakt.tv": "1.x.x",
"trakt.tv-ondeck": "0.x",
"underscore": "1.x.x",
"upnp-mediarenderer-client": "1.x.x",
"urijs": "1.x.x",
"xmlbuilder": "4.1.0"
},
Expand Down
61 changes: 28 additions & 33 deletions src/app/lib/device/chromecast.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function (App) {
'use strict';

var chromecast = require('chromecast-js'),
var chromecasts = require('chromecasts')(),
collection = App.Device.Collection;

var Chromecast = App.Device.Generic.extend({
Expand All @@ -16,8 +16,8 @@

initialize: function (attrs) {
this.device = attrs.device;
this.attributes.id = this._makeID(this.device.config.name);
this.attributes.name = this.device.config.name;
this.attributes.id = this._makeID(this.device.name);
this.attributes.name = this.device.name;
this.attributes.address = this.device.host;
},

Expand All @@ -26,21 +26,16 @@
var subtitle = streamModel.get('subFile');
var cover = streamModel.get('cover');
var url = streamModel.get('src');
var attr= streamModel.attributes;
this.attributes.url = url;
var media;

if (subtitle) {
media = {
url: url,
subtitles: [{
url: 'http:' + url.split(':')[1] + ':9999/subtitle.vtt',
name: 'Subtitles',
language: 'en-US'
}],
cover: {
title: streamModel.get('title'),
url: streamModel.get('cover')
},
title: streamModel.get('title'),
images: streamModel.get('cover'),
subtitles: ['http:' + url.split(':')[1] + ':9999/subtitle.vtt'],

subtitles_style: {
backgroundColor: AdvSettings.get('subtitle_decoration') === 'Opaque Background' ? '#000000FF' : '#00000000', // color of background - see http://dev.w3.org/csswg/css-color/#hex-notation
foregroundColor: AdvSettings.get('subtitle_color') + 'ff', // color of text - see http://dev.w3.org/csswg/css-color/#hex-notation
Expand All @@ -57,27 +52,26 @@
};
} else {
media = {
url: url,
cover: {
title: streamModel.get('title'),
url: streamModel.get('cover')
}
images: cover,
title: streamModel.get('title')
};
}
win.info('Chromecast: play ' + url + ' on \'' + this.get('name') + '\'');
win.info('Chromecast: connecting to ' + this.device.host);

this.device.play(media, 0, function (err, status) {
self.device.play(url, media, function (err, status) {
if (err) {
win.error('chromecast.play error: ', err);

} else {
win.info('Playing ' + url + ' on ' + self.get('name'));
self.set('loadedMedia', status.media);
}
});
this.device.on('status', function (status) {
win.info('Playing ' + url + ' on ' + self.get('name'));
self.set('loadedMedia', status.media);
}
});
this.device.on('status', function (status) {
self._internalStatusUpdated(status);
});

});

},

pause: function () {
Expand Down Expand Up @@ -108,7 +102,7 @@

seekTo: function (newCurrentTime) {
win.info('Chromecast: seek to %ss', newCurrentTime);
this.get('device').seekTo(newCurrentTime, function (err, status) {
this.get('device').seek(newCurrentTime, function (err, status) {
if (err) {
win.error('Chromecast.seekTo:Error', err);
}
Expand All @@ -135,8 +129,8 @@

updateStatus: function () {
var self = this;

this.get('device').getStatus(function (err, status) {
this.get('device').status(function (err, status) {
if (err) {
return win.info('Chromecast.updateStatus:Error', err);
}
Expand All @@ -150,16 +144,17 @@
}
// If this is the active device, propagate the status event.
if (collection.selected.id === this.id) {

App.vent.trigger('device:status', status);
}
}
});

var browser = new chromecast.Browser();

browser.on('deviceOn', function (device) {
collection.add(new Chromecast({
device: device
chromecasts.update();
chromecasts.on('update', function (player) {
collection.add(new Chromecast({
device: player
}));
});

Expand Down
130 changes: 90 additions & 40 deletions src/app/lib/device/dlna.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
(function (App) {
'use strict';
var Browser = require('nodecast-js');
var MediaRendererClient = require('upnp-mediarenderer-client');
var dlnacasts = require('dlnacasts')();
var xmlb = require('xmlbuilder');
var collection = App.Device.Collection;
var browser = new Browser();


var makeID = function (baseID) {
return 'dlna-' + baseID.replace('-', '');
Expand All @@ -17,74 +17,124 @@
makeID: makeID,

initialize: function (attrs) {
this.device = attrs.device;
this.client = new MediaRendererClient(this.device.xml);
this.attributes.name = this.device.name;
this.attributes.address = this.device.host;
this.player = attrs.player;
this.attributes.name = this.player.name;
this.attributes.address = this.player.host;
},

play: function (streamModel) {
var metadata = {
title: 'Butter DLNA', // upnp-mediarendered-client object
type: 'video',
url: streamModel.get('src'),
protocolInfo: 'http-get:*:video/mp4:*'
};

if (streamModel.get('subFile')) { // inject subtitles
metadata.subtitlesUrl = 'http:' + metadata.url.split(':')[1] + ':9999/video.srt';
var url = streamModel.get('src');
var self = this;
var media;
var url_video = url;
var url_subtitle = 'http:' + url.split(':')[1] + ':9999/video.srt';
var metadata = null;
var subtitle = streamModel.get('subFile');
if (subtitle) {
media = {
title: streamModel.get('title'),
subtitles: ['http:' + url.split(':')[1] + ':9999/subtitle.vtt'],
};
} else {
media = {
title: streamModel.get('title')
};
}

this.client.load(metadata.url, {
metadata: metadata,
autoplay: true,
}, function (err, result) {
if (err) {
throw err;
}
win.info('DLNA: play ' + url + ' on \'' + this.get('name') + '\'');
win.info('DLNA: connecting to ' + this.player.host);

self.player.play(url_video, media , function (err, status) {
if (err) {
win.error('DLNA.play error: ', err);
} else {
win.info('Playing ' + url + ' on ' + self.get('name'));
self.set('loadedMedia', status.media);
}
});
},
this.player.on('status', function (status) {
self._internalStatusUpdated(status);
});
},

stop: function () {
this.client.stop();
this.player.stop();
},

pause: function () {
this.client.pause();
this.player.pause();
},

forward: function () {
this.client.seek(30);
this.player.seek(30);
},

backward: function () {
this.client.seek(-30);
this.player.seek(-30);
},

seek: function (seconds) {
win.info('DLNA: seek %s', seconds);
this.get('player').seek(seconds, function (err, status) {
if (err) {
win.error('DLNA.seek:Error', err);
}
});
},
seekTo: function (newCurrentTime) {
win.info('DLNA: seek to %ss', newCurrentTime);
this.get('player').seek(newCurrentTime, function (err, status) {
if (err) {
win.error('DLNA.seek:Error', err);
}
});
},

seekPercentage: function (percentage) {
win.info('DLNA: seek percentage %s%', percentage.toFixed(2));
var newCurrentTime = this.player._status.duration / 100 * percentage;
this.seekTo(newCurrentTime.toFixed());
},

unpause: function () {
this.client.play();
this.player.play();
},
updateStatus: function () {
var self = this;
this.get('player').status(function (err, status) {
if (err) {
return win.info('DLNA.updateStatus:Error', err);
}
self._internalStatusUpdated(status);
});
},

_internalStatusUpdated: function (status) {
if (status === undefined) {
status = this.player._status;
}
// If this is the active device, propagate the status event.
if (collection.selected.id === this.id) {
App.vent.trigger('device:status', status);
}
}
});


browser.onDevice(function (device) {
device.onError(function (err) {
win.error('DNLA device error', err);
});

dlnacasts.on('update', function (player) {
if (collection.where({
id: device.host
id: player.host
}).length === 0) {
win.info('Found DLNA Device: %s at %s', device.name, device.host);
win.info('Found DLNA Device: %s at %s', player.name, player.host);
collection.add(new Dlna({
id: device.host,
device: device
id: player.host,
player: player
}));
}
});

win.info('Scanning: Local Network for DLNA devices');
browser.start();
dlnacasts.update();


App.Device.Dlna = Dlna;
})(window.App);
20 changes: 15 additions & 5 deletions src/app/lib/views/player/loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
if (state === 'playingExternally') {
this.ui.stateTextDownload.hide();
this.ui.progressbar.hide();
if (streamInfo.get('player') && streamInfo.get('player').get('type') === 'chromecast') {
if (streamInfo.get('player') && streamInfo.get('player').get('type') === 'chromecast' | streamInfo.get('player').get('type') === 'dlna') {
this.ui.controls.css('visibility', 'visible');
this.ui.playingbarBox.css('visibility', 'visible');
this.ui.playingbar.css('width', '0%');
Expand Down Expand Up @@ -152,12 +152,22 @@
},

onDeviceStatus: function (status) {
if (status.media !== undefined && status.media.duration !== undefined) {
var streamInfo = this.model.get('streamInfo');
if (status.media !== undefined && status.media.duration !== undefined && streamInfo.get('player').get('type') === 'chromecast' )
{
// Update playingbar width
var playedPercent = status.currentTime / status.media.duration * 100;
this.ui.playingbar.css('width', playedPercent.toFixed(1) + '%');
win.debug('ExternalStream: %s: %ss / %ss (%s%)', status.playerState,
status.currentTime.toFixed(1), status.media.duration.toFixed(), playedPercent.toFixed(1));
}
if (status.playerState !== undefined && status.duration !== undefined && streamInfo.get('player').get('type') === 'dlna')
{
// Update playingbar width
var playedPercent = status.currentTime / status.media.duration * 100;
this.ui.playingbar.css('width', playedPercent.toFixed(1) + '%');
var playedPercent2 = status.currentTime / status.duration * 100;
this.ui.playingbar.css('width', playedPercent2.toFixed(1) + '%');
win.debug('ExternalStream: %s: %ss / %ss (%s%)', status.playerState,
status.currentTime.toFixed(1), status.media.duration.toFixed(), playedPercent.toFixed(1));
status.currentTime.toFixed(1), status.duration.toFixed(), playedPercent2.toFixed(1));
}
if (!this.extPlayerStatusUpdater && status.playerState === 'PLAYING') {
// First PLAYING state. Start requesting device status update every 5 sec
Expand Down