/// @class MediaRecorder /** Provides a recorder to record fotos, audio and video from a web browser client using the JavaScript getUserMedia feature. @param constraints The constraints as JSON data: @code { video: true, // true if you want to record video audio: true // true if you want to record audio } @endcode You can record video or audio only or both together. if not specified, defaults to @ref defaultconstraints. @note Special thanks to the following projects: - For the basics regarding getUserMedia: http://www.html5rocks.com/en/tutorials/getusermedia/intro/ - For a simple example of a media recorder: https://github.com/samdutton/simpl/blob/gh-pages/mediarecorder @note Supported browsers are: - Firefox 29 or later - Chrome 47 or later, with @c "Enable experimental Web Platform features" enabled from @c "chrome://flags" @note This class must be used from within a secure context. A secure context is an encrypted SSL connection through HTTPS, or the special address @c localhost. @todo Why are mediaSource and sourceBuffer needed? They are not further referenced. */ function MediaStreamRecorder(constraints) { /// @name private variables ///@{ var events = []; var mediaSource = new MediaSource(); var stream; var mediaRecorder; var recordedBlobs = []; var sourceBuffer; var defaultconstraints = { audio: true, video: true }; ///@} /// @name private methods ///@{ function createURL(data) { var urlCreator = window.URL || window.webkitURL; return urlCreator ? urlCreator.createObjectURL(data) : data; } ///@} /// @name internal event handlers ///@{ /// Create Source Buffer on Event Source Open function handleSourceOpen(event) { sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"'); } /// Store Data in local Binary Large Object function handleDataAvailable(event) { if (event.data && event.data.size > 0) { recordedBlobs.push(event.data); } } /// Propagate @c ready Event to Registered Handler function handleReady() { if (events['ready']) events['ready'](); } /// Propagate @c start Event to Registered Handler function handleStart() { if (events['stop']) events['start'](); } /// Propagate @c stop Event to Registered Handler function handleStop(event) { if (events['stop']) events['stop'](event); } ///@} /// @name public methods ///@{ /// Register Eventhandler /** @param eventname The following events are available: - @c ready Video preview stream is ready for use - @c stop Recording has stopped, parameter: @event - @c start Recording has started @param eventhandler callback function to be called when the event occurs. */ this.on = function(eventname, eventhandler) { events[eventname] = eventhandler; } /// Get Stream to the Preview /** @return Data URL prepared to be used in a HTML @c src attribute within a @c audio or @c video tag. */ this.preview = function() { return createURL(stream); } /// Get Stream to the Recording /** @param callback Callback function that will be called with a data url to be used in a HTML @c src attribute within a @c audio or @c video tag, or to be used in a HTML @c href attribute in a @c a tag for downloading the recording. */ this.recording = function(callback) { var reader = new FileReader(); reader.onload = function(e) { callback(e.target.result); } reader.readAsDataURL(new Blob(recordedBlobs, {type: 'video/webm'})); } /// Start Stream Recording /** @throws Exception if browser is not supported */ this.start = function() { // The nested try blocks will be simplified when Chrome 47 moves to Stable var options = {mimeType: 'video/webm'}; recordedBlobs = []; try { mediaRecorder = new MediaRecorder(stream, options); } catch (e0) { try { options = {mimeType: 'video/webm,codecs=vp9'}; mediaRecorder = new MediaRecorder(stream, options); } catch (e1) { try { options = 'video/vp8'; // Chrome 47 mediaRecorder = new MediaRecorder(stream, options); } catch (e2) { throw { text: 'MediaRecorder is not supported by browser', e0: e0, e1: e1, e2: e2 }; } } } mediaRecorder.onstop = handleStop; mediaRecorder.ondataavailable = handleDataAvailable; mediaRecorder.start(10); // collect 10ms of data handleStart(); } /// Stop Stream Recording /** Use recording() to get access to the result. */ this.stop = function () { if (mediaRecorder) mediaRecorder.stop(); delete mediaRecorder; mediaRecorder = null; } /// Close Preview Stream /** Closes the stream and releases the camera. This should always be called to cleanup, when the camera is no more needed. */ this.release = function() { stop(); stream.getTracks().forEach(function(track) {track.stop()}); delete stream; stream = null; } ///@} /// @name initialization ///@{ mediaSource.addEventListener('sourceopen', handleSourceOpen, false); if (!constraints) constraints = defaultconstraints; navigator.mediaDevices.getUserMedia(constraints) .then(function(s) { stream = s; handleReady(); }).catch(function(error) { throw error; }); ///@} }