Commit 4efc0abd by Oliver Woodman

Implement DASH Live.

Note: This adds support for the majority of DASH live streams,
however we do not yet correctly support live streams that rely
on UtcTimingElements in their manifests.

Issue: #52
parent 6652f864
...@@ -47,7 +47,7 @@ public class DemoUtil { ...@@ -47,7 +47,7 @@ public class DemoUtil {
public static final String CONTENT_TYPE_EXTRA = "content_type"; public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id"; public static final String CONTENT_ID_EXTRA = "content_id";
public static final int TYPE_DASH_VOD = 0; public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1; public static final int TYPE_SS = 1;
public static final int TYPE_OTHER = 2; public static final int TYPE_OTHER = 2;
......
...@@ -46,13 +46,13 @@ package com.google.android.exoplayer.demo; ...@@ -46,13 +46,13 @@ package com.google.android.exoplayer.demo;
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?" "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&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D." + "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH_VOD, false, + "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, false,
false), false),
new Sample("Google Play (DASH)", "3aa39fa2cc27967f", new Sample("Google Play (DASH)", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?" "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&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A." + "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH_VOD, false, + "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, false,
false), false),
new Sample("Super speed (SmoothStreaming)", "uid:ss:superspeed", new Sample("Super speed (SmoothStreaming)", "uid:ss:superspeed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism", "http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
...@@ -66,13 +66,13 @@ package com.google.android.exoplayer.demo; ...@@ -66,13 +66,13 @@ package com.google.android.exoplayer.demo;
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?" "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&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D." + "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH_VOD, false, + "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, false,
true), true),
new Sample("Google Play", "3aa39fa2cc27967f", new Sample("Google Play", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?" "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&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A." + "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH_VOD, false, + "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, false,
true), true),
}; };
...@@ -81,12 +81,12 @@ package com.google.android.exoplayer.demo; ...@@ -81,12 +81,12 @@ package com.google.android.exoplayer.demo;
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?" "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&" + "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=A3EC7EE53ABE601B357F7CAB8B54AD0702CA85A7." + "expire=19000000000&signature=A3EC7EE53ABE601B357F7CAB8B54AD0702CA85A7."
+ "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH_VOD, false, true), + "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH, false, true),
new Sample("Google Play", "3aa39fa2cc27967f", new Sample("Google Play", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?" "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&" + "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=B752B262C6D7262EC4E4EB67901E5D8F7058A81D." + "expire=19000000000&signature=B752B262C6D7262EC4E4EB67901E5D8F7058A81D."
+ "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH_VOD, false, true), + "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH, false, true),
}; };
public static final Sample[] SMOOTHSTREAMING = new Sample[] { public static final Sample[] SMOOTHSTREAMING = new Sample[] {
...@@ -103,32 +103,32 @@ package com.google.android.exoplayer.demo; ...@@ -103,32 +103,32 @@ package com.google.android.exoplayer.demo;
"http://www.youtube.com/api/manifest/dash/id/d286538032258a1c/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=41EA40A027A125A16292E0A5E3277A3B5FA9B938." + "&expire=19000000000&signature=41EA40A027A125A16292E0A5E3277A3B5FA9B938."
+ "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH, true, true),
new Sample("WV: HDCP not required", "48fcc369939ac96c", new Sample("WV: HDCP not required", "48fcc369939ac96c",
"http://www.youtube.com/api/manifest/dash/id/48fcc369939ac96c/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=315911BDCEED0FB0C763455BDCC97449DAAFA9E8." + "&expire=19000000000&signature=315911BDCEED0FB0C763455BDCC97449DAAFA9E8."
+ "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH, true, true),
new Sample("WV: HDCP required", "e06c39f1151da3df", new Sample("WV: HDCP required", "e06c39f1151da3df",
"http://www.youtube.com/api/manifest/dash/id/e06c39f1151da3df/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=A47A1E13E7243BD567601A75F79B34644D0DC592." + "&expire=19000000000&signature=A47A1E13E7243BD567601A75F79B34644D0DC592."
+ "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH, true, true),
new Sample("WV: Secure video path required", "0894c7c8719b28a0", new Sample("WV: Secure video path required", "0894c7c8719b28a0",
"http://www.youtube.com/api/manifest/dash/id/0894c7c8719b28a0/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=2847EE498970F6B45176766CD2802FEB4D4CB7B2." + "&expire=19000000000&signature=2847EE498970F6B45176766CD2802FEB4D4CB7B2."
+ "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH, true, true),
new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a", new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a",
"http://www.youtube.com/api/manifest/dash/id/efd045b1eb61888a/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=61611F115EEEC7BADE5536827343FFFE2D83D14F." + "&expire=19000000000&signature=61611F115EEEC7BADE5536827343FFFE2D83D14F."
+ "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH, true, true),
new Sample("WV: 30s license duration", "f9a34cab7b05881a", new Sample("WV: 30s license duration", "f9a34cab7b05881a",
"http://www.youtube.com/api/manifest/dash/id/f9a34cab7b05881a/source/youtube?" "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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6." + "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH, true, true),
}; };
public static final Sample[] MISC = new Sample[] { public static final Sample[] MISC = new Sample[] {
......
...@@ -19,7 +19,7 @@ import com.google.android.exoplayer.ExoPlayer; ...@@ -19,7 +19,7 @@ import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.VideoSurfaceView; import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.demo.DemoUtil; import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.R; import com.google.android.exoplayer.demo.R;
import com.google.android.exoplayer.demo.full.player.DashVodRendererBuilder; 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.DefaultRendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer; 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.DemoPlayer.RendererBuilder;
...@@ -172,8 +172,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba ...@@ -172,8 +172,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
case DemoUtil.TYPE_SS: case DemoUtil.TYPE_SS:
return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(), contentId, return new SmoothStreamingRendererBuilder(userAgent, contentUri.toString(), contentId,
new SmoothStreamingTestMediaDrmCallback(), debugTextView); new SmoothStreamingTestMediaDrmCallback(), debugTextView);
case DemoUtil.TYPE_DASH_VOD: case DemoUtil.TYPE_DASH:
return new DashVodRendererBuilder(userAgent, contentUri.toString(), contentId, return new DashRendererBuilder(userAgent, contentUri.toString(), contentId,
new WidevineTestMediaDrmCallback(contentId), debugTextView); new WidevineTestMediaDrmCallback(contentId), debugTextView);
default: default:
return new DefaultRendererBuilder(this, contentUri, debugTextView); return new DefaultRendererBuilder(this, contentUri, debugTextView);
......
...@@ -64,7 +64,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -64,7 +64,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200; private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60; private static final int AUDIO_BUFFER_SEGMENTS = 60;
private static final int TTML_BUFFER_SEGMENTS = 2; private static final int TEXT_BUFFER_SEGMENTS = 2;
private static final int LIVE_EDGE_LATENCY_MS = 30000; private static final int LIVE_EDGE_LATENCY_MS = 30000;
private final String userAgent; private final String userAgent;
...@@ -149,10 +149,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -149,10 +149,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
} }
} }
} }
int[] videoTrackIndices = new int[videoTrackIndexList.size()]; int[] videoTrackIndices = Util.toArray(videoTrackIndexList);
for (int i = 0; i < videoTrackIndexList.size(); i++) {
videoTrackIndices[i] = videoTrackIndexList.get(i);
}
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
...@@ -221,7 +218,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -221,7 +218,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
} }
textChunkSource = new MultiTrackChunkSource(textChunkSources); textChunkSource = new MultiTrackChunkSource(textChunkSources);
ChunkSampleSource ttmlSampleSource = new ChunkSampleSource(textChunkSource, loadControl, ChunkSampleSource ttmlSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
TTML_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_TEXT); DemoPlayer.TYPE_TEXT);
textRenderer = new TextTrackRenderer(ttmlSampleSource, new TtmlParser(), player, textRenderer = new TextTrackRenderer(ttmlSampleSource, new TtmlParser(), player,
mainHandler.getLooper()); mainHandler.getLooper());
......
...@@ -40,22 +40,26 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; ...@@ -40,22 +40,26 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; 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.media.MediaCodec;
import android.os.Handler; import android.os.Handler;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* A {@link RendererBuilder} for DASH VOD. * A {@link RendererBuilder} for DASH.
*/ */
/* package */ class DashVodRendererBuilder implements RendererBuilder, /* package */ class DashRendererBuilder implements RendererBuilder,
ManifestCallback<MediaPresentationDescription> { ManifestCallback<MediaPresentationDescription> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024; private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200; private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60; private static final int AUDIO_BUFFER_SEGMENTS = 60;
private static final int LIVE_EDGE_LATENCY_MS = 30000;
private final SimplePlayerActivity playerActivity; private final SimplePlayerActivity playerActivity;
private final String userAgent; private final String userAgent;
...@@ -63,8 +67,9 @@ import java.util.ArrayList; ...@@ -63,8 +67,9 @@ import java.util.ArrayList;
private final String contentId; private final String contentId;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
public DashVodRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url, public DashRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
String contentId) { String contentId) {
this.playerActivity = playerActivity; this.playerActivity = playerActivity;
this.userAgent = userAgent; this.userAgent = userAgent;
...@@ -76,8 +81,8 @@ import java.util.ArrayList; ...@@ -76,8 +81,8 @@ import java.util.ArrayList;
public void buildRenderers(RendererBuilderCallback callback) { public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback; this.callback = callback;
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
ManifestFetcher<MediaPresentationDescription> manifestFetcher = manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(parser, contentId, url,
new ManifestFetcher<MediaPresentationDescription>(parser, contentId, url, userAgent); userAgent);
manifestFetcher.singleLoad(playerActivity.getMainLooper(), this); manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
} }
...@@ -88,48 +93,50 @@ import java.util.ArrayList; ...@@ -88,48 +93,50 @@ import java.util.ArrayList;
@Override @Override
public void onManifest(String contentId, MediaPresentationDescription manifest) { public void onManifest(String contentId, MediaPresentationDescription manifest) {
Period period = manifest.periods.get(0);
Handler mainHandler = playerActivity.getMainHandler(); Handler mainHandler = playerActivity.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE)); LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Obtain Representations for playback. // Determine which video representations we should use for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize(); int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
Representation audioRepresentation = null; int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
ArrayList<Representation> videoRepresentationsList = new ArrayList<Representation>(); List<Representation> videoRepresentations =
Period period = manifest.periods.get(0); period.adaptationSets.get(videoAdaptationSetIndex).representations;
for (int i = 0; i < period.adaptationSets.size(); i++) { ArrayList<Integer> videoRepresentationIndexList = new ArrayList<Integer>();
AdaptationSet adaptationSet = period.adaptationSets.get(i); for (int i = 0; i < videoRepresentations.size(); i++) {
int adaptationSetType = adaptationSet.type; Format format = videoRepresentations.get(i).format;
for (int j = 0; j < adaptationSet.representations.size(); j++) { if (format.width * format.height > maxDecodableFrameSize) {
Representation representation = adaptationSet.representations.get(j); // Filtering stream that device cannot play
if (audioRepresentation == null && adaptationSetType == AdaptationSet.TYPE_AUDIO) { } else if (!format.mimeType.equals(MimeTypes.VIDEO_MP4)
audioRepresentation = representation; && !format.mimeType.equals(MimeTypes.VIDEO_WEBM)) {
} else if (adaptationSetType == AdaptationSet.TYPE_VIDEO) { // Filtering unsupported mime type
Format format = representation.format;
if (format.width * format.height <= maxDecodableFrameSize) {
videoRepresentationsList.add(representation);
} else { } else {
// The device isn't capable of playing this stream. videoRepresentationIndexList.add(i);
}
} }
} }
}
Representation[] videoRepresentations = new Representation[videoRepresentationsList.size()];
videoRepresentationsList.toArray(videoRepresentations);
// Build the video renderer. // Build the video renderer.
final MediaCodecVideoTrackRenderer videoRenderer;
if (videoRepresentationIndexList.isEmpty()) {
videoRenderer = null;
} else {
int[] videoRepresentationIndices = Util.toArray(videoRepresentationIndexList);
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource videoChunkSource = new DashChunkSource(videoDataSource, ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations); videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter),
LIVE_EDGE_LATENCY_MS);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true); VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
}
// Build the audio renderer. // Build the audio renderer.
int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource audioChunkSource = new DashChunkSource(audioDataSource, ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new FormatEvaluator.FixedEvaluator(), audioRepresentation); new int[] {0}, audioDataSource, new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true); AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer( MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
......
...@@ -61,10 +61,6 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call ...@@ -61,10 +61,6 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call
private static final String TAG = "PlayerActivity"; private static final String TAG = "PlayerActivity";
public static final int TYPE_DASH_VOD = 0;
public static final int TYPE_SS_VOD = 1;
public static final int TYPE_OTHER = 2;
private MediaController mediaController; private MediaController mediaController;
private Handler mainHandler; private Handler mainHandler;
private View shutterView; private View shutterView;
...@@ -90,7 +86,7 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call ...@@ -90,7 +86,7 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call
Intent intent = getIntent(); Intent intent = getIntent();
contentUri = intent.getData(); contentUri = intent.getData();
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, TYPE_OTHER); contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA); contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
mainHandler = new Handler(getMainLooper()); mainHandler = new Handler(getMainLooper());
...@@ -165,11 +161,11 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call ...@@ -165,11 +161,11 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call
private RendererBuilder getRendererBuilder() { private RendererBuilder getRendererBuilder() {
String userAgent = DemoUtil.getUserAgent(this); String userAgent = DemoUtil.getUserAgent(this);
switch (contentType) { switch (contentType) {
case TYPE_SS_VOD: case DemoUtil.TYPE_SS:
return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(), return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
contentId); contentId);
case TYPE_DASH_VOD: case DemoUtil.TYPE_DASH:
return new DashVodRendererBuilder(this, userAgent, contentUri.toString(), contentId); return new DashRendererBuilder(this, userAgent, contentUri.toString(), contentId);
default: default:
return new DefaultRendererBuilder(this, contentUri); return new DefaultRendererBuilder(this, contentUri);
} }
......
...@@ -38,6 +38,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; ...@@ -38,6 +38,7 @@ import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.Util;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Handler; import android.os.Handler;
...@@ -115,10 +116,7 @@ import java.util.ArrayList; ...@@ -115,10 +116,7 @@ import java.util.ArrayList;
} }
} }
} }
int[] videoTrackIndices = new int[videoTrackIndexList.size()]; int[] videoTrackIndices = Util.toArray(videoTrackIndexList);
for (int i = 0; i < videoTrackIndexList.size(); i++) {
videoTrackIndices[i] = videoTrackIndexList.get(i);
}
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
......
...@@ -46,6 +46,10 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent { ...@@ -46,6 +46,10 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
this.selectedSource = sources[0]; this.selectedSource = sources[0];
} }
public MultiTrackChunkSource(List<ChunkSource> sources) {
this(toChunkSourceArray(sources));
}
/** /**
* Gets the number of tracks that this source can switch between. May be called safely from any * Gets the number of tracks that this source can switch between. May be called safely from any
* thread. * thread.
...@@ -107,4 +111,10 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent { ...@@ -107,4 +111,10 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
selectedSource.onChunkLoadError(chunk, e); selectedSource.onChunkLoadError(chunk, e);
} }
private static ChunkSource[] toChunkSourceArray(List<ChunkSource> sources) {
ChunkSource[] chunkSourceArray = new ChunkSource[sources.size()];
sources.toArray(chunkSourceArray);
return chunkSourceArray;
}
} }
...@@ -56,4 +56,21 @@ public class Period { ...@@ -56,4 +56,21 @@ public class Period {
this.adaptationSets = Collections.unmodifiableList(adaptationSets); this.adaptationSets = Collections.unmodifiableList(adaptationSets);
} }
/**
* Returns the index of the first adaptation set of a given type, or -1 if no adaptation set of
* the specified type exists.
*
* @param type An adaptation set type.
* @return The index of the first adaptation set of the specified type, or -1.
*/
public int getAdaptationSetIndex(int type) {
int adaptationCount = adaptationSets.size();
for (int i = 0; i < adaptationCount; i++) {
if (adaptationSets.get(i).type == type) {
return i;
}
}
return -1;
}
} }
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.util; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Pair; import android.util.Pair;
...@@ -29,13 +30,26 @@ import java.net.URLConnection; ...@@ -29,13 +30,26 @@ import java.net.URLConnection;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
/** /**
* Performs both single and repeated loads of media manfifests. * Performs both single and repeated loads of media manifests.
* *
* @param <T> The type of manifest. * @param <T> The type of manifest.
*/ */
public class ManifestFetcher<T> implements Loader.Callback { public class ManifestFetcher<T> implements Loader.Callback {
/** /**
* Interface definition for a callback to be notified of {@link ManifestFetcher} events.
*/
public interface EventListener {
public void onManifestRefreshStarted();
public void onManifestRefreshed();
public void onManifestError(IOException e);
}
/**
* Callback for the result of a single load. * Callback for the result of a single load.
* *
* @param <T> The type of manifest. * @param <T> The type of manifest.
...@@ -61,9 +75,12 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -61,9 +75,12 @@ public class ManifestFetcher<T> implements Loader.Callback {
} }
/* package */ final ManifestParser<T> parser; /* package */ final ManifestParser<T> parser;
/* package */ final String manifestUrl;
/* package */ final String contentId; /* package */ final String contentId;
/* package */ final String userAgent; /* package */ final String userAgent;
private final Handler eventHandler;
private final EventListener eventListener;
/* package */ volatile String manifestUrl;
private int enabledCount; private int enabledCount;
private Loader loader; private Loader loader;
...@@ -76,6 +93,11 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -76,6 +93,11 @@ public class ManifestFetcher<T> implements Loader.Callback {
private volatile T manifest; private volatile T manifest;
private volatile long manifestLoadTimestamp; private volatile long manifestLoadTimestamp;
public ManifestFetcher(ManifestParser<T> parser, String contentId, String manifestUrl,
String userAgent) {
this(parser, contentId, manifestUrl, userAgent, null, null);
}
/** /**
* @param parser A parser to parse the loaded manifest data. * @param parser A parser to parse the loaded manifest data.
* @param contentId The content id of the content being loaded. May be null. * @param contentId The content id of the content being loaded. May be null.
...@@ -83,11 +105,22 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -83,11 +105,22 @@ public class ManifestFetcher<T> implements Loader.Callback {
* @param userAgent The User-Agent string that should be used. * @param userAgent The User-Agent string that should be used.
*/ */
public ManifestFetcher(ManifestParser<T> parser, String contentId, String manifestUrl, public ManifestFetcher(ManifestParser<T> parser, String contentId, String manifestUrl,
String userAgent) { String userAgent, Handler eventHandler, EventListener eventListener) {
this.parser = parser; this.parser = parser;
this.contentId = contentId; this.contentId = contentId;
this.manifestUrl = manifestUrl; this.manifestUrl = manifestUrl;
this.userAgent = userAgent; this.userAgent = userAgent;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
}
/**
* Updates the manifest location.
*
* @param manifestUrl The manifest location.
*/
public void updateManifestUrl(String manifestUrl) {
this.manifestUrl = manifestUrl;
} }
/** /**
...@@ -173,6 +206,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -173,6 +206,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
if (!loader.isLoading()) { if (!loader.isLoading()) {
currentLoadable = new ManifestLoadable(); currentLoadable = new ManifestLoadable();
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
notifyManifestRefreshStarted();
} }
} }
...@@ -187,6 +221,8 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -187,6 +221,8 @@ public class ManifestFetcher<T> implements Loader.Callback {
manifestLoadTimestamp = SystemClock.elapsedRealtime(); manifestLoadTimestamp = SystemClock.elapsedRealtime();
loadExceptionCount = 0; loadExceptionCount = 0;
loadException = null; loadException = null;
notifyManifestRefreshed();
} }
@Override @Override
...@@ -204,12 +240,47 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -204,12 +240,47 @@ public class ManifestFetcher<T> implements Loader.Callback {
loadExceptionCount++; loadExceptionCount++;
loadExceptionTimestamp = SystemClock.elapsedRealtime(); loadExceptionTimestamp = SystemClock.elapsedRealtime();
loadException = new IOException(exception); loadException = new IOException(exception);
notifyManifestError(loadException);
} }
private long getRetryDelayMillis(long errorCount) { private long getRetryDelayMillis(long errorCount) {
return Math.min((errorCount - 1) * 1000, 5000); return Math.min((errorCount - 1) * 1000, 5000);
} }
private void notifyManifestRefreshStarted() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onManifestRefreshStarted();
}
});
}
}
private void notifyManifestRefreshed() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onManifestRefreshed();
}
});
}
}
private void notifyManifestError(final IOException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onManifestError(e);
}
});
}
}
private class SingleFetchHelper implements Loader.Callback { private class SingleFetchHelper implements Loader.Callback {
private final Looper callbackLooper; private final Looper callbackLooper;
......
...@@ -70,12 +70,14 @@ public class PlayerControl implements MediaPlayerControl { ...@@ -70,12 +70,14 @@ public class PlayerControl implements MediaPlayerControl {
@Override @Override
public int getCurrentPosition() { public int getCurrentPosition() {
return (int) exoPlayer.getCurrentPosition(); return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0
: (int) exoPlayer.getCurrentPosition();
} }
@Override @Override
public int getDuration() { public int getDuration() {
return (int) exoPlayer.getDuration(); return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0
: (int) exoPlayer.getDuration();
} }
@Override @Override
...@@ -95,8 +97,9 @@ public class PlayerControl implements MediaPlayerControl { ...@@ -95,8 +97,9 @@ public class PlayerControl implements MediaPlayerControl {
@Override @Override
public void seekTo(int timeMillis) { public void seekTo(int timeMillis) {
// MediaController arrow keys generate unbounded values. long seekPosition = exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0
exoPlayer.seekTo(Math.min(Math.max(0, timeMillis), getDuration())); : Math.min(Math.max(0, timeMillis), getDuration());
exoPlayer.seekTo(seekPosition);
} }
} }
...@@ -399,4 +399,22 @@ public final class Util { ...@@ -399,4 +399,22 @@ public final class Util {
return scaledTimestamps; return scaledTimestamps;
} }
/**
* Converts a list of integers to a primitive array.
*
* @param list A list of integers.
* @return The list in array form, or null if the input list was null.
*/
public static int[] toArray(List<Integer> list) {
if (list == null) {
return null;
}
int length = list.size();
int[] intArray = new int[length];
for (int i = 0; i < length; i++) {
intArray[i] = list.get(i);
}
return intArray;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment