3

I am creating an Android app that streams videos using the Http Live Streaming protocol from a web application. Currently I am streaming the video playlist and segments, which were created by Amazon Elastic Transcoder, from an Amazon S3 bucket. This works perfectly using a simple VideoView and setting the video path to the URL of the .m3u8 playlist on S3.

I have requirements to use Amazon CloudFront for delivery and restrict all public access to the S3 buckets where the playlist and segments are stored. Based on my research, the only way to do this is to dynamically generate the HLS playlist to contain properly signed URLs for the segments.

Rather than generate the playlist and store it serverside, which would require periodic clean up of resources, my current attempt at a solution is the following. The Android app will retrieve the stream information and the media playlists so they can be recreated locally.

For example, we're streaming a video that has two streams: low quality and high quality. The information about the stream (bandwidth, codecs, etc) will be returned with the complete HLS playlist for each of these streams. The key thing here is the media playlists will have properly signed URLs for the video segments. The Android app will write these media playlists out to local temp files and generate the variant playlist. So the local filesystem would have the following files:

Variant playlist:

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
hi.m3u8

low.m3u8 file:

#EXTM3U
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:8,
https://priv.example.com/fileSequence2680-low.ts
#EXTINF:8,
https://priv.example.com/fileSequence2681-low.ts
#EXTINF:8,
https://priv.example.com/fileSequence2682-low.ts

hi.m3u8 file:

#EXTM3U
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:8,
https://priv.example.com/fileSequence2680-hi.ts
#EXTINF:8,
https://priv.example.com/fileSequence2681-hi.ts
#EXTINF:8,
https://priv.example.com/fileSequence2682-hi.ts

I have proven this will work with a video player (VLC) that supports HLS.

I'm generating a proper variant playlist and attempting to set that as the path for the VideoView. I have tried using both setVideoPath and setVideoURI but to no avail. The only interesting difference in logcat between using the dynamically generated playlist and streaming directly from S3 is the MediaPlayer instance that is created. When streaming from a local file (file://), AwesomePlayer is instantiated. When streaming from the internet over HTTPS (https://), NuPlayer is instantiated. The error I get with the dynamically generated playlist is:

ERROR/AwesomePlayer(1837): setDataSource_l() extractor is NULL, return UNKNOWN_ERROR

After spending a few hours digging through the Android source code, I found a property named media.stagefright.use-nuplayer that will force NuPlayer to always be used if set. However, there doesn't seem to be a clean way to set this native system property from Java.

Is there some way at the application layer I can force NuPlayer to be used or another way to achieve what I want?

I am testing this on a Samsung Galaxy S2 running Android 4.1.2.

Code:

public class VideoPlayerActivity extends Activity
{
    private static final String TAG = VideoPlayerActivity.class.getCanonicalName();

    // URL intentionally changed to something generic to hide internal resources
    private static final String BASE_URL = "https://s3.amazonaws.com/bucket/";

    private static final String VARIANT_PLAYLIST_PATH = BASE_URL + "variant.m3u8";
    private static final String MEDIA_PLAYLIST_PATH = BASE_URL + "media1.m3u8";

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_player);

        File variantPlaylist = createVariantPlaylist();

        final VideoView videoView = (VideoView) findViewById(R.id.videoView);
        videoView.setVideoPath(variantPlaylist.getAbsolutePath());

        MediaController mediaController = new MediaController(this);
        mediaController.setAnchorView(videoView);

        videoView.setMediaController(mediaController);

        videoView.setOnPreparedListener( new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                Log.i(TAG, "Duration = " + videoView.getDuration());
            }
        });

        videoView.start();
    }

    private File createVariantPlaylist() {
        File variantPlaylist = null;

        try {
            File directory = this.getFilesDir();
            variantPlaylist = File.createTempFile("variant", ".m3u8", directory);

            FileWriter fileWriter = new FileWriter(variantPlaylist.getAbsoluteFile());
            BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

            bufferedWriter.write("#EXTM3U");
            bufferedWriter.write(LINE_SEPARATOR);
            bufferedWriter.write("#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=400x170,CODECS=\"avc1.42001e,mp4a.40.2\",BANDWIDTH=474000");
            bufferedWriter.write(LINE_SEPARATOR);
            bufferedWriter.write(MEDIA_PLAYLIST_PATH);

            bufferedWriter.close();
        }
        catch (IOException ioe) {
            Log.e(TAG, "Error occurred creating variant playlist");
            Log.e(TAG, ioe.getMessage());
        }

        return variantPlaylist;
    }
}
John Rotenstein
  • 241,921
  • 22
  • 380
  • 470
Bobby Vandiver
  • 306
  • 3
  • 11
  • I think you're misunderstanding how to use S3/CloundFront. It's Cloudfront that will have API-controlled access to S3 -- the video player will only get things from CloudFront. – vipw Dec 29 '13 at 17:37
  • I understand that. The video player will have to access the playlist and segments using signed URLs to CloudFront and not directly access S3. The code above is an attempt to dynamically generate a playlist in a similar scenario as what I will need for interacting with CloudFront. I've just tried to eliminate complexity and focus on the real issue I'm trying to solve, which is why I'm going directly to S3. – Bobby Vandiver Dec 29 '13 at 18:44
  • Why would you want signed URLs to CloudFront? If you need to limit access to the content, use an encryption key (#EXT-X-KEY), and do the access control at that point. – vipw Jan 03 '14 at 13:21
  • You're right. That is a better solution. Thanks! :) – Bobby Vandiver Jan 04 '14 at 05:48

0 Answers0