https nodejs rtmp publisher use openssl
新增https 功能
由於呢在chrome 呢 如果調用getUserMedia api 則必需要https 代理來小改造一下
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
<!doctype> | |
<html> | |
<head> | |
<title>MediaRecorder to RTMP Demo</title> | |
<script src="/socket.io/socket.io.js"></script> | |
</head> | |
<body style="max-width:800px;height:100%;margin:auto;"> | |
<h1> | |
MediaRecorder to RTMP Demo | |
</h1> | |
<label for="option_width" >Size:</label> | |
<input type="text" id="option_width" value="480"/> × | |
<input type="text" id="option_height" value="360"/> | |
<br> | |
<label for="option_url" >RTMP Destination:</label> | |
<input type="text" id="option_url" value="rtmp://localhost/live"/> | |
<br> | |
<button id="button_start">Start streaming</button> | |
<hr/> | |
<div> | |
<p id="output_message"></p> | |
<video id="output_video" autoplay=true></video> | |
</div> | |
<hr/> | |
<textarea readonly="true" id="output_console" cols=91 rows=5> | |
</textarea> | |
<script> | |
function fail(str){alert(str+"\nPlease download the latest version of Firefox!");location.replace('http://mozilla.org/firefox');} | |
var output_console=document.getElementById('output_console'), | |
output_message=document.getElementById('output_message'), | |
output_video=document.getElementById('output_video'), | |
option_url=document.getElementById('option_url'), | |
option_width=document.getElementById('option_width'), | |
option_height=document.getElementById('option_height'), | |
button_start=document.getElementById('button_start'), | |
height=option_height.value, | |
width=option_width.value, | |
url=option_url.value='rtmp://'+location.host.split(':')[0]+':1935/live/test'; | |
option_height.onchange=option_height.onkeyup=function(){height=1*this.value;} | |
option_width.onchange=option_width.onkeyup=function(){width=1*this.value;} | |
option_url.onchange=option_url.onkeyup=function(){url=this.value;} | |
button_start.onclick=requestMedia; | |
function video_show(stream){ | |
if ("srcObject" in output_video) { | |
output_video.srcObject = stream; | |
} else { | |
output_video.src = window.URL.createObjectURL(stream); | |
} | |
output_video.addEventListener( "loadedmetadata", function (e) { | |
output_message.innerHTML="Local video source size:"+output_video.videoWidth+"x"+output_video.videoHeight; | |
}, false ); | |
} | |
function show_output(str){ | |
output_console.value+="\n"+str; | |
output_console.scrollTop = output_console.scrollHeight; | |
} | |
navigator.getUserMedia = (navigator.getUserMedia || | |
navigator.mozGetUserMedia || | |
navigator.msGetUserMedia || | |
navigator.webkitGetUserMedia); | |
if(!navigator.getUserMedia){fail('No getUserMedia() available.');} | |
if(!MediaRecorder){fail('No MediaRecorder available.');} | |
var mediaRecorder; | |
var socket = io.connect("https://localhost", {secure: true}); | |
//({ | |
// option 1 | |
// ca: fs.readFileSync("https://localhost:443",'./abels-cert.pem'), | |
// option 2. WARNING: it leaves you vulnerable to MITM attacks! | |
// requestCert: false, | |
// rejectUnauthorized: false | |
//}); | |
//var socket = io. | |
socket.on('message',function(m){ | |
console.log('recv server message',m); | |
show_output('SERVER:'+m); | |
}); | |
socket.on('fatal',function(m){ | |
show_output('ERROR: unexpected:'+m); | |
alert('Error:'+m); | |
mediaRecorder.stop(); | |
//should reload? | |
}); | |
socket.on('ffmpeg_stderr',function(m){ | |
show_output('FFMPEG:'+m); | |
}); | |
socket.on('disconnect', function () { | |
show_output('ERROR: server disconnected!'); | |
mediaRecorder.stop(); | |
}); | |
function requestMedia(){ | |
var constraints = { audio: true, | |
video:{ | |
width: { min: width, ideal: width, max: width }, | |
height: { min: height, ideal: height, max: height }, | |
} | |
}; | |
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) { | |
video_show(stream);//only show locally, not remotely | |
socket.emit('config_rtmpDestination',url); | |
socket.emit('start','start'); | |
mediaRecorder = new MediaRecorder(stream); | |
mediaRecorder.start(2000); | |
mediaRecorder.onstop = function(e) { | |
stream.stop(); | |
} | |
mediaRecorder.ondataavailable = function(e) { | |
socket.emit("binarystream",e.data); | |
//chunks.push(e.data); | |
} | |
}).catch(function(err) { | |
console.log('The following error occured: ' + err); | |
show_output('Local getUserMedia ERROR:'+err); | |
}); | |
} | |
</script> | |
</body> | |
</html> |
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
var express = require('express'); | |
var app = express(); | |
//var http = require('http').Server(app); | |
//var ffmpeg = require('fluent-ffmpeg'); | |
//var stream = require('stream'); | |
var spawn = require('child_process').spawn; | |
var fs = require('fs'); | |
app.use(express.static('static')); | |
const server = require('https').createServer({ | |
key: fs.readFileSync('abels-key.pem'), | |
cert: fs.readFileSync('abels-cert.pem') | |
},app); | |
//testing | |
var io = require('socket.io')(server); | |
spawn('ffmpeg',['-h']).on('error',function(m){ | |
console.error("FFMpeg not found in system cli; please install ffmpeg properly or make a softlink to ./!"); | |
process.exit(-1); | |
}); | |
io.on('connection', function(socket){ | |
socket.emit('message','Hello from mediarecorder-to-rtmp server!'); | |
socket.emit('message','Please set rtmp destination before start streaming.'); | |
var ffmpeg_process, feedStream=false; | |
socket.on('config_rtmpDestination',function(m){ | |
if(typeof m != 'string'){ | |
socket.emit('fatal','rtmp destination setup error.'); | |
return; | |
} | |
var regexValidator=/^rtmp:\/\/[^\s]*$/;//TODO: should read config | |
if(!regexValidator.test(m)){ | |
socket.emit('fatal','rtmp address rejected.'); | |
return; | |
} | |
socket._rtmpDestination=m; | |
socket.emit('message','rtmp destination set to:'+m); | |
}); | |
//socket._vcodec='libvpx';//from firefox default encoder | |
socket.on('config_vcodec',function(m){ | |
if(typeof m != 'string'){ | |
socket.emit('fatal','input codec setup error.'); | |
return; | |
} | |
if(!/^[0-9a-z]{2,}$/.test(m)){ | |
socket.emit('fatal','input codec contains illegal character?.'); | |
return; | |
}//for safety | |
socket._vcodec=m; | |
}); | |
socket.on('start',function(m){ | |
if(ffmpeg_process || feedStream){ | |
socket.emit('fatal','stream already started.'); | |
return; | |
} | |
if(!socket._rtmpDestination){ | |
socket.emit('fatal','no destination given.'); | |
return; | |
} | |
var ops=[ | |
'-i','-', | |
'-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', | |
'-an', //TODO: give up audio for now... | |
//'-async', '1', | |
'-filter_complex', 'aresample=44100', //necessary for trunked streaming? | |
//'-strict', 'experimental', '-c:a', 'aac', '-b:a', '128k', | |
'-bufsize', '1000', | |
'-f', 'flv', socket._rtmpDestination | |
]; | |
console.log(socket._rtmpDestination); | |
ffmpeg_process=spawn('ffmpeg', ops); | |
feedStream=function(data){ | |
ffmpeg_process.stdin.write(data); | |
//write exception cannot be caught here. | |
} | |
ffmpeg_process.stderr.on('data',function(d){ | |
socket.emit('ffmpeg_stderr',''+d); | |
}); | |
ffmpeg_process.on('error',function(e){ | |
console.log('child process error'+e); | |
socket.emit('fatal','ffmpeg error!'+e); | |
feedStream=false; | |
socket.disconnect(); | |
}); | |
ffmpeg_process.on('exit',function(e){ | |
console.log('child process exit'+e); | |
socket.emit('fatal','ffmpeg exit!'+e); | |
socket.disconnect(); | |
}); | |
}); | |
socket.on('binarystream',function(m){ | |
if(!feedStream){ | |
socket.emit('fatal','rtmp not set yet.'); | |
return; | |
} | |
feedStream(m); | |
}); | |
socket.on('disconnect', function () { | |
feedStream=false; | |
if(ffmpeg_process) | |
try{ | |
ffmpeg_process.stdin.end(); | |
ffmpeg_process.kill('SIGINT'); | |
}catch(e){console.warn('killing ffmoeg process attempt failed...');} | |
}); | |
socket.on('error',function(e){ | |
console.log('socket.io error:'+e); | |
}); | |
}); | |
io.on('error',function(e){ | |
console.log('socket.io error:'+e); | |
}); | |
//http.listen(8888, function(){ | |
// console.log('http and websocket listening on *:8888'); | |
//}); | |
server.listen(443, function(){ | |
console.log('https and websocket listening on *:443'); | |
}); | |
process.on('uncaughtException', function(err) { | |
// handle the error safely | |
console.log(err) | |
// Note: after client disconnect, the subprocess will cause an Error EPIPE, which can only be caught this way. | |
}) |