102

I would like to record the users webcam and audio and save it to a file on the server. These files would then be able to be served up to other users.

I have no problems with playback, however I'm having problems getting the content to record.

My understanding is that the getUserMedia .record() function has not yet been written - only a proposal has been made for it so far.

I would like to create a peer connection on my server using the PeerConnectionAPI. I understand this is a bit hacky, but I'm thinking it should be possible to create a peer on the server and record what the client-peer sends.

If this is possible, I should then be able to save this data to flv or any other video format.

My preference is actually to record the webcam + audio client-side, to allow the client to re-record videos if they didn't like their first attempt before uploading. This would also allow for interruptions in network connections. I've seen some code which allows recording of individual 'images' from the webcam by sending the data to the canvas - that's cool, but I need the audio too.

Here's the client side code I have so far:

  <video autoplay></video>

<script language="javascript" type="text/javascript">
function onVideoFail(e) {
    console.log('webcam fail!', e);
  };

function hasGetUserMedia() {
  // Note: Opera is unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

window.URL = window.URL || window.webkitURL;
navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia || navigator.msGetUserMedia;

var video = document.querySelector('video');
var streamRecorder;
var webcamstream;

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = window.URL.createObjectURL(stream);
    webcamstream = stream;
//  streamrecorder = webcamstream.record();
  }, onVideoFail);
} else {
    alert ('failed');
}

function startRecording() {
    streamRecorder = webcamstream.record();
    setTimeout(stopRecording, 10000);
}
function stopRecording() {
    streamRecorder.getRecordedData(postVideoToServer);
}
function postVideoToServer(videoblob) {
/*  var x = new XMLHttpRequest();
    x.open('POST', 'uploadMessage');
    x.send(videoblob);
*/
    var data = {};
    data.video = videoblob;
    data.metadata = 'test metadata';
    data.action = "upload_video";
    jQuery.post("http://www.foundthru.co.uk/uploadvideo.php", data, onUploadSuccess);
}
function onUploadSuccess() {
    alert ('video uploaded');
}

</script>

<div id="webcamcontrols">
    <a class="recordbutton" href="javascript:startRecording();">RECORD</a>
</div>
iammilind
  • 68,093
  • 33
  • 169
  • 336
Dave Hilditch
  • 5,299
  • 4
  • 27
  • 35

9 Answers9

50

You should definitely have a look at Kurento. It provides a WebRTC server infrastructure that allows you to record from a WebRTC feed and much more. You can also find some examples for the application you are planning here. It is really easy to add recording capabilities to that demo, and store the media file in a URI (local disk or wherever).

The project is licensed under LGPL Apache 2.0


EDIT 1

Since this post, we've added a new tutorial that shows how to add the recorder in a couple of scenarios

Disclaimer: I'm part of the team that develops Kurento.

igracia
  • 3,543
  • 1
  • 18
  • 23
  • Looks promising. Do you know where I can find information on how it scales? E.g., how many streams can be processed by a server, etc.? – Redtopia Jan 07 '15 at 05:17
  • 3
    @Redtopia In some recent load tests we were able to get 150 one2one connections of webrtc on an i5/16GB RAM. You can expect that these numbers will be better in the future, but don't expect miracles: there is a lot of encryption going on for SRTP, and that is demanding. We are looking into hardware-accelerated encryption/decryption, and the numbers will go higher, and though I can't assure you how much better it will be until we test it more thoroughly, we expect a 3x improvement – igracia Jan 28 '15 at 15:41
  • In my experience, the Kurento media server isn't necessarily ready for production. I couldn't get the latest version of their Java tutorial to compile at all using Maven, complained about some missing POM modules which I couldnt find anywhere. I googled and the only reply from a member of Kurento team was RTFM and study more Maven. Of course you get what you pay for, but still I don't want to spend my time on technology like that. Recording the video client-side and uploading periodically could be easier, for example this looks promising https://github.com/streamproc/MediaStreamRecorder – user344146 Dec 28 '15 at 06:34
  • 2
    @user344146 That was probably me answering. Would you mind sharing a link to that post? If you got that answer, it's probably because you asked something that was already there or in the list. It looks like you were trying to compile a SNAPSHOT version. Those artifacts don't get published in central, so either you checkout a release of the tutorials or use our internal dev repo. This has been answered in the list many times, there is an entry in the documentation about working with development versions... We took the time to write it, so it would be nice of you to take the time to read it. – igracia Dec 28 '15 at 11:38
  • 2
    I'm just using Kurento to make such recording. I'ts not complicated, but need a little bit of time, to understand the concept- because some of docs are really mean- and finding what can I send to kurento, or description of events and so on can be sometimes really frustrating. But anyway- a open project like this is really a great job and worth of using. Kurento is working in linux only (windows version is not official and does not work with full functionality). – Krystian Nov 30 '16 at 10:31
  • @igracia any example to be able to use with asp.,net mvc – AMH Jan 02 '17 at 12:52
  • @AMH NOt that I know of, but it shouldn't be hard for you to port it. – igracia Jan 03 '17 at 14:45
  • @igracia is it possible to record a video message for later viewing just like the same way we have voice mail stuff using webRTC ? – Bilbo Baggins Jan 17 '17 at 07:52
  • @BilboBaggins Sure! You can use a recorder to record the message, and then use a player to stream the message. – igracia Jan 19 '17 at 19:52
  • @igracia Thanks for answer, I would like to know that the current version of Kurento supports which Operating systesm apart from Ubuntu 14.0 LTS, why is Ubuntu compulsory? Which Java version it supports( I checked the documentation and pom.xml but couldn't find it) ? Does it support Ubuntu 16.04LTS ? – Bilbo Baggins Jan 20 '17 at 07:19
  • 1
    Found answers for above questions (posting here for others), Kurento currently supports JDK 7.0, It is not that it has to be dependent on Ubuntu 14.04, it should support later versions as well, but Kurento is not tested officially on other versions of Ubuntu/other linux version. Also Kurento releases 64 bit versions as readily available for isntallation, however you can install 32 bit version of server but you have to build it first. – Bilbo Baggins Jan 23 '17 at 07:02
  • 2
    Unfortunately, as stated in my answer, the development of Kurento has slowed down severely after the Twilio acquisition. I recommend using Janus instead. – jamix Jan 03 '18 at 10:38
22

I believe using kurento or other MCUs just for recording videos would be bit of overkill, especially considering the fact that Chrome has MediaRecorder API support from v47 and Firefox since v25. So at this junction, you might not even need an external js library to do the job, try this demo I made to record video/ audio using MediaRecorder:

Demo - would work in chrome and firefox (intentionally left out pushing blob to server code)

Github Code Source

If running firefox, you could test it in here itself( chrome needs https):

'use strict'

let log = console.log.bind(console),
  id = val => document.getElementById(val),
  ul = id('ul'),
  gUMbtn = id('gUMbtn'),
  start = id('start'),
  stop = id('stop'),
  stream,
  recorder,
  counter = 1,
  chunks,
  media;


gUMbtn.onclick = e => {
  let mv = id('mediaVideo'),
    mediaOptions = {
      video: {
        tag: 'video',
        type: 'video/webm',
        ext: '.mp4',
        gUM: {
          video: true,
          audio: true
        }
      },
      audio: {
        tag: 'audio',
        type: 'audio/ogg',
        ext: '.ogg',
        gUM: {
          audio: true
        }
      }
    };
  media = mv.checked ? mediaOptions.video : mediaOptions.audio;
  navigator.mediaDevices.getUserMedia(media.gUM).then(_stream => {
    stream = _stream;
    id('gUMArea').style.display = 'none';
    id('btns').style.display = 'inherit';
    start.removeAttribute('disabled');
    recorder = new MediaRecorder(stream);
    recorder.ondataavailable = e => {
      chunks.push(e.data);
      if (recorder.state == 'inactive') makeLink();
    };
    log('got media successfully');
  }).catch(log);
}

start.onclick = e => {
  start.disabled = true;
  stop.removeAttribute('disabled');
  chunks = [];
  recorder.start();
}


stop.onclick = e => {
  stop.disabled = true;
  recorder.stop();
  start.removeAttribute('disabled');
}



function makeLink() {
  let blob = new Blob(chunks, {
      type: media.type
    }),
    url = URL.createObjectURL(blob),
    li = document.createElement('li'),
    mt = document.createElement(media.tag),
    hf = document.createElement('a');
  mt.controls = true;
  mt.src = url;
  hf.href = url;
  hf.download = `${counter++}${media.ext}`;
  hf.innerHTML = `donwload ${hf.download}`;
  li.appendChild(mt);
  li.appendChild(hf);
  ul.appendChild(li);
}
      button {
        margin: 10px 5px;
      }
      li {
        margin: 10px;
      }
      body {
        width: 90%;
        max-width: 960px;
        margin: 0px auto;
      }
      #btns {
        display: none;
      }
      h1 {
        margin-bottom: 100px;
      }
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<h1> MediaRecorder API example</h1>

<p>For now it is supported only in Firefox(v25+) and Chrome(v47+)</p>
<div id='gUMArea'>
  <div>
    Record:
    <input type="radio" name="media" value="video" checked id='mediaVideo'>Video
    <input type="radio" name="media" value="audio">audio
  </div>
  <button class="btn btn-default" id='gUMbtn'>Request Stream</button>
</div>
<div id='btns'>
  <button class="btn btn-default" id='start'>Start</button>
  <button class="btn btn-default" id='stop'>Stop</button>
</div>
<div>
  <ul class="list-unstyled" id='ul'></ul>
</div>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
mido
  • 24,198
  • 15
  • 92
  • 117
18

Please, check the RecordRTC

RecordRTC is MIT licensed on github.

Dmitry
  • 661
  • 5
  • 21
  • 2
    That is pretty awesome -- my question: can that record video and audio together (live a real video rather than two separate things?) – Brian Dear Jun 30 '13 at 18:07
  • Agreed - awesome, but it looks like it only records the data separately. – Dave Hilditch Jul 01 '13 at 12:07
  • 3
    @BrianDear there is one [RecordRTC-together](https://github.com/H0201030/record-rtc-together) – Mifeng Nov 10 '13 at 16:23
  • 3
    This approach works via Whammy.js in Chrome. This is problematic since the quality tends to be much lower from the emulation Whammy provides for Chrome's lack of a MediaStreamRecorder. What essentially happens is WhammyRecorder points a video tag to the MediaStream object URL and then takes webp snapshots of a canvas element at a certain frame rate. It then uses Whammy to put all those frames together into a webm video. – Vinay Feb 02 '16 at 04:24
8

yes, as you understood, MediaStreamRecorder is currently unimplemented.

MediaStreamRecorder is a WebRTC API for recording getUserMedia() streams . It allows web apps to create a file from a live audio/video session.

alternatively you may do like this http://ericbidelman.tumblr.com/post/31486670538/creating-webm-video-from-getusermedia but audio is missing part.

kongaraju
  • 9,344
  • 11
  • 55
  • 78
  • 1
    Yep, and you can capture the audio file, send it to the server, and combine them there to create a real video file on the server side. But this solution might be very slow on the client side depending on its computer configuration, as it has to create image files using a canvas AND capture the audio, and all of this in the RAM... Btw, [firefox team are working on it](https://hacks.mozilla.org/2013/04/webrtc-update-our-first-implementation-will-be-in-release-soon-welcome-to-the-party-but-please-watch-your-head/), so hopefully they will release it soon. – Firas May 21 '13 at 11:38
4

You can use RecordRTC-together, which is based on RecordRTC.

It supports recording video and audio together in separate files. You will need tool like ffmpeg to merge two files into one on server.

Mifeng
  • 1,525
  • 15
  • 26
3

Web Call Server 4 can record WebRTC audio and video to WebM container. The recording is done using Vorbis codec for audio and VP8 codec for video. Iniitial WebRTC codecs are Opus or G.711 and VP8. So, the server-side recording requires either Opus/G.711 to Vorbis server-side transcoding or VP8-H.264 transcoding if it is necessary to use another container, i.e. AVI.

Bob42
  • 41
  • 2
0

For the record I also don't have enough knowledge about this,

But I found this on Git hub-

<!DOCTYPE html>
 <html>
<head>
  <title>XSockets.WebRTC Client example</title>
  <meta charset="utf-8" />


<style>
body {

  }
.localvideo {
position: absolute;
right: 10px;
top: 10px;
}

.localvideo video {
max-width: 240px;
width:100%;
margin-right:auto;
margin-left:auto;
border: 2px solid #333;

 }
 .remotevideos {
height:120px;
background:#dadada;
padding:10px; 
}

.remotevideos video{
max-height:120px;
float:left;
 }
</style>
</head>
<body>
<h1>XSockets.WebRTC Client example </h1>
<div class="localvideo">
    <video autoplay></video>
</div>

<h2>Remote videos</h2>
<div class="remotevideos">

</div>
<h2>Recordings  ( Click on your camera stream to start record)</h2>
<ul></ul>


<h2>Trace</h2>
<div id="immediate"></div>
<script src="XSockets.latest.js"></script>
<script src="adapter.js"></script>
<script src="bobBinder.js"></script>
<script src="xsocketWebRTC.js"></script>
<script>
    var $ = function (selector, el) {
        if (!el) el = document;
        return el.querySelector(selector);
    }
    var trace = function (what, obj) {
        var pre = document.createElement("pre");
        pre.textContent = JSON.stringify(what) + " - " + JSON.stringify(obj || "");
        $("#immediate").appendChild(pre);
    };
    var main = (function () {
        var broker;
        var rtc;
        trace("Ready");
        trace("Try connect the connectionBroker");
        var ws = new XSockets.WebSocket("wss://rtcplaygrouund.azurewebsites.net:443", ["connectionbroker"], {
            ctx: '23fbc61c-541a-4c0d-b46e-1a1f6473720a'
        });
        var onError = function (err) {
            trace("error", arguments);
        };
        var recordMediaStream = function (stream) {
            if ("MediaRecorder" in window === false) {
                trace("Recorder not started MediaRecorder not available in this browser. ");
                return;
            }
            var recorder = new XSockets.MediaRecorder(stream);
            recorder.start();
            trace("Recorder started.. ");
            recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };
        };
        var addRemoteVideo = function (peerId, mediaStream) {
            var remoteVideo = document.createElement("video");
            remoteVideo.setAttribute("autoplay", "autoplay");
            remoteVideo.setAttribute("rel", peerId);
            attachMediaStream(remoteVideo, mediaStream);
            $(".remotevideos").appendChild(remoteVideo);
        };
        var onConnectionLost = function (remotePeer) {
            trace("onconnectionlost", arguments);
            var peerId = remotePeer.PeerId;
            var videoToRemove = $("video[rel='" + peerId + "']");
            $(".remotevideos").removeChild(videoToRemove);
        };
        var oncConnectionCreated = function () {
            console.log(arguments, rtc);
            trace("oncconnectioncreated", arguments);
        };
        var onGetUerMedia = function (stream) {
            trace("Successfully got some userMedia , hopefully a goat will appear..");
            rtc.connectToContext(); // connect to the current context?
        };
        var onRemoteStream = function (remotePeer) {
            addRemoteVideo(remotePeer.PeerId, remotePeer.stream);
            trace("Opps, we got a remote stream. lets see if its a goat..");
        };
        var onLocalStream = function (mediaStream) {
            trace("Got a localStream", mediaStream.id);
            attachMediaStream($(".localvideo video "), mediaStream);
            // if user click, video , call the recorder
            $(".localvideo video ").addEventListener("click", function () {
                recordMediaStream(rtc.getLocalStreams()[0]);
            });
        };
        var onContextCreated = function (ctx) {
            trace("RTC object created, and a context is created - ", ctx);
            rtc.getUserMedia(rtc.userMediaConstraints.hd(false), onGetUerMedia, onError);
        };
        var onOpen = function () {
            trace("Connected to the brokerController - 'connectionBroker'");
            rtc = new XSockets.WebRTC(this);
            rtc.onlocalstream = onLocalStream;
            rtc.oncontextcreated = onContextCreated;
            rtc.onconnectioncreated = oncConnectionCreated;
            rtc.onconnectionlost = onConnectionLost;
            rtc.onremotestream = onRemoteStream;
            rtc.onanswer = function (event) {
            };
            rtc.onoffer = function (event) {
            };
        };
        var onConnected = function () {
            trace("connection to the 'broker' server is established");
            trace("Try get the broker controller form server..");
            broker = ws.controller("connectionbroker");
            broker.onopen = onOpen;
        };
        ws.onconnected = onConnected;
    });
    document.addEventListener("DOMContentLoaded", main);
</script>

On Line number 89 in my case code OnrecordComplete actually append a link of recorder file, if you will click on that link it will start the download, you can save that path to your server as a file.

The Recording code looks something like this

recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };

The blobUrl holds the path. I solved my problem with this, hope someone will find this useful

tiwarinitin94
  • 114
  • 2
  • 13
0

Currently the browsers support recording on the client side.

https://webrtc.github.io/samples/

One can push the recorded file after the connection has been ended to server by uploading through some HTTP requests.

https://webrtc.github.io/samples/src/content/getusermedia/record/ https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/record

This has some kind of drawbacks that in case if the user just closes the tab and don't run these operations in the backend side, it may not uploaded the files fully to the server.

As a more stable solution Ant Media Server can record the stream on server side and recording functionality is one of the basic feature of Ant Media Server.

antmedia.io

Note: I'm a member of Ant Media team.

Mohit Dubey
  • 1
  • 1
  • 11
-6

Technically you can use FFMPEG on backend to mix video and audio