diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml
index ff3821f8fb..88b5eb5a93 100644
--- a/demo/src/main/AndroidManifest.xml
+++ b/demo/src/main/AndroidManifest.xml
@@ -42,12 +42,7 @@
-
-
-
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java b/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
index a2915f53ff..dae5773100 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
@@ -44,16 +44,11 @@ public class DemoUtil {
public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
- public static final String CONTENT_TYPE_EXTRA = "content_type";
- public static final String CONTENT_ID_EXTRA = "content_id";
-
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_OTHER = 2;
public static final int TYPE_HLS = 3;
- public static final boolean EXPOSE_EXPERIMENTAL_FEATURES = false;
-
private static final CookieManager defaultCookieManager;
static {
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
similarity index 93%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
index 52b00246e5..c9ece110b1 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full;
+package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer;
+import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.util.VerboseLogUtil;
import android.media.MediaCodec.CryptoException;
@@ -63,8 +63,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
public void onStateChanged(boolean playWhenReady, int state) {
- Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " +
- getStateString(state) + "]");
+ Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
+ + getStateString(state) + "]");
}
@Override
@@ -81,8 +81,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
- Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes +
- ", " + getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
+ Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
+ + getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
}
@Override
@@ -104,21 +104,21 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
public void onLoadCompleted(int sourceId, long bytesLoaded) {
if (VerboseLogUtil.isTagEnabled(TAG)) {
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
- Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " +
- downloadTime + "]");
+ Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
+ + "]");
}
}
@Override
public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
- Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " +
- Integer.toString(trigger) + "]");
+ Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", "
+ + Integer.toString(trigger) + "]");
}
@Override
public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
- Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " +
- Integer.toString(trigger) + "]");
+ Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", "
+ + Integer.toString(trigger) + "]");
}
// DemoPlayer.InternalErrorListener
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
similarity index 94%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
index 9ff45850d2..6c5bb7536a 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
@@ -13,21 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full;
+package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
-import com.google.android.exoplayer.demo.DemoUtil;
-import com.google.android.exoplayer.demo.R;
-import com.google.android.exoplayer.demo.full.player.DashRendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
-import com.google.android.exoplayer.demo.full.player.HlsRendererBuilder;
-import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
-import com.google.android.exoplayer.demo.full.player.UnsupportedDrmException;
+import com.google.android.exoplayer.demo.player.DashRendererBuilder;
+import com.google.android.exoplayer.demo.player.DefaultRendererBuilder;
+import com.google.android.exoplayer.demo.player.DemoPlayer;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
+import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
+import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
+import com.google.android.exoplayer.demo.player.UnsupportedDrmException;
import com.google.android.exoplayer.metadata.TxxxMetadata;
import com.google.android.exoplayer.text.CaptionStyleCompat;
import com.google.android.exoplayer.text.SubtitleView;
@@ -65,11 +63,14 @@ import java.util.Map;
/**
* An activity that plays media using {@link DemoPlayer}.
*/
-public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
+public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener,
AudioCapabilitiesReceiver.Listener {
- private static final String TAG = "FullPlayerActivity";
+ public static final String CONTENT_TYPE_EXTRA = "content_type";
+ public static final String CONTENT_ID_EXTRA = "content_id";
+
+ private static final String TAG = "PlayerActivity";
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
private static final int MENU_GROUP_TRACKS = 1;
@@ -110,10 +111,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
Intent intent = getIntent();
contentUri = intent.getData();
- contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
- contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
+ contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
+ contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
- setContentView(R.layout.player_activity_full);
+ setContentView(R.layout.player_activity);
View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() {
@Override
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
index 94478c48e7..3a9fdc5529 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
@@ -15,15 +15,17 @@
*/
package com.google.android.exoplayer.demo;
+import com.google.android.exoplayer.MediaCodecUtil;
+import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.demo.Samples.Sample;
-import com.google.android.exoplayer.demo.full.FullPlayerActivity;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity;
+import com.google.android.exoplayer.util.MimeTypes;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -38,6 +40,8 @@ import android.widget.TextView;
*/
public class SampleChooserActivity extends Activity {
+ private static final String TAG = "SampleChooserActivity";
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -46,21 +50,25 @@ public class SampleChooserActivity extends Activity {
ListView sampleList = (ListView) findViewById(R.id.sample_list);
final SampleAdapter sampleAdapter = new SampleAdapter(this);
- sampleAdapter.add(new Header("Simple player"));
- sampleAdapter.addAll((Object[]) Samples.SIMPLE);
sampleAdapter.add(new Header("YouTube DASH"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4);
sampleAdapter.add(new Header("Widevine GTS DASH"));
sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS);
sampleAdapter.add(new Header("SmoothStreaming"));
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);
- sampleAdapter.add(new Header("Misc"));
- sampleAdapter.addAll((Object[]) Samples.MISC);
sampleAdapter.add(new Header("HLS"));
sampleAdapter.addAll((Object[]) Samples.HLS);
- if (DemoUtil.EXPOSE_EXPERIMENTAL_FEATURES) {
- sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
- sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
+ sampleAdapter.add(new Header("Misc"));
+ sampleAdapter.addAll((Object[]) Samples.MISC);
+
+ // Add WebM samples if the device has a VP9 decoder.
+ try {
+ if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false) != null) {
+ sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
+ sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
+ }
+ } catch (DecoderQueryException e) {
+ Log.e(TAG, "Failed to query vp9 decoder", e);
}
sampleList.setAdapter(sampleAdapter);
@@ -76,12 +84,10 @@ public class SampleChooserActivity extends Activity {
}
private void onSampleSelected(Sample sample) {
- Class> playerActivityClass = sample.fullPlayer ? FullPlayerActivity.class
- : SimplePlayerActivity.class;
- Intent mpdIntent = new Intent(this, playerActivityClass)
+ Intent mpdIntent = new Intent(this, PlayerActivity.class)
.setData(Uri.parse(sample.uri))
- .putExtra(DemoUtil.CONTENT_ID_EXTRA, sample.contentId)
- .putExtra(DemoUtil.CONTENT_TYPE_EXTRA, sample.type);
+ .putExtra(PlayerActivity.CONTENT_ID_EXTRA, sample.contentId)
+ .putExtra(PlayerActivity.CONTENT_TYPE_EXTRA, sample.type);
startActivity(mpdIntent);
}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
index b3eb4af93c..7817123830 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer.demo;
+import java.util.Locale;
+
/**
* Holds statically defined sample definitions.
*/
@@ -26,72 +28,53 @@ package com.google.android.exoplayer.demo;
public final String contentId;
public final String uri;
public final int type;
- public final boolean fullPlayer;
- public Sample(String name, String contentId, String uri, int type, boolean fullPlayer) {
+ public Sample(String name, String uri, int type) {
+ this(name, name.toLowerCase(Locale.US).replaceAll("\\s", ""), uri, type);
+ }
+
+ public Sample(String name, String contentId, String uri, int type) {
this.name = name;
this.contentId = contentId;
this.uri = uri;
this.type = type;
- this.fullPlayer = fullPlayer;
}
}
- public static final Sample[] SIMPLE = new Sample[] {
- new Sample("Google Glass (DASH)", "bf5bb2419360daf1",
- "http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
- + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
- + "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
- + "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, false),
- new Sample("Google Play (DASH)", "3aa39fa2cc27967f",
- "http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
- + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
- + "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
- + "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, false),
- new Sample("Super speed (SmoothStreaming)", "uid:ss:superspeed",
- "http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
- DemoUtil.TYPE_SS, false),
- new Sample("Apple master playlist (HLS)", "uid:hls:applemaster",
- "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
- + "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, false),
- new Sample("Dizzy (Misc)", "uid:misc:dizzy",
- "http://html5demos.com/assets/dizzy.mp4", DemoUtil.TYPE_OTHER, false),
- };
-
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
- new Sample("Google Glass", "bf5bb2419360daf1",
+ new Sample("Google Glass",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
- + "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, true),
- new Sample("Google Play", "3aa39fa2cc27967f",
+ + "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH),
+ new Sample("Google Play",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
- + "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH),
};
public static final Sample[] YOUTUBE_DASH_WEBM = new Sample[] {
- new Sample("Google Glass", "bf5bb2419360daf1",
+ new Sample("Google Glass",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=A3EC7EE53ABE601B357F7CAB8B54AD0702CA85A7."
- + "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH, true),
- new Sample("Google Play", "3aa39fa2cc27967f",
+ + "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH),
+ new Sample("Google Play",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=B752B262C6D7262EC4E4EB67901E5D8F7058A81D."
- + "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH),
};
public static final Sample[] SMOOTHSTREAMING = new Sample[] {
- new Sample("Super speed", "uid:ss:superspeed",
+ new Sample("Super speed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
- DemoUtil.TYPE_SS, true),
- new Sample("Super speed (PlayReady)", "uid:ss:pr:superspeed",
+ DemoUtil.TYPE_SS),
+ new Sample("Super speed (PlayReady)",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism",
- DemoUtil.TYPE_SS, true),
+ DemoUtil.TYPE_SS),
};
public static final Sample[] WIDEVINE_GTS = new Sample[] {
@@ -99,54 +82,54 @@ package com.google.android.exoplayer.demo;
"http://www.youtube.com/api/manifest/dash/id/d286538032258a1c/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=41EA40A027A125A16292E0A5E3277A3B5FA9B938."
- + "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH),
new Sample("WV: HDCP not required", "48fcc369939ac96c",
"http://www.youtube.com/api/manifest/dash/id/48fcc369939ac96c/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=315911BDCEED0FB0C763455BDCC97449DAAFA9E8."
- + "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH),
new Sample("WV: HDCP required", "e06c39f1151da3df",
"http://www.youtube.com/api/manifest/dash/id/e06c39f1151da3df/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=A47A1E13E7243BD567601A75F79B34644D0DC592."
- + "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH),
new Sample("WV: Secure video path required", "0894c7c8719b28a0",
"http://www.youtube.com/api/manifest/dash/id/0894c7c8719b28a0/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=2847EE498970F6B45176766CD2802FEB4D4CB7B2."
- + "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH),
new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a",
"http://www.youtube.com/api/manifest/dash/id/efd045b1eb61888a/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=61611F115EEEC7BADE5536827343FFFE2D83D14F."
- + "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH),
new Sample("WV: 30s license duration", "f9a34cab7b05881a",
"http://www.youtube.com/api/manifest/dash/id/f9a34cab7b05881a/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
- + "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH, true),
+ + "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH),
};
public static final Sample[] HLS = new Sample[] {
- new Sample("Apple master playlist", "uid:hls:applemaster",
+ new Sample("Apple master playlist",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
- + "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, true),
- new Sample("Apple master playlist advanced", "uid:hls:applemasteradvanced",
+ + "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS),
+ new Sample("Apple master playlist advanced",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
- + "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS, true),
- new Sample("Apple single media playlist", "uid:hls:applesinglemedia",
+ + "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS),
+ new Sample("Apple single media playlist",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
- + "prog_index.m3u8", DemoUtil.TYPE_HLS, true),
+ + "prog_index.m3u8", DemoUtil.TYPE_HLS),
};
public static final Sample[] MISC = new Sample[] {
- new Sample("Dizzy", "uid:misc:dizzy", "http://html5demos.com/assets/dizzy.mp4",
- DemoUtil.TYPE_OTHER, true),
- new Sample("Dizzy (https->http redirect)", "uid:misc:dizzy2", "https://goo.gl/MtUDEj",
- DemoUtil.TYPE_OTHER, true),
- new Sample("Apple AAC 10s", "uid:misc:appleaacseg", "https://devimages.apple.com.edgekey.net/"
+ new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4",
+ DemoUtil.TYPE_OTHER),
+ new Sample("Dizzy (https->http redirect)", "https://goo.gl/MtUDEj",
+ DemoUtil.TYPE_OTHER),
+ new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
- DemoUtil.TYPE_OTHER, true),
+ DemoUtil.TYPE_OTHER),
};
private Samples() {}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/SmoothStreamingTestMediaDrmCallback.java b/demo/src/main/java/com/google/android/exoplayer/demo/SmoothStreamingTestMediaDrmCallback.java
similarity index 95%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/SmoothStreamingTestMediaDrmCallback.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/SmoothStreamingTestMediaDrmCallback.java
index b193860423..ace3f1ee08 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/SmoothStreamingTestMediaDrmCallback.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/SmoothStreamingTestMediaDrmCallback.java
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full;
+package com.google.android.exoplayer.demo;
-import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/WidevineTestMediaDrmCallback.java b/demo/src/main/java/com/google/android/exoplayer/demo/WidevineTestMediaDrmCallback.java
similarity index 95%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/WidevineTestMediaDrmCallback.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/WidevineTestMediaDrmCallback.java
index f2425589db..378c74c202 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/WidevineTestMediaDrmCallback.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/WidevineTestMediaDrmCallback.java
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full;
+package com.google.android.exoplayer.demo;
-import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import android.annotation.TargetApi;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
similarity index 98%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
index dca0b2a57d..3f14a58bdb 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
import com.google.android.exoplayer.DefaultLoadControl;
@@ -38,8 +38,8 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.demo.DemoUtil;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
similarity index 96%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
index d848dd3908..c0b9d50417 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaCodecTrackRenderer;
@@ -82,8 +82,8 @@ import android.widget.TextView;
}
private String getRenderString() {
- return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() +
- ", " + renderer.codecCounters.getDebugString();
+ return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString()
+ + ", " + renderer.codecCounters.getDebugString();
}
private String getQualityString() {
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java
similarity index 92%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java
index 5bf369a5fc..36c2e58879 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.source.DefaultSampleSource;
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
similarity index 99%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
index a7ba111990..e20b87c21a 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
import com.google.android.exoplayer.DummyTrackRenderer;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
similarity index 94%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
index cab7ffcc33..326a0689d5 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
similarity index 98%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
index b31ea0a50d..22a342bfc7 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
@@ -27,8 +27,8 @@ import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
+import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/UnsupportedDrmException.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/UnsupportedDrmException.java
similarity index 95%
rename from demo/src/main/java/com/google/android/exoplayer/demo/full/player/UnsupportedDrmException.java
rename to demo/src/main/java/com/google/android/exoplayer/demo/player/UnsupportedDrmException.java
index 3776b8bef5..e71e1b04d3 100644
--- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/UnsupportedDrmException.java
+++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/UnsupportedDrmException.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.google.android.exoplayer.demo.full.player;
+package com.google.android.exoplayer.demo.player;
/**
* Exception thrown when the required level of DRM is not supported.
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashRendererBuilder.java
deleted file mode 100644
index e850421479..0000000000
--- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashRendererBuilder.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.demo.simple;
-
-import com.google.android.exoplayer.DefaultLoadControl;
-import com.google.android.exoplayer.LoadControl;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.chunk.ChunkSampleSource;
-import com.google.android.exoplayer.chunk.ChunkSource;
-import com.google.android.exoplayer.chunk.Format;
-import com.google.android.exoplayer.chunk.FormatEvaluator;
-import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
-import com.google.android.exoplayer.dash.DashChunkSource;
-import com.google.android.exoplayer.dash.mpd.AdaptationSet;
-import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
-import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
-import com.google.android.exoplayer.dash.mpd.Period;
-import com.google.android.exoplayer.dash.mpd.Representation;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
-import com.google.android.exoplayer.upstream.BufferPool;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
-import com.google.android.exoplayer.upstream.UriDataSource;
-import com.google.android.exoplayer.util.ManifestFetcher;
-import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.google.android.exoplayer.util.Util;
-
-import android.media.MediaCodec;
-import android.os.Handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A {@link RendererBuilder} for DASH.
- */
-/* package */ class DashRendererBuilder implements RendererBuilder,
- ManifestCallback {
-
- private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
- private static final int VIDEO_BUFFER_SEGMENTS = 200;
- private static final int AUDIO_BUFFER_SEGMENTS = 60;
- private static final int LIVE_EDGE_LATENCY_MS = 30000;
-
- private final SimplePlayerActivity playerActivity;
- private final String userAgent;
- private final String url;
- private final String contentId;
-
- private RendererBuilderCallback callback;
- private ManifestFetcher manifestFetcher;
-
- public DashRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
- String contentId) {
- this.playerActivity = playerActivity;
- this.userAgent = userAgent;
- this.url = url;
- this.contentId = contentId;
- }
-
- @Override
- public void buildRenderers(RendererBuilderCallback callback) {
- this.callback = callback;
- MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
- manifestFetcher = new ManifestFetcher(parser, contentId, url,
- userAgent);
- manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
- }
-
- @Override
- public void onManifestError(String contentId, IOException e) {
- callback.onRenderersError(e);
- }
-
- @Override
- public void onManifest(String contentId, MediaPresentationDescription manifest) {
- Period period = manifest.periods.get(0);
- Handler mainHandler = playerActivity.getMainHandler();
- LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
- DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
-
- // Determine which video representations we should use for playback.
- int maxDecodableFrameSize;
- try {
- maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
- } catch (DecoderQueryException e) {
- callback.onRenderersError(e);
- return;
- }
-
- int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
- List videoRepresentations =
- period.adaptationSets.get(videoAdaptationSetIndex).representations;
- ArrayList videoRepresentationIndexList = new ArrayList();
- for (int i = 0; i < videoRepresentations.size(); i++) {
- Format format = videoRepresentations.get(i).format;
- if (format.width * format.height > maxDecodableFrameSize) {
- // Filtering stream that device cannot play
- } else if (!format.mimeType.equals(MimeTypes.VIDEO_MP4)
- && !format.mimeType.equals(MimeTypes.VIDEO_WEBM)) {
- // Filtering unsupported mime type
- } else {
- videoRepresentationIndexList.add(i);
- }
- }
-
- // Build the video renderer.
- final MediaCodecVideoTrackRenderer videoRenderer;
- if (videoRepresentationIndexList.isEmpty()) {
- videoRenderer = null;
- } else {
- int[] videoRepresentationIndices = Util.toArray(videoRepresentationIndexList);
- DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
- ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex,
- videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter),
- LIVE_EDGE_LATENCY_MS);
- ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
- VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
- videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
- }
-
- // Build the audio renderer.
- int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
- DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
- ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
- new int[] {0}, audioDataSource, new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
- SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
- AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
- MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
- audioSampleSource);
- callback.onRenderers(videoRenderer, audioRenderer);
- }
-
-}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java
deleted file mode 100644
index 849e0ad986..0000000000
--- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.demo.simple;
-
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
-import com.google.android.exoplayer.source.DefaultSampleSource;
-import com.google.android.exoplayer.source.FrameworkSampleExtractor;
-
-import android.media.MediaCodec;
-import android.net.Uri;
-
-/**
- * A {@link RendererBuilder} for streams that can be read using
- * {@link android.media.MediaExtractor}.
- */
-/* package */ class DefaultRendererBuilder implements RendererBuilder {
-
- private final SimplePlayerActivity playerActivity;
- private final Uri uri;
-
- public DefaultRendererBuilder(SimplePlayerActivity playerActivity, Uri uri) {
- this.playerActivity = playerActivity;
- this.uri = uri;
- }
-
- @Override
- public void buildRenderers(RendererBuilderCallback callback) {
- // Build the video and audio renderers.
- DefaultSampleSource sampleSource =
- new DefaultSampleSource(new FrameworkSampleExtractor(playerActivity, uri, null), 2);
- MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
- playerActivity, 50);
- MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
-
- // Invoke the callback.
- callback.onRenderers(videoRenderer, audioRenderer);
- }
-
-}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
deleted file mode 100644
index 1f08b63b38..0000000000
--- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.demo.simple;
-
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.demo.full.player.DemoPlayer;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
-import com.google.android.exoplayer.hls.HlsChunkSource;
-import com.google.android.exoplayer.hls.HlsPlaylist;
-import com.google.android.exoplayer.hls.HlsPlaylistParser;
-import com.google.android.exoplayer.hls.HlsSampleSource;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
-import com.google.android.exoplayer.upstream.UriDataSource;
-import com.google.android.exoplayer.util.ManifestFetcher;
-import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
-
-import android.media.MediaCodec;
-
-import java.io.IOException;
-
-/**
- * A {@link RendererBuilder} for HLS.
- */
-/* package */ class HlsRendererBuilder implements RendererBuilder,
- ManifestCallback {
-
- private final SimplePlayerActivity playerActivity;
- private final String userAgent;
- private final String url;
- private final String contentId;
-
- private RendererBuilderCallback callback;
-
- public HlsRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
- String contentId) {
- this.playerActivity = playerActivity;
- this.userAgent = userAgent;
- this.url = url;
- this.contentId = contentId;
- }
-
- @Override
- public void buildRenderers(RendererBuilderCallback callback) {
- this.callback = callback;
- HlsPlaylistParser parser = new HlsPlaylistParser();
- ManifestFetcher playlistFetcher =
- new ManifestFetcher(parser, contentId, url, userAgent);
- playlistFetcher.singleLoad(playerActivity.getMainLooper(), this);
- }
-
- @Override
- public void onManifestError(String contentId, IOException e) {
- callback.onRenderersError(e);
- }
-
- @Override
- public void onManifest(String contentId, HlsPlaylist manifest) {
- DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
-
- DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
- HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
- HlsChunkSource.ADAPTIVE_MODE_SPLICE);
- HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
- MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
- playerActivity, 50);
- MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
-
- TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
- renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
- renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
- callback.onRenderers(videoRenderer, audioRenderer);
- }
-
-}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java
deleted file mode 100644
index 6577921bbc..0000000000
--- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.demo.simple;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.ExoPlayer;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.VideoSurfaceView;
-import com.google.android.exoplayer.demo.DemoUtil;
-import com.google.android.exoplayer.demo.R;
-import com.google.android.exoplayer.util.PlayerControl;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.media.MediaCodec.CryptoException;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.MediaController;
-import android.widget.Toast;
-
-/**
- * An activity that plays media using {@link ExoPlayer}.
- */
-public class SimplePlayerActivity extends Activity implements SurfaceHolder.Callback,
- ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener {
-
- /**
- * Builds renderers for the player.
- */
- public interface RendererBuilder {
-
- void buildRenderers(RendererBuilderCallback callback);
-
- }
-
- public static final int RENDERER_COUNT = 2;
- public static final int TYPE_VIDEO = 0;
- public static final int TYPE_AUDIO = 1;
-
- private static final String TAG = "PlayerActivity";
-
- private MediaController mediaController;
- private Handler mainHandler;
- private View shutterView;
- private VideoSurfaceView surfaceView;
-
- private ExoPlayer player;
- private RendererBuilder builder;
- private RendererBuilderCallback callback;
- private MediaCodecVideoTrackRenderer videoRenderer;
-
- private boolean autoPlay = true;
- private long playerPosition;
-
- private Uri contentUri;
- private int contentType;
- private String contentId;
-
- // Activity lifecycle
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- contentUri = intent.getData();
- contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
- contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
-
- mainHandler = new Handler(getMainLooper());
- builder = getRendererBuilder();
-
- setContentView(R.layout.player_activity_simple);
- View root = findViewById(R.id.root);
- root.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View arg0, MotionEvent arg1) {
- if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
- toggleControlsVisibility();
- }
- return true;
- }
- });
-
- mediaController = new MediaController(this);
- mediaController.setAnchorView(root);
- shutterView = findViewById(R.id.shutter);
- surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
- surfaceView.getHolder().addCallback(this);
-
- DemoUtil.setDefaultCookieManager();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // Setup the player
- player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
- player.addListener(this);
- player.seekTo(playerPosition);
- // Build the player controls
- mediaController.setMediaPlayer(new PlayerControl(player));
- mediaController.setEnabled(true);
- // Request the renderers
- callback = new RendererBuilderCallback();
- builder.buildRenderers(callback);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // Release the player
- if (player != null) {
- playerPosition = player.getCurrentPosition();
- player.release();
- player = null;
- }
- callback = null;
- videoRenderer = null;
- shutterView.setVisibility(View.VISIBLE);
- }
-
- // Public methods
-
- public Handler getMainHandler() {
- return mainHandler;
- }
-
- // Internal methods
-
- private void toggleControlsVisibility() {
- if (mediaController.isShowing()) {
- mediaController.hide();
- } else {
- mediaController.show(0);
- }
- }
-
- private RendererBuilder getRendererBuilder() {
- String userAgent = DemoUtil.getUserAgent(this);
- switch (contentType) {
- case DemoUtil.TYPE_SS:
- return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
- contentId);
- case DemoUtil.TYPE_DASH:
- return new DashRendererBuilder(this, userAgent, contentUri.toString(), contentId);
- case DemoUtil.TYPE_HLS:
- return new HlsRendererBuilder(this, userAgent, contentUri.toString(), contentId);
- default:
- return new DefaultRendererBuilder(this, contentUri);
- }
- }
-
- private void onRenderers(RendererBuilderCallback callback,
- MediaCodecVideoTrackRenderer videoRenderer, MediaCodecAudioTrackRenderer audioRenderer) {
- if (this.callback != callback) {
- return;
- }
- this.callback = null;
- this.videoRenderer = videoRenderer;
- player.prepare(videoRenderer, audioRenderer);
- maybeStartPlayback();
- }
-
- private void maybeStartPlayback() {
- Surface surface = surfaceView.getHolder().getSurface();
- if (videoRenderer == null || surface == null || !surface.isValid()) {
- // We're not ready yet.
- return;
- }
- player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
- if (autoPlay) {
- player.setPlayWhenReady(true);
- autoPlay = false;
- }
- }
-
- private void onRenderersError(RendererBuilderCallback callback, Exception e) {
- if (this.callback != callback) {
- return;
- }
- this.callback = null;
- onError(e);
- }
-
- private void onError(Exception e) {
- Log.e(TAG, "Playback failed", e);
- Toast.makeText(this, R.string.failed, Toast.LENGTH_SHORT).show();
- finish();
- }
-
- // ExoPlayer.Listener implementation
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- // Do nothing.
- }
-
- @Override
- public void onPlayWhenReadyCommitted() {
- // Do nothing.
- }
-
- @Override
- public void onPlayerError(ExoPlaybackException e) {
- onError(e);
- }
-
- // MediaCodecVideoTrackRenderer.Listener
-
- @Override
- public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
- surfaceView.setVideoWidthHeightRatio(
- height == 0 ? 1 : (pixelWidthHeightRatio * width) / height);
- }
-
- @Override
- public void onDrawnToSurface(Surface surface) {
- shutterView.setVisibility(View.GONE);
- }
-
- @Override
- public void onDroppedFrames(int count, long elapsed) {
- Log.d(TAG, "Dropped frames: " + count);
- }
-
- @Override
- public void onDecoderInitializationError(DecoderInitializationException e) {
- // This is for informational purposes only. Do nothing.
- }
-
- @Override
- public void onCryptoError(CryptoException e) {
- // This is for informational purposes only. Do nothing.
- }
-
- // SurfaceHolder.Callback implementation
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- maybeStartPlayback();
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- // Do nothing.
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- if (videoRenderer != null) {
- player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
- }
- }
-
- /* package */ final class RendererBuilderCallback {
-
- public void onRenderers(MediaCodecVideoTrackRenderer videoRenderer,
- MediaCodecAudioTrackRenderer audioRenderer) {
- SimplePlayerActivity.this.onRenderers(this, videoRenderer, audioRenderer);
- }
-
- public void onRenderersError(Exception e) {
- SimplePlayerActivity.this.onRenderersError(this, e);
- }
-
- }
-
-}
diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
deleted file mode 100644
index a5c5569a32..0000000000
--- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer.demo.simple;
-
-import com.google.android.exoplayer.DefaultLoadControl;
-import com.google.android.exoplayer.LoadControl;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.chunk.ChunkSampleSource;
-import com.google.android.exoplayer.chunk.ChunkSource;
-import com.google.android.exoplayer.chunk.FormatEvaluator;
-import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
-import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
-import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
-import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
-import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
-import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
-import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
-import com.google.android.exoplayer.upstream.BufferPool;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
-import com.google.android.exoplayer.upstream.UriDataSource;
-import com.google.android.exoplayer.util.ManifestFetcher;
-import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
-import com.google.android.exoplayer.util.Util;
-
-import android.media.MediaCodec;
-import android.os.Handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * A {@link RendererBuilder} for SmoothStreaming.
- */
-/* package */ class SmoothStreamingRendererBuilder implements RendererBuilder,
- ManifestCallback {
-
- private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
- private static final int VIDEO_BUFFER_SEGMENTS = 200;
- private static final int AUDIO_BUFFER_SEGMENTS = 60;
- private static final int LIVE_EDGE_LATENCY_MS = 30000;
-
- private final SimplePlayerActivity playerActivity;
- private final String userAgent;
- private final String url;
- private final String contentId;
-
- private RendererBuilderCallback callback;
- private ManifestFetcher manifestFetcher;
-
- public SmoothStreamingRendererBuilder(SimplePlayerActivity playerActivity, String userAgent,
- String url, String contentId) {
- this.playerActivity = playerActivity;
- this.userAgent = userAgent;
- this.url = url;
- this.contentId = contentId;
- }
-
- @Override
- public void buildRenderers(RendererBuilderCallback callback) {
- this.callback = callback;
- SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
- manifestFetcher = new ManifestFetcher(parser, contentId,
- url + "/Manifest", userAgent);
- manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
- }
-
- @Override
- public void onManifestError(String contentId, IOException e) {
- callback.onRenderersError(e);
- }
-
- @Override
- public void onManifest(String contentId, SmoothStreamingManifest manifest) {
- Handler mainHandler = playerActivity.getMainHandler();
- LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
- DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
-
- // Obtain stream elements for playback.
- int maxDecodableFrameSize;
- try {
- maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
- } catch (DecoderQueryException e) {
- callback.onRenderersError(e);
- return;
- }
- int audioStreamElementIndex = -1;
- int videoStreamElementIndex = -1;
- ArrayList videoTrackIndexList = new ArrayList();
- for (int i = 0; i < manifest.streamElements.length; i++) {
- if (audioStreamElementIndex == -1
- && manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
- audioStreamElementIndex = i;
- } else if (videoStreamElementIndex == -1
- && manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
- videoStreamElementIndex = i;
- StreamElement streamElement = manifest.streamElements[i];
- for (int j = 0; j < streamElement.tracks.length; j++) {
- TrackElement trackElement = streamElement.tracks[j];
- if (trackElement.maxWidth * trackElement.maxHeight <= maxDecodableFrameSize) {
- videoTrackIndexList.add(j);
- } else {
- // The device isn't capable of playing this stream.
- }
- }
- }
- }
- int[] videoTrackIndices = Util.toArray(videoTrackIndexList);
-
- // Build the video renderer.
- DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
- ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
- videoStreamElementIndex, videoTrackIndices, videoDataSource,
- new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
- ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
- VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
- MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
-
- // Build the audio renderer.
- DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
- ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
- audioStreamElementIndex, new int[] {0}, audioDataSource,
- new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
- SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
- AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
- MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
- audioSampleSource);
- callback.onRenderers(videoRenderer, audioRenderer);
- }
-
-}
diff --git a/demo/src/main/res/layout/player_activity_full.xml b/demo/src/main/res/layout/player_activity.xml
similarity index 100%
rename from demo/src/main/res/layout/player_activity_full.xml
rename to demo/src/main/res/layout/player_activity.xml
diff --git a/demo/src/main/res/layout/player_activity_simple.xml b/demo/src/main/res/layout/player_activity_simple.xml
deleted file mode 100644
index 767f20439f..0000000000
--- a/demo/src/main/res/layout/player_activity_simple.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
index e72734e80a..96019112e7 100644
--- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
+++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
@@ -375,10 +375,13 @@ import java.util.List;
}
private void updatePositionUs() {
- positionUs = timeSourceTrackRenderer != null &&
- enabledRenderers.contains(timeSourceTrackRenderer) ?
- timeSourceTrackRenderer.getCurrentPositionUs() :
- mediaClock.getPositionUs();
+ if (timeSourceTrackRenderer != null && enabledRenderers.contains(timeSourceTrackRenderer)
+ && !timeSourceTrackRenderer.isEnded()) {
+ positionUs = timeSourceTrackRenderer.getCurrentPositionUs();
+ mediaClock.setPositionUs(positionUs);
+ } else {
+ positionUs = mediaClock.getPositionUs();
+ }
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java b/library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java
index 3a84099d86..d501e26bcb 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk.parser;
+import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
@@ -78,6 +79,11 @@ public interface Extractor {
*/
public MediaFormat getFormat();
+ /**
+ * Returns the duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
+ */
+ public long getDurationUs();
+
/**
* Returns the pssh information parsed from the stream.
*
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
index 9afdb8151e..01646ea66f 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
@@ -24,21 +24,18 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
import com.google.android.exoplayer.mp4.Atom;
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.mp4.Atom.LeafAtom;
+import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
+import com.google.android.exoplayer.mp4.Mp4Util;
import com.google.android.exoplayer.mp4.Track;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
-import com.google.android.exoplayer.util.Assertions;
-import com.google.android.exoplayer.util.CodecSpecificDataUtil;
-import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaExtractor;
-import android.util.Pair;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -67,14 +64,8 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM
| RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER;
- private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
- /** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
- private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
- /** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
- private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
- 192, 224, 256, 320, 384, 448, 512, 576, 640};
// Parser states
private static final int STATE_READING_ATOM_HEADER = 0;
@@ -82,10 +73,6 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final int STATE_READING_ENCRYPTION_DATA = 2;
private static final int STATE_READING_SAMPLE = 3;
- // Atom data offsets
- private static final int ATOM_HEADER_SIZE = 8;
- private static final int FULL_ATOM_HEADER_SIZE = 12;
-
// Atoms that the parser cares about
private static final Set PARSED_ATOMS;
static {
@@ -173,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public FragmentedMp4Extractor(int workaroundFlags) {
this.workaroundFlags = workaroundFlags;
parserState = STATE_READING_ATOM_HEADER;
- atomHeader = new ParsableByteArray(ATOM_HEADER_SIZE);
+ atomHeader = new ParsableByteArray(Mp4Util.ATOM_HEADER_SIZE);
extendedTypeScratch = new byte[16];
containerAtoms = new Stack();
fragmentRun = new TrackFragment();
@@ -210,6 +197,11 @@ public final class FragmentedMp4Extractor implements Extractor {
return track == null ? null : track.mediaFormat;
}
+ @Override
+ public long getDurationUs() {
+ return track == null ? C.UNKNOWN_TIME_US : track.durationUs;
+ }
+
@Override
public int read(NonBlockingInputStream inputStream, SampleHolder out)
throws ParserException {
@@ -227,7 +219,7 @@ public final class FragmentedMp4Extractor implements Extractor {
results |= readEncryptionData(inputStream);
break;
default:
- results |= readOrSkipSample(inputStream, out);
+ results |= readOrSkipSample(inputStream, out);
break;
}
}
@@ -276,14 +268,14 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private int readAtomHeader(NonBlockingInputStream inputStream) {
- int remainingBytes = ATOM_HEADER_SIZE - atomBytesRead;
+ int remainingBytes = Mp4Util.ATOM_HEADER_SIZE - atomBytesRead;
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
if (bytesRead == -1) {
return RESULT_END_OF_STREAM;
}
rootAtomBytesRead += bytesRead;
atomBytesRead += bytesRead;
- if (atomBytesRead != ATOM_HEADER_SIZE) {
+ if (atomBytesRead != Mp4Util.ATOM_HEADER_SIZE) {
return RESULT_NEED_MORE_DATA;
}
@@ -305,10 +297,10 @@ public final class FragmentedMp4Extractor implements Extractor {
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
enterState(STATE_READING_ATOM_HEADER);
containerAtoms.add(new ContainerAtom(atomType,
- rootAtomBytesRead + atomSize - ATOM_HEADER_SIZE));
+ rootAtomBytesRead + atomSize - Mp4Util.ATOM_HEADER_SIZE));
} else {
atomData = new ParsableByteArray(atomSize);
- System.arraycopy(atomHeader.data, 0, atomData.data, 0, ATOM_HEADER_SIZE);
+ System.arraycopy(atomHeader.data, 0, atomData.data, 0, Mp4Util.ATOM_HEADER_SIZE);
enterState(STATE_READING_ATOM_PAYLOAD);
}
} else {
@@ -371,13 +363,13 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private void onMoovContainerAtomRead(ContainerAtom moov) {
- List moovChildren = moov.children;
+ List moovChildren = moov.leafChildren;
int moovChildrenSize = moovChildren.size();
for (int i = 0; i < moovChildrenSize; i++) {
- Atom child = moovChildren.get(i);
+ LeafAtom child = moovChildren.get(i);
if (child.type == Atom.TYPE_pssh) {
- ParsableByteArray psshAtom = ((LeafAtom) child).data;
- psshAtom.setPosition(FULL_ATOM_HEADER_SIZE);
+ ParsableByteArray psshAtom = child.data;
+ psshAtom.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
int dataSize = psshAtom.readInt();
byte[] data = new byte[dataSize];
@@ -387,7 +379,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
- track = parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
+ track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
}
private void onMoofContainerAtomRead(ContainerAtom moof) {
@@ -412,7 +404,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a trex atom (defined in 14496-12).
*/
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
- trex.setPosition(FULL_ATOM_HEADER_SIZE + 4);
+ trex.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
int defaultSampleDuration = trex.readUnsignedIntToInt();
int defaultSampleSize = trex.readUnsignedIntToInt();
@@ -421,388 +413,6 @@ public final class FragmentedMp4Extractor implements Extractor {
defaultSampleSize, defaultSampleFlags);
}
- /**
- * Parses a trak atom (defined in 14496-12).
- */
- private static Track parseTrak(ContainerAtom trak) {
- ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
- int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
- Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
- || trackType == Track.TYPE_TEXT);
-
- Pair header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
- int id = header.first;
- // TODO: This value should be used to set a duration field on the Track object
- // instantiated below, however we've found examples where the value is 0. Revisit whether we
- // should set it anyway (and just have it be wrong for bad media streams).
- // long duration = header.second;
- long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
- ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
- .getContainerAtomOfType(Atom.TYPE_stbl);
-
- Pair sampleDescriptions =
- parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
- return new Track(id, trackType, timescale, sampleDescriptions.first, sampleDescriptions.second);
- }
-
- /**
- * Parses a tkhd atom (defined in 14496-12).
- *
- * @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
- * the movie header box). The duration is set to -1 if the duration is unspecified.
- */
- private static Pair parseTkhd(ParsableByteArray tkhd) {
- tkhd.setPosition(ATOM_HEADER_SIZE);
- int fullAtom = tkhd.readInt();
- int version = parseFullAtomVersion(fullAtom);
-
- tkhd.skip(version == 0 ? 8 : 16);
-
- int trackId = tkhd.readInt();
- tkhd.skip(4);
-
- boolean durationUnknown = true;
- int durationPosition = tkhd.getPosition();
- int durationByteCount = version == 0 ? 4 : 8;
- for (int i = 0; i < durationByteCount; i++) {
- if (tkhd.data[durationPosition + i] != -1) {
- durationUnknown = false;
- break;
- }
- }
- long duration;
- if (durationUnknown) {
- tkhd.skip(durationByteCount);
- duration = -1;
- } else {
- duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
- }
-
- return Pair.create(trackId, duration);
- }
-
- /**
- * Parses an hdlr atom (defined in 14496-12).
- *
- * @param hdlr The hdlr atom to parse.
- * @return The track type.
- */
- private static int parseHdlr(ParsableByteArray hdlr) {
- hdlr.setPosition(FULL_ATOM_HEADER_SIZE + 4);
- return hdlr.readInt();
- }
-
- /**
- * Parses an mdhd atom (defined in 14496-12).
- *
- * @param mdhd The mdhd atom to parse.
- * @return The media timescale, defined as the number of time units that pass in one second.
- */
- private static long parseMdhd(ParsableByteArray mdhd) {
- mdhd.setPosition(ATOM_HEADER_SIZE);
- int fullAtom = mdhd.readInt();
- int version = parseFullAtomVersion(fullAtom);
-
- mdhd.skip(version == 0 ? 8 : 16);
- return mdhd.readUnsignedInt();
- }
-
- private static Pair parseStsd(ParsableByteArray stsd) {
- stsd.setPosition(FULL_ATOM_HEADER_SIZE);
- int numberOfEntries = stsd.readInt();
- MediaFormat mediaFormat = null;
- TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
- for (int i = 0; i < numberOfEntries; i++) {
- int childStartPosition = stsd.getPosition();
- int childAtomSize = stsd.readInt();
- int childAtomType = stsd.readInt();
- if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
- || childAtomType == Atom.TYPE_encv) {
- Pair avc =
- parseAvcFromParent(stsd, childStartPosition, childAtomSize);
- mediaFormat = avc.first;
- trackEncryptionBoxes[i] = avc.second;
- } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
- || childAtomType == Atom.TYPE_ac_3) {
- Pair audioSampleEntry =
- parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
- mediaFormat = audioSampleEntry.first;
- trackEncryptionBoxes[i] = audioSampleEntry.second;
- } else if (childAtomType == Atom.TYPE_TTML) {
- mediaFormat = MediaFormat.createTtmlFormat();
- }
- stsd.setPosition(childStartPosition + childAtomSize);
- }
- return Pair.create(mediaFormat, trackEncryptionBoxes);
- }
-
- private static Pair parseAvcFromParent(ParsableByteArray parent,
- int position, int size) {
- parent.setPosition(position + ATOM_HEADER_SIZE);
-
- parent.skip(24);
- int width = parent.readUnsignedShort();
- int height = parent.readUnsignedShort();
- float pixelWidthHeightRatio = 1;
- parent.skip(50);
-
- List initializationData = null;
- TrackEncryptionBox trackEncryptionBox = null;
- int childPosition = parent.getPosition();
- while (childPosition - position < size) {
- parent.setPosition(childPosition);
- int childStartPosition = parent.getPosition();
- int childAtomSize = parent.readInt();
- int childAtomType = parent.readInt();
- if (childAtomType == Atom.TYPE_avcC) {
- initializationData = parseAvcCFromParent(parent, childStartPosition);
- } else if (childAtomType == Atom.TYPE_sinf) {
- trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
- } else if (childAtomType == Atom.TYPE_pasp) {
- pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
- }
- childPosition += childAtomSize;
- }
-
- MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
- width, height, pixelWidthHeightRatio, initializationData);
- return Pair.create(format, trackEncryptionBox);
- }
-
- private static Pair parseAudioSampleEntry(
- ParsableByteArray parent, int atomType, int position, int size) {
- parent.setPosition(position + ATOM_HEADER_SIZE);
- parent.skip(16);
- int channelCount = parent.readUnsignedShort();
- int sampleSize = parent.readUnsignedShort();
- parent.skip(4);
- int sampleRate = parent.readUnsignedFixedPoint1616();
- int bitrate = MediaFormat.NO_VALUE;
-
- byte[] initializationData = null;
- TrackEncryptionBox trackEncryptionBox = null;
- int childPosition = parent.getPosition();
- while (childPosition - position < size) {
- parent.setPosition(childPosition);
- int childStartPosition = parent.getPosition();
- int childAtomSize = parent.readInt();
- int childAtomType = parent.readInt();
- if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
- if (childAtomType == Atom.TYPE_esds) {
- initializationData = parseEsdsFromParent(parent, childStartPosition);
- // TODO: Do we really need to do this? See [Internal: b/10903778]
- // Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
- Pair audioSpecificConfig =
- CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
- sampleRate = audioSpecificConfig.first;
- channelCount = audioSpecificConfig.second;
- } else if (childAtomType == Atom.TYPE_sinf) {
- trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
- }
- } else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
- // TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
- Ac3Format ac3Format =
- parseAc3SpecificBoxFromParent(parent, childStartPosition);
- if (ac3Format != null) {
- sampleRate = ac3Format.sampleRate;
- channelCount = ac3Format.channelCount;
- bitrate = ac3Format.bitrate;
- }
-
- // TODO: Add support for encrypted AC-3.
- trackEncryptionBox = null;
- } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
- sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
- trackEncryptionBox = null;
- }
- childPosition += childAtomSize;
- }
-
- String mimeType;
- if (atomType == Atom.TYPE_ac_3) {
- mimeType = MimeTypes.AUDIO_AC3;
- } else if (atomType == Atom.TYPE_ec_3) {
- mimeType = MimeTypes.AUDIO_EC3;
- } else {
- mimeType = MimeTypes.AUDIO_AAC;
- }
-
- MediaFormat format = MediaFormat.createAudioFormat(
- mimeType, sampleSize, channelCount, sampleRate, bitrate,
- initializationData == null ? null : Collections.singletonList(initializationData));
- return Pair.create(format, trackEncryptionBox);
- }
-
- private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
- // Start of the dac3 atom (defined in ETSI TS 102 366)
- parent.setPosition(position + ATOM_HEADER_SIZE);
-
- // fscod (sample rate code)
- int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
- int sampleRate;
- switch (fscod) {
- case 0:
- sampleRate = 48000;
- break;
- case 1:
- sampleRate = 44100;
- break;
- case 2:
- sampleRate = 32000;
- break;
- default:
- // TODO: The decoder should not use this stream.
- return null;
- }
-
- int nextByte = parent.readUnsignedByte();
-
- // Map acmod (audio coding mode) onto a channel count.
- int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
-
- // lfeon (low frequency effects on)
- if ((nextByte & 0x04) != 0) {
- channelCount++;
- }
-
- // Map bit_rate_code onto a bit-rate in kbit/s.
- int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
-
- return new Ac3Format(channelCount, sampleRate, bitrate);
- }
-
- private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
- // Start of the dec3 atom (defined in ETSI TS 102 366)
- parent.setPosition(position + ATOM_HEADER_SIZE);
- // TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
- return 0;
- }
-
- private static List parseAvcCFromParent(ParsableByteArray parent, int position) {
- parent.setPosition(position + ATOM_HEADER_SIZE + 4);
- // Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
- int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
- if (nalUnitLength != 4) {
- // readSample currently relies on a nalUnitLength of 4.
- // TODO: Consider handling the case where it isn't.
- throw new IllegalStateException();
- }
- List initializationData = new ArrayList();
- // TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
- // expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
- int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
- for (int j = 0; j < numSequenceParameterSets; j++) {
- initializationData.add(parseChildNalUnit(parent));
- }
- int numPictureParamterSets = parent.readUnsignedByte();
- for (int j = 0; j < numPictureParamterSets; j++) {
- initializationData.add(parseChildNalUnit(parent));
- }
- return initializationData;
- }
-
- private static byte[] parseChildNalUnit(ParsableByteArray atom) {
- int length = atom.readUnsignedShort();
- int offset = atom.getPosition();
- atom.skip(length);
- return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
- }
-
- private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
- int size) {
- int childPosition = position + ATOM_HEADER_SIZE;
-
- TrackEncryptionBox trackEncryptionBox = null;
- while (childPosition - position < size) {
- parent.setPosition(childPosition);
- int childAtomSize = parent.readInt();
- int childAtomType = parent.readInt();
- if (childAtomType == Atom.TYPE_frma) {
- parent.readInt(); // dataFormat.
- } else if (childAtomType == Atom.TYPE_schm) {
- parent.skip(4);
- parent.readInt(); // schemeType. Expect cenc
- parent.readInt(); // schemeVersion. Expect 0x00010000
- } else if (childAtomType == Atom.TYPE_schi) {
- trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
- }
- childPosition += childAtomSize;
- }
-
- return trackEncryptionBox;
- }
-
- private static float parsePaspFromParent(ParsableByteArray parent, int position) {
- parent.setPosition(position + ATOM_HEADER_SIZE);
- int hSpacing = parent.readUnsignedIntToInt();
- int vSpacing = parent.readUnsignedIntToInt();
- return (float) hSpacing / vSpacing;
- }
-
- private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
- int size) {
- int childPosition = position + ATOM_HEADER_SIZE;
- while (childPosition - position < size) {
- parent.setPosition(childPosition);
- int childAtomSize = parent.readInt();
- int childAtomType = parent.readInt();
- if (childAtomType == Atom.TYPE_tenc) {
- parent.skip(4);
- int firstInt = parent.readInt();
- boolean defaultIsEncrypted = (firstInt >> 8) == 1;
- int defaultInitVectorSize = firstInt & 0xFF;
- byte[] defaultKeyId = new byte[16];
- parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
- return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
- }
- childPosition += childAtomSize;
- }
- return null;
- }
-
- private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
- parent.setPosition(position + ATOM_HEADER_SIZE + 4);
- // Start of the ES_Descriptor (defined in 14496-1)
- parent.skip(1); // ES_Descriptor tag
- int varIntByte = parent.readUnsignedByte();
- while (varIntByte > 127) {
- varIntByte = parent.readUnsignedByte();
- }
- parent.skip(2); // ES_ID
-
- int flags = parent.readUnsignedByte();
- if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
- parent.skip(2);
- }
- if ((flags & 0x40 /* URL_Flag */) != 0) {
- parent.skip(parent.readUnsignedShort());
- }
- if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
- parent.skip(2);
- }
-
- // Start of the DecoderConfigDescriptor (defined in 14496-1)
- parent.skip(1); // DecoderConfigDescriptor tag
- varIntByte = parent.readUnsignedByte();
- while (varIntByte > 127) {
- varIntByte = parent.readUnsignedByte();
- }
- parent.skip(13);
-
- // Start of AudioSpecificConfig (defined in 14496-3)
- parent.skip(1); // AudioSpecificConfig tag
- varIntByte = parent.readUnsignedByte();
- int varInt = varIntByte & 0x7F;
- while (varIntByte > 127) {
- varIntByte = parent.readUnsignedByte();
- varInt = varInt << 8;
- varInt |= varIntByte & 0x7F;
- }
- byte[] initializationData = new byte[varInt];
- parent.readBytes(initializationData, 0, varInt);
- return initializationData;
- }
-
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
@@ -836,11 +446,11 @@ public final class FragmentedMp4Extractor implements Extractor {
parseSenc(senc.data, out);
}
- int childrenSize = traf.children.size();
+ int childrenSize = traf.leafChildren.size();
for (int i = 0; i < childrenSize; i++) {
- Atom atom = traf.children.get(i);
+ LeafAtom atom = traf.leafChildren.get(i);
if (atom.type == Atom.TYPE_uuid) {
- parseUuid(((LeafAtom) atom).data, out, extendedTypeScratch);
+ parseUuid(atom.data, out, extendedTypeScratch);
}
}
}
@@ -848,9 +458,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
TrackFragment out) {
int vectorSize = encryptionBox.initializationVectorSize;
- saiz.setPosition(ATOM_HEADER_SIZE);
+ saiz.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = saiz.readInt();
- int flags = parseFullAtomFlags(fullAtom);
+ int flags = Mp4Util.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) {
saiz.skip(8);
}
@@ -885,9 +495,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
ParsableByteArray tfhd) {
- tfhd.setPosition(ATOM_HEADER_SIZE);
+ tfhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = tfhd.readInt();
- int flags = parseFullAtomFlags(fullAtom);
+ int flags = Mp4Util.parseFullAtomFlags(fullAtom);
tfhd.skip(4); // trackId
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
@@ -910,13 +520,13 @@ public final class FragmentedMp4Extractor implements Extractor {
/**
* Parses a tfdt atom (defined in 14496-12).
*
- * @return baseMediaDecodeTime. The sum of the decode durations of all earlier samples in the
+ * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
* media, expressed in the media's timescale.
*/
private static long parseTfdt(ParsableByteArray tfdt) {
- tfdt.setPosition(ATOM_HEADER_SIZE);
+ tfdt.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = tfdt.readInt();
- int version = parseFullAtomVersion(fullAtom);
+ int version = Mp4Util.parseFullAtomVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
}
@@ -931,9 +541,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
- trun.setPosition(ATOM_HEADER_SIZE);
+ trun.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = trun.readInt();
- int flags = parseFullAtomFlags(fullAtom);
+ int flags = Mp4Util.parseFullAtomFlags(fullAtom);
int sampleCount = trun.readUnsignedIntToInt();
if ((flags & 0x01 /* data_offset_present */) != 0) {
@@ -991,7 +601,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
byte[] extendedTypeScratch) {
- uuid.setPosition(ATOM_HEADER_SIZE);
+ uuid.setPosition(Mp4Util.ATOM_HEADER_SIZE);
uuid.readBytes(extendedTypeScratch, 0, 16);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
@@ -1010,9 +620,9 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
- senc.setPosition(ATOM_HEADER_SIZE + offset);
+ senc.setPosition(Mp4Util.ATOM_HEADER_SIZE + offset);
int fullAtom = senc.readInt();
- int flags = parseFullAtomFlags(fullAtom);
+ int flags = Mp4Util.parseFullAtomFlags(fullAtom);
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
// TODO: Implement this.
@@ -1034,9 +644,9 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a sidx atom (defined in 14496-12).
*/
private static SegmentIndex parseSidx(ParsableByteArray atom) {
- atom.setPosition(ATOM_HEADER_SIZE);
+ atom.setPosition(Mp4Util.ATOM_HEADER_SIZE);
int fullAtom = atom.readInt();
- int version = parseFullAtomVersion(fullAtom);
+ int version = Mp4Util.parseFullAtomVersion(fullAtom);
atom.skip(4);
long timescale = atom.readUnsignedInt();
@@ -1176,17 +786,8 @@ public final class FragmentedMp4Extractor implements Extractor {
inputStream.read(outputData, sampleSize);
if (track.type == Track.TYPE_VIDEO) {
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
- // delimited content. Replace length prefixes with start codes.
- int sampleOffset = outputData.position() - sampleSize;
- int position = sampleOffset;
- while (position < sampleOffset + sampleSize) {
- outputData.position(position);
- int length = readUnsignedIntToInt(outputData);
- outputData.position(position);
- outputData.put(NAL_START_CODE);
- position += length + 4;
- }
- outputData.position(sampleOffset + sampleSize);
+ // delimited content.
+ Mp4Util.replaceLengthPrefixesWithAvcStartCodes(outputData, sampleSize);
}
out.size = sampleSize;
}
@@ -1236,51 +837,4 @@ public final class FragmentedMp4Extractor implements Extractor {
}
}
- /**
- * Parses the version number out of the additional integer component of a full atom.
- */
- private static int parseFullAtomVersion(int fullAtomInt) {
- return 0x000000FF & (fullAtomInt >> 24);
- }
-
- /**
- * Parses the atom flags out of the additional integer component of a full atom.
- */
- private static int parseFullAtomFlags(int fullAtomInt) {
- return 0x00FFFFFF & fullAtomInt;
- }
-
- /**
- * Reads an unsigned integer into an integer. This method is suitable for use when it can be
- * assumed that the top bit will always be set to zero.
- *
- * @throws IllegalArgumentException If the top bit of the input data is set.
- */
- private static int readUnsignedIntToInt(ByteBuffer data) {
- int result = 0xFF & data.get();
- for (int i = 1; i < 4; i++) {
- result <<= 8;
- result |= 0xFF & data.get();
- }
- if (result < 0) {
- throw new IllegalArgumentException("Top bit not zero: " + result);
- }
- return result;
- }
-
- /** Represents the format for AC-3 audio. */
- private static final class Ac3Format {
-
- public final int channelCount;
- public final int sampleRate;
- public final int bitrate;
-
- public Ac3Format(int channelCount, int sampleRate, int bitrate) {
- this.channelCount = channelCount;
- this.sampleRate = sampleRate;
- this.bitrate = bitrate;
- }
-
- }
-
}
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
index 5d23ae8eb1..829d604a77 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk.parser.webm;
+import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
@@ -184,6 +185,11 @@ public final class WebmExtractor implements Extractor {
return format;
}
+ @Override
+ public long getDurationUs() {
+ return durationUs == UNKNOWN ? C.UNKNOWN_TIME_US : durationUs;
+ }
+
@Override
public Map getPsshInfo() {
// TODO: Parse pssh data from Webm streams.
diff --git a/library/src/main/java/com/google/android/exoplayer/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
index 7136d26890..3451d31929 100644
--- a/library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
+++ b/library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
@@ -15,52 +15,64 @@
*/
package com.google.android.exoplayer.mp4;
+import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
public abstract class Atom {
- public static final int TYPE_avc1 = 0x61766331;
- public static final int TYPE_avc3 = 0x61766333;
- public static final int TYPE_esds = 0x65736473;
- public static final int TYPE_mdat = 0x6D646174;
- public static final int TYPE_mp4a = 0x6D703461;
- public static final int TYPE_ac_3 = 0x61632D33; // ac-3
- public static final int TYPE_dac3 = 0x64616333;
- public static final int TYPE_ec_3 = 0x65632D33; // ec-3
- public static final int TYPE_dec3 = 0x64656333;
- public static final int TYPE_tfdt = 0x74666474;
- public static final int TYPE_tfhd = 0x74666864;
- public static final int TYPE_trex = 0x74726578;
- public static final int TYPE_trun = 0x7472756E;
- public static final int TYPE_sidx = 0x73696478;
- public static final int TYPE_moov = 0x6D6F6F76;
- public static final int TYPE_trak = 0x7472616B;
- public static final int TYPE_mdia = 0x6D646961;
- public static final int TYPE_minf = 0x6D696E66;
- public static final int TYPE_stbl = 0x7374626C;
- public static final int TYPE_avcC = 0x61766343;
- public static final int TYPE_moof = 0x6D6F6F66;
- public static final int TYPE_traf = 0x74726166;
- public static final int TYPE_mvex = 0x6D766578;
- public static final int TYPE_tkhd = 0x746B6864;
- public static final int TYPE_mdhd = 0x6D646864;
- public static final int TYPE_hdlr = 0x68646C72;
- public static final int TYPE_stsd = 0x73747364;
- public static final int TYPE_pssh = 0x70737368;
- public static final int TYPE_sinf = 0x73696E66;
- public static final int TYPE_schm = 0x7363686D;
- public static final int TYPE_schi = 0x73636869;
- public static final int TYPE_tenc = 0x74656E63;
- public static final int TYPE_encv = 0x656E6376;
- public static final int TYPE_enca = 0x656E6361;
- public static final int TYPE_frma = 0x66726D61;
- public static final int TYPE_saiz = 0x7361697A;
- public static final int TYPE_uuid = 0x75756964;
- public static final int TYPE_senc = 0x73656E63;
- public static final int TYPE_pasp = 0x70617370;
- public static final int TYPE_TTML = 0x54544D4C;
+ public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
+ public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
+ public static final int TYPE_esds = getAtomTypeInteger("esds");
+ public static final int TYPE_mdat = getAtomTypeInteger("mdat");
+ public static final int TYPE_mp4a = getAtomTypeInteger("mp4a");
+ public static final int TYPE_ac_3 = getAtomTypeInteger("ac-3");
+ public static final int TYPE_dac3 = getAtomTypeInteger("dac3");
+ public static final int TYPE_ec_3 = getAtomTypeInteger("ec-3");
+ public static final int TYPE_dec3 = getAtomTypeInteger("dec3");
+ public static final int TYPE_tfdt = getAtomTypeInteger("tfdt");
+ public static final int TYPE_tfhd = getAtomTypeInteger("tfhd");
+ public static final int TYPE_trex = getAtomTypeInteger("trex");
+ public static final int TYPE_trun = getAtomTypeInteger("trun");
+ public static final int TYPE_sidx = getAtomTypeInteger("sidx");
+ public static final int TYPE_moov = getAtomTypeInteger("moov");
+ public static final int TYPE_trak = getAtomTypeInteger("trak");
+ public static final int TYPE_mdia = getAtomTypeInteger("mdia");
+ public static final int TYPE_minf = getAtomTypeInteger("minf");
+ public static final int TYPE_stbl = getAtomTypeInteger("stbl");
+ public static final int TYPE_avcC = getAtomTypeInteger("avcC");
+ public static final int TYPE_moof = getAtomTypeInteger("moof");
+ public static final int TYPE_traf = getAtomTypeInteger("traf");
+ public static final int TYPE_mvex = getAtomTypeInteger("mvex");
+ public static final int TYPE_tkhd = getAtomTypeInteger("tkhd");
+ public static final int TYPE_mdhd = getAtomTypeInteger("mdhd");
+ public static final int TYPE_hdlr = getAtomTypeInteger("hdlr");
+ public static final int TYPE_stsd = getAtomTypeInteger("stsd");
+ public static final int TYPE_pssh = getAtomTypeInteger("pssh");
+ public static final int TYPE_sinf = getAtomTypeInteger("sinf");
+ public static final int TYPE_schm = getAtomTypeInteger("schm");
+ public static final int TYPE_schi = getAtomTypeInteger("schi");
+ public static final int TYPE_tenc = getAtomTypeInteger("tenc");
+ public static final int TYPE_encv = getAtomTypeInteger("encv");
+ public static final int TYPE_enca = getAtomTypeInteger("enca");
+ public static final int TYPE_frma = getAtomTypeInteger("frma");
+ public static final int TYPE_saiz = getAtomTypeInteger("saiz");
+ public static final int TYPE_uuid = getAtomTypeInteger("uuid");
+ public static final int TYPE_senc = getAtomTypeInteger("senc");
+ public static final int TYPE_pasp = getAtomTypeInteger("pasp");
+ public static final int TYPE_TTML = getAtomTypeInteger("TTML");
+ public static final int TYPE_vmhd = getAtomTypeInteger("vmhd");
+ public static final int TYPE_smhd = getAtomTypeInteger("smhd");
+ public static final int TYPE_mp4v = getAtomTypeInteger("mp4v");
+ public static final int TYPE_stts = getAtomTypeInteger("stts");
+ public static final int TYPE_stss = getAtomTypeInteger("stss");
+ public static final int TYPE_stsc = getAtomTypeInteger("stsc");
+ public static final int TYPE_stsz = getAtomTypeInteger("stsz");
+ public static final int TYPE_stco = getAtomTypeInteger("stco");
+ public static final int TYPE_co64 = getAtomTypeInteger("co64");
public final int type;
@@ -68,6 +80,12 @@ public abstract class Atom {
this.type = type;
}
+ @Override
+ public String toString() {
+ return getAtomTypeString(type);
+ }
+
+ /** An MP4 atom that is a leaf. */
public static final class LeafAtom extends Atom {
public final ParsableByteArray data;
@@ -79,43 +97,75 @@ public abstract class Atom {
}
+ /** An MP4 atom that has child atoms. */
public static final class ContainerAtom extends Atom {
- public final ArrayList children;
- public final int endByteOffset;
+ public final long endByteOffset;
+ public final List leafChildren;
+ public final List containerChildren;
- public ContainerAtom(int type, int endByteOffset) {
+ public ContainerAtom(int type, long endByteOffset) {
super(type);
+
+ leafChildren = new ArrayList();
+ containerChildren = new ArrayList();
this.endByteOffset = endByteOffset;
- children = new ArrayList();
}
- public void add(Atom atom) {
- children.add(atom);
+ public void add(LeafAtom atom) {
+ leafChildren.add(atom);
+ }
+
+ public void add(ContainerAtom atom) {
+ containerChildren.add(atom);
}
public LeafAtom getLeafAtomOfType(int type) {
- int childrenSize = children.size();
+ int childrenSize = leafChildren.size();
for (int i = 0; i < childrenSize; i++) {
- Atom atom = children.get(i);
+ LeafAtom atom = leafChildren.get(i);
if (atom.type == type) {
- return (LeafAtom) atom;
+ return atom;
}
}
return null;
}
public ContainerAtom getContainerAtomOfType(int type) {
- int childrenSize = children.size();
+ int childrenSize = containerChildren.size();
for (int i = 0; i < childrenSize; i++) {
- Atom atom = children.get(i);
+ ContainerAtom atom = containerChildren.get(i);
if (atom.type == type) {
- return (ContainerAtom) atom;
+ return atom;
}
}
return null;
}
+ @Override
+ public String toString() {
+ return getAtomTypeString(type)
+ + " leaves: " + Arrays.toString(leafChildren.toArray(new LeafAtom[0]))
+ + " containers: " + Arrays.toString(containerChildren.toArray(new ContainerAtom[0]));
+ }
+
+ }
+
+ private static String getAtomTypeString(int type) {
+ return "" + (char) (type >> 24)
+ + (char) ((type >> 16) & 0xFF)
+ + (char) ((type >> 8) & 0xFF)
+ + (char) (type & 0xFF);
+ }
+
+ private static int getAtomTypeInteger(String typeName) {
+ Assertions.checkArgument(typeName.length() == 4);
+ int result = 0;
+ for (int i = 0; i < 4; i++) {
+ result <<= 8;
+ result |= typeName.charAt(i);
+ }
+ return result;
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
new file mode 100644
index 0000000000..dc7e580ca5
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.mp4;
+
+import com.google.android.exoplayer.C;
+import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
+import com.google.android.exoplayer.util.Assertions;
+import com.google.android.exoplayer.util.CodecSpecificDataUtil;
+import com.google.android.exoplayer.util.MimeTypes;
+import com.google.android.exoplayer.util.ParsableByteArray;
+import com.google.android.exoplayer.util.Util;
+
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
+public final class CommonMp4AtomParsers {
+
+ /** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
+ private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
+ /** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
+ private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
+ 192, 224, 256, 320, 384, 448, 512, 576, 640};
+
+ /**
+ * Parses a trak atom (defined in 14496-12).
+ *
+ * @return A {@link Track} instance.
+ */
+ public static Track parseTrak(Atom.ContainerAtom trak) {
+ Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
+ int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
+ Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
+ || trackType == Track.TYPE_TEXT || trackType == Track.TYPE_TIME_CODE);
+
+ Pair header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
+ int id = header.first;
+ long duration = header.second;
+ long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
+ long durationUs;
+ if (duration == -1) {
+ durationUs = C.UNKNOWN_TIME_US;
+ } else {
+ durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale);
+ }
+ Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
+ .getContainerAtomOfType(Atom.TYPE_stbl);
+
+ Pair sampleDescriptions =
+ parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
+ return new Track(id, trackType, timescale, durationUs, sampleDescriptions.first,
+ sampleDescriptions.second);
+ }
+
+ /**
+ * Parses a tkhd atom (defined in 14496-12).
+ *
+ * @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
+ * the movie header box). The duration is set to -1 if the duration is unspecified.
+ */
+ private static Pair parseTkhd(ParsableByteArray tkhd) {
+ tkhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
+ int fullAtom = tkhd.readInt();
+ int version = Mp4Util.parseFullAtomVersion(fullAtom);
+
+ tkhd.skip(version == 0 ? 8 : 16);
+
+ int trackId = tkhd.readInt();
+ tkhd.skip(4);
+
+ boolean durationUnknown = true;
+ int durationPosition = tkhd.getPosition();
+ int durationByteCount = version == 0 ? 4 : 8;
+ for (int i = 0; i < durationByteCount; i++) {
+ if (tkhd.data[durationPosition + i] != -1) {
+ durationUnknown = false;
+ break;
+ }
+ }
+ long duration;
+ if (durationUnknown) {
+ tkhd.skip(durationByteCount);
+ duration = -1;
+ } else {
+ duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
+ }
+
+ return Pair.create(trackId, duration);
+ }
+
+ /**
+ * Parses an hdlr atom.
+ *
+ * @param hdlr The hdlr atom to parse.
+ * @return The track type.
+ */
+ private static int parseHdlr(ParsableByteArray hdlr) {
+ hdlr.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
+ return hdlr.readInt();
+ }
+
+ /**
+ * Parses an mdhd atom (defined in 14496-12).
+ *
+ * @param mdhd The mdhd atom to parse.
+ * @return The media timescale, defined as the number of time units that pass in one second.
+ */
+ private static long parseMdhd(ParsableByteArray mdhd) {
+ mdhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
+ int fullAtom = mdhd.readInt();
+ int version = Mp4Util.parseFullAtomVersion(fullAtom);
+
+ mdhd.skip(version == 0 ? 8 : 16);
+ return mdhd.readUnsignedInt();
+ }
+
+ private static Pair parseStsd(ParsableByteArray stsd) {
+ stsd.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
+ int numberOfEntries = stsd.readInt();
+ MediaFormat mediaFormat = null;
+ TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
+ for (int i = 0; i < numberOfEntries; i++) {
+ int childStartPosition = stsd.getPosition();
+ int childAtomSize = stsd.readInt();
+ Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
+ int childAtomType = stsd.readInt();
+ if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
+ || childAtomType == Atom.TYPE_encv) {
+ Pair avc =
+ parseAvcFromParent(stsd, childStartPosition, childAtomSize);
+ mediaFormat = avc.first;
+ trackEncryptionBoxes[i] = avc.second;
+ } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
+ || childAtomType == Atom.TYPE_ac_3) {
+ Pair audioSampleEntry =
+ parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
+ mediaFormat = audioSampleEntry.first;
+ trackEncryptionBoxes[i] = audioSampleEntry.second;
+ } else if (childAtomType == Atom.TYPE_TTML) {
+ mediaFormat = MediaFormat.createTtmlFormat();
+ } else if (childAtomType == Atom.TYPE_mp4v) {
+ mediaFormat = parseMp4vFromParent(stsd, childStartPosition, childAtomSize);
+ }
+ stsd.setPosition(childStartPosition + childAtomSize);
+ }
+ return Pair.create(mediaFormat, trackEncryptionBoxes);
+ }
+
+ /** Returns the media format for an avc1 box. */
+ private static Pair parseAvcFromParent(ParsableByteArray parent,
+ int position, int size) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+
+ parent.skip(24);
+ int width = parent.readUnsignedShort();
+ int height = parent.readUnsignedShort();
+ float pixelWidthHeightRatio = 1;
+ parent.skip(50);
+
+ List initializationData = null;
+ TrackEncryptionBox trackEncryptionBox = null;
+ int childPosition = parent.getPosition();
+ while (childPosition - position < size) {
+ parent.setPosition(childPosition);
+ int childStartPosition = parent.getPosition();
+ int childAtomSize = parent.readInt();
+ if (childAtomSize == 0 && parent.getPosition() - position == size) {
+ // Handle optional terminating four zero bytes in MOV files.
+ break;
+ }
+ Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
+ int childAtomType = parent.readInt();
+ if (childAtomType == Atom.TYPE_avcC) {
+ initializationData = parseAvcCFromParent(parent, childStartPosition);
+ } else if (childAtomType == Atom.TYPE_sinf) {
+ trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
+ } else if (childAtomType == Atom.TYPE_pasp) {
+ pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
+ }
+ childPosition += childAtomSize;
+ }
+
+ MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
+ width, height, pixelWidthHeightRatio, initializationData);
+ return Pair.create(format, trackEncryptionBox);
+ }
+
+ private static List parseAvcCFromParent(ParsableByteArray parent, int position) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
+ // Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
+ int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
+ if (nalUnitLength != 4) {
+ // readSample currently relies on a nalUnitLength of 4.
+ // TODO: Consider handling the case where it isn't.
+ throw new IllegalStateException();
+ }
+ List initializationData = new ArrayList();
+ // TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
+ // expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
+ int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
+ for (int j = 0; j < numSequenceParameterSets; j++) {
+ initializationData.add(Mp4Util.parseChildNalUnit(parent));
+ }
+ int numPictureParameterSets = parent.readUnsignedByte();
+ for (int j = 0; j < numPictureParameterSets; j++) {
+ initializationData.add(Mp4Util.parseChildNalUnit(parent));
+ }
+ return initializationData;
+ }
+
+ private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
+ int size) {
+ int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
+
+ TrackEncryptionBox trackEncryptionBox = null;
+ while (childPosition - position < size) {
+ parent.setPosition(childPosition);
+ int childAtomSize = parent.readInt();
+ int childAtomType = parent.readInt();
+ if (childAtomType == Atom.TYPE_frma) {
+ parent.readInt(); // dataFormat.
+ } else if (childAtomType == Atom.TYPE_schm) {
+ parent.skip(4);
+ parent.readInt(); // schemeType. Expect cenc
+ parent.readInt(); // schemeVersion. Expect 0x00010000
+ } else if (childAtomType == Atom.TYPE_schi) {
+ trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
+ }
+ childPosition += childAtomSize;
+ }
+
+ return trackEncryptionBox;
+ }
+
+ private static float parsePaspFromParent(ParsableByteArray parent, int position) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+ int hSpacing = parent.readUnsignedIntToInt();
+ int vSpacing = parent.readUnsignedIntToInt();
+ return (float) hSpacing / vSpacing;
+ }
+
+ private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
+ int size) {
+ int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
+ while (childPosition - position < size) {
+ parent.setPosition(childPosition);
+ int childAtomSize = parent.readInt();
+ int childAtomType = parent.readInt();
+ if (childAtomType == Atom.TYPE_tenc) {
+ parent.skip(4);
+ int firstInt = parent.readInt();
+ boolean defaultIsEncrypted = (firstInt >> 8) == 1;
+ int defaultInitVectorSize = firstInt & 0xFF;
+ byte[] defaultKeyId = new byte[16];
+ parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
+ return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
+ }
+ childPosition += childAtomSize;
+ }
+ return null;
+ }
+
+ /** Returns the media format for an mp4v box. */
+ private static MediaFormat parseMp4vFromParent(ParsableByteArray parent,
+ int position, int size) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+
+ parent.skip(24);
+ int width = parent.readUnsignedShort();
+ int height = parent.readUnsignedShort();
+ parent.skip(50);
+
+ List initializationData = new ArrayList(1);
+ int childPosition = parent.getPosition();
+ while (childPosition - position < size) {
+ parent.setPosition(childPosition);
+ int childStartPosition = parent.getPosition();
+ int childAtomSize = parent.readInt();
+ Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
+ int childAtomType = parent.readInt();
+ if (childAtomType == Atom.TYPE_esds) {
+ initializationData.add(parseEsdsFromParent(parent, childStartPosition));
+ }
+ childPosition += childAtomSize;
+ }
+
+ return MediaFormat.createVideoFormat(
+ MimeTypes.VIDEO_MP4V, MediaFormat.NO_VALUE, width, height, initializationData);
+ }
+
+ private static Pair parseAudioSampleEntry(
+ ParsableByteArray parent, int atomType, int position, int size) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+ parent.skip(16);
+ int channelCount = parent.readUnsignedShort();
+ int sampleSize = parent.readUnsignedShort();
+ parent.skip(4);
+ int sampleRate = parent.readUnsignedFixedPoint1616();
+ int bitrate = MediaFormat.NO_VALUE;
+
+ byte[] initializationData = null;
+ TrackEncryptionBox trackEncryptionBox = null;
+ int childPosition = parent.getPosition();
+ while (childPosition - position < size) {
+ parent.setPosition(childPosition);
+ int childStartPosition = parent.getPosition();
+ int childAtomSize = parent.readInt();
+ Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
+ int childAtomType = parent.readInt();
+ if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
+ if (childAtomType == Atom.TYPE_esds) {
+ initializationData = parseEsdsFromParent(parent, childStartPosition);
+ // TODO: Do we really need to do this? See [Internal: b/10903778]
+ // Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
+ Pair audioSpecificConfig =
+ CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
+ sampleRate = audioSpecificConfig.first;
+ channelCount = audioSpecificConfig.second;
+ } else if (childAtomType == Atom.TYPE_sinf) {
+ trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
+ }
+ } else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
+ // TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
+ Ac3Format ac3Format =
+ parseAc3SpecificBoxFromParent(parent, childStartPosition);
+ if (ac3Format != null) {
+ sampleRate = ac3Format.sampleRate;
+ channelCount = ac3Format.channelCount;
+ bitrate = ac3Format.bitrate;
+ }
+
+ // TODO: Add support for encrypted AC-3.
+ trackEncryptionBox = null;
+ } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
+ sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
+ trackEncryptionBox = null;
+ }
+ childPosition += childAtomSize;
+ }
+
+ String mimeType;
+ if (atomType == Atom.TYPE_ac_3) {
+ mimeType = MimeTypes.AUDIO_AC3;
+ } else if (atomType == Atom.TYPE_ec_3) {
+ mimeType = MimeTypes.AUDIO_EC3;
+ } else {
+ mimeType = MimeTypes.AUDIO_AAC;
+ }
+
+ MediaFormat format = MediaFormat.createAudioFormat(
+ mimeType, sampleSize, channelCount, sampleRate, bitrate,
+ initializationData == null ? null : Collections.singletonList(initializationData));
+ return Pair.create(format, trackEncryptionBox);
+ }
+
+ /** Returns codec-specific initialization data contained in an esds box. */
+ private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
+ // Start of the ES_Descriptor (defined in 14496-1)
+ parent.skip(1); // ES_Descriptor tag
+ int varIntByte = parent.readUnsignedByte();
+ while (varIntByte > 127) {
+ varIntByte = parent.readUnsignedByte();
+ }
+ parent.skip(2); // ES_ID
+
+ int flags = parent.readUnsignedByte();
+ if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
+ parent.skip(2);
+ }
+ if ((flags & 0x40 /* URL_Flag */) != 0) {
+ parent.skip(parent.readUnsignedShort());
+ }
+ if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
+ parent.skip(2);
+ }
+
+ // Start of the DecoderConfigDescriptor (defined in 14496-1)
+ parent.skip(1); // DecoderConfigDescriptor tag
+ varIntByte = parent.readUnsignedByte();
+ while (varIntByte > 127) {
+ varIntByte = parent.readUnsignedByte();
+ }
+ parent.skip(13);
+
+ // Start of AudioSpecificConfig (defined in 14496-3)
+ parent.skip(1); // AudioSpecificConfig tag
+ varIntByte = parent.readUnsignedByte();
+ int varInt = varIntByte & 0x7F;
+ while (varIntByte > 127) {
+ varIntByte = parent.readUnsignedByte();
+ varInt = varInt << 8;
+ varInt |= varIntByte & 0x7F;
+ }
+ byte[] initializationData = new byte[varInt];
+ parent.readBytes(initializationData, 0, varInt);
+ return initializationData;
+ }
+
+ private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
+ // Start of the dac3 atom (defined in ETSI TS 102 366)
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+
+ // fscod (sample rate code)
+ int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
+ int sampleRate;
+ switch (fscod) {
+ case 0:
+ sampleRate = 48000;
+ break;
+ case 1:
+ sampleRate = 44100;
+ break;
+ case 2:
+ sampleRate = 32000;
+ break;
+ default:
+ // TODO: The decoder should not use this stream.
+ return null;
+ }
+
+ int nextByte = parent.readUnsignedByte();
+
+ // Map acmod (audio coding mode) onto a channel count.
+ int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
+
+ // lfeon (low frequency effects on)
+ if ((nextByte & 0x04) != 0) {
+ channelCount++;
+ }
+
+ // Map bit_rate_code onto a bit-rate in kbit/s.
+ int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
+
+ return new Ac3Format(channelCount, sampleRate, bitrate);
+ }
+
+ private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
+ // Start of the dec3 atom (defined in ETSI TS 102 366)
+ parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
+ // TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
+ return 0;
+ }
+
+ private CommonMp4AtomParsers() {
+ // Prevent instantiation.
+ }
+
+ /** Represents the format for AC-3 audio. */
+ private static final class Ac3Format {
+
+ public final int channelCount;
+ public final int sampleRate;
+ public final int bitrate;
+
+ public Ac3Format(int channelCount, int sampleRate, int bitrate) {
+ this.channelCount = channelCount;
+ this.sampleRate = sampleRate;
+ this.bitrate = bitrate;
+ }
+
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java b/library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
new file mode 100644
index 0000000000..471689e24a
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer.mp4;
+
+import com.google.android.exoplayer.util.CodecSpecificDataUtil;
+import com.google.android.exoplayer.util.ParsableByteArray;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility methods and constants for parsing fragmented and unfragmented MP4 files.
+ */
+public final class Mp4Util {
+
+ /** Size of an atom header, in bytes. */
+ public static final int ATOM_HEADER_SIZE = 8;
+
+ /** Size of a long atom header, in bytes. */
+ public static final int LONG_ATOM_HEADER_SIZE = 16;
+
+ /** Size of a full atom header, in bytes. */
+ public static final int FULL_ATOM_HEADER_SIZE = 12;
+
+ /** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
+ private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
+
+ /** Parses the version number out of the additional integer component of a full atom. */
+ public static int parseFullAtomVersion(int fullAtomInt) {
+ return 0x000000FF & (fullAtomInt >> 24);
+ }
+
+ /** Parses the atom flags out of the additional integer component of a full atom. */
+ public static int parseFullAtomFlags(int fullAtomInt) {
+ return 0x00FFFFFF & fullAtomInt;
+ }
+
+ /**
+ * Reads an unsigned integer into an integer. This method is suitable for use when it can be
+ * assumed that the top bit will always be set to zero.
+ *
+ * @throws IllegalArgumentException If the top bit of the input data is set.
+ */
+ public static int readUnsignedIntToInt(ByteBuffer data) {
+ int result = 0xFF & data.get();
+ for (int i = 1; i < 4; i++) {
+ result <<= 8;
+ result |= 0xFF & data.get();
+ }
+ if (result < 0) {
+ throw new IllegalArgumentException("Top bit not zero: " + result);
+ }
+ return result;
+ }
+
+ /**
+ * Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
+ * {@code size} bytes preceding the buffer's position.
+ */
+ public static void replaceLengthPrefixesWithAvcStartCodes(ByteBuffer buffer, int size) {
+ int sampleOffset = buffer.position() - size;
+ int position = sampleOffset;
+ while (position < sampleOffset + size) {
+ buffer.position(position);
+ int length = readUnsignedIntToInt(buffer);
+ buffer.position(position);
+ buffer.put(NAL_START_CODE);
+ position += length + 4;
+ }
+ buffer.position(sampleOffset + size);
+ }
+
+ /** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
+ public static byte[] parseChildNalUnit(ParsableByteArray atom) {
+ int length = atom.readUnsignedShort();
+ int offset = atom.getPosition();
+ atom.skip(length);
+ return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/mp4/Track.java b/library/src/main/java/com/google/android/exoplayer/mp4/Track.java
index f718ec17b9..313e3272f6 100644
--- a/library/src/main/java/com/google/android/exoplayer/mp4/Track.java
+++ b/library/src/main/java/com/google/android/exoplayer/mp4/Track.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.mp4;
+import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
@@ -43,6 +44,10 @@ public final class Track {
* Type of a meta track.
*/
public static final int TYPE_META = 0x6D657461;
+ /**
+ * Type of a time-code track.
+ */
+ public static final int TYPE_TIME_CODE = 0x746D6364;
/**
* The track identifier.
@@ -50,7 +55,8 @@ public final class Track {
public final int id;
/**
- * One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT} and {@link #TYPE_META}.
+ * One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT}, {@link #TYPE_META} and
+ * {@link #TYPE_TIME_CODE}.
*/
public final int type;
@@ -59,6 +65,11 @@ public final class Track {
*/
public final long timescale;
+ /**
+ * The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
+ */
+ public final long durationUs;
+
/**
* The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise.
*/
@@ -69,11 +80,12 @@ public final class Track {
*/
public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes;
- public Track(int id, int type, long timescale, MediaFormat mediaFormat,
+ public Track(int id, int type, long timescale, long durationUs, MediaFormat mediaFormat,
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes) {
this.id = id;
this.type = type;
this.timescale = timescale;
+ this.durationUs = durationUs;
this.mediaFormat = mediaFormat;
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
index 7cc4384649..6e04658ef9 100644
--- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
@@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
: Track.TYPE_AUDIO;
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
- extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat,
- trackEncryptionBoxes));
+ extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale,
+ initialManifest.durationUs, mediaFormat, trackEncryptionBoxes));
extractors.put(trackIndex, extractor);
}
this.maxHeight = maxHeight;
diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java
index 6ff3015afa..7b977aa7cc 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java
@@ -123,7 +123,7 @@ public class SubtitleView extends View {
@Override
public void setBackgroundColor(int color) {
backgroundColor = color;
- invalidate();
+ forceUpdate(false);
}
/**
@@ -134,8 +134,7 @@ public class SubtitleView extends View {
public void setText(CharSequence text) {
textBuilder.setLength(0);
textBuilder.append(text);
- hasMeasurements = false;
- requestLayout();
+ forceUpdate(true);
}
/**
@@ -147,9 +146,7 @@ public class SubtitleView extends View {
if (textPaint.getTextSize() != size) {
textPaint.setTextSize(size);
innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
- hasMeasurements = false;
- requestLayout();
- invalidate();
+ forceUpdate(true);
}
}
@@ -165,17 +162,22 @@ public class SubtitleView extends View {
edgeColor = style.edgeColor;
setTypeface(style.typeface);
super.setBackgroundColor(style.windowColor);
- hasMeasurements = false;
- requestLayout();
+ forceUpdate(true);
}
private void setTypeface(Typeface typeface) {
if (textPaint.getTypeface() != typeface) {
textPaint.setTypeface(typeface);
+ forceUpdate(true);
+ }
+ }
+
+ private void forceUpdate(boolean needsLayout) {
+ if (needsLayout) {
hasMeasurements = false;
requestLayout();
- invalidate();
}
+ invalidate();
}
@Override
diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
index be9abaec56..5d331a78b2 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
@@ -56,20 +56,28 @@ public class WebvttParser implements SubtitleParser {
private static final Pattern WEBVTT_METADATA_HEADER =
Pattern.compile(WEBVTT_METADATA_HEADER_STRING);
+ private static final String WEBVTT_CUE_IDENTIFIER_STRING = "^(?!.*(-->)).*$";
+ private static final Pattern WEBVTT_CUE_IDENTIFIER =
+ Pattern.compile(WEBVTT_CUE_IDENTIFIER_STRING);
+
private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}";
private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING);
private static final Pattern MEDIA_TIMESTAMP_OFFSET = Pattern.compile(OFFSET + "\\d+");
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+");
+ private static final String WEBVTT_CUE_TAG_STRING = "\\<.*?>";
+
private final boolean strictParsing;
+ private final boolean filterTags;
public WebvttParser() {
- this(true);
+ this(true, true);
}
- public WebvttParser(boolean strictParsing) {
+ public WebvttParser(boolean strictParsing, boolean filterTags) {
this.strictParsing = strictParsing;
+ this.filterTags = filterTags;
}
@Override
@@ -137,8 +145,15 @@ public class WebvttParser implements SubtitleParser {
// process the cues and text
while ((line = webvttData.readLine()) != null) {
+ // parse the cue identifier (if present) {
+ Matcher matcher = WEBVTT_CUE_IDENTIFIER.matcher(line);
+ if (matcher.find()) {
+ // ignore the identifier (we currently don't use it) and read the next line
+ line = webvttData.readLine();
+ }
+
// parse the cue timestamps
- Matcher matcher = WEBVTT_TIMESTAMP.matcher(line);
+ matcher = WEBVTT_TIMESTAMP.matcher(line);
long startTime;
long endTime;
String text = "";
@@ -159,7 +174,7 @@ public class WebvttParser implements SubtitleParser {
// parse text
while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
- text += line.trim() + "\n";
+ text += processCueText(line.trim()) + "\n";
}
WebvttCue cue = new WebvttCue(startTime, endTime, text);
@@ -193,6 +208,19 @@ public class WebvttParser implements SubtitleParser {
return startTimeUs;
}
+ protected String processCueText(String line) {
+ if (filterTags) {
+ line = line.replaceAll(WEBVTT_CUE_TAG_STRING, "");
+ line = line.replaceAll("<", "<");
+ line = line.replaceAll(">", ">");
+ line = line.replaceAll(" ", " ");
+ line = line.replaceAll("&", "&");
+ return line;
+ } else {
+ return line;
+ }
+ }
+
protected void handleNoncompliantLine(String line) throws ParserException {
if (strictParsing) {
throw new ParserException("Unexpected line: " + line);
diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
index 768d7061a1..0438f87371 100644
--- a/library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
@@ -27,24 +27,27 @@ public class ByteArrayDataSource implements DataSource {
private final byte[] data;
private int readPosition;
+ private int remainingBytes;
/**
* @param data The data to be read.
*/
public ByteArrayDataSource(byte[] data) {
- this.data = Assertions.checkNotNull(data);
+ Assertions.checkNotNull(data);
+ Assertions.checkArgument(data.length > 0);
+ this.data = data;
}
@Override
public long open(DataSpec dataSpec) throws IOException {
- if (dataSpec.length == C.LENGTH_UNBOUNDED) {
- Assertions.checkArgument(dataSpec.position < data.length);
- } else {
- Assertions.checkArgument(dataSpec.position + dataSpec.length <= data.length);
- }
readPosition = (int) dataSpec.position;
- return (dataSpec.length == C.LENGTH_UNBOUNDED) ? (data.length - dataSpec.position)
- : dataSpec.length;
+ remainingBytes = (int) ((dataSpec.length == C.LENGTH_UNBOUNDED)
+ ? (data.length - dataSpec.position) : dataSpec.length);
+ if (remainingBytes <= 0 || readPosition + remainingBytes > data.length) {
+ throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length
+ + "], length: " + data.length);
+ }
+ return remainingBytes;
}
@Override
@@ -54,8 +57,13 @@ public class ByteArrayDataSource implements DataSource {
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
+ if (remainingBytes == 0) {
+ return -1;
+ }
+ length = Math.min(length, remainingBytes);
System.arraycopy(data, readPosition, buffer, offset, length);
readPosition += length;
+ remainingBytes -= length;
return length;
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
index dd55492f65..11d49103e7 100644
--- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
+++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
@@ -29,6 +29,7 @@ public class MimeTypes {
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc";
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
+ public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";