Skip to content

Commit

Permalink
RecordRTC/PHP/FFmpeg synced audio/video recording demo added.
Browse files Browse the repository at this point in the history
This demo can record both audio/video; and merge both files in single
WebM container.

Both audio video are synced using ffmpeg commands!

You can record longest possible audio/video files. At least, minimum 5
minutes recording is tested!

issues?
1. Browser hangs for at least 30 seconds for encoding large video webp
array into WebM.
2. Video is not 100% smooth....though it is at least 98% stable!

One thing you MUST NEVER forget; you MUST be using latest ffmpeg
installation; at least the one which supports "libvpx" and "libvorbis".
  • Loading branch information
muaz-khan committed Mar 31, 2014
1 parent e3eb163 commit f3d6d5f
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 11 deletions.
37 changes: 32 additions & 5 deletions RecordRTC-Development/RecordRTC.js
Expand Up @@ -74,11 +74,38 @@ function RecordRTC(mediaStream, config) {
function getDataURL(callback, _mediaRecorder) {
if (!callback) throw 'Pass a callback function over getDataURL.';

var reader = new FileReader();
reader.readAsDataURL(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
reader.onload = function(event) {
callback(event.target.result);
};
_getDataURL();

function _getDataURL() {
if (!!window.Worker) {
var webWorker = processInWebWorker(function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
});

webWorker.onmessage = function(event) {
callback(event.data);
};

webWorker.postMessage(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
} else {
var reader = new FileReader();
reader.readAsDataURL(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
reader.onload = function(event) {
callback(event.target.result);
};
}
}

function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (e) {readFile(e.data);}'], {
type: 'application/javascript'
}));

var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
}
}

var WARNING = 'It seems that "startRecording" is not invoked for ' + config.type + ' recorder.';
Expand Down
5 changes: 5 additions & 0 deletions RecordRTC/PHP-and-FFmpeg/.htaccess
@@ -0,0 +1,5 @@
php_value session.gc_maxlifetime 10800
php_value max_input_time 10800
php_value max_execution_time 10800
php_value upload_max_filesize 500M
php_value post_max_size 500M
173 changes: 173 additions & 0 deletions RecordRTC/PHP-and-FFmpeg/index.html
@@ -0,0 +1,173 @@
<!--
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC
-->

<!DOCTYPE html>
<html lang="en">
<head>
<title>RecordRTC / PHP / FFmpeg</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan">
<meta name="author" content="Muaz Khan">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

<style>
body, html {
background: black;
color: white;
text-align: center;
}

button, a {
background: -webkit-gradient(linear, 50% 0, 50% 100%, color-stop(0%, #fff), color-stop(100%, #eaeaea));
background: -webkit-linear-gradient(top, #fff, #eaeaea);
background: -moz-linear-gradient(top, #fff, #eaeaea);
background: -o-linear-gradient(top, #fff, #eaeaea);
background: linear-gradient(top, #fff, #eaeaea);
border: 1px solid white;
border-radius: 5px;
color: #4f4f4f;
padding: 8px 15px;
text-decoration: none;
text-transform: capitalize;
}

button:hover, a:hover, button:active, a:active, button:focus, a:focus {
-moz-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
background: #303030;
border-color: white;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
color: white;
}

button[disabled] {
background: transparent;
border-color: rgb(83, 81, 81);
color: rgb(139, 133, 133);
}
</style>

<!-- script used for audio/video/gif recording -->
<script src="//www.webrtc-experiment.com/RecordRTC.js"> </script>
</head>

<body style="text-align: center;">
<h1>
<a href="https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC">RecordRTC</a>
<a href="https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC-to-PHP">PHP</a>
<a href="https://github.com/muaz-khan/WebRTC-Experiment/tree/master/ffmpeg">FFmpeg</a>
</h1>
<section class="experiment">
<p style="text-align: center;">
<video id="preview" controls style="border: 1px solid rgb(15, 158, 238); height: 240px; max-width: 100%; vertical-align: top; width: 320px;"></video>
</p>

<button id="record">Record</button>
<button id="stop" disabled>Stop</button>

<div id="container" style="padding: 1em 2em;"></div>
</section>

<script>
function PostBlob(audioBlob, videoBlob, fileName) {
var formData = new FormData();
formData.append('filename', fileName);
formData.append('audio-blob', audioBlob);
formData.append('video-blob', videoBlob);
xhr('save.php', formData, function(ffmpeg_output) {
document.querySelector('h1').innerHTML = ffmpeg_output.replace( /\\n/g , '<br />');
preview.src = 'uploads/' + fileName + '-merged.webm';
preview.play();
preview.muted = false;
});
}

var record = document.getElementById('record');
var stop = document.getElementById('stop');

var audio = document.querySelector('audio');

var recordVideo = document.getElementById('record-video');
var preview = document.getElementById('preview');

var container = document.getElementById('container');

var isFirefox = !!navigator.mozGetUserMedia;

var recordAudio, recordVideo;
record.onclick = function() {
record.disabled = true;
!window.stream && navigator.getUserMedia({
audio: true,
video: true
}, function(stream) {
window.stream = stream;
onstream();
}, function(error) {
alert(JSON.stringify(error, null, '\t'));
});

window.stream && onstream();

function onstream() {
preview.src = window.URL.createObjectURL(stream);
preview.play();
preview.muted = true;

recordAudio = RecordRTC(stream, {
bufferSize: 16384
});
recordAudio.startRecording();

if (!isFirefox) {
recordVideo = RecordRTC(stream, {
type: 'video'
});
recordVideo.startRecording();
}

stop.disabled = false;
}
};

var fileName;
stop.onclick = function() {
document.querySelector('h1').innerHTML = 'Getting Blobs...';

record.disabled = false;
stop.disabled = true;

preview.src = '';
preview.poster = 'ajax-loader.gif';

fileName = Math.round(Math.random() * 99999999) + 99999999;

if (!isFirefox) {
recordAudio.stopRecording(function() {
document.querySelector('h1').innerHTML = 'Got audio-blob. Getting video-blob...';
recordVideo.stopRecording(function() {
document.querySelector('h1').innerHTML = 'Uploading to server...';
PostBlob(recordAudio.getBlob(), recordVideo.getBlob(), fileName);
});
});
}
};

function xhr(url, data, callback) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
callback(request.responseText);
}
};
request.open('POST', url);
request.send(data);
}
</script>
</body>
</html>
88 changes: 88 additions & 0 deletions RecordRTC/PHP-and-FFmpeg/save.php
@@ -0,0 +1,88 @@
<?php
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC

// make sure that you're using newest ffmpeg version!

// because we've different ffmpeg commands for windows & linux
// that's why following script is used to fetch target OS
$OSList = array
(
'Windows 3.11' => 'Win16',
'Windows 95' => '(Windows 95)|(Win95)|(Windows_95)',
'Windows 98' => '(Windows 98)|(Win98)',
'Windows 2000' => '(Windows NT 5.0)|(Windows 2000)',
'Windows XP' => '(Windows NT 5.1)|(Windows XP)',
'Windows Server 2003' => '(Windows NT 5.2)',
'Windows Vista' => '(Windows NT 6.0)',
'Windows 7' => '(Windows NT 7.0)',
'Windows NT 4.0' => '(Windows NT 4.0)|(WinNT4.0)|(WinNT)|(Windows NT)',
'Windows ME' => 'Windows ME',
'Open BSD' => 'OpenBSD',
'Sun OS' => 'SunOS',
'Linux' => '(Linux)|(X11)',
'Mac OS' => '(Mac_PowerPC)|(Macintosh)',
'QNX' => 'QNX',
'BeOS' => 'BeOS',
'OS/2' => 'OS/2',
'Search Bot'=>'(nuhk)|(Googlebot)|(Yammybot)|(Openbot)|(Slurp)|(MSNBot)|(Ask Jeeves/Teoma)|(ia_archiver)'
);
// Loop through the array of user agents and matching operating systems
foreach($OSList as $CurrOS=>$Match)
{
// Find a match
if (eregi($Match, $_SERVER['HTTP_USER_AGENT']))
{
// We found the correct match
break;
}
}

// if it is audio-blob
if (isset($_FILES["audio-blob"])) {
$uploadDirectory = 'uploads/'.$_POST["filename"].'.wav';
if (!move_uploaded_file($_FILES["audio-blob"]["tmp_name"], $uploadDirectory)) {
echo("Problem writing audio file to disk!");
}
else {
// if it is video-blob
if (isset($_FILES["video-blob"])) {
$uploadDirectory = 'uploads/'.$_POST["filename"].'.webm';
if (!move_uploaded_file($_FILES["video-blob"]["tmp_name"], $uploadDirectory)) {
echo("Problem writing video file to disk!");
}
else {
$audioFile = 'uploads/'.$_POST["filename"].'.wav';
$videoFile = 'uploads/'.$_POST["filename"].'.webm';

$mergedFile = 'uploads/'.$_POST["filename"].'-merged.webm';

// ffmpeg depends on yasm
// libvpx depends on libvorbis
// libvorbis depends on libogg
// make sure that you're using newest ffmpeg version!

if(!strrpos($CurrOS, "Windows")) {
$cmd = '-i '.$audioFile.' -itsoffset -00:00:02 -i '.$videoFile.' -map 0:0 -map 1:0 '.$mergedFile;
}
else {
$cmd = ' -i '.$audioFile.' -itsoffset -00:00:02 -i '.$videoFile.' -c:v mpeg4 -c:a vorbis -b:v 64k -b:a 12k -strict experimental '.$mergedFile;
}

exec('ffmpeg '.$cmd.' 2>&1', $out, $ret);
if ($ret){
echo "There was a problem!\n";
print_r($cmd.'\n');
print_r($out);
} else {
echo "Ffmpeg successfully merged audi/video files into single WebM container!\n";

unlink($audioFile);
unlink($videoFile);
}
}
}
}
}
?>
40 changes: 34 additions & 6 deletions RecordRTC/RecordRTC.js
@@ -1,4 +1,4 @@
// Last time updated at 27 March 2014, 16:32:23
// Last time updated at 31 March 2014, 16:32:23

// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
Expand Down Expand Up @@ -88,11 +88,38 @@ function RecordRTC(mediaStream, config) {
function getDataURL(callback, _mediaRecorder) {
if (!callback) throw 'Pass a callback function over getDataURL.';

var reader = new FileReader();
reader.readAsDataURL(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
reader.onload = function(event) {
callback(event.target.result);
};
_getDataURL();

function _getDataURL() {
if (!!window.Worker) {
var webWorker = processInWebWorker(function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
});

webWorker.onmessage = function(event) {
callback(event.data);
};

webWorker.postMessage(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
} else {
var reader = new FileReader();
reader.readAsDataURL(_mediaRecorder ? _mediaRecorder.recordedBlob : mediaRecorder.recordedBlob);
reader.onload = function(event) {
callback(event.target.result);
};
}
}

function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (e) {readFile(e.data);}'], {
type: 'application/javascript'
}));

var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
}
}

var WARNING = 'It seems that "startRecording" is not invoked for ' + config.type + ' recorder.';
Expand Down Expand Up @@ -816,6 +843,7 @@ function WhammyRecorder(mediaStream) {
// should we provide an option to record via Whammy.js or MediaRecorder API is a better solution?

var Whammy = (function() {

function toWebM(frames) {
var info = checkFrames(frames);

Expand Down

0 comments on commit f3d6d5f

Please sign in to comment.