--- 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: