Created
March 26, 2015 22:16
-
-
Save ilyesgouta/800d0f05fd28da0248c9 to your computer and use it in GitHub Desktop.
A node.js snippet to stream media files to a Chromecast device
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A node.js snippet to stream media files to a Chromecast device. | |
// An ac3 or dts audio stream in matroska containers is transcoded to mp3 | |
// using ffmeg. Seeking using left and right keys won't be available. | |
var Client = require('castv2-client').Client; | |
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver; | |
var mdns = require('mdns'); | |
var ffmpeg = require('fluent-ffmpeg'); | |
var mime = require('mime'); | |
var path = require('path'); | |
var send = require('send'); | |
var sprintf = require("sprintf-js").sprintf; | |
var keypress = require('keypress'); | |
var browser = mdns.createBrowser(mdns.tcp('googlecast')); | |
var fs = require('fs'); | |
var http = require('http'); | |
var file = process.argv[2]; | |
if (!file) { | |
console.log('Please specify a media file as an argument.'); | |
return; | |
} | |
console.log('media to serve: ' + file); | |
var Player; | |
var start; | |
var current; | |
var paused; | |
var httpResponse; | |
var transcode; // ffmpeg transcoding command | |
browser.on('serviceUp', function(service) { | |
console.log('found device "%s" at %s:%d', service.name, service.addresses[0], service.port); | |
ondeviceup(service.addresses[0]); | |
browser.stop(); | |
}); | |
browser.start(); | |
keypress(process.stdin); | |
function ondeviceup(host) { | |
var client = new Client(); | |
client.connect(host, function() { | |
console.log('connected, launching app ...'); | |
client.launch(DefaultMediaReceiver, function(err, player) { | |
var media = { | |
contentId: 'http://192.168.1.2:8080/content', | |
contentType: mime.lookup(file), | |
streamType: 'BUFFERED' | |
}; | |
console.log('media mime: ' + media.contentType); | |
var transcodeAudio = false; | |
// mkv might need transcoding audio (ac3 or dts) to mp3 | |
if (media.contentType == 'video/x-matroska') { | |
ffmpeg.ffprobe(file, function(err, metadata) { | |
console.dir(metadata); | |
if ((metadata.format.nb_streams > 1) | |
&& (metadata.streams[0].codec_name == 'ac3' | |
|| metadata.streams[1].codec_name == 'ac3' | |
|| metadata.streams[0].codec_name == 'dca' | |
|| metadata.streams[1].codec_name == 'dca')) { | |
transcodeAudio = true; | |
console.log('transcoding required -> switching streaming mode to live'); | |
media.streamType = 'LIVE'; | |
} | |
}); | |
} | |
transcode = null; | |
// start the http server | |
var server = http.createServer(function(request, response) { | |
if (transcodeAudio) { | |
httpResponse = response; | |
transcode = ffmpeg(file) | |
.videoCodec('copy') | |
.audioCodec('libmp3lame') | |
.audioBitrate('192k') | |
.format('matroska') | |
.on('error', function(err) { | |
console.log('an error occurred: ' + err.message); | |
}); | |
transcode.pipe(response); | |
} else | |
send(request, file).pipe(response); | |
}).listen(8080); | |
server.on('request', function(request, response) { | |
var start = 0, end = 0; | |
var stat = fs.statSync(file); | |
var range = request.headers['range']; | |
if (range != null) { | |
start = parseInt(range.slice(range.indexOf('bytes=') + 6, range.indexOf('-'))); | |
end = parseInt(range.slice(range.indexOf('-') + 1, range.length)); | |
} | |
if (isNaN(end) || end == 0) | |
end = stat.size - 1; | |
response.statusCode = 206; | |
response.setHeader('Content-Range', 'bytes ' | |
+ start | |
+ '-' | |
+ end | |
+ '/' | |
+ stat.size); | |
}); | |
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) { | |
console.log('media loaded playerState=%s', status.playerState); | |
Player = player; | |
start = new Date(); | |
current = 0; | |
paused = false; | |
}); | |
}); | |
}); | |
client.on('error', function(err) { | |
console.log('Error: %s', err.message); | |
client.close(); | |
}); | |
} | |
process.stdin.on('keypress', function (ch, key) { | |
if (key && key.name == 'space' && Player) { | |
paused = !paused; | |
if (paused) | |
Player.pause(); | |
else | |
Player.play(); | |
} | |
if (key && key.name == 'right' && Player) { | |
var now = new Date(); | |
var diff = now - start; | |
current = current + diff / 1000 + 10; // +10 sec | |
start = now; | |
console.log(sprintf("seeking to: %02d:%02d:%02d", | |
Math.floor(current / 3600), | |
Math.floor((current % 3600) / 60), | |
Math.floor(current % 60) )); | |
if (transcode) { | |
transcode.kill(); | |
console.log("restarting trasncoding at " + current + "seconds"); | |
transcode = ffmpeg(file) | |
.videoCodec('copy') | |
.audioCodec('libmp3lame') | |
.audioBitrate('192k') | |
.format('matroska') | |
.seekInput(current) | |
.on('error', function(err) { | |
console.log('an error occurred: ' + err.message); | |
}); | |
transcode.pipe(httpResponse); | |
} | |
Player.seek(current, function(err, status) { | |
}); | |
} | |
if (key && key.name == 'left' && Player) { | |
var now = new Date(); | |
var diff = now - start; | |
current = current + diff / 1000 - 10; // -10 sec | |
start = now; | |
console.log(sprintf("seeking to: %02d:%02d:%02d", | |
Math.floor(current / 3600), | |
Math.floor((current % 3600) / 60), | |
Math.floor(current % 60) )); | |
if (transcode) { | |
transcode.kill(); | |
console.log("restarting trasncoding at " + current + "seconds"); | |
transcode = ffmpeg(file) | |
.videoCodec('copy') | |
.audioCodec('libmp3lame') | |
.audioBitrate('192k') | |
.format('matroska') | |
.seekInput(current) | |
.on('error', function(err) { | |
console.log('an error occurred: ' + err.message); | |
}); | |
transcode.pipe(httpResponse); | |
} | |
Player.seek(current, function(err, status) { | |
}); | |
} | |
if (key && key.ctrl && key.name == 'c') | |
process.exit(); | |
}); | |
process.stdin.setRawMode(true); | |
process.stdin.resume(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment