5

I am trying to upload chunks of base64 to node js server and save those chunks into one file

let chunks = [];

app.post('/api', (req, res) => {
    let {blob} = req.body;
    //converting chunks of base64 to buffer
    chunks.push(Buffer.from(blob, 'base64'));
    res.json({gotit:true})

});

app.post('/finish', (req, res) => {
    let buf = Buffer.concat(chunks);
    fs.writeFile('finalvideo.webm', buf, (err) => {
        console.log('Ahh....', err)
    });
    console.log('SAVED')
    res.json({save:true})
});

Problem with the above code is video is not playable I don't why Am I really doing something wrong and I've also tried writable streams it is not working either

UPDATE - I

Instead of sending blobs I've implemented to send binary but even though I am facing a problem like TypeError: First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.

client.js

 postBlob = async blob => {
       let arrayBuffer = await new Response(blob).arrayBuffer();
        let binary = new Uint8Array(arrayBuffer)
        console.log(binary) // logging typed Uint8Array
        axios.post('/api',{binary})
            .then(res => {
                console.log(res)
            })

    };

server.js

 let chunks = [];

    app.post('/api', (req, res) => {
        let {binary} = req.body;



        let chunkBuff = Buffer.from(binary) // This code throwing Error
        chunks.push(chunkBuff);

        console.log(chunkBuff)

         res.json({gotit:true})

    });

//Somehow combine those chunks into one file
app.post('/finish', (req, res) => {
    console.log('Combinig the files',chunks.length);

     let buf = Buffer.concat(chunks);

    console.log(buf) //empty buff
    fs.writeFile('save.webm', buf, (err) => {
        console.log('Ahh....', err)
    });

    res.json({save:true})
});

UPDATE - II

I am able to receive the binary chunk and append to a stream but in the final video only first chunk is playing I don't know what happened to other chunks and the video ends.

code

const writeMyStream = fs.createWriteStream(__dirname+'/APPENDED.webm', {flags:'a', encoding:null});

app.post('/api', (req, res) => {
    let {binary} = req.body;
 let chunkBuff = Buffer.from(new Uint8Array(binary));
    writeMyStream.write(chunkBuff);
res.json({gotit:true})

});

UPDATE - III

my client code | Note: I've tried other ways to upload blobs I've commented out

     customRecordStream = stream => {



            let recorder = new MediaStreamRecorder(stream);
            recorder.mimeType = 'video/webm;codecs=vp9';


            recorder.ondataavailable = this.postBlob 
            recorder.start(INT_REC)

        };

 postBlob = async blob => {


        let arrayBuffer = await new Response(blob).arrayBuffer();
        let binary = new Uint8Array(arrayBuffer)


            axios.post('/api',{binary})
                .then(res => {
                    console.log(res)
                })
        // let binaryUi8 = new Uint8Array(arrayBuffer);
        // let binArr = Array.from(binaryUi8);
        // // console.log(new Uint8Array(arrayBuffer))
        //
        // console.log(blob);


        // console.log(binArr)

        // let formData = new FormData();
        // formData.append('fname', 'test.webm')
        // formData.append("file", blob);
        //
        // console.log(formData,'Checjk Me',blob)
        // axios({
        //     method:'post',
        //     url:'/api',
        //     data:formData,
        //     config: { headers: {'Content-Type': 'multipart/form-data' }}
        // }).then(res => {
        //     console.log(res,'FROM SERBER')
        //
        // })
        //
        //
        //     .then(res => {
        //         console.log(res)
        //     })

        // this.blobToDataURL(blob, (blobURL) => {
        //
        //     axios.post('/api',{blob:blobURL})
        //         .then(res => {
        //             console.log(res)
        //         })
        // })


    };
Nane
  • 2,370
  • 6
  • 34
  • 74
  • Why base-64 encode this? Chances are, you're doing the encoding/decoding segmentation wrong. Just send binary data, and it will be far more efficient! – Brad Jun 30 '19 at 15:38
  • Sounds a great idea to send binary data but can I combine those binaries into one video file? – Nane Jun 30 '19 at 15:42
  • Yes, best thing to do is just write out a stream to a file. But if you can't do that, you can also append to a file, or write a series of buffers, etc. etc. – Brad Jun 30 '19 at 15:54
  • it would be great if you posted an example – Nane Jun 30 '19 at 15:56
  • There are like 6 different pieces to this. Post a specific example. For example, post a question that asks how to PUT your binary file to your server, and show the code you have so far. Then, post a question about receiving that in Node.js and show your code so far. – Brad Jun 30 '19 at 16:01
  • Hey @Brad I've updated the question – Nane Jul 01 '19 at 08:14
  • Do you have the client side code to show? How are you populating the request.body /sending the webm file to the server? – willascend Jul 04 '19 at 18:21
  • Sure, See the updated question @willascend – Nane Jul 05 '19 at 05:34
  • Hi Nane, did you get a chance to try this out? – willascend Jul 07 '19 at 03:03

3 Answers3

11

I was able to get this working by converting to base64 encoding on the front-end with the FileReader api. On the backend, create a new Buffer from the data chunk sent and write it to a file stream. Some key things with my code sample:

  1. I'm using fetch because I didn't want to pull in axios.
  2. When using fetch, you have to make sure you use bodyParser on the backend
  3. I'm not sure how much data you're collecting in your chunks (i.e. the duration value passed to the start method on the MediaRecorder object), but you'll want to make sure your backend can handle the size of the data chunk coming in. I set mine really high to 50MB, but this may not be necessary.
  4. I never close the write stream explicitly... you could potentially do this in your /final route. Otherwise, createWriteStream defaults to AutoClose, so the node process will do it automatically.

Full working example below:

Front End:

const mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
let mediaRecorder;
let sourceBuffer;

function customRecordStream(stream) {
  // should actually check to see if the given mimeType is supported on the browser here.
  let options = { mimeType: 'video/webm;codecs=vp9' };
  recorder = new MediaRecorder(window.stream, options);
  recorder.ondataavailable = postBlob 
  recorder.start(INT_REC)
};

function postBlob(event){
  if (event.data && event.data.size > 0) {
    sendBlobAsBase64(event.data);
  }
}

function handleSourceOpen(event) {
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
} 

function sendBlobAsBase64(blob) {
  const reader = new FileReader();

  reader.addEventListener('load', () => {
    const dataUrl = reader.result;
    const base64EncodedData = dataUrl.split(',')[1];
    console.log(base64EncodedData)
    sendDataToBackend(base64EncodedData);
  });

  reader.readAsDataURL(blob);
};

function sendDataToBackend(base64EncodedData) {
  const body = JSON.stringify({
    data: base64EncodedData
  });
  fetch('/api', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body
  }).then(res => {
    return res.json()
  }).then(json => console.log(json));
}; 

Back End:

const fs = require('fs');
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const server = require('http').createServer(app);

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ limit: "50MB", type:'application/json'}));

app.post('/api', (req, res) => {
  try {
    const { data } = req.body;
    const dataBuffer = new Buffer(data, 'base64');
    const fileStream = fs.createWriteStream('finalvideo.webm', {flags: 'a'});
    fileStream.write(dataBuffer);
    console.log(dataBuffer);
    return res.json({gotit: true});
  } catch (error) {
    console.log(error);
    return res.json({gotit: false});
  }
});
willascend
  • 1,503
  • 8
  • 15
  • Thanks for your detailed answer but the problem is the library I've used to record blob is the problem ```MediaStreamRecorder``` and I removed this and built it with plain javascript and it is working – Nane Jul 07 '19 at 08:13
  • 1
    Awesome! Glad you got it working. Next time, please be sure to include whatever libraries you may be using so you can get the most comprehensive answers to address your issue. – willascend Jul 07 '19 at 13:28
  • I went ahead and added the rest of my client side code which using MediaRecorder so others can benefit as well. – willascend Jul 07 '19 at 13:38
  • @willascend, from the subject of topic, I assume that for single video, we transfer multiple chunk files to the server on specific interval of time and merge those files on the server side to make a combined video file. I am able to send the files on the server side but facing the issue to merge the files in java application on server side. I take a look of your back end code and realize that we invoke it once only. For example, we are having video recording of X person, so we are suppose to get chunk C1, C2, C3 and it will be pushed on server. – Divyesh Kanzariya May 30 '20 at 13:05
  • At server side we have to club these C1, C2, C3 and have to prepare combined video file of X person. It would be great help if you can just guide me on this issue as your server side code doesn't contain merge of files. Here with I am attaching my client side and server side code for your reference. @nane https://gist.github.com/geekdiv/1d4e70ae08e8178c4ea2316a7dc5d392 – Divyesh Kanzariya May 30 '20 at 13:06
  • @DivyeshKanzariya there is no need to merge per se... it's already done. We create a `writeStream` in append mode against the static filename "finalvideo.webm". Each time we receive a chunk at the `/api` endpoint it appends that chunk to the file. Once all chunks are received, the video file is complete. Please post a new question if this doesn't help. – willascend May 30 '20 at 17:17
1

Inspired by @willascend answer:

Backend-side:

app.use(express.raw());
app.post('/video-chunck', (req, res) => {
    fs.createWriteStream('myvideo.webm', { flags: 'a' }).write(req.body);
    res.sendStatus(200);
});

Frontend-side:

mediaRecorder.ondataavailable = event => {
    if (event.data && event.data.size > 0) {
        fetch(this.serverUrl + '/video-chunck', {
            method: 'POST',
            headers: {'Content-Type': 'application/octet-stream'},
            body: event.data
        });
    }
};

My express version is 4.17.1

Thibaud Michel
  • 352
  • 3
  • 13
-1

i faced the same problem today as a solution in back-end i used fs.appendfile

fs.appendFile(Path, rawData, function (err) {
        if (err) throw err;
        console.log('Chunck Saved!');
    })