Search code examples
recordrtc

record and save video to server w/RecordRTC


I am trying to record a video and then save it to the server. My issue is the file is not being saved to the server and I am not sure why. The issue seems to be that it is not creating the blob or maybe not able to get the file as a blob?

I say that because in the Console I see this error

stopRecording failure TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.

Here is my file

<html>
  <head>
     <script src="https://cdn.webrtc-experiment.com/RecordRTC.js"></script>
      <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

 </head>
 <body>

 <button id="btn-start-recording">Start Recording</button>
 <button id="btn-stop-recording" disabled="disabled">Stop Recording</button>
<button id="btn-save-recording" disabled="disabled">Stop& Save Recording</button>
<!--
   2. Include a video element that will display the current video stream
   and as well to show the recorded video at the end.
-->
<hr>
<video id="my-preview" controls autoplay></video>

 <!-- 4. Initialize and prepare the video recorder logic -->
<script>
    // Store a reference of the preview video element and a global reference to the recorder instance
var video = document.getElementById('my-preview');
var recorder;

// When the user clicks on start video recording
document.getElementById('btn-start-recording').addEventListener("click", function(){
    // Disable start recording button
    this.disabled = true;

    // Request access to the media devices
    navigator.mediaDevices.getUserMedia({
        audio: true, 
        video: true
    }).then(function(stream) {
        // Display a live preview on the video element of the page
        setSrcObject(stream, video);

        // Start to display the preview on the video element
        // and mute the video to disable the echo issue !
        video.play();
        video.muted = true;

        // Initialize the recorder
        recorder = new RecordRTCPromisesHandler(stream, {
            mimeType: 'video/webm',
            bitsPerSecond: 128000
        });

        // Start recording the video
        recorder.startRecording().then(function() {
            console.info('Recording video ...');
        }).catch(function(error) {
            console.error('Cannot start video recording: ', error);
        });

        // release stream on stopRecording
        recorder.stream = stream;

        // Enable stop recording button
        document.getElementById('btn-stop-recording').disabled = false;
        document.getElementById('btn-save-recording').disabled = false;
    }).catch(function(error) {
        console.error("Cannot access media devices: ", error);
    });
}, false);

// When the user clicks on Stop video recording
document.getElementById('btn-stop-recording').addEventListener("click", function(){
    this.disabled = true;

    recorder.stopRecording().then(function() {
        console.info('stopRecording success');

        // Retrieve recorded video as blob and display in the preview element
        var videoBlob = recorder.getBlob();
        video.src = URL.createObjectURL(videoBlob);
        video.play();

        // Unmute video on preview
        video.muted = false;

        // Stop the device streaming
        recorder.stream.stop();

        // Enable record button again !
        document.getElementById('btn-start-recording').disabled = false;
    }).catch(function(error) {
        console.error('stopRecording failure', error);
    });
}, false);


//lets save the video
document.getElementById('btn-save-recording').addEventListener("click", function(){
        // Retrieve recorded video as blob and display in the preview element
        var videoBlob = recorder.getBlob();
        video.src = URL.createObjectURL(videoBlob);
        video.play();

        // Unmute video on preview
        video.muted = false;

        // Stop the device streaming
        recorder.stream.stop();

        var formData = new FormData();
        formData.append('video', player.recordedData.video);
        // Execute the ajax request, in this case we have a very simple PHP script
       // that accepts and save the uploaded "video" file
       xhr('upload-videoclaim.php', formData, function (fName) {
           console.log("Video succesfully uploaded !");
       })
       // Helper function to send 
       function xhr(url, data, callback) {
         var request = new XMLHttpRequest();
         request.onreadystatechange = function () {
            if (request.readyState == 4 && request.status == 200) {
              callback(location.href + request.responseText);
            }
         };
        request.open('POST', url);
        request.send(data);
       }

});

</script>
</body>
</html>

Solution

  • See the MDN docs on parameters for URL.createObjectURL:

    A File, Blob, or MediaSource object to create an object URL for.

    Just try passing something else to URL.createObjectURL that it doesn't expect like a number. You'll get a similar / same error message.

    Now see the implementation of RecordRTCPromisesHandler.getBlob(). It returns a Promise:

    /**
     * This method returns the recorded blob.
     * @method
     * @memberof RecordRTCPromisesHandler
     * @example
     * recorder.stopRecording().then(function() {
     *     recorder.getBlob().then(function(blob) {})
     * }).catch(errorCB);
     */
    this.getBlob = function() {
        return new Promise(function(resolve, reject) {
            try {
                resolve(self.recordRTC.getBlob());
            } catch (e) {
                reject(e);
            }
        });
    };
    

    Look at the @example given in the JSDoc comment. It does recorder.getBlob().then(<callback>). Follow the example.

    Option 1 (nested Promise.then()):

    recorder.stopRecording().then(function() {
        console.info('stopRecording success');
        // do whatever else you want here.
        recorder.getBlob().then(videoBlob => {
            video.src = URL.createObjectURL(videoBlob);
            video.play();
            video.muted = false;
            // ...
        });
        }).catch(function(error) {
        // ...
    

    Option 2 (chained Promise.then()):

    recorder.stopRecording().then(() => recorder.getBlob()).then(videoBlob => {
        video.src = URL.createObjectURL(videoBlob);
        video.play();
        video.muted = false;
        // ...
    }).catch(function(error) {
    // ...
    

    Option 3 (async-await):

    try {
        await recorder.stopRecording();
        const videoBlob = await recorder.getBlob();
        video.src = URL.createObjectURL(videoBlob);
        video.play();
        video.muted = false;
        // ...
    } catch(error) {
    // ...
    

    I'm not sure, but part of why you didn't understand the problem might have been due to limitations of intellisense when writing JS in HTML, and particularly when using a library without downloading the source locally so that it's visible to the intellisense facilities. I wouldn't really fault you for that. If you think it's appropriate, you could consider politely asking the maintainer of the library to add to their library's website's docuentation to also document the Promise-based interfaces of the library, or even writing up PRs to do that.