How to play an encrypted Video or Audio file locally or from a remote server on Android (Part 2)

Mohsen Mousavi
3 min readOct 27, 2023

--

Introduction

A Media Player will never be astonishing if it doesn’t provide you with online media streaming. So pursuing Part 1 of this article, where we discussed how to play an encrypted local file using a custom ExoPlayer’s DataSource, in this part of my article, I am going to elaborate on how to play a Video or an Audio file from a remote server on the fly.

Since there are some identical parts to Part 1, I’m not going to repeat them here for the sake of conciseness. So I strongly recommend taking a look at Part 1, before continuing with this one if you haven’t.

The source code of this article is also available on my GitHub page.

Stram an encrypted remote Media File

As I mentioned in the first part, for playing an encrypted media file, we have to create a custom ExoPlayer DataSource, which its open() and read() are implemented according to our needs. Having only an HTTP URL, here is how to play an encrypted file on the fly.

First, we need to make an HttpConnectionMaker class that helps ExoPlayer to make HTTP requests, when it tends to get a specific chunk of streaming file. It also should take care of different connection errors, protocol redirections, GZip compression, and so forth as well as closing the connection in the case of errors. Here is the implementation:

For the sake of conciseness, I moved the full implementation of the above class here.

Do not get distracted by the boilerplate since it just carries out the task of making an HTTP connection and handling different probable scenarios using make(dataSpec: DataSpec) which will be called by DataSourcs's open() function to request more data to stream. However, an important method to note is buildRangeRequestHeader(position: Long, length: Long): String? returning the header value that defines the range of chunks that might look like "bytes=0-1000" specifying the start and the end of the chunk. This is a popular convention among servers to serve the chunk request for a streaming file.

To ensure a request with an appropriate range, the length parameter should have a value different from C.LENGTH_UNSET otherwise the request range would start from positoin till the end of the streaming file, putting a significant load on the server and may consume a huge amount of Internet data. This situation won’t happen unless you set a cache factory which I will explain in another article.

Now it’s time to create HttpCipherEncryptedDataSource and implement its methods:

and also the implementation of open() which is almost the same as the open function of FileCipherEncrypedDataSource in Part 1.

Note the modifyBytesBlocks(dataSpec: DataSpec): DataSpec function at the beginning, this method modifies the starting position of each chunk since it has to start from the beginning of a Cipher’s blockSize and store it inside a new DataSpec. In order to acquire this necessity, we implement the aforementioned function as follows:

setupCipherInputStream() will be the same as implemented in Part 1 except instead of encryptedFileInputStream pass encryptedStream :

Our next implementation should be read() :

And finally close() that is:

Other functions getUri() and addTransferListener() are implemented the same as Part 1 as well as injecting our new DataSource HttpCipherEncryptedDataSource inside an ExoPlayer DataSourceFactroy.

Conclusion

In this two-part series, we’ve thoroughly explored the intricacies of playing encrypted remote or local media files on Android. From custom data sources to encrypted content streaming, we’ve covered it all.

Our journey doesn’t end here. I encourage you to explore the provided source code on my GitHub page, experiment with your projects, and seek clarification in discussions.

Encrypted media playback is a valuable skill in the Android development landscape. Look forward to witnessing the innovative solutions you’ll create. Happy coding!

--

--