Bug 1628792 - p2: update GeckoHls* for API changes in exoplayer. r=agi,geckoview-reviewers
authorJohn Lin <jolin@mozilla.com>
Fri, 05 Jun 2020 02:06:14 +0000
changeset 534056 6531c192fa7bfb8c96927bde8beac13c531b6591
parent 534055 ac59ea91af14128af9aef40ff2c8960361abe8bc
child 534057 89b2ab5018a8c1482e46d9f709da604cf6574370
push id37482
push usernbeleuzu@mozilla.com
push dateFri, 05 Jun 2020 14:35:19 +0000
treeherdermozilla-central@c835da226e6d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersagi, geckoview-reviewers
bugs1628792
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1628792 - p2: update GeckoHls* for API changes in exoplayer. r=agi,geckoview-reviewers Differential Revision: https://phabricator.services.mozilla.com/D78390
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsAudioRenderer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsRendererBase.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsVideoRenderer.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsAudioRenderer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsAudioRenderer.java
@@ -18,16 +18,17 @@ import org.mozilla.thirdparty.com.google
 import org.mozilla.thirdparty.com.google.android.exoplayer2.RendererCapabilities;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.decoder.DecoderInputBuffer;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
 
 import java.nio.ByteBuffer;
+import java.util.List;
 
 public class GeckoHlsAudioRenderer extends GeckoHlsRendererBase {
     public GeckoHlsAudioRenderer(final GeckoHlsPlayer.ComponentEventDispatcher eventDispatcher) {
         super(C.TRACK_TYPE_AUDIO, eventDispatcher);
         assertTrue(Build.VERSION.SDK_INT >= 16);
         LOGTAG = getClass().getSimpleName();
         DEBUG = !BuildConfig.MOZILLA_OFFICIAL;
     }
@@ -50,43 +51,44 @@ public class GeckoHlsAudioRenderer exten
          *                           the format's top-level type, or because it's
          *                           a specialized renderer for a different mime type.
          * ADAPTIVE_NOT_SEAMLESS : The Renderer can adapt between formats,
          *                         but may suffer a brief discontinuity (~50-100ms)
          *                         when adaptation occurs.
          */
         String mimeType = format.sampleMimeType;
         if (!MimeTypes.isAudio(mimeType)) {
-            return RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
+            return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
         }
-        MediaCodecInfo decoderInfo = null;
+        List<MediaCodecInfo> decoderInfos = null;
         try {
             MediaCodecSelector mediaCodecSelector = MediaCodecSelector.DEFAULT;
-            decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false);
+            decoderInfos = mediaCodecSelector.getDecoderInfos(mimeType, false, false);
         } catch (MediaCodecUtil.DecoderQueryException e) {
             Log.e(LOGTAG, e.getMessage());
         }
-        if (decoderInfo == null) {
-            return RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE;
+        if (decoderInfos == null || decoderInfos.isEmpty()) {
+            return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
         }
+        MediaCodecInfo info = decoderInfos.get(0);
         /*
          *  Note : If the code can make it to this place, ExoPlayer assumes
          *         support for unknown sampleRate and channelCount when
          *         SDK version is less than 21, otherwise, further check is needed
          *         if there's no sampleRate/channelCount in format.
          */
         boolean decoderCapable = (Build.VERSION.SDK_INT < 21) ||
                                  ((format.sampleRate == Format.NO_VALUE ||
-                                  decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) &&
+                                 info.isAudioSampleRateSupportedV21(format.sampleRate)) &&
                                  (format.channelCount == Format.NO_VALUE ||
-                                  decoderInfo.isAudioChannelCountSupportedV21(format.channelCount)));
-        int formatSupport = decoderCapable ?
-            RendererCapabilities.FORMAT_HANDLED :
-            RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES;
-        return RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | formatSupport;
+                                 info.isAudioChannelCountSupportedV21(format.channelCount)));
+        return RendererCapabilities.create(
+                decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES,
+                ADAPTIVE_NOT_SEAMLESS,
+                TUNNELING_NOT_SUPPORTED);
     }
 
     @Override
     protected final void createInputBuffer() {
         // We're not able to estimate the size for audio from format. So we rely
         // on the dynamic allocation mechanism provided in DecoderInputBuffer.
         mInputBuffer = null;
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
@@ -9,54 +9,50 @@ import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.util.Log;
 
 import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.DefaultLoadControl;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlaybackException;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlayer;
-import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlayerFactory;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.PlaybackParameters;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.RendererCapabilities;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.Timeline;
-import org.mozilla.thirdparty.com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceEventListener;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroup;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.TrackGroupArray;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.HlsMediaSource;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelection;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.trackselection.TrackSelectionArray;
-import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource;
-import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultAllocator;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.HttpDataSource;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.annotation.ReflectionTarget;
 import org.mozilla.geckoview.BuildConfig;
 
-import java.io.IOException;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @ReflectionTarget
 public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener {
     private static final String LOGTAG = "GeckoHlsPlayer";
-    private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
+    private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter.Builder(null).build();
     private static final int MAX_TIMELINE_ITEM_LINES = 3;
     private static final boolean DEBUG = !BuildConfig.MOZILLA_OFFICIAL;
 
     private static final AtomicInteger sPlayerId = new AtomicInteger(0);
     /*
      *  Because we treat GeckoHlsPlayer as a source data provider.
      *  It will be created and initialized with a URL by HLSResource in
      *  Gecko media pipleine (in cpp). Once HLSDemuxer is created later, we
@@ -73,17 +69,16 @@ public class GeckoHlsPlayer implements B
     private enum MediaDecoderPlayState {
         PLAY_STATE_PREPARING,
         PLAY_STATE_PAUSED,
         PLAY_STATE_PLAYING
     }
     // Default value is PLAY_STATE_PREPARING and it will be set to PLAY_STATE_PLAYING
     // once HTMLMediaElement calls PlayInternal().
     private MediaDecoderPlayState mMediaDecoderPlayState = MediaDecoderPlayState.PLAY_STATE_PREPARING;
-    private DataSource.Factory mMediaDataSourceFactory;
 
     private Handler mMainHandler;
     private HandlerThread mThread;
     private ExoPlayer mPlayer;
     private GeckoHlsRendererBase[] mRenderers;
     private DefaultTrackSelector mTrackSelector;
     private MediaSource mMediaSource;
     private SourceEventListener mSourceEventListener;
@@ -197,87 +192,38 @@ public class GeckoHlsPlayer implements B
         if (mTracksInfo.videoReady() && mTracksInfo.audioReady()) {
             if (mDemuxerCallbacks != null) {
                 mDemuxerCallbacks.onInitialized(mTracksInfo.hasAudio(), mTracksInfo.hasVideo());
             }
             mIsDemuxerInitDone = true;
         }
     }
 
-    private final class SourceEventListener implements AdaptiveMediaSourceEventListener {
-        public void onLoadStarted(final DataSpec dataSpec,
-                                  final int dataType,
-                                  final int trackType,
-                                  final Format trackFormat,
-                                  final int trackSelectionReason,
-                                  final Object trackSelectionData,
-                                  final long mediaStartTimeMs,
-                                  final long mediaEndTimeMs,
-                                  final long elapsedRealtimeMs) {
+    private final class SourceEventListener implements MediaSourceEventListener {
+        public void onLoadStarted(
+                final int windowIndex,
+                final MediaSource.MediaPeriodId mediaPeriodId,
+                final LoadEventInfo loadEventInfo,
+                final MediaLoadData mediaLoadData) {
+            if (mediaLoadData.dataType != C.DATA_TYPE_MEDIA) {
+                // Don't report non-media URLs.
+                return;
+            }
             if (mMainHandler != null && mResourceCallbacks != null) {
                 mMainHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         if (DEBUG) {
-                            Log.d(LOGTAG, "[SourceEvent] load-started: url=" + dataSpec.uri + " track=" + trackType + " format=" + trackFormat);
+                            Log.d(LOGTAG, "on-load: url=" + loadEventInfo.uri);
                         }
-                        mResourceCallbacks.onLoad(dataSpec.uri.toString());
+                        mResourceCallbacks.onLoad(loadEventInfo.uri.toString());
                     }
                 });
             }
         }
-        // Not interested in the following events.
-        public void onLoadCompleted(final DataSpec dataSpec,
-                                    final int dataType,
-                                    final int trackType,
-                                    final Format trackFormat,
-                                    final int trackSelectionReason,
-                                    final Object trackSelectionData,
-                                    final long mediaStartTimeMs,
-                                    final long mediaEndTimeMs,
-                                    final long elapsedRealtimeMs,
-                                    final long loadDurationMs,
-                                    final long bytesLoaded) {
-        }
-        public void onLoadCanceled(final DataSpec dataSpec,
-                                   final int dataType,
-                                   final int trackType,
-                                   final Format trackFormat,
-                                   final int trackSelectionReason,
-                                   final Object trackSelectionData,
-                                   final long mediaStartTimeMs,
-                                   final long mediaEndTimeMs,
-                                   final long elapsedRealtimeMs,
-                                   final long loadDurationMs,
-                                   final long bytesLoaded) {
-        }
-        public void onLoadError(final DataSpec dataSpec,
-                                final int dataType,
-                                final int trackType,
-                                final Format trackFormat,
-                                final int trackSelectionReason,
-                                final Object trackSelectionData,
-                                final long mediaStartTimeMs,
-                                final long mediaEndTimeMs,
-                                final long elapsedRealtimeMs,
-                                final long loadDurationMs,
-                                final long bytesLoaded,
-                                final IOException error,
-                                final boolean wasCanceled) {
-        }
-        public void onUpstreamDiscarded(final int trackType,
-                                        final long mediaStartTimeMs,
-                                        final long mediaEndTimeMs) {
-        }
-        public void onDownstreamFormatChanged(final int trackType,
-                                              final Format trackFormat,
-                                              final int trackSelectionReason,
-                                              final Object trackSelectionData,
-                                              final long mediaTimeMs) {
-        }
     }
 
     public final class ComponentEventDispatcher {
         // Called on GeckoHlsPlayerThread from GeckoHls{Audio,Video}Renderer/ExoPlayer
         public void onDataArrived(final int trackType) {
             assertTrue(mMainHandler != null);
             assertTrue(mComponentListener != null);
 
@@ -367,20 +313,20 @@ public class GeckoHlsPlayer implements B
                     return;
                 }
                 mTracksInfo.onAudioInfoUpdated();
                 checkInitDone();
             }
         }
     }
 
-    private DataSource.Factory buildDataSourceFactory(final Context ctx,
+    private HlsMediaSource.Factory buildDataSourceFactory(final Context ctx,
                                                       final DefaultBandwidthMeter bandwidthMeter) {
-        return new DefaultDataSourceFactory(ctx, bandwidthMeter,
-                buildHttpDataSourceFactory(bandwidthMeter));
+        return new HlsMediaSource.Factory(new DefaultDataSourceFactory(ctx, bandwidthMeter,
+                buildHttpDataSourceFactory(bandwidthMeter)));
     }
 
     private HttpDataSource.Factory buildHttpDataSourceFactory(
             final DefaultBandwidthMeter bandwidthMeter) {
         return new DefaultHttpDataSourceFactory(
             BuildConfig.USER_AGENT_GECKOVIEW_MOBILE,
             bandwidthMeter /* listener */,
             DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
@@ -454,19 +400,19 @@ public class GeckoHlsPlayer implements B
             !mExoplayerSuspended &&
             mMediaDecoderPlayState == MediaDecoderPlayState.PLAY_STATE_PLAYING) {
             resumeExoplayer();
         }
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
-    public void onPositionDiscontinuity() {
+    public void onPositionDiscontinuity(final int reason) {
         if (DEBUG) {
-            Log.d(LOGTAG, "positionDiscontinuity");
+            Log.d(LOGTAG, "positionDiscontinuity: reason=" + reason);
         }
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
     public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
         if (DEBUG) {
             Log.d(LOGTAG, "playbackParameters " +
@@ -567,17 +513,17 @@ public class GeckoHlsPlayer implements B
             }
         }
         mTracksInfo.updateNumOfVideoTracks(numVideoTracks);
         mTracksInfo.updateNumOfAudioTracks(numAudioTracks);
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
-    public synchronized void onTimelineChanged(final Timeline timeline, final Object manifest) {
+    public synchronized void onTimelineChanged(final Timeline timeline, final int reason) {
         // For now, we use the interface ExoPlayer.getDuration() for gecko,
         // so here we create local variable 'window' & 'peroid' to obtain
         // the dynamic duration.
         // See. http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Timeline.html
         // for further information.
         Timeline.Window window = new Timeline.Window();
         mIsTimelineStatic = !timeline.isEmpty()
                 && !timeline.getWindow(timeline.getWindowCount() - 1, window).isDynamic;
@@ -680,31 +626,34 @@ public class GeckoHlsPlayer implements B
 
         // Prepare customized renderer
         mRenderers = new GeckoHlsRendererBase[2];
         mVRenderer = new GeckoHlsVideoRenderer(mComponentEventDispatcher);
         mARenderer = new GeckoHlsAudioRenderer(mComponentEventDispatcher);
         mRenderers[0] = mVRenderer;
         mRenderers[1] = mARenderer;
 
-        DefaultLoadControl dlc =
-            new DefaultLoadControl(
-                new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
-                DEFAULT_MIN_BUFFER_MS,
-                DEFAULT_MAX_BUFFER_MS,
-                DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
-                DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
+        DefaultLoadControl dlc = new DefaultLoadControl.Builder()
+                .setAllocator(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE))
+                .setBufferDurationsMs(DEFAULT_MIN_BUFFER_MS,
+                        DEFAULT_MAX_BUFFER_MS,
+                        DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
+                        DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
+                .createDefaultLoadControl();
         // Create ExoPlayer instance with specific components.
-        mPlayer = ExoPlayerFactory.newInstance(mRenderers, mTrackSelector, dlc);
+        mPlayer = new ExoPlayer.Builder(ctx, mRenderers)
+                .setTrackSelector(mTrackSelector)
+                .setLoadControl(dlc)
+                .build();
         mPlayer.addListener(this);
 
         Uri uri = Uri.parse(url);
-        mMediaDataSourceFactory = buildDataSourceFactory(ctx, BANDWIDTH_METER);
+        mMediaSource = buildDataSourceFactory(ctx, BANDWIDTH_METER).createMediaSource(uri);
         mSourceEventListener = new SourceEventListener();
-        mMediaSource = new HlsMediaSource(uri, mMediaDataSourceFactory, mMainHandler, mSourceEventListener);
+        mMediaSource.addEventListener(mMainHandler, mSourceEventListener);
         if (DEBUG) {
             Log.d(LOGTAG, "Uri is " + uri +
                           ", ContentType is " + Util.inferContentType(uri.getLastPathSegment()));
         }
         mPlayer.setPlayWhenReady(false);
         mPlayer.prepare(mMediaSource);
         mIsPlayerInitDone = true;
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsRendererBase.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsRendererBase.java
@@ -9,16 +9,17 @@ import android.util.Log;
 import org.mozilla.geckoview.BuildConfig;
 
 import org.mozilla.thirdparty.com.google.android.exoplayer2.BaseRenderer;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.decoder.DecoderInputBuffer;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlaybackException;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.FormatHolder;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.RendererCapabilities;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.Iterator;
 
 public abstract class GeckoHlsRendererBase extends BaseRenderer {
     protected static final int QUEUED_INPUT_SAMPLE_DURATION_THRESHOLD = 1000000; //1sec
@@ -186,17 +187,20 @@ public abstract class GeckoHlsRendererBa
         }
         if (DEBUG) {
             Log.d(LOGTAG, "Initializing ... ");
         }
         try {
             createInputBuffer();
             mInitialized = true;
         } catch (OutOfMemoryError e) {
-            throw ExoPlaybackException.createForRenderer(new RuntimeException(e), getIndex());
+            throw ExoPlaybackException.createForRenderer(new RuntimeException(e),
+                                                         getIndex(),
+                                                         mFormats.isEmpty() ? null : getFormat(mFormats.size() - 1),
+                                                         RendererCapabilities.FORMAT_HANDLED);
         }
     }
 
     /*
      * The place we get demuxed data from HlsMediaSource(ExoPlayer).
      * The data will then be converted to GeckoHLSSample and deliver to
      * GeckoHlsDemuxerWrapper for further use.
      * If the return value is ture, that means a GeckoHLSSample is queued
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsVideoRenderer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsVideoRenderer.java
@@ -18,16 +18,17 @@ import org.mozilla.thirdparty.com.google
 import org.mozilla.thirdparty.com.google.android.exoplayer2.decoder.DecoderInputBuffer;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.RendererCapabilities;
 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
 
 import java.nio.ByteBuffer;
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class GeckoHlsVideoRenderer extends GeckoHlsRendererBase {
     /*
      * By configuring these states, initialization data is provided for
      * ExoPlayer's HlsMediaSource to parse HLS bitstream and then provide samples
      * starting with an Access Unit Delimiter including SPS/PPS for TS,
      * and provide samples starting with an AUD without SPS/PPS for FMP4.
@@ -84,59 +85,62 @@ public class GeckoHlsVideoRenderer exten
          *                           a specialized renderer for a different mime type.
          * ADAPTIVE_NOT_SEAMLESS : The Renderer can adapt between formats,
          *                         but may suffer a brief discontinuity (~50-100ms)
          *                         when adaptation occurs.
          * ADAPTIVE_SEAMLESS : The Renderer can seamlessly adapt between formats.
          */
         final String mimeType = format.sampleMimeType;
         if (!MimeTypes.isVideo(mimeType)) {
-            return RendererCapabilities.FORMAT_UNSUPPORTED_TYPE;
+            return RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE);
         }
 
-        MediaCodecInfo decoderInfo = null;
+        List<MediaCodecInfo> decoderInfos = null;
         try {
             MediaCodecSelector mediaCodecSelector = MediaCodecSelector.DEFAULT;
-            decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false);
+            decoderInfos = mediaCodecSelector.getDecoderInfos(mimeType, false, false);
         } catch (MediaCodecUtil.DecoderQueryException e) {
             Log.e(LOGTAG, e.getMessage());
         }
-        if (decoderInfo == null) {
-            return RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE;
+        if (decoderInfos == null || decoderInfos.isEmpty()) {
+            return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
         }
 
-        boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs);
+        boolean decoderCapable = false;
+        MediaCodecInfo info = null;
+        for (MediaCodecInfo i : decoderInfos) {
+            if (i.isCodecSupported(format)) {
+                decoderCapable = true;
+                info = i;
+            }
+        }
         if (decoderCapable && format.width > 0 && format.height > 0) {
             if (Build.VERSION.SDK_INT < 21) {
                 try {
                     decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize();
                 } catch (MediaCodecUtil.DecoderQueryException e) {
                     Log.e(LOGTAG, e.getMessage());
                 }
                 if (!decoderCapable) {
                     if (DEBUG) {
                         Log.d(LOGTAG, "Check [legacyFrameSize, " +
                                       format.width + "x" + format.height + "]");
                     }
                 }
             } else {
-                decoderCapable =
-                    decoderInfo.isVideoSizeAndRateSupportedV21(format.width,
-                                                               format.height,
-                                                               format.frameRate);
+                decoderCapable = info.isVideoSizeAndRateSupportedV21(format.width,
+                                                                     format.height,
+                                                                     format.frameRate);
             }
         }
 
-        int adaptiveSupport = decoderInfo.adaptive ?
-            RendererCapabilities.ADAPTIVE_SEAMLESS :
-            RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
-        int formatSupport = decoderCapable ?
-            RendererCapabilities.FORMAT_HANDLED :
-            RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES;
-        return adaptiveSupport | formatSupport;
+        return RendererCapabilities.create(
+                decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES,
+                info != null && info.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS,
+                TUNNELING_NOT_SUPPORTED);
     }
 
     @Override
     protected final void createInputBuffer() throws ExoPlaybackException {
         assertTrue(mFormats.size() > 0);
         // Calculate maximum size which might be used for target format.
         Format currentFormat = mFormats.get(mFormats.size() - 1);
         mCodecMaxValues = getCodecMaxValues(currentFormat, mStreamFormats);
@@ -144,17 +148,20 @@ public class GeckoHlsVideoRenderer exten
         // Note : Though we are able to dynamically enlarge buffer size by
         // creating DecoderInputBuffer with specific BufferReplacementMode, we
         // still allocate a calculated max size buffer for it at first to reduce
         // runtime overhead.
         try {
             mInputBuffer = ByteBuffer.wrap(new byte[mCodecMaxValues.inputSize]);
         } catch (OutOfMemoryError e) {
             Log.e(LOGTAG, "cannot allocate input buffer of size " + mCodecMaxValues.inputSize, e);
-            throw ExoPlaybackException.createForRenderer(new Exception(e), getIndex());
+            throw ExoPlaybackException.createForRenderer(new Exception(e),
+                    getIndex(),
+                    mFormats.isEmpty() ? null : getFormat(mFormats.size() - 1),
+                    RendererCapabilities.FORMAT_HANDLED);
         }
     }
 
     @Override
     protected void resetRenderer() {
         if (DEBUG) {
             Log.d(LOGTAG, "[resetRenderer] mInitialized = " + mInitialized);
         }
@@ -424,17 +431,17 @@ public class GeckoHlsVideoRenderer exten
                 nextKeyFrameTime = sample.info.presentationTimeUs;
                 break;
             }
         }
         return nextKeyFrameTime;
     }
 
     @Override
-    protected void onStreamChanged(final Format[] formats) {
+    protected void onStreamChanged(final Format[] formats, final long offsetUs) {
         mStreamFormats = formats;
     }
 
     private static CodecMaxValues getCodecMaxValues(final Format format,
                                                     final Format[] streamFormats) {
         int maxWidth = format.width;
         int maxHeight = format.height;
         int maxInputSize = getMaxInputSize(format);