DEVELOPERS BLOG

Secure Multimedia Playback on Android with BlackBerry Dynamics

BlackBerry Dynamics allows you to secure data in transit using its secure connectivity.  It also provides solutions to secure your data secure while at rest using its secure file system and secure database.  But what about keeping your data secure while it’s in use?

In many scenarios, securing data in use isn’t much of a concern because that data is only briefly stored in RAM only available to your application. But what about large pieces of data, such as audio or video files that are read from flash memory?  If you were to attempt to use a URI in the Android MediaPlayer that points to a video file stored in the BlackBerry Dynamics file system, it will fail to play because the Android MediaPlayer is unable to understand the encrypted file.  Some developers have attempted to work around this issue by copying a media file outside of the BlackBerry Dynamics secure file system for playback, and then delete it when playback is complete.   While this does allow for playback of the file, it does not keep your media files secure.  While that temporary but unencrypted file exists on the regular file system, other applications could gain access to it.

The way to solve this problem, and keep your media files secure is to implement a solution that allows playback directly from the BlackBerry Dynamics secure file system. I’m going to show you a couple of ways this can be done.  One solution will involve creating a custom MediaDataSource that can read from the BlackBerry Dynamics secure file system and provide the Android MediaPlayer with the data it needs for playback.  The second will take a similar approach, but make use of the popular open source media player called ExoPlayer.

Choosing Between Android Media Player and ExoPlayer

If you are creating a new application targeting current versions of Android, the solution that uses MediaPlayer  and MediaDataSource is likely the approach to take.  MediaDataSource was added in API level 23, so it is a relatively new API.  If you need to support older versions of Android, refer to the solution below that uses ExoPlayer, which supports API level 16.  There are some additional elements to consider, which are well described in the Pros and Cons section of the ExoPlayer documentation.

Using Android Media Player

A complete working sample project that uses this solution is available on the BlackBerry Github site here: BDVideoPlayBack

The first step is to extend MediaDataSource and ensure we are using the BlackBerry Dynamics file system APIs.

1
2
3
4
5
6
7
8
9
import com.good.gd.file.File;
import com.good.gd.file.RandomAccessFile;
 
 
public class BDMediaDataSource extends MediaDataSource
{
    private RandomAccessFile randFile;
    private long size;

Next we create a constructor that accepts a String that contains the path and filename to the encrypted file. In this method, we open the file using the BlackBerry Dynamics file APIs.

1
2
3
4
5
6
public BDMediaDataSource(String fileName) throws IOException
{
    File file = new File(fileName);
    size = file.length();
    randFile = new RandomAccessFile(file, "r");
}

Next there are three methods of MediaDataSource that need to be overridden. The readAt method is used by the MediaPlayer to request a specific portion of the file.  Again, we use the BlackBerry Dynamics file APIs to read and return the requested portion of the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public int readAt(long position, byte[] buffer, int offset, int length) throws IOException
{
 
    if (randFile != null)
    {
        randFile.seek(position);
 
        return randFile.read(buffer, offset, length);
    }
    else
    {
        return -1;
    }
}

The next two methods are getSize and close. getSize returns the size of the file.  Note that this is the size of the unencrypted file, which is what is returned by the BlackBerry Dynamics file APIs.  The final method is close, which is called when the file should be closed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public long getSize() throws IOException
{
    return size;
}
 
@Override
public void close() throws IOException
{
    if (randFile != null)
    {
        randFile.close();
        randFile = null;
    }
}

BDMediaDataSource is now ready to be used with MediaPlayer. The following code snippet demonstrates how that can be done.

1
2
3
4
5
6
7
8
9
10
MediaPlayer mp = new MediaPlayer();
BDMediaDataSource source = new BDMediaDataSource("file.mp4");
mp.setDataSource(source);
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.start();
    }
});
mp.prepareAsync();

Using ExoPlayer

A complete working sample project that uses this solution is available on the BlackBerry Github site here: ExoPlayerVideoPlayBack

The solution that uses ExoPlayer has a very similar design to what was done using Android Player above. For ExoPlayer, we extend DataSource, which is used to provide data that will be played back and use the BlackBerry Dynamics file system APIs to read the securely stored media files.  An empty constructor is also defined.

1
2
3
4
5
6
7
8
9
10
11
12
import com.good.gd.file.GDFileSystem;
import com.good.gd.file.FileInputStream;
 
public final class BDDataSource implements DataSource
{
 
    private FileInputStream inputStream;
    private boolean fileOpen;
    private Uri filePath;
    private long bytesRemaining = 0;
 
public BDDataSource() {}

Next we override the open method, which passes in the file to be opened. In this method, we initialize FileInputStream that will be used to read the data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public long open(DataSpec dataSpec) throws IOException
{
    filePath = dataSpec.uri;
    //Trim "file:///" that's prefixed to the start of the URI.
    inputStream = GDFileSystem.openFileInput(filePath.toString().substring(8));
 
    if ( inputStream != null )
    {
        fileOpen = true;
        bytesRemaining =  inputStream.available();
    }
 
    return bytesRemaining;
}

The next method to override is the read method, where we use the FileInputStream initialized in the open method to read the file at the desired position.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
    if (bytesRemaining == 0)
    {
        return -1;
    }
    else
    {
        int bytesRead = 0;
        bytesRead = inputStream.read(buffer, offset, readLength);
 
        if (bytesRead > 0) {
            bytesRemaining -= bytesRead;
        }
 
        return bytesRead;
    }
}

The getUri method is also overridden and returns the path to the file in the BlackBerry Dynamics file system.

1
2
3
4
@Override
public Uri getUri() {
    return filePath;
}

The final method overridden is the close method, that closes the FileInputStream and underlying file when playback ends.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void close() throws IOException {
    filePath = null;
    if (inputStream != null)
    {
        try
        {
            inputStream.close();
        }
        catch (IOException e)
        {
            throw new IOException(e);
        }
        finally
        {
            inputStream = null;
            if (fileOpen)
            {
                fileOpen = false;
            }
        }
    }
}

The code sample below shows how BDDataSource could be used as a DataSource by ExoPlayer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SimpleExoPlayer player;
 
// 1. Create a default TrackSelector
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
        new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
        new DefaultTrackSelector(videoTrackSelectionFactory);
 
// 2. Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();
 
// 3. Create the player
player = ExoPlayerFactory.newSimpleInstance(v.getContext(), trackSelector, loadControl);
 
// Bind the player to the view.
simpleExoPlayerView.setPlayer(player);
 
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new BDDataSourceFactory();
 
// Produces Extractor instances for parsing the media data.
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
 
// This is the MediaSource representing the media to be played.
Uri uri = Uri.fromFile(new File("file.mp4"));
 
MediaSource videoSource = new ExtractorMediaSource(uri,
        dataSourceFactory, extractorsFactory, null, null);
 
// Prepare the player with the source.
player.prepare(videoSource);

Wrapping Up

Both samples provide similar solutions that you can use from Android API level 16 through to current versions of Android. Give them a try and make sure your data is secure when in transit, at rest and in use.

 

Need Development Help? The BlackBerry Developer Community Forum has your Answer…Join the conversation, engage with peers, share ideas, and get your app development questions answered. Developer Community Forum

Mark Sohm

About Mark Sohm

Senior Technical Solutions Manager on the Solution Architects team.

Mark Sohm joined BlackBerry in 2003 and currently works as a Senior Technical Solutions Manager on the Solutions Architects team. Mark Sohm has been helping developers create applications using BlackBerry technologies for over 15 years, starting way back with the very first BlackBerry JDK on BlackBerry OS 3.6 through to BlackBerry 10 and now Android with BlackBerry Dynamics and Android Enterprise.