/// @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;
        });
    ///@}
}
