Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exoplayer adaptive hls streaming

I am looking for good and simple example/explanation how to implement ExoPlayer for HLS Adaptive streaming. I am a newbie and do not have experience and knowledge so I can figure how to do this from code example on git.

There are too many 'moving parts' so beginner can understand and reuse it in own projects.

Can somebody help me to learn and understand how to use/implement ExoPlayer in order to achieve this functionality?

Thanks!

like image 704
svarog Avatar asked Feb 14 '17 14:02

svarog


People also ask

What is ExoPlayer HLS?

ExoPlayer is the best alternative to android's built-in MediaPlayer API which is used to control the playback of audio/video files and streams. It supports various features such as Dynamic Adaptive Streaming over HTTP(DASH), HTTP Live Streaming(HLS), Smooth Streaming, and Common Encryption.

Does YouTube use ExoPlayer?

Screenshot: The YouTube Android app, which uses ExoPlayer as its video player. ExoPlayer is an app-level media player built on top of low-level media APIs in Android. It is an open source project used by Google apps, including YouTube and Google TV.

Does ExoPlayer support M3U8?

mpd or . m3u8 video file from server using FFMPEG. Exoplayer does not support mp4 file format for adaptive streaming but you can choose mp4 like format for auto streaming.

Who uses ExoPlayer?

ExoPlayerView is one of the most used UI components in many apps such as YouTube, Netflix, and many video streaming platforms.


2 Answers

The easiest way to get started using ExoPlayer is to add it as a gradle dependency. You need to make sure you have the jcenter repository included in the build.gradle file in the root of your project:

repositories { jcenter() }

Next, include the following in your module's build.gradle file:

compile 'com.google.android.exoplayer:exoplayer:r2.2.0'

1. Your Layout File

    <?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.exoplayer2.ui.SimpleExoPlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true"
        app:resize_mode="fill"/>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"/>

</FrameLayout>

2. Your Class File(Activity)

    public class VideoPlayerActivity extends AppCompatActivity implements ExoPlayer.EventListener {

    private SimpleExoPlayerView simpleExoPlayerView;
    private String hlsVideoUri = "http://playertest.longtailvideo.com/adaptive/bbbfull/bbbfull.m3u8";
    private SimpleExoPlayer player;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_player);

        // 1. Create a default TrackSelector
        Handler mainHandler = new Handler();
        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(this, trackSelector, loadControl);

        simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
        simpleExoPlayerView.setPlayer(player);

        // Measures bandwidth during playback. Can be null if not required.
        DefaultBandwidthMeter defaultBandwidthMeter = new DefaultBandwidthMeter();
        // Produces DataSource instances through which media data is loaded.
        DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
                Util.getUserAgent(this, "Exo2"), defaultBandwidthMeter);
        // Produces Extractor instances for parsing the media data.
        ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
        // This is the MediaSource representing the media to be played.
        HlsMediaSource hlsMediaSource = new HlsMediaSource(Uri.parse(hlsVideoUri), dataSourceFactory, mainHandler, new AdaptiveMediaSourceEventListener() {
            @Override
            public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) {

            }

            @Override
            public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {

            }

            @Override
            public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {

            }

            @Override
            public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) {

            }

            @Override
            public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {

            }

            @Override
            public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaTimeMs) {

            }
        });

        player.addListener(this);
        player.prepare(hlsMediaSource);
        simpleExoPlayerView.requestFocus();
        player.setPlayWhenReady(true);

        progressBar = (ProgressBar) findViewById(R.id.progressBar);
    }

    @Override
    public void onTimelineChanged(Timeline timeline, Object manifest) {

    }

    @Override
    public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

    }

    @Override
    public void onLoadingChanged(boolean isLoading) {

    }

    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {

        switch (playbackState) {
            case Player.STATE_BUFFERING:
                //You can use progress dialog to show user that video is preparing or buffering so please wait
                progressBar.setVisibility(View.VISIBLE);
                break;
            case Player.STATE_IDLE:
                //idle state
                break;
            case Player.STATE_READY:
                // dismiss your dialog here because our video is ready to play now
                progressBar.setVisibility(View.GONE);
                break;
            case Player.STATE_ENDED:
                // do your processing after ending of video
                break;
        }
    }

    @Override
    public void onPlayerError(ExoPlaybackException error) {

        AlertDialog.Builder adb = new AlertDialog.Builder(VideoPlayerActivity.this);
        adb.setTitle("Could not able to stream video");
        adb.setMessage("It seems that something is going wrong.\nPlease try again.");
        adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                finish(); // take out user from this activity. you can skip this
            }
        });
        AlertDialog ad = adb.create();
        ad.show();
    }

    @Override
    public void onPositionDiscontinuity() {

    }

    @Override
    protected void onPause() {
        super.onPause();
        if (player != null) {
            player.setPlayWhenReady(false); //to pause a video because now our video player is not in focus
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        player.release();
    }
}

I think this is enough for beginner. Also keep in mind that this library's standard audio and video components rely on Android’s MediaCodec API, which was released in Android 4.1 (API level 16). So it will not work on android 4.0 and below.

Don't forget to add this permission to the manifest file :

<uses-permission android:name="android.permission.INTERNET"/>

like image 178
Vicky Avatar answered Sep 19 '22 10:09

Vicky


The answer from @Vicky will work, but has one flaw.

The bandwidth meter that you pass to the track selector must be the same one that the datasource factory uses. The data source factory maintains the bandwidth estimate by calling the BW meter methods, and the adaptive track selection process gets the estimate to decide which track to adapt to.

If they are not the same instance, the adaptive selection always gets -1 as the BW, and picks some middle ground option.

The demo ExoPlayer apps also have this flaw. They pass false to the useBwMeter in buildDataSource(), which means no updating of the BW estimate EDIT: Actually, this BW meter is for the manifest loader. It does not need to use the BW meter.

like image 22
Darren Avatar answered Sep 20 '22 10:09

Darren