Cumulations Logo

 

The team at Cumulations Technologies got an opportunity to work on video streaming app where we had to implement offline streaming in the app.
Like other video streaming apps we followed 2 basic steps:

  • Download and encrypt
  • Decrypt and play

Let’s see how we can achieve above 2 steps in detail.

1 . Download and encrypt

     We need to encrypt the downloaded file for security purpose so that other apps won’t be able to read and decode the video. There are other monetization reasons as well which we don’t have to worry about here. Now back to the topic, open the input stream and read 1024 bytes (this number is up to you), before writing to file’s output stream encrypt the chunk. We used AES algorithm to encrypt the chunk. Keep reading data in chunks from the input stream until it exhausts and encrypts every chunk before writing it into the file. After all the chunks are read, encrypted and written to the file, flush and close both input and output streams to release memory.
Below is the code snippet for download and encrypt step:

                           

     URL url = new URL("path/to/video_file");
     URLConnection connection = url.openConnection();
     connection.connect();
     InputStream input = connection.getInputStream();

     // file path to external storage where encrypted video content will be stored
     FileOutputStream output = new FileOutputStream(filepath);

     	while ((count = input.read(data, 0, readbyte)) != -1) {
           if (count < readbyte) {
               if ((lenghtOfFile - total) > readbyte) {
                        while (true) {
                            int seccount = input.read(data, pos, (readbyte - pos));
                            pos = pos + seccount;
                            if (pos == readbyte) {
                                break;
                            }
                        }
               }
           }

                // encrypt data read before writing to output stream
                encryptedData = SimpleCrypto.encrypt(key, iv, data);
                output.write(encryptedData);
        }


       // function to encrypt and decrypt data bytes
	public class SimpleCrypto {
		public static byte[] encrypt(byte[] key, byte iv[], byte[] clear)
        	   throws Exception {
       			SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
       			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

       			IvParameterSpec ivspec = new IvParameterSpec(iv);

       			cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
      			byte[] encrypted = cipher.doFinal(clear);
       			return encrypted;
		}

		public static byte[] decrypt(byte[] key, byte[] iv, byte[] encrypted)
		    throws Exception {
			SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
			Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
	
			IvParameterSpec ivspec = new IvParameterSpec(iv);
			cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec);
	
			byte[] decrypted = cipher.doFinal(encrypted);
			return decrypted;
		}
	}

Before jumping to next step let’s take a step back and try to understand why we had to encrypt every chunk and not the whole file. This is more of a memory constraint issue, generally, video files consume memory in terms of 100 MBs. If we had to encrypt the whole file we have to load complete file into RAM which would have caused OutOfMemoryException leading to crash, same would be the case with decrypt and play where we have to load a huge file into the RAM.

2 . Decrypt and play

     In the previous step, we saw how to download and encrypt the video file. Here we will see how we can decrypt and play the downloaded file. Similar to the previous step we have to load chunks of data to RAM, decrypt and then send data to video player (VideoView/SurfaceView/TextureView) for rendering. To achieve this we will follow client/server model where a local server will decrypt the data chunks and video player will act as a local client (both are called local since they are running on the same machine).

  • Server implementation - Let’s see how a server can be implemented to decrypt the downloaded file chunk by chunk and pass it to the client (video player in our case) for rendering.

            1. Create ServerSocket and listen for a request from the server.
            2. After receiving the connection from a client open the downloaded file.
            3. Start decrypting content inside file chunk by chunk and send it over OutputStream.

	ServerSocket serverSocket = new ServerSocket();
	serverSocket.bind(new InetSocketAddress("0.0.0.0", 9990));

	// this is a blocking call, control will be blocked until client sends the request
	Socket finalAccept = serverSocket.accept();
	OutputStream outputStream = finalAccept.getOutputStream();

	// open downloaded file
	FileInputStream inputStream = new FileInputStream("path/to/video_file");
	while ((count = inputStream.read(data, 0, readbyte)) != -1) {
 	   if (count < readbyte) {
   	     if ((lenghtOfFile - total) > readbyte) {
     	       while (true) {
          	      int seccount = inputStream.read(data, pos, (readbyte - pos));
            	      pos = pos + seccount;
             	      if (pos == readbyte) {
                         break;
                      }
            	}
              }
    	    }

   	 // encrypt data read before writing to output stream
   	 decryptedData = SimpleCrypto.decrypt(key, iv, readbyte);
   	 outputStream.write(decryptedData);
	}

 

  • Client implementation - This is not as difficult as server side mainly because video player does the job. The only thing we need to do is set the video URL as the data source to the video player as below.

                     VideoView.setVideoPath("http://localhost:9990/");
                                         OR
                     VideoView.setVideoPath("http://0.0.0.0:9990/");

After calling above API request goes to a server (local) since it is listening at the specified IP and port. Once the server receives the request it decrypts the video file chunk by chunk and passes the stream to VideoView (client) which renders on the screen.