Bug 1368954 - [Part1] Use reflection to avoid build bustage when source code is only included in Nightly. draft
authorKilik Kuo <kikuo@mozilla.com>
Thu, 15 Jun 2017 11:16:21 +0800
changeset 594521 6ad82c7ad32e1f6cb58e1b13bc89b4dac584f98f
parent 593717 b266a8d8fd595b84a7d6218d7b8c6b7af0b5027c
child 594522 33fc845e58440d47836d2e00a0d39cb22bbf3ad1
push id64041
push userbmo:kikuo@mozilla.com
push dateThu, 15 Jun 2017 03:25:14 +0000
bugs1368954
milestone56.0a1
Bug 1368954 - [Part1] Use reflection to avoid build bustage when source code is only included in Nightly. 1) Provide a BaseHlsPlayer as the interface used in related java wrappers. 2) Create and get the player instance from factory via reflection to decouple the source code dependency. MozReview-Commit-ID: 5wsHSOjSeXV
mobile/android/base/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/BaseHlsPlayer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSDemuxerWrapper.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoPlayerFactory.java
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -410,22 +410,29 @@ gvjar.sources += [geckoview_source_dir +
     'gfx/SurfaceAllocatorService.java',
     'gfx/SurfaceTextureListener.java',
     'gfx/ViewTransform.java',
     'gfx/VsyncSource.java',
     'InputConnectionListener.java',
     'InputMethods.java',
     'media/AsyncCodec.java',
     'media/AsyncCodecFactory.java',
+    'media/BaseHlsPlayer.java',
     'media/Codec.java',
     'media/CodecProxy.java',
     'media/FormatParam.java',
+    'media/GeckoAudioInfo.java',
+    'media/GeckoHLSDemuxerWrapper.java',
+    'media/GeckoHLSResourceWrapper.java',
+    'media/GeckoHLSSample.java',
     'media/GeckoMediaDrm.java',
     'media/GeckoMediaDrmBridgeV21.java',
     'media/GeckoMediaDrmBridgeV23.java',
+    'media/GeckoPlayerFactory.java',
+    'media/GeckoVideoInfo.java',
     'media/JellyBeanAsyncCodec.java',
     'media/LollipopAsyncCodec.java',
     'media/MediaDrmProxy.java',
     'media/MediaManager.java',
     'media/RemoteManager.java',
     'media/RemoteMediaDrmBridge.java',
     'media/RemoteMediaDrmBridgeStub.java',
     'media/Sample.java',
@@ -454,25 +461,20 @@ gvjar.sources += [geckoview_source_dir +
 gvjar.sources += [geckoview_thirdparty_source_dir + f for f in [
     'java/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java',
     'java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java',
     'java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java',
 ]]
 
 if CONFIG['MOZ_ANDROID_HLS_SUPPORT']:
     gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
-        'media/GeckoAudioInfo.java',
         'media/GeckoHlsAudioRenderer.java',
-        'media/GeckoHLSDemuxerWrapper.java',
         'media/GeckoHlsPlayer.java',
         'media/GeckoHlsRendererBase.java',
-        'media/GeckoHLSResourceWrapper.java',
-        'media/GeckoHLSSample.java',
         'media/GeckoHlsVideoRenderer.java',
-        'media/GeckoVideoInfo.java',
         'media/Utils.java',
     ]]
 
 
 gvjar.extra_jars += [
     CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
     CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
     CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/BaseHlsPlayer.java
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public interface BaseHlsPlayer {
+
+    public enum TrackType {
+        UNDEFINED,
+        AUDIO,
+        VIDEO,
+        TEXT,
+    }
+
+    public enum ResourceError {
+        BASE(-100),
+        UNKNOWN(-101),
+        PLAYER(-102),
+        UNSUPPORTED(-103);
+
+        private int mNumVal;
+        private ResourceError(int numVal) {
+            mNumVal = numVal;
+        }
+        public int code() {
+            return mNumVal;
+        }
+    }
+
+    public enum DemuxerError {
+        BASE(-200),
+        UNKNOWN(-201),
+        PLAYER(-202),
+        UNSUPPORTED(-203);
+
+        private int mNumVal;
+        private DemuxerError(int numVal) {
+            mNumVal = numVal;
+        }
+        public int code() {
+            return mNumVal;
+        }
+    }
+
+    public interface DemuxerCallbacks {
+        void onInitialized(boolean hasAudio, boolean hasVideo);
+        void onError(int errorCode);
+    }
+
+    public interface ResourceCallbacks {
+        void onDataArrived();
+        void onError(int errorCode);
+    }
+
+    // Used to identify player instance.
+    public int getId();
+
+    // =======================================================================
+    // API for GeckoHLSResourceWrapper
+    // =======================================================================
+    public void addResourceWrapperCallbackListener(ResourceCallbacks callback);
+
+    public void init(String url);
+
+    public boolean isLiveStream();
+
+    // =======================================================================
+    // API for GeckoHLSDemuxerWrapper
+    // =======================================================================
+    public void addDemuxerWrapperCallbackListener(DemuxerCallbacks callback);
+
+    public ConcurrentLinkedQueue<GeckoHLSSample> getSamples(TrackType trackType, int number);
+
+    public long getDuration();
+
+    public long getBufferedPosition();
+
+    public int getNumberOfTracks(TrackType trackType);
+
+    public GeckoVideoInfo getVideoInfo(int index);
+
+    public GeckoAudioInfo getAudioInfo(int index);
+
+    public boolean seek(long positionUs);
+
+    public long getNextKeyFrameTime();
+
+    public void release();
+}
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSDemuxerWrapper.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSDemuxerWrapper.java
@@ -2,47 +2,44 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 package org.mozilla.gecko.media;
 
 import android.util.Log;
 
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.util.MimeTypes;
-
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNIObject;
 
 public final class GeckoHLSDemuxerWrapper {
     private static final String LOGTAG = "GeckoHLSDemuxerWrapper";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     // NOTE : These TRACK definitions should be synced with Gecko.
     public enum TrackType {
         UNDEFINED(0),
         AUDIO(1),
         VIDEO(2),
         TEXT(3);
         private int mType;
         private TrackType(int type) {
             mType = type;
         }
         public int value() {
             return mType;
         }
     }
 
-    private GeckoHlsPlayer mPlayer = null;
+    private BaseHlsPlayer mPlayer = null;
 
     public static class Callbacks extends JNIObject
-        implements GeckoHlsPlayer.DemuxerCallbacks {
+    implements BaseHlsPlayer.DemuxerCallbacks {
 
         @WrapForJNI(calledFrom = "gecko")
         Callbacks() {}
 
         @Override
         @WrapForJNI
         public native void onInitialized(boolean hasAudio, boolean hasVideo);
 
@@ -57,120 +54,88 @@ public final class GeckoHLSDemuxerWrappe
     } // Callbacks
 
     private static void assertTrue(boolean condition) {
         if (DEBUG && !condition) {
             throw new AssertionError("Expected condition to be true");
         }
     }
 
-    private GeckoHlsPlayer.TrackType getPlayerTrackType(int trackType) {
+    private BaseHlsPlayer.TrackType getPlayerTrackType(int trackType) {
         if (trackType == TrackType.AUDIO.value()) {
-            return GeckoHlsPlayer.TrackType.AUDIO;
+            return BaseHlsPlayer.TrackType.AUDIO;
         } else if (trackType == TrackType.VIDEO.value()) {
-            return GeckoHlsPlayer.TrackType.VIDEO;
+            return BaseHlsPlayer.TrackType.VIDEO;
         } else if (trackType == TrackType.TEXT.value()) {
-            return GeckoHlsPlayer.TrackType.TEXT;
+            return BaseHlsPlayer.TrackType.TEXT;
         }
-        return GeckoHlsPlayer.TrackType.UNDEFINED;
+        return BaseHlsPlayer.TrackType.UNDEFINED;
     }
 
     @WrapForJNI
     public long getBuffered() {
         assertTrue(mPlayer != null);
         return mPlayer.getBufferedPosition();
     }
 
     @WrapForJNI(calledFrom = "gecko")
-    public static GeckoHLSDemuxerWrapper create(GeckoHlsPlayer player,
-                                                GeckoHlsPlayer.DemuxerCallbacks callback) {
-        return new GeckoHLSDemuxerWrapper(player, callback);
+    public static GeckoHLSDemuxerWrapper create(int id, BaseHlsPlayer.DemuxerCallbacks callback) {
+        return new GeckoHLSDemuxerWrapper(id, callback);
     }
 
     @WrapForJNI
     public int getNumberOfTracks(int trackType) {
-        int tracks = mPlayer != null ? mPlayer.getNumberOfTracks(getPlayerTrackType(trackType)) : 0;
+        assertTrue(mPlayer != null);
+        int tracks = mPlayer.getNumberOfTracks(getPlayerTrackType(trackType));
         if (DEBUG) Log.d(LOGTAG, "[GetNumberOfTracks] type : " + trackType + ", num = " + tracks);
         return tracks;
     }
 
     @WrapForJNI
     public GeckoAudioInfo getAudioInfo(int index) {
         assertTrue(mPlayer != null);
-
         if (DEBUG) Log.d(LOGTAG, "[getAudioInfo] formatIndex : " + index);
-        Format fmt = mPlayer.getAudioTrackFormat(index);
-        if (fmt == null) {
-            return null;
-        }
-        /* According to https://github.com/google/ExoPlayer/blob
-         * /d979469659861f7fe1d39d153b90bdff1ab479cc/library/core/src/main
-         * /java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java#L221-L224,
-         * if the input audio format is not raw, exoplayer would assure that
-         * the sample's pcm encoding bitdepth is 16.
-         * For HLS content, it should always be 16.
-         */
-        assertTrue(!MimeTypes.AUDIO_RAW.equals(fmt.sampleMimeType));
-        // For HLS content, csd-0 is enough.
-        byte[] csd = fmt.initializationData.isEmpty() ? null : fmt.initializationData.get(0);
-        GeckoAudioInfo aInfo = new GeckoAudioInfo(fmt.sampleRate, fmt.channelCount,
-                                                  16, 0, mPlayer.getDuration(),
-                                                  fmt.sampleMimeType, csd);
+        GeckoAudioInfo aInfo = mPlayer.getAudioInfo(index);
         return aInfo;
     }
 
     @WrapForJNI
     public GeckoVideoInfo getVideoInfo(int index) {
         assertTrue(mPlayer != null);
-
         if (DEBUG) Log.d(LOGTAG, "[getVideoInfo] formatIndex : " + index);
-        Format fmt = mPlayer.getVideoTrackFormat(index);
-        if (fmt == null) {
-            return null;
-        }
-        GeckoVideoInfo vInfo = new GeckoVideoInfo(fmt.width, fmt.height,
-                                                  fmt.width, fmt.height,
-                                                  fmt.rotationDegrees, fmt.stereoMode,
-                                                  mPlayer.getDuration(), fmt.sampleMimeType,
-                                                  null, null);
+        GeckoVideoInfo vInfo = mPlayer.getVideoInfo(index);
         return vInfo;
     }
 
     @WrapForJNI
     public boolean seek(long seekTime) {
         // seekTime : microseconds.
         assertTrue(mPlayer != null);
         if (DEBUG) Log.d(LOGTAG, "seek  : " + seekTime + " (Us)");
         return mPlayer.seek(seekTime);
     }
 
-    GeckoHLSDemuxerWrapper(GeckoHlsPlayer player,
-                           GeckoHlsPlayer.DemuxerCallbacks callback) {
+    GeckoHLSDemuxerWrapper(int id, BaseHlsPlayer.DemuxerCallbacks callback) {
         if (DEBUG) Log.d(LOGTAG, "Constructing GeckoHLSDemuxerWrapper ...");
         assertTrue(callback != null);
-        assertTrue(player != null);
         try {
-            this.mPlayer = player;
-            this.mPlayer.addDemuxerWrapperCallbackListener(callback);
+            mPlayer = GeckoPlayerFactory.getPlayer(id);
+            mPlayer.addDemuxerWrapperCallbackListener(callback);
         } catch (Exception e) {
             Log.e(LOGTAG, "Constructing GeckoHLSDemuxerWrapper ... error", e);
-            callback.onError(GeckoHlsPlayer.DemuxerError.UNKNOWN.code());
+            callback.onError(BaseHlsPlayer.DemuxerError.UNKNOWN.code());
         }
     }
 
     @WrapForJNI
     private GeckoHLSSample[] getSamples(int mediaType, int number) {
+        assertTrue(mPlayer != null);
         ConcurrentLinkedQueue<GeckoHLSSample> samples = null;
         // getA/VSamples will always return a non-null instance.
-        if (mediaType == TrackType.VIDEO.value()) {
-            samples = mPlayer.getVideoSamples(number);
-        } else if (mediaType == TrackType.AUDIO.value()) {
-            samples = mPlayer.getAudioSamples(number);
-        }
-
+        samples = mPlayer.getSamples(getPlayerTrackType(mediaType), number);
         assertTrue(samples.size() <= number);
         return samples.toArray(new GeckoHLSSample[samples.size()]);
     }
 
     @WrapForJNI
     private long getNextKeyFrameTime() {
         assertTrue(mPlayer != null);
         return mPlayer.getNextKeyFrameTime();
@@ -187,13 +152,14 @@ public final class GeckoHLSDemuxerWrappe
         if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
         if (mPlayer != null) {
             release();
         }
     }
 
     private void release() {
         assertTrue(mPlayer != null);
-        if (DEBUG) Log.d(LOGTAG, "release GeckoHlsPlayer...");
+        if (DEBUG) Log.d(LOGTAG, "release BaseHlsPlayer...");
+        GeckoPlayerFactory.removePlayer(mPlayer);
         mPlayer.release();
         mPlayer = null;
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
@@ -7,21 +7,21 @@ package org.mozilla.gecko.media;
 import android.util.Log;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNIObject;
 
 public class GeckoHLSResourceWrapper {
     private static final String LOGTAG = "GeckoHLSResourceWrapper";
     private static final boolean DEBUG = false;
-    private GeckoHlsPlayer mPlayer = null;
+    private BaseHlsPlayer mPlayer = null;
     private boolean mDestroy = false;
 
     public static class Callbacks extends JNIObject
-    implements GeckoHlsPlayer.ResourceCallbacks {
+    implements BaseHlsPlayer.ResourceCallbacks {
         @WrapForJNI(calledFrom = "gecko")
         Callbacks() {}
 
         @Override
         @WrapForJNI(dispatchTo = "gecko")
         public native void onDataArrived();
 
         @Override
@@ -30,50 +30,56 @@ public class GeckoHLSResourceWrapper {
 
         @Override // JNIObject
         protected void disposeNative() {
             throw new UnsupportedOperationException();
         }
     } // Callbacks
 
     private GeckoHLSResourceWrapper(String url,
-                                    GeckoHlsPlayer.ResourceCallbacks callback) {
+                                    BaseHlsPlayer.ResourceCallbacks callback) {
         if (DEBUG) Log.d(LOGTAG, "GeckoHLSResourceWrapper created with url = " + url);
         assertTrue(callback != null);
 
-        mPlayer = new GeckoHlsPlayer();
-        mPlayer.addResourceWrapperCallbackListener(callback);
-        mPlayer.init(url);
+        mPlayer = GeckoPlayerFactory.getPlayer();
+        try {
+            mPlayer.addResourceWrapperCallbackListener(callback);
+            mPlayer.init(url);
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Failed to create GeckoHlsResourceWrapper !", e);
+            callback.onError(BaseHlsPlayer.ResourceError.UNKNOWN.code());
+        }
     }
 
     @WrapForJNI(calledFrom = "gecko")
     public static GeckoHLSResourceWrapper create(String url,
-                                                 GeckoHlsPlayer.ResourceCallbacks callback) {
+                                                 BaseHlsPlayer.ResourceCallbacks callback) {
         return new GeckoHLSResourceWrapper(url, callback);
     }
 
     @WrapForJNI(calledFrom = "gecko")
-    public GeckoHlsPlayer GetPlayer() {
+    public int getPlayerId() {
         // GeckoHLSResourceWrapper should always be created before others
         assertTrue(!mDestroy);
         assertTrue(mPlayer != null);
-        return mPlayer;
+        return mPlayer.getId();
     }
 
     private static void assertTrue(boolean condition) {
         if (DEBUG && !condition) {
             throw new AssertionError("Expected condition to be true");
         }
     }
 
     @WrapForJNI // Called when native object is mDestroy.
     private void destroy() {
         if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
         if (mDestroy) {
             return;
         }
         mDestroy = true;
         if (mPlayer != null) {
+            GeckoPlayerFactory.removePlayer(mPlayer);
             mPlayer.release();
             mPlayer = 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
@@ -27,29 +27,44 @@ import com.google.android.exoplayer2.tra
 import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
 import com.google.android.exoplayer2.trackselection.TrackSelection;
 import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
 import com.google.android.exoplayer2.upstream.DataSource;
 import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
 import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
 import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.util.MimeTypes;
 import com.google.android.exoplayer2.util.Util;
 
+import org.mozilla.gecko.annotation.ReflectionTarget;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
 
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
-public class GeckoHlsPlayer implements ExoPlayer.EventListener {
+@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 int MAX_TIMELINE_ITEM_LINES = 3;
     private static boolean DEBUG = false;
 
+    private static 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
+     *  need to bridge this HLSResource to the created demuxer. And they share
+     *  the same GeckoHlsPlayer.
+     *  mPlayerId is a token used for Gecko media pipeline to obtain corresponding player.
+     */
+    private final int mPlayerId;
+
     private DataSource.Factory mMediaDataSourceFactory;
 
     private Handler mMainHandler;
     private ExoPlayer mPlayer;
     private GeckoHlsRendererBase[] mRenderers;
     private DefaultTrackSelector mTrackSelector;
     private MediaSource mMediaSource;
     private ComponentListener mComponentListener;
@@ -105,73 +120,27 @@ public class GeckoHlsPlayer implements E
         public boolean audioReady() {
             return !hasAudio() || (mAudioInfoUpdated && mAudioDataArrived);
         }
     }
     private HlsMediaTracksInfo mTracksInfo = null;
 
     private boolean mIsPlayerInitDone = false;
     private boolean mIsDemuxerInitDone = false;
-    private DemuxerCallbacks mDemuxerCallbacks;
-    private ResourceCallbacks mResourceCallbacks;
 
-    public enum TrackType {
-        UNDEFINED,
-        AUDIO,
-        VIDEO,
-        TEXT,
-    }
-
-    public enum ResourceError {
-        BASE(-100),
-        UNKNOWN(-101),
-        PLAYER(-102),
-        UNSUPPORTED(-103);
-
-        private int mNumVal;
-        private ResourceError(int numVal) {
-            mNumVal = numVal;
-        }
-        public int code() {
-            return mNumVal;
-        }
-    }
-
-    public enum DemuxerError {
-        BASE(-200),
-        UNKNOWN(-201),
-        PLAYER(-202),
-        UNSUPPORTED(-203);
-
-        private int mNumVal;
-        private DemuxerError(int numVal) {
-            mNumVal = numVal;
-        }
-        public int code() {
-            return mNumVal;
-        }
-    }
-
-    public interface DemuxerCallbacks {
-        void onInitialized(boolean hasAudio, boolean hasVideo);
-        void onError(int errorCode);
-    }
-
-    public interface ResourceCallbacks {
-        void onDataArrived();
-        void onError(int errorCode);
-    }
+    private BaseHlsPlayer.DemuxerCallbacks mDemuxerCallbacks;
+    private BaseHlsPlayer.ResourceCallbacks mResourceCallbacks;
 
     private static void assertTrue(boolean condition) {
       if (DEBUG && !condition) {
         throw new AssertionError("Expected condition to be true");
       }
     }
 
-    public void checkInitDone() {
+    protected void checkInitDone() {
         assertTrue(mDemuxerCallbacks != null);
         assertTrue(mTracksInfo != null);
         if (mIsDemuxerInitDone) {
             return;
         }
         if (DEBUG) {
             Log.d(LOGTAG, "[checkInitDone] VReady:" + mTracksInfo.videoReady() +
                     ",AReady:" + mTracksInfo.audioReady() +
@@ -261,22 +230,22 @@ public class GeckoHlsPlayer implements E
         public void onAudioInputFormatChanged(Format format) {
             assertTrue(mTracksInfo != null);
             if (DEBUG) { Log.d(LOGTAG, "[CB] onAudioInputFormatChanged [" + format + "]"); }
             mTracksInfo.onAudioInfoUpdated();
             checkInitDone();
         }
     }
 
-    public DataSource.Factory buildDataSourceFactory(Context ctx, DefaultBandwidthMeter bandwidthMeter) {
+    private DataSource.Factory buildDataSourceFactory(Context ctx, DefaultBandwidthMeter bandwidthMeter) {
         return new DefaultDataSourceFactory(ctx, bandwidthMeter,
                 buildHttpDataSourceFactory(bandwidthMeter));
     }
 
-    public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
+    private HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
         return new DefaultHttpDataSourceFactory(AppConstants.USER_AGENT_FENNEC_MOBILE, bandwidthMeter);
     }
 
     private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
         if (DEBUG) { Log.d(LOGTAG, "buildMediaSource uri[" + uri + "]" + ", overridedExt[" + overrideExtension + "]"); }
         int type = Util.inferContentType(TextUtils.isEmpty(overrideExtension)
                                          ? uri.getLastPathSegment()
                                          : "." + overrideExtension);
@@ -284,26 +253,39 @@ public class GeckoHlsPlayer implements E
             case C.TYPE_HLS:
                 return new HlsMediaSource(uri, mMediaDataSourceFactory, mMainHandler, null);
             default:
                 mResourceCallbacks.onError(ResourceError.UNSUPPORTED.code());
                 throw new IllegalArgumentException("Unsupported type: " + type);
         }
     }
 
-    GeckoHlsPlayer() {
-        if (DEBUG) { Log.d(LOGTAG, " construct"); }
+    // To make sure that each player has a unique id, GeckoHlsPlayer should be
+    // created only from synchronized APIs in GeckoPlayerFactory.
+    public GeckoHlsPlayer() {
+        mPlayerId = sPlayerId.incrementAndGet();
+        if (DEBUG) { Log.d(LOGTAG, " construct player with id(" + mPlayerId + ")"); }
     }
 
-    void addResourceWrapperCallbackListener(ResourceCallbacks callback) {
+    // Should be only called by GeckoPlayerFactory and GeckoHLSResourceWrapper.
+    // The mPlayerId is used to make sure that the same GeckoHlsPlayer is used by
+    // corresponding HLSResource and HLSDemuxer for each media playback.
+    @Override
+    public int getId() {
+        return mPlayerId;
+    }
+
+    @Override
+    public void addResourceWrapperCallbackListener(BaseHlsPlayer.ResourceCallbacks callback) {
         if (DEBUG) { Log.d(LOGTAG, " addResourceWrapperCallbackListener ..."); }
         mResourceCallbacks = callback;
     }
 
-    void addDemuxerWrapperCallbackListener(DemuxerCallbacks callback) {
+    @Override
+    public void addDemuxerWrapperCallbackListener(BaseHlsPlayer.DemuxerCallbacks callback) {
         if (DEBUG) { Log.d(LOGTAG, " addDemuxerWrapperCallbackListener ..."); }
         mDemuxerCallbacks = callback;
     }
 
     @Override
     public void onLoadingChanged(boolean isLoading) {
         if (DEBUG) { Log.d(LOGTAG, "loading [" + isLoading + "]"); }
         if (!isLoading) {
@@ -510,19 +492,20 @@ public class GeckoHlsPlayer implements E
                 && selection.indexOf(trackIndex) != C.INDEX_UNSET);
       }
 
       private static String getTrackStatusString(boolean enabled) {
         return enabled ? "[X]" : "[ ]";
       }
 
     // =======================================================================
-    // API for GeckoHlsResourceWrapper
+    // API for GeckoHLSResourceWrapper
     // =======================================================================
-    synchronized void init(String url) {
+    @Override
+    public synchronized void init(String url) {
         if (DEBUG) { Log.d(LOGTAG, " init"); }
         assertTrue(mResourceCallbacks != null);
         if (mIsPlayerInitDone) {
             return;
         }
         Context ctx = GeckoAppShell.getApplicationContext();
         mComponentListener = new ComponentListener();
         mComponentEventDispatcher = new ComponentEventDispatcher();
@@ -549,78 +532,122 @@ public class GeckoHlsPlayer implements E
         Uri uri = Uri.parse(url);
         mMediaDataSourceFactory = buildDataSourceFactory(ctx, BANDWIDTH_METER);
         mMediaSource = buildMediaSource(uri, null);
 
         mPlayer.prepare(mMediaSource);
         mIsPlayerInitDone = true;
     }
 
+    @Override
     public boolean isLiveStream() {
         return !mIsTimelineStatic;
     }
 
     // =======================================================================
-    // API for GeckoHlsDemuxerWrapper
+    // API for GeckoHLSDemuxerWrapper
     // =======================================================================
-    public ConcurrentLinkedQueue<GeckoHLSSample> getVideoSamples(int number) {
-        return mVRenderer != null ? mVRenderer.getQueuedSamples(number) :
-                                    new ConcurrentLinkedQueue<GeckoHLSSample>();
+    @Override
+    public ConcurrentLinkedQueue<GeckoHLSSample> getSamples(TrackType trackType,
+                                                            int number) {
+        if (DEBUG) { Log.d(LOGTAG, "(" + trackType + ") getSamples : " + number); }
+        if (trackType == TrackType.VIDEO) {
+            return mVRenderer != null ? mVRenderer.getQueuedSamples(number) :
+                                        new ConcurrentLinkedQueue<GeckoHLSSample>();
+        } else if (trackType == TrackType.AUDIO) {
+            return mARenderer != null ? mARenderer.getQueuedSamples(number) :
+                                        new ConcurrentLinkedQueue<GeckoHLSSample>();
+        } else {
+            return new ConcurrentLinkedQueue<GeckoHLSSample>();
+        }
     }
 
-    public ConcurrentLinkedQueue<GeckoHLSSample> getAudioSamples(int number) {
-        return mARenderer != null ? mARenderer.getQueuedSamples(number) :
-                                    new ConcurrentLinkedQueue<GeckoHLSSample>();
-    }
-
+    @Override
     public long getDuration() {
         assertTrue(mPlayer != null);
         if (isLiveStream()) {
             return 0L;
         }
         // Value returned by getDuration() is in milliseconds.
         long duration = mPlayer.getDuration() * 1000;
         if (DEBUG) { Log.d(LOGTAG, "getDuration : " + duration  + "(Us)"); }
         return duration;
     }
 
+    @Override
     public long getBufferedPosition() {
         assertTrue(mPlayer != null);
         // Value returned by getBufferedPosition() is in milliseconds.
         long bufferedPos = mPlayer.getBufferedPosition() * 1000;
         if (DEBUG) { Log.d(LOGTAG, "getBufferedPosition : " + bufferedPos + "(Us)"); }
         return bufferedPos;
     }
 
+    @Override
     public synchronized int getNumberOfTracks(TrackType trackType) {
-        if (DEBUG) { Log.d(LOGTAG, "getNumberOfTracks"); }
+        if (DEBUG) { Log.d(LOGTAG, "getNumberOfTracks : type " + trackType); }
         assertTrue(mTracksInfo != null);
 
         if (trackType == TrackType.VIDEO) {
             return mTracksInfo.getNumOfVideoTracks();
         } else if (trackType == TrackType.AUDIO) {
             return mTracksInfo.getNumOfAudioTracks();
         }
         return 0;
     }
 
-    public Format getVideoTrackFormat(int index) {
-        if (DEBUG) { Log.d(LOGTAG, "getVideoTrackFormat"); }
+    @Override
+    public GeckoVideoInfo getVideoInfo(int index) {
+        if (DEBUG) { Log.d(LOGTAG, "getVideoInfo"); }
         assertTrue(mVRenderer != null);
         assertTrue(mTracksInfo != null);
-        return mTracksInfo.hasVideo() ? mVRenderer.getFormat(index) : null;
+        if (!mTracksInfo.hasVideo()) {
+            return null;
+        }
+        Format fmt = mVRenderer.getFormat(index);
+        if (fmt == null) {
+            return null;
+        }
+        GeckoVideoInfo vInfo = new GeckoVideoInfo(fmt.width, fmt.height,
+                                                  fmt.width, fmt.height,
+                                                  fmt.rotationDegrees, fmt.stereoMode,
+                                                  getDuration(), fmt.sampleMimeType,
+                                                  null, null);
+        return vInfo;
     }
 
-    public Format getAudioTrackFormat(int index) {
-        if (DEBUG) { Log.d(LOGTAG, "getAudioTrackFormat"); }
+    @Override
+    public GeckoAudioInfo getAudioInfo(int index) {
+        if (DEBUG) { Log.d(LOGTAG, "getAudioInfo"); }
         assertTrue(mARenderer != null);
         assertTrue(mTracksInfo != null);
-        return mTracksInfo.hasAudio() ? mARenderer.getFormat(index) : null;
+        if (!mTracksInfo.hasAudio()) {
+            return null;
+        }
+        Format fmt = mARenderer.getFormat(index);
+        if (fmt == null) {
+            return null;
+        }
+        /* According to https://github.com/google/ExoPlayer/blob
+         * /d979469659861f7fe1d39d153b90bdff1ab479cc/library/core/src/main
+         * /java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java#L221-L224,
+         * if the input audio format is not raw, exoplayer would assure that
+         * the sample's pcm encoding bitdepth is 16.
+         * For HLS content, it should always be 16.
+         */
+        assertTrue(!MimeTypes.AUDIO_RAW.equals(fmt.sampleMimeType));
+        // For HLS content, csd-0 is enough.
+        byte[] csd = fmt.initializationData.isEmpty() ? null : fmt.initializationData.get(0);
+        GeckoAudioInfo aInfo = new GeckoAudioInfo(fmt.sampleRate, fmt.channelCount,
+                                                  16, 0, getDuration(),
+                                                  fmt.sampleMimeType, csd);
+        return aInfo;
     }
 
+    @Override
     public boolean seek(long positionUs) {
         // positionUs : microseconds.
         // NOTE : 1) It's not possible to seek media by tracktype via ExoPlayer Interface.
         //        2) positionUs is samples PTS from MFR, we need to re-adjust it
         //           for ExoPlayer by subtracting sample start time.
         //        3) Time unit for ExoPlayer.seek() is milliseconds.
         try {
             // TODO : Gather Timeline Period / Window information to develop
@@ -641,23 +668,25 @@ public class GeckoHlsPlayer implements E
             mPlayer.seekTo(positionUs / 1000 - startTime / 1000);
         } catch (Exception e) {
             mDemuxerCallbacks.onError(DemuxerError.UNKNOWN.code());
             return false;
         }
         return true;
     }
 
+    @Override
     public long getNextKeyFrameTime() {
         long nextKeyFrameTime = mVRenderer != null
             ? mVRenderer.getNextKeyFrameTime()
             : Long.MAX_VALUE;
         return nextKeyFrameTime;
     }
 
+    @Override
     public void release() {
         if (DEBUG) { Log.d(LOGTAG, "releasing  ..."); }
         if (mPlayer != null) {
             mPlayer.removeListener(this);
             mPlayer.stop();
             mPlayer.release();
             mVRenderer = null;
             mARenderer = null;
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoPlayerFactory.java
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public final class GeckoPlayerFactory {
+    public static final ArrayList<BaseHlsPlayer> sPlayerList = new ArrayList<BaseHlsPlayer>();
+
+    synchronized static BaseHlsPlayer getPlayer() {
+        try {
+            final Class<?> cls = Class.forName("org.mozilla.gecko.media.GeckoHlsPlayer");
+            BaseHlsPlayer player = (BaseHlsPlayer) cls.newInstance();
+            sPlayerList.add(player);
+            return player;
+        } catch (Exception e) {
+            Log.e("GeckoPlayerFactory", "Class GeckoHlsPlayer not found or failed to create", e);
+        }
+        return null;
+    }
+
+    synchronized static BaseHlsPlayer getPlayer(int id) {
+        for (BaseHlsPlayer player : sPlayerList) {
+            if (player.getId() == id) {
+                return player;
+            }
+        }
+        Log.w("GeckoPlayerFactory", "No player found with id : " + id);
+        return null;
+    }
+
+    synchronized static void removePlayer(@NonNull BaseHlsPlayer player) {
+        int index = sPlayerList.indexOf(player);
+        if (index >= 0) {
+            sPlayerList.remove(player);
+            Log.d("GeckoPlayerFactory", "HlsPlayer with id(" + player.getId() + ") is removed.");
+        }
+    }
+}
\ No newline at end of file
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1932,17 +1932,17 @@ auto GeckoAudioInfo::Rate() const -> int
 }
 
 const char GeckoHLSDemuxerWrapper::name[] =
         "org/mozilla/gecko/media/GeckoHLSDemuxerWrapper";
 
 constexpr char GeckoHLSDemuxerWrapper::Create_t::name[];
 constexpr char GeckoHLSDemuxerWrapper::Create_t::signature[];
 
-auto GeckoHLSDemuxerWrapper::Create(mozilla::jni::Object::Param a0, mozilla::jni::Object::Param a1) -> GeckoHLSDemuxerWrapper::LocalRef
+auto GeckoHLSDemuxerWrapper::Create(int32_t a0, mozilla::jni::Object::Param a1) -> GeckoHLSDemuxerWrapper::LocalRef
 {
     return mozilla::jni::Method<Create_t>::Call(GeckoHLSDemuxerWrapper::Context(), nullptr, a0, a1);
 }
 
 constexpr char GeckoHLSDemuxerWrapper::Destroy_t::name[];
 constexpr char GeckoHLSDemuxerWrapper::Destroy_t::signature[];
 
 auto GeckoHLSDemuxerWrapper::Destroy() const -> void
@@ -2029,40 +2029,40 @@ constexpr char GeckoHLSDemuxerWrapper::C
 constexpr char GeckoHLSDemuxerWrapper::Callbacks::OnError_t::signature[];
 
 constexpr char GeckoHLSDemuxerWrapper::Callbacks::OnInitialized_t::name[];
 constexpr char GeckoHLSDemuxerWrapper::Callbacks::OnInitialized_t::signature[];
 
 const char GeckoHLSResourceWrapper::name[] =
         "org/mozilla/gecko/media/GeckoHLSResourceWrapper";
 
-constexpr char GeckoHLSResourceWrapper::GetPlayer_t::name[];
-constexpr char GeckoHLSResourceWrapper::GetPlayer_t::signature[];
-
-auto GeckoHLSResourceWrapper::GetPlayer() const -> mozilla::jni::Object::LocalRef
-{
-    return mozilla::jni::Method<GetPlayer_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
-}
-
 constexpr char GeckoHLSResourceWrapper::Create_t::name[];
 constexpr char GeckoHLSResourceWrapper::Create_t::signature[];
 
 auto GeckoHLSResourceWrapper::Create(mozilla::jni::String::Param a0, mozilla::jni::Object::Param a1) -> GeckoHLSResourceWrapper::LocalRef
 {
     return mozilla::jni::Method<Create_t>::Call(GeckoHLSResourceWrapper::Context(), nullptr, a0, a1);
 }
 
 constexpr char GeckoHLSResourceWrapper::Destroy_t::name[];
 constexpr char GeckoHLSResourceWrapper::Destroy_t::signature[];
 
 auto GeckoHLSResourceWrapper::Destroy() const -> void
 {
     return mozilla::jni::Method<Destroy_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
 }
 
+constexpr char GeckoHLSResourceWrapper::GetPlayerId_t::name[];
+constexpr char GeckoHLSResourceWrapper::GetPlayerId_t::signature[];
+
+auto GeckoHLSResourceWrapper::GetPlayerId() const -> int32_t
+{
+    return mozilla::jni::Method<GetPlayerId_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
+}
+
 const char GeckoHLSResourceWrapper::Callbacks::name[] =
         "org/mozilla/gecko/media/GeckoHLSResourceWrapper$Callbacks";
 
 constexpr char GeckoHLSResourceWrapper::Callbacks::New_t::name[];
 constexpr char GeckoHLSResourceWrapper::Callbacks::New_t::signature[];
 
 auto GeckoHLSResourceWrapper::Callbacks::New() -> Callbacks::LocalRef
 {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -5570,31 +5570,31 @@ public:
 
     class Callbacks;
 
     struct Create_t {
         typedef GeckoHLSDemuxerWrapper Owner;
         typedef GeckoHLSDemuxerWrapper::LocalRef ReturnType;
         typedef GeckoHLSDemuxerWrapper::Param SetterType;
         typedef mozilla::jni::Args<
-                mozilla::jni::Object::Param,
+                int32_t,
                 mozilla::jni::Object::Param> Args;
         static constexpr char name[] = "create";
         static constexpr char signature[] =
-                "(Lorg/mozilla/gecko/media/GeckoHlsPlayer;Lorg/mozilla/gecko/media/GeckoHlsPlayer$DemuxerCallbacks;)Lorg/mozilla/gecko/media/GeckoHLSDemuxerWrapper;";
+                "(ILorg/mozilla/gecko/media/BaseHlsPlayer$DemuxerCallbacks;)Lorg/mozilla/gecko/media/GeckoHLSDemuxerWrapper;";
         static const bool isStatic = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
-    static auto Create(mozilla::jni::Object::Param, mozilla::jni::Object::Param) -> GeckoHLSDemuxerWrapper::LocalRef;
+    static auto Create(int32_t, mozilla::jni::Object::Param) -> GeckoHLSDemuxerWrapper::LocalRef;
 
     struct Destroy_t {
         typedef GeckoHLSDemuxerWrapper Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "destroy";
         static constexpr char signature[] =
@@ -5846,45 +5846,26 @@ class GeckoHLSResourceWrapper : public m
 {
 public:
     static const char name[];
 
     explicit GeckoHLSResourceWrapper(const Context& ctx) : ObjectBase<GeckoHLSResourceWrapper>(ctx) {}
 
     class Callbacks;
 
-    struct GetPlayer_t {
-        typedef GeckoHLSResourceWrapper Owner;
-        typedef mozilla::jni::Object::LocalRef ReturnType;
-        typedef mozilla::jni::Object::Param SetterType;
-        typedef mozilla::jni::Args<> Args;
-        static constexpr char name[] = "GetPlayer";
-        static constexpr char signature[] =
-                "()Lorg/mozilla/gecko/media/GeckoHlsPlayer;";
-        static const bool isStatic = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-        static const mozilla::jni::CallingThread callingThread =
-                mozilla::jni::CallingThread::GECKO;
-        static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::CURRENT;
-    };
-
-    auto GetPlayer() const -> mozilla::jni::Object::LocalRef;
-
     struct Create_t {
         typedef GeckoHLSResourceWrapper Owner;
         typedef GeckoHLSResourceWrapper::LocalRef ReturnType;
         typedef GeckoHLSResourceWrapper::Param SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
                 mozilla::jni::Object::Param> Args;
         static constexpr char name[] = "create";
         static constexpr char signature[] =
-                "(Ljava/lang/String;Lorg/mozilla/gecko/media/GeckoHlsPlayer$ResourceCallbacks;)Lorg/mozilla/gecko/media/GeckoHLSResourceWrapper;";
+                "(Ljava/lang/String;Lorg/mozilla/gecko/media/BaseHlsPlayer$ResourceCallbacks;)Lorg/mozilla/gecko/media/GeckoHLSResourceWrapper;";
         static const bool isStatic = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
@@ -5905,16 +5886,35 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto Destroy() const -> void;
 
+    struct GetPlayerId_t {
+        typedef GeckoHLSResourceWrapper Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getPlayerId";
+        static constexpr char signature[] =
+                "()I";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto GetPlayerId() const -> int32_t;
+
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::ANY;
 
 };
 
 class GeckoHLSResourceWrapper::Callbacks : public mozilla::jni::ObjectBase<Callbacks>
 {
 public: