Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play video using base64 encoded string in React-Native

I have encrypted a video file using base64 encoding format and AES-encryption. I decrypt data stream-by-stream and append/write each stream (as a .mp4 file) to achieve the final video but it takes a lot of time to achieve a final output.

Major Edit:

I have found resources html5-media-and-data-uri which helps to play data in a webview, but it doesn't play videos with 2,000,000+ base64 characters.

Function to decrypt a file and initialize HTML code within web view with base64 data

decryptfile() {
RNFetchBlob.fs
  .readStream(
    RNFetchBlob.fs.dirs.DCIMDir + "/encryptfile1.dat",
    "base64",
    2796256, //character to be read at a time for decryption
    2500  // Time taken before each stream enters for decryption
  )
  .then(stream => {
    let data = "";
    stream.open();
    stream.onData(chunk => {
      var decipherText = simpleCrypto.decrypt(chunk.toString()); // Decryption
      decryptText = decryptText + decipherText; // appending decrypted data
    });
    stream.onEnd(() => {
      htmlCode =
        `<html>
           <body>
              <video style="width: 50%; height: 50%; margin-top: 10%; margin-left: 22%;"
              controls>
                <source type="video/mp4" src="data:video/mp4;base64,` +
                 decryptText.toString() + // final decrypted data
                `">
             </video>
          </body>
        </html>`;
      this.setState({ playvideo: !this.state.playvideo }); // state set for playing video at end of decryption
      console.log("File decrypted");
    });
  });
}

Web View Code

<WebView
     style={{ flex: 1 }}
     source={{ html: htmlCode }}
 />

Need help in finding ways/alternatives to play videos from base64 in react-native.

This is an Offline e-learning app where videos are stored on SD-CARD and data is decrypted on the fly and played in a video player.

like image 736
Ron Astle Lobo Avatar asked Jan 24 '19 14:01

Ron Astle Lobo


1 Answers

It's possible to solve it by making your WebView embedded javascript code to read the file content from the device storage, in order to avoid having this large base64 literal inline.

As described here, the recommended workaround is to use a blob URL.

Basically you have to:

  • 1: Import RNFetchBlob and simpleCrypto inside the WebView. (see here)
  • 2: Fetch the file and decrypt it,
  • 3: Create the blob url and set it to the <video> src attribute.

Your code is going to look something like this:

componentDidMount() {
  this.setState({htmlCode: `
    <html>
      <body>
        <video id="myvideo" style="width: 50%; height: 50%; margin-top: 10%; margin-left: 22%;" controls></video>

        <script src="${RNFetchBlob.fs.dirs.MainBundleDir}/bundle.js"></script>
        <script src="${path/to/simpleCrypto.js}"></script>

        <script type="text/javascript">
          function b64toBlob(b64Data, contentType, sliceSize) {
             //... returns a new Blob using the b64Data.
          }

          //for simplicity, onDecodeEnd abstracts your decryption code
        RNFetchBlob.fs.readStream('encryptfile1.dat').onDecodeEnd((decryptText) => {
          var blob = b64toBlob(base64Video, "video/mp4");
          var url = URL.createObjectURL(blob);
          document.getElementById('myvideo').src = url;
        })
      </script>
    </body>
  </html>
`})

render() {
  return (
    <WebView 
      style={{ flex: 1 }} 
      source={{ html: this.state.htmlCode, baseUrl: RNFetchBlob.fs.dirs.DCIMDir }}> 
   </WebView>
  );
}

Disclaimer

  • 1: I was able to reproduce your limitation problem using a Webview that embedded a large base64 inline literal (2.5mb). It worked for a 850kb base64 video.
  • 2: The Blob URL approach did solve this limitation on a web view, by fetching a remote json file containing the larger base64 video.
  • 3: I didn't test the import RNFetchBlob into WebView approach. Again, as described here it seems to work.

Additional Code

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

PS

I myself wouldn't go for this base64 approach. I would rather have some native code, triggered from React Native, that would write and read encrypted videos. The read would generate a temp.mp4 to be loaded from the react-native-video module. Under the hoods, encryption is done at the bytes level. So we don't need to parse it back and forth to base64. We're dealing with binary files after all.

like image 72
diogenesgg Avatar answered Oct 17 '22 07:10

diogenesgg