Commit e34ef401 by ojw28

Merge pull request #503 from google/dev

dev -> dev-webm-vp9-opus
parents ece3ac63 cb1e6ad4
Showing with 790 additions and 1038 deletions
/*
* 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;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
/**
* Utility methods for the demo application.
*/
public class DemoUtil {
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_MP4 = 3;
public static final int TYPE_MP3 = 4;
public static final int TYPE_M4A = 5;
public static final int TYPE_WEBM = 6;
public static final int TYPE_TS = 7;
public static final int TYPE_AAC = 8;
private static final CookieManager defaultCookieManager;
static {
defaultCookieManager = new CookieManager();
defaultCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}
public static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws IOException {
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(data != null);
urlConnection.setDoInput(true);
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
}
}
if (data != null) {
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(data);
out.close();
}
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
return convertInputStreamToByteArray(in);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
private static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte data[] = new byte[1024];
int count;
while ((count = inputStream.read(data)) != -1) {
bos.write(data, 0, count);
}
bos.flush();
bos.close();
inputStream.close();
bytes = bos.toByteArray();
return bytes;
}
public static void setDefaultCookieManager() {
CookieHandler currentHandler = CookieHandler.getDefault();
if (currentHandler != defaultCookieManager) {
CookieHandler.setDefault(defaultCookieManager);
}
}
}
...@@ -25,7 +25,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; ...@@ -25,7 +25,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.ExtractorRendererBuilder; import com.google.android.exoplayer.demo.player.ExtractorRendererBuilder;
import com.google.android.exoplayer.demo.player.HlsRendererBuilder; import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder; import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.demo.player.UnsupportedDrmException; import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor; import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
...@@ -37,6 +37,7 @@ import com.google.android.exoplayer.metadata.TxxxMetadata; ...@@ -37,6 +37,7 @@ import com.google.android.exoplayer.metadata.TxxxMetadata;
import com.google.android.exoplayer.text.CaptionStyleCompat; import com.google.android.exoplayer.text.CaptionStyleCompat;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleLayout; import com.google.android.exoplayer.text.SubtitleLayout;
import com.google.android.exoplayer.util.DebugTextViewHelper;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import com.google.android.exoplayer.util.VerboseLogUtil; import com.google.android.exoplayer.util.VerboseLogUtil;
...@@ -65,6 +66,9 @@ import android.widget.PopupMenu.OnMenuItemClickListener; ...@@ -65,6 +66,9 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -75,14 +79,30 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -75,14 +79,30 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener, DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener,
AudioCapabilitiesReceiver.Listener { AudioCapabilitiesReceiver.Listener {
public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2;
public static final int TYPE_MP4 = 3;
public static final int TYPE_MP3 = 4;
public static final int TYPE_FMP4 = 5;
public static final int TYPE_WEBM = 6;
public static final int TYPE_TS = 7;
public static final int TYPE_AAC = 8;
public static final int TYPE_M4A = 9;
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";
private static final String TAG = "PlayerActivity"; private static final String TAG = "PlayerActivity";
private static final int MENU_GROUP_TRACKS = 1; private static final int MENU_GROUP_TRACKS = 1;
private static final int ID_OFFSET = 2; private static final int ID_OFFSET = 2;
private static final CookieManager defaultCookieManager;
static {
defaultCookieManager = new CookieManager();
defaultCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}
private EventLogger eventLogger; private EventLogger eventLogger;
private MediaController mediaController; private MediaController mediaController;
private View debugRootView; private View debugRootView;
...@@ -97,6 +117,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -97,6 +117,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private Button retryButton; private Button retryButton;
private DemoPlayer player; private DemoPlayer player;
private DebugTextViewHelper debugViewHelper;
private boolean playerNeedsPrepare; private boolean playerNeedsPrepare;
private long playerPosition; private long playerPosition;
...@@ -162,7 +183,10 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -162,7 +183,10 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
audioButton = (Button) findViewById(R.id.audio_controls); audioButton = (Button) findViewById(R.id.audio_controls);
textButton = (Button) findViewById(R.id.text_controls); textButton = (Button) findViewById(R.id.text_controls);
DemoUtil.setDefaultCookieManager(); CookieHandler currentHandler = CookieHandler.getDefault();
if (currentHandler != defaultCookieManager) {
CookieHandler.setDefault(defaultCookieManager);
}
} }
@Override @Override
...@@ -220,31 +244,26 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -220,31 +244,26 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private RendererBuilder getRendererBuilder() { private RendererBuilder getRendererBuilder() {
String userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); String userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
switch (contentType) { switch (contentType) {
case DemoUtil.TYPE_SS: case TYPE_SS:
return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(), return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
new SmoothStreamingTestMediaDrmCallback(), debugTextView); new SmoothStreamingTestMediaDrmCallback());
case DemoUtil.TYPE_DASH: case TYPE_DASH:
return new DashRendererBuilder(this, userAgent, contentUri.toString(), return new DashRendererBuilder(this, userAgent, contentUri.toString(),
new WidevineTestMediaDrmCallback(contentId), debugTextView, audioCapabilities); new WidevineTestMediaDrmCallback(contentId), audioCapabilities);
case DemoUtil.TYPE_HLS: case TYPE_HLS:
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), debugTextView, return new HlsRendererBuilder(this, userAgent, contentUri.toString(), audioCapabilities);
audioCapabilities); case TYPE_M4A: // There are no file format differences between M4A and MP4.
case DemoUtil.TYPE_M4A: // There are no file format differences between M4A and MP4. case TYPE_MP4:
case DemoUtil.TYPE_MP4: return new ExtractorRendererBuilder(this, userAgent, contentUri, new Mp4Extractor());
return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, case TYPE_MP3:
new Mp4Extractor()); return new ExtractorRendererBuilder(this, userAgent, contentUri, new Mp3Extractor());
case DemoUtil.TYPE_MP3: case TYPE_TS:
return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, return new ExtractorRendererBuilder(this, userAgent, contentUri,
new Mp3Extractor());
case DemoUtil.TYPE_TS:
return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView,
new TsExtractor(0, audioCapabilities)); new TsExtractor(0, audioCapabilities));
case DemoUtil.TYPE_AAC: case TYPE_AAC:
return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, return new ExtractorRendererBuilder(this, userAgent, contentUri, new AdtsExtractor());
new AdtsExtractor()); case TYPE_WEBM:
case DemoUtil.TYPE_WEBM: return new ExtractorRendererBuilder(this, userAgent, contentUri, new WebmExtractor());
return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView,
new WebmExtractor());
default: default:
throw new IllegalStateException("Unsupported type: " + contentType); throw new IllegalStateException("Unsupported type: " + contentType);
} }
...@@ -265,6 +284,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -265,6 +284,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
player.addListener(eventLogger); player.addListener(eventLogger);
player.setInfoListener(eventLogger); player.setInfoListener(eventLogger);
player.setInternalErrorListener(eventLogger); player.setInternalErrorListener(eventLogger);
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
debugViewHelper.start();
} }
if (playerNeedsPrepare) { if (playerNeedsPrepare) {
player.prepare(); player.prepare();
...@@ -277,6 +298,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -277,6 +298,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private void releasePlayer() { private void releasePlayer() {
if (player != null) { if (player != null) {
debugViewHelper.stop();
debugViewHelper = null;
playerPosition = player.getCurrentPosition(); playerPosition = player.getCurrentPosition();
player.release(); player.release();
player = null; player = null;
...@@ -322,11 +345,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -322,11 +345,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
if (e instanceof UnsupportedDrmException) { if (e instanceof UnsupportedDrmException) {
// Special case DRM failures. // Special case DRM failures.
UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e; UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
int stringId = unsupportedDrmException.reason == UnsupportedDrmException.REASON_NO_DRM int stringId = Util.SDK_INT < 18 ? R.string.drm_error_not_supported
? R.string.drm_error_not_supported
: unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME : unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
? R.string.drm_error_unsupported_scheme ? R.string.drm_error_unsupported_scheme : R.string.drm_error_unknown;
: R.string.drm_error_unknown;
Toast.makeText(getApplicationContext(), stringId, Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), stringId, Toast.LENGTH_LONG).show();
} }
playerNeedsPrepare = true; playerNeedsPrepare = true;
......
...@@ -47,12 +47,12 @@ import java.util.Locale; ...@@ -47,12 +47,12 @@ import java.util.Locale;
"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,source,id,as&ip=0.0.0.0&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7." + "ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7."
+ "8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", DemoUtil.TYPE_DASH), + "8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", PlayerActivity.TYPE_DASH),
new Sample("Google Play", new Sample("Google Play",
"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,source,id,as&ip=0.0.0.0&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=A2716F75795F5D2AF0E88962FFCD10DB79384F29." + "ipbits=0&expire=19000000000&signature=A2716F75795F5D2AF0E88962FFCD10DB79384F29."
+ "84308FF04844498CE6FBCE4731507882B8307798&key=ik0", DemoUtil.TYPE_DASH), + "84308FF04844498CE6FBCE4731507882B8307798&key=ik0", PlayerActivity.TYPE_DASH),
}; };
public static final Sample[] YOUTUBE_DASH_WEBM = new Sample[] { public static final Sample[] YOUTUBE_DASH_WEBM = new Sample[] {
...@@ -60,21 +60,21 @@ import java.util.Locale; ...@@ -60,21 +60,21 @@ import java.util.Locale;
"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,source,id,as&ip=0.0.0.0&" + "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=249B04F79E984D7F86B4D8DB48AE6FAF41C17AB3." + "ipbits=0&expire=19000000000&signature=249B04F79E984D7F86B4D8DB48AE6FAF41C17AB3."
+ "7B9F0EC0505E1566E59B8E488E9419F253DDF413&key=ik0", DemoUtil.TYPE_DASH), + "7B9F0EC0505E1566E59B8E488E9419F253DDF413&key=ik0", PlayerActivity.TYPE_DASH),
new Sample("Google Play", new Sample("Google Play",
"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,source,id,as&ip=0.0.0.0&" + "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=B1C2A74783AC1CC4865EB312D7DD2D48230CC9FD." + "ipbits=0&expire=19000000000&signature=B1C2A74783AC1CC4865EB312D7DD2D48230CC9FD."
+ "BD153B9882175F1F94BFE5141A5482313EA38E8D&key=ik0", DemoUtil.TYPE_DASH), + "BD153B9882175F1F94BFE5141A5482313EA38E8D&key=ik0", PlayerActivity.TYPE_DASH),
}; };
public static final Sample[] SMOOTHSTREAMING = new Sample[] { public static final Sample[] SMOOTHSTREAMING = new Sample[] {
new Sample("Super speed", new Sample("Super speed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism", "http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
DemoUtil.TYPE_SS), PlayerActivity.TYPE_SS),
new Sample("Super speed (PlayReady)", new Sample("Super speed (PlayReady)",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism", "http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism",
DemoUtil.TYPE_SS), PlayerActivity.TYPE_SS),
}; };
public static final Sample[] WIDEVINE_GTS = new Sample[] { public static final Sample[] WIDEVINE_GTS = new Sample[] {
...@@ -82,72 +82,72 @@ import java.util.Locale; ...@@ -82,72 +82,72 @@ import java.util.Locale;
"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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=477CF7D478BE26C205045D507E9358F85F84C065." + "&ipbits=0&expire=19000000000&signature=477CF7D478BE26C205045D507E9358F85F84C065."
+ "8971631EB657BC33EC2F48A2FF4211956760C3E9&key=ik0", DemoUtil.TYPE_DASH), + "8971631EB657BC33EC2F48A2FF4211956760C3E9&key=ik0", PlayerActivity.TYPE_DASH),
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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=171DAE48D00B5BE7434BC1A9F84DAE0463C7EA7A." + "&ipbits=0&expire=19000000000&signature=171DAE48D00B5BE7434BC1A9F84DAE0463C7EA7A."
+ "0925B4DBB5605BEE9F5D088C48F25F5108E96191&key=ik0", DemoUtil.TYPE_DASH), + "0925B4DBB5605BEE9F5D088C48F25F5108E96191&key=ik0", PlayerActivity.TYPE_DASH),
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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=8D3B8AF4E3F72B7F127C8D0D39B7AFCF37B30519." + "&ipbits=0&expire=19000000000&signature=8D3B8AF4E3F72B7F127C8D0D39B7AFCF37B30519."
+ "A118BADEBF3582AD2CC257B0EE6E579C6955D8AA&key=ik0", DemoUtil.TYPE_DASH), + "A118BADEBF3582AD2CC257B0EE6E579C6955D8AA&key=ik0", PlayerActivity.TYPE_DASH),
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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=A41D835C7387885A4A820628F57E481E00095931." + "&ipbits=0&expire=19000000000&signature=A41D835C7387885A4A820628F57E481E00095931."
+ "9D50DBEEB5E37344647EE11BDA129A7FCDE8B7B9&key=ik0", DemoUtil.TYPE_DASH), + "9D50DBEEB5E37344647EE11BDA129A7FCDE8B7B9&key=ik0", PlayerActivity.TYPE_DASH),
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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=A97C9032C9D0C74F1643DB17C178873887C229E4." + "&ipbits=0&expire=19000000000&signature=A97C9032C9D0C74F1643DB17C178873887C229E4."
+ "0A657BF6F23C8BC1538F276137383478330B76DE&key=ik0", DemoUtil.TYPE_DASH), + "0A657BF6F23C8BC1538F276137383478330B76DE&key=ik0", PlayerActivity.TYPE_DASH),
new Sample("WV: 30s license duration (fails at ~30s)", "f9a34cab7b05881a", new Sample("WV: 30s license duration (fails at ~30s)", "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,source,id,as&ip=0.0.0.0" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0"
+ "&ipbits=0&expire=19000000000&signature=80648A12A7D5FC1FA02B52B4250E4EB74CF0C5FD." + "&ipbits=0&expire=19000000000&signature=80648A12A7D5FC1FA02B52B4250E4EB74CF0C5FD."
+ "66A261130CA137AA5C541EA9CED2DBF240829EE6&key=ik0", DemoUtil.TYPE_DASH), + "66A261130CA137AA5C541EA9CED2DBF240829EE6&key=ik0", PlayerActivity.TYPE_DASH),
}; };
public static final Sample[] HLS = new Sample[] { public static final Sample[] HLS = new Sample[] {
new Sample("Apple master playlist", new Sample("Apple master playlist",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/" "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS), + "bipbop_4x3_variant.m3u8", PlayerActivity.TYPE_HLS),
new Sample("Apple master playlist advanced", new Sample("Apple master playlist advanced",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/" "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS), + "bipbop_16x9_variant.m3u8", PlayerActivity.TYPE_HLS),
new Sample("Apple TS media playlist", new Sample("Apple TS media playlist",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/" "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
+ "prog_index.m3u8", DemoUtil.TYPE_HLS), + "prog_index.m3u8", PlayerActivity.TYPE_HLS),
new Sample("Apple AAC media playlist", new Sample("Apple AAC media playlist",
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/" "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/"
+ "prog_index.m3u8", DemoUtil.TYPE_HLS), + "prog_index.m3u8", PlayerActivity.TYPE_HLS),
new Sample("Apple ID3 metadata", "http://devimages.apple.com/samplecode/adDemo/ad.m3u8", new Sample("Apple ID3 metadata", "http://devimages.apple.com/samplecode/adDemo/ad.m3u8",
DemoUtil.TYPE_HLS), PlayerActivity.TYPE_HLS),
}; };
public static final Sample[] MISC = new Sample[] { public static final Sample[] MISC = new Sample[] {
new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4", new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4",
DemoUtil.TYPE_MP4), PlayerActivity.TYPE_MP4),
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/" new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac", + "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
DemoUtil.TYPE_AAC), PlayerActivity.TYPE_AAC),
new Sample("Apple TS 10s", "https://devimages.apple.com.edgekey.net/streaming/examples/" new Sample("Apple TS 10s", "https://devimages.apple.com.edgekey.net/streaming/examples/"
+ "bipbop_4x3/gear1/fileSequence0.ts", + "bipbop_4x3/gear1/fileSequence0.ts",
DemoUtil.TYPE_TS), PlayerActivity.TYPE_TS),
new Sample("Big Buck Bunny (MP4 Video)", new Sample("Big Buck Bunny (MP4 Video)",
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube&" "http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube&"
+ "sparams=ip,ipbits,expire,source,id&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=" + "sparams=ip,ipbits,expire,source,id&ip=0.0.0.0&ipbits=0&expire=19000000000&signature="
+ "513F28C7FDCBEC60A66C86C9A393556C99DC47FB.04C88036EEE12565A1ED864A875A58F15D8B5300" + "513F28C7FDCBEC60A66C86C9A393556C99DC47FB.04C88036EEE12565A1ED864A875A58F15D8B5300"
+ "&key=ik0", + "&key=ik0",
DemoUtil.TYPE_MP4), PlayerActivity.TYPE_MP4),
new Sample("Google Play (MP3 Audio)", new Sample("Google Play (MP3 Audio)",
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3", "http://storage.googleapis.com/exoplayer-test-media-0/play.mp3",
DemoUtil.TYPE_MP3), PlayerActivity.TYPE_MP3),
new Sample("Google Glass (WebM Video with Vorbis Audio)", new Sample("Google Glass (WebM Video with Vorbis Audio)",
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm", "http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm",
DemoUtil.TYPE_WEBM), PlayerActivity.TYPE_WEBM),
}; };
private Samples() {} private Samples() {}
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaDrm.KeyRequest; import android.media.MediaDrm.KeyRequest;
...@@ -48,7 +49,7 @@ public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback { ...@@ -48,7 +49,7 @@ public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback {
@Override @Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return DemoUtil.executePost(url, null, null); return Util.executePost(url, null, null);
} }
@Override @Override
...@@ -57,7 +58,7 @@ public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback { ...@@ -57,7 +58,7 @@ public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback {
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
url = PLAYREADY_TEST_DEFAULT_URI; url = PLAYREADY_TEST_DEFAULT_URI;
} }
return DemoUtil.executePost(url, request.getData(), KEY_REQUEST_PROPERTIES); return Util.executePost(url, request.getData(), KEY_REQUEST_PROPERTIES);
} }
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.demo; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaDrm.KeyRequest; import android.media.MediaDrm.KeyRequest;
...@@ -43,7 +44,7 @@ public class WidevineTestMediaDrmCallback implements MediaDrmCallback { ...@@ -43,7 +44,7 @@ public class WidevineTestMediaDrmCallback implements MediaDrmCallback {
@Override @Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return DemoUtil.executePost(url, null, null); return Util.executePost(url, null, null);
} }
@Override @Override
...@@ -52,7 +53,7 @@ public class WidevineTestMediaDrmCallback implements MediaDrmCallback { ...@@ -52,7 +53,7 @@ public class WidevineTestMediaDrmCallback implements MediaDrmCallback {
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
url = defaultUri; url = defaultUri;
} }
return DemoUtil.executePost(url, request.getData(), null); return Util.executePost(url, request.getData(), null);
} }
} }
...@@ -42,9 +42,9 @@ import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver; ...@@ -42,9 +42,9 @@ import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback; import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback; 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.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.text.TextTrackRenderer; import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.ttml.TtmlParser; import com.google.android.exoplayer.text.ttml.TtmlParser;
import com.google.android.exoplayer.text.webvtt.WebvttParser; import com.google.android.exoplayer.text.webvtt.WebvttParser;
...@@ -57,14 +57,10 @@ import com.google.android.exoplayer.util.ManifestFetcher; ...@@ -57,14 +57,10 @@ 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 com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.UnsupportedSchemeException;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.widget.TextView;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -104,7 +100,6 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -104,7 +100,6 @@ public class DashRendererBuilder implements RendererBuilder,
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private final TextView debugTextView;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private DemoPlayer player; private DemoPlayer player;
...@@ -116,12 +111,11 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -116,12 +111,11 @@ public class DashRendererBuilder implements RendererBuilder,
private long elapsedRealtimeOffset; private long elapsedRealtimeOffset;
public DashRendererBuilder(Context context, String userAgent, String url, public DashRendererBuilder(Context context, String userAgent, String url,
MediaDrmCallback drmCallback, TextView debugTextView, AudioCapabilities audioCapabilities) { MediaDrmCallback drmCallback, AudioCapabilities audioCapabilities) {
this.context = context; this.context = context;
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.drmCallback = drmCallback; this.drmCallback = drmCallback;
this.debugTextView = debugTextView;
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
} }
...@@ -192,20 +186,18 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -192,20 +186,18 @@ public class DashRendererBuilder implements RendererBuilder,
// Check drm support if necessary. // Check drm support if necessary.
boolean filterHdContent = false; boolean filterHdContent = false;
DrmSessionManager drmSessionManager = null; StreamingDrmSessionManager drmSessionManager = null;
if (hasContentProtection) { if (hasContentProtection) {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
callback.onRenderersError( callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_NO_DRM)); new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return; return;
} }
try { try {
Pair<DrmSessionManager, Boolean> drmSessionManagerData = drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(
V18Compat.getDrmSessionManagerData(player, drmCallback); player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
drmSessionManager = drmSessionManagerData.first;
// HD streams require L1 security.
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection() filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection()
&& !drmSessionManagerData.second; && getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
return; return;
...@@ -226,23 +218,18 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -226,23 +218,18 @@ public class DashRendererBuilder implements RendererBuilder,
// Build the video renderer. // Build the video renderer.
final MediaCodecVideoTrackRenderer videoRenderer; final MediaCodecVideoTrackRenderer videoRenderer;
final TrackRenderer debugRenderer;
if (videoRepresentationIndices == null || videoRepresentationIndices.length == 0) { if (videoRepresentationIndices == null || videoRepresentationIndices.length == 0) {
videoRenderer = null; videoRenderer = null;
debugRenderer = null;
} else { } else {
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource, videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset);
mainHandler, player);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, player, videoRenderer, bandwidthMeter) : null;
} }
// Build the audio chunk sources. // Build the audio chunk sources.
...@@ -259,7 +246,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -259,7 +246,7 @@ public class DashRendererBuilder implements RendererBuilder,
format.audioSamplingRate + "Hz)"); format.audioSamplingRate + "Hz)");
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex, audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS, new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS,
elapsedRealtimeOffset, mainHandler, player)); elapsedRealtimeOffset));
codecs.add(format.codecs); codecs.add(format.codecs);
} }
...@@ -316,8 +303,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -316,8 +303,7 @@ public class DashRendererBuilder implements RendererBuilder,
Representation representation = representations.get(j); Representation representation = representations.get(j);
textTrackNameList.add(representation.format.id); textTrackNameList.add(representation.format.id);
textChunkSourceList.add(new DashChunkSource(manifestFetcher, i, new int[] {j}, textChunkSourceList.add(new DashChunkSource(manifestFetcher, i, new int[] {j},
textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset));
mainHandler, player));
} }
} }
} }
...@@ -355,26 +341,7 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -355,26 +341,7 @@ public class DashRendererBuilder implements RendererBuilder,
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer; renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer; callback.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
callback.onRenderers(trackNames, multiTrackChunkSources, renderers);
}
@TargetApi(18)
private static class V18Compat {
public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
StreamingDrmSessionManager streamingDrmSessionManager =
StreamingDrmSessionManager.newWidevineInstance(player.getPlaybackLooper(), drmCallback,
null, player.getMainHandler(), player);
return Pair.create((DrmSessionManager) streamingDrmSessionManager,
getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e);
}
} }
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) { private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
...@@ -383,6 +350,4 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -383,6 +350,4 @@ public class DashRendererBuilder implements RendererBuilder,
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN; .equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN;
} }
}
} }
/*
* 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.player;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaCodecTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import android.widget.TextView;
/**
* A {@link TrackRenderer} that periodically updates debugging information displayed by a
* {@link TextView}.
*/
/* package */ class DebugTrackRenderer extends TrackRenderer implements Runnable {
private final TextView textView;
private final DemoPlayer player;
private final MediaCodecTrackRenderer renderer;
private final BandwidthMeter bandwidthMeter;
private volatile boolean pendingFailure;
private volatile long currentPositionUs;
public DebugTrackRenderer(TextView textView, DemoPlayer player,
MediaCodecTrackRenderer renderer) {
this(textView, player, renderer, null);
}
public DebugTrackRenderer(TextView textView, DemoPlayer player, MediaCodecTrackRenderer renderer,
BandwidthMeter bandwidthMeter) {
this.textView = textView;
this.player = player;
this.renderer = renderer;
this.bandwidthMeter = bandwidthMeter;
}
public void injectFailure() {
pendingFailure = true;
}
@Override
protected boolean isEnded() {
return true;
}
@Override
protected boolean isReady() {
return true;
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
maybeFail();
return STATE_PREPARED;
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
maybeFail();
if (positionUs < currentPositionUs || positionUs > currentPositionUs + 1000000) {
currentPositionUs = positionUs;
textView.post(this);
}
}
@Override
public void run() {
textView.setText(getRenderString());
}
private String getRenderString() {
return getTimeString() + " " + getQualityString() + " " + getBandwidthString() + " "
+ renderer.codecCounters.getDebugString();
}
private String getTimeString() {
return "ms(" + (currentPositionUs / 1000) + ")";
}
private String getQualityString() {
Format format = player.getVideoFormat();
return format == null ? "id:? br:? h:?"
: "id:" + format.id + " br:" + format.bitrate + " h:" + format.height;
}
private String getBandwidthString() {
if (bandwidthMeter == null
|| bandwidthMeter.getBitrateEstimate() == BandwidthMeter.NO_ESTIMATE) {
return "bw:?";
} else {
return "bw:" + (bandwidthMeter.getBitrateEstimate() / 1000);
}
}
@Override
protected long getCurrentPositionUs() {
return currentPositionUs;
}
@Override
protected long getDurationUs() {
return TrackRenderer.MATCH_LONGEST_US;
}
@Override
protected long getBufferedPositionUs() {
return TrackRenderer.END_OF_TRACK_US;
}
@Override
protected void seekTo(long timeUs) {
currentPositionUs = timeUs;
}
private void maybeFail() throws ExoPlaybackException {
if (pendingFailure) {
pendingFailure = false;
throw new ExoPlaybackException("fail() was called on DebugTrackRenderer");
}
}
}
...@@ -15,10 +15,12 @@ ...@@ -15,10 +15,12 @@
*/ */
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DummyTrackRenderer; import com.google.android.exoplayer.DummyTrackRenderer;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecTrackRenderer;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TimeRange; import com.google.android.exoplayer.TimeRange;
...@@ -27,13 +29,14 @@ import com.google.android.exoplayer.audio.AudioTrack; ...@@ -27,13 +29,14 @@ import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.hls.HlsSampleSource; import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.TextRenderer; import com.google.android.exoplayer.text.TextRenderer;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.util.DebugTextViewHelper;
import com.google.android.exoplayer.util.PlayerControl; import com.google.android.exoplayer.util.PlayerControl;
import android.media.MediaCodec.CryptoException; import android.media.MediaCodec.CryptoException;
...@@ -55,7 +58,8 @@ import java.util.concurrent.CopyOnWriteArrayList; ...@@ -55,7 +58,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener, public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener, HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer { StreamingDrmSessionManager.EventListener, TextRenderer,
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
/** /**
* Builds renderers for the player. * Builds renderers for the player.
...@@ -85,9 +89,10 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -85,9 +89,10 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
* multiple tracks. An individual element may be null if it does not have multiple tracks. * multiple tracks. An individual element may be null if it does not have multiple tracks.
* @param renderers Renderers indexed by {@link DemoPlayer} TYPE_* constants. An individual * @param renderers Renderers indexed by {@link DemoPlayer} TYPE_* constants. An individual
* element may be null if there do not exist tracks of the corresponding type. * element may be null if there do not exist tracks of the corresponding type.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null.
*/ */
void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources, void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources,
TrackRenderer[] renderers); TrackRenderer[] renderers, BandwidthMeter bandwidthMeter);
/** /**
* Invoked if a {@link RendererBuilder} encounters an error. * Invoked if a {@link RendererBuilder} encounters an error.
* *
...@@ -164,12 +169,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -164,12 +169,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public static final int DISABLED_TRACK = -1; public static final int DISABLED_TRACK = -1;
public static final int PRIMARY_TRACK = 0; public static final int PRIMARY_TRACK = 0;
public static final int RENDERER_COUNT = 5; public static final int RENDERER_COUNT = 4;
public static final int TYPE_VIDEO = 0; public static final int TYPE_VIDEO = 0;
public static final int TYPE_AUDIO = 1; public static final int TYPE_AUDIO = 1;
public static final int TYPE_TEXT = 2; public static final int TYPE_TEXT = 2;
public static final int TYPE_TIMED_METADATA = 3; public static final int TYPE_METADATA = 3;
public static final int TYPE_DEBUG = 4;
private static final int RENDERER_BUILDING_STATE_IDLE = 1; private static final int RENDERER_BUILDING_STATE_IDLE = 1;
private static final int RENDERER_BUILDING_STATE_BUILDING = 2; private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
...@@ -188,9 +192,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -188,9 +192,11 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private Surface surface; private Surface surface;
private InternalRendererBuilderCallback builderCallback; private InternalRendererBuilderCallback builderCallback;
private TrackRenderer videoRenderer; private TrackRenderer videoRenderer;
private CodecCounters codecCounters;
private Format videoFormat; private Format videoFormat;
private int videoTrackToRestore; private int videoTrackToRestore;
private BandwidthMeter bandwidthMeter;
private MultiTrackChunkSource[] multiTrackSources; private MultiTrackChunkSource[] multiTrackSources;
private String[][] trackNames; private String[][] trackNames;
private int[] selectedTracks; private int[] selectedTracks;
...@@ -276,10 +282,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -276,10 +282,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
} }
public Format getVideoFormat() {
return videoFormat;
}
public void setBackgrounded(boolean backgrounded) { public void setBackgrounded(boolean backgrounded) {
if (this.backgrounded == backgrounded) { if (this.backgrounded == backgrounded) {
return; return;
...@@ -311,7 +313,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -311,7 +313,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
/* package */ void onRenderers(String[][] trackNames, /* package */ void onRenderers(String[][] trackNames,
MultiTrackChunkSource[] multiTrackSources, TrackRenderer[] renderers) { MultiTrackChunkSource[] multiTrackSources, TrackRenderer[] renderers,
BandwidthMeter bandwidthMeter) {
builderCallback = null; builderCallback = null;
// Normalize the results. // Normalize the results.
if (trackNames == null) { if (trackNames == null) {
...@@ -334,7 +337,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -334,7 +337,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
// Complete preparation. // Complete preparation.
this.trackNames = trackNames; this.trackNames = trackNames;
this.videoRenderer = renderers[TYPE_VIDEO]; this.videoRenderer = renderers[TYPE_VIDEO];
this.codecCounters = videoRenderer instanceof MediaCodecTrackRenderer
? ((MediaCodecTrackRenderer) videoRenderer).codecCounters
: renderers[TYPE_AUDIO] instanceof MediaCodecTrackRenderer
? ((MediaCodecTrackRenderer) renderers[TYPE_AUDIO]).codecCounters : null;
this.multiTrackSources = multiTrackSources; this.multiTrackSources = multiTrackSources;
this.bandwidthMeter = bandwidthMeter;
pushSurface(false); pushSurface(false);
pushTrackSelection(TYPE_VIDEO, true); pushTrackSelection(TYPE_VIDEO, true);
pushTrackSelection(TYPE_AUDIO, true); pushTrackSelection(TYPE_AUDIO, true);
...@@ -388,6 +396,22 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -388,6 +396,22 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
return playerState; return playerState;
} }
@Override
public Format getFormat() {
return videoFormat;
}
@Override
public BandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
}
@Override
public CodecCounters getCodecCounters() {
return codecCounters;
}
@Override
public long getCurrentPosition() { public long getCurrentPosition() {
return player.getCurrentPosition(); return player.getCurrentPosition();
} }
...@@ -495,9 +519,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -495,9 +519,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
@Override @Override
public void onDecoderInitialized( public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
String decoderName,
long elapsedRealtimeMs,
long initializationDurationMs) { long initializationDurationMs) {
if (infoListener != null) { if (infoListener != null) {
infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs); infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs);
...@@ -513,27 +535,17 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -513,27 +535,17 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
processCues(cues); if (captionListener != null && selectedTracks[TYPE_TEXT] != DISABLED_TRACK) {
} captionListener.onCues(cues);
@Override
public void onSeekRangeChanged(TimeRange seekRange) {
if (infoListener != null) {
infoListener.onSeekRangeChanged(seekRange);
} }
} }
/* package */ MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>
getId3MetadataRenderer() {
return new MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>() {
@Override @Override
public void onMetadata(Map<String, Object> metadata) { public void onMetadata(Map<String, Object> metadata) {
if (id3MetadataListener != null) { if (id3MetadataListener != null && selectedTracks[TYPE_METADATA] != DISABLED_TRACK) {
id3MetadataListener.onId3Metadata(metadata); id3MetadataListener.onId3Metadata(metadata);
} }
} }
};
}
@Override @Override
public void onPlayWhenReadyCommitted() { public void onPlayWhenReadyCommitted() {
...@@ -620,13 +632,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -620,13 +632,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
} }
/* package */ void processCues(List<Cue> cues) {
if (captionListener == null || selectedTracks[TYPE_TEXT] == DISABLED_TRACK) {
return;
}
captionListener.onCues(cues);
}
private class InternalRendererBuilderCallback implements RendererBuilderCallback { private class InternalRendererBuilderCallback implements RendererBuilderCallback {
private boolean canceled; private boolean canceled;
...@@ -637,9 +642,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -637,9 +642,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
@Override @Override
public void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources, public void onRenderers(String[][] trackNames, MultiTrackChunkSource[] multiTrackSources,
TrackRenderer[] renderers) { TrackRenderer[] renderers, BandwidthMeter bandwidthMeter) {
if (!canceled) { if (!canceled) {
DemoPlayer.this.onRenderers(trackNames, multiTrackSources, renderers); DemoPlayer.this.onRenderers(trackNames, multiTrackSources, renderers, bandwidthMeter);
} }
} }
......
...@@ -29,7 +29,6 @@ import com.google.android.exoplayer.upstream.DefaultUriDataSource; ...@@ -29,7 +29,6 @@ import com.google.android.exoplayer.upstream.DefaultUriDataSource;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.net.Uri; import android.net.Uri;
import android.widget.TextView;
/** /**
* A {@link RendererBuilder} for streams that can be read using an {@link Extractor}. * A {@link RendererBuilder} for streams that can be read using an {@link Extractor}.
...@@ -41,15 +40,12 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -41,15 +40,12 @@ public class ExtractorRendererBuilder implements RendererBuilder {
private final Context context; private final Context context;
private final String userAgent; private final String userAgent;
private final Uri uri; private final Uri uri;
private final TextView debugTextView;
private final Extractor extractor; private final Extractor extractor;
public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, Extractor extractor) {
TextView debugTextView, Extractor extractor) {
this.context = context; this.context = context;
this.userAgent = userAgent; this.userAgent = userAgent;
this.uri = uri; this.uri = uri;
this.debugTextView = debugTextView;
this.extractor = extractor; this.extractor = extractor;
} }
...@@ -67,16 +63,11 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -67,16 +63,11 @@ public class ExtractorRendererBuilder implements RendererBuilder {
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player); null, true, player.getMainHandler(), player);
// Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, player, videoRenderer, bandwidthMeter) : null;
// Invoke the callback. // Invoke the callback.
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer; callback.onRenderers(null, null, renderers, bandwidthMeter);
callback.onRenderers(null, null, renderers);
} }
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
...@@ -32,6 +34,7 @@ import com.google.android.exoplayer.metadata.Id3Parser; ...@@ -32,6 +34,7 @@ import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
...@@ -40,7 +43,6 @@ import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; ...@@ -40,7 +43,6 @@ import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Handler; import android.os.Handler;
import android.widget.TextView;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
...@@ -50,24 +52,22 @@ import java.util.Map; ...@@ -50,24 +52,22 @@ import java.util.Map;
*/ */
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> { public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> {
private static final int REQUESTED_BUFFER_SIZE = 18 * 1024 * 1024; private static final int BUFFER_SEGMENT_SIZE = 256 * 1024;
private static final long REQUESTED_BUFFER_DURATION_MS = 40000; private static final int BUFFER_SEGMENTS = 64;
private final Context context; private final Context context;
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final TextView debugTextView;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private DemoPlayer player; private DemoPlayer player;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
public HlsRendererBuilder(Context context, String userAgent, String url, TextView debugTextView, public HlsRendererBuilder(Context context, String userAgent, String url,
AudioCapabilities audioCapabilities) { AudioCapabilities audioCapabilities) {
this.context = context; this.context = context;
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.debugTextView = debugTextView;
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
} }
...@@ -89,6 +89,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -89,6 +89,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override @Override
public void onSingleManifest(HlsPlaylist manifest) { public void onSingleManifest(HlsPlaylist manifest) {
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
int[] variantIndices = null; int[] variantIndices = null;
...@@ -106,30 +107,24 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -106,30 +107,24 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities); variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3, REQUESTED_BUFFER_SIZE, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
REQUESTED_BUFFER_DURATION_MS, mainHandler, player, DemoPlayer.TYPE_VIDEO); BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 3, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
MetadataTrackRenderer<Map<String, Object>> id3Renderer = MetadataTrackRenderer<Map<String, Object>> id3Renderer =
new MetadataTrackRenderer<>(sampleSource, new Id3Parser(), new MetadataTrackRenderer<>(sampleSource, new Id3Parser(), player, mainHandler.getLooper());
player.getId3MetadataRenderer(), mainHandler.getLooper());
Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player, Eia608TrackRenderer closedCaptionRenderer = new Eia608TrackRenderer(sampleSource, player,
mainHandler.getLooper()); mainHandler.getLooper());
// Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, player, videoRenderer, bandwidthMeter) : null;
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT]; TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TIMED_METADATA] = id3Renderer; renderers[DemoPlayer.TYPE_METADATA] = id3Renderer;
renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer; renderers[DemoPlayer.TYPE_TEXT] = closedCaptionRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer; callback.onRenderers(null, null, renderers, bandwidthMeter);
callback.onRenderers(null, null, renderers);
} }
} }
...@@ -32,6 +32,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba ...@@ -32,6 +32,7 @@ import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallba
import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.drm.UnsupportedDrmException;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
...@@ -46,16 +47,12 @@ import com.google.android.exoplayer.upstream.DefaultUriDataSource; ...@@ -46,16 +47,12 @@ import com.google.android.exoplayer.upstream.DefaultUriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.UnsupportedSchemeException;
import android.os.Handler; import android.os.Handler;
import android.widget.TextView;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID;
/** /**
* A {@link RendererBuilder} for SmoothStreaming. * A {@link RendererBuilder} for SmoothStreaming.
...@@ -73,19 +70,17 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -73,19 +70,17 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final MediaDrmCallback drmCallback; private final MediaDrmCallback drmCallback;
private final TextView debugTextView;
private DemoPlayer player; private DemoPlayer player;
private RendererBuilderCallback callback; private RendererBuilderCallback callback;
private ManifestFetcher<SmoothStreamingManifest> manifestFetcher; private ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
public SmoothStreamingRendererBuilder(Context context, String userAgent, String url, public SmoothStreamingRendererBuilder(Context context, String userAgent, String url,
MediaDrmCallback drmCallback, TextView debugTextView) { MediaDrmCallback drmCallback) {
this.context = context; this.context = context;
this.userAgent = userAgent; this.userAgent = userAgent;
this.url = url; this.url = url;
this.drmCallback = drmCallback; this.drmCallback = drmCallback;
this.debugTextView = debugTextView;
} }
@Override @Override
...@@ -118,12 +113,12 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -118,12 +113,12 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
if (manifest.protectionElement != null) { if (manifest.protectionElement != null) {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
callback.onRenderersError( callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_NO_DRM)); new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
return; return;
} }
try { try {
drmSessionManager = V18Compat.getDrmSessionManager(manifest.protectionElement.uuid, player, drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid,
drmCallback); player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
return; return;
...@@ -159,10 +154,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -159,10 +154,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
// Build the video renderer. // Build the video renderer.
final MediaCodecVideoTrackRenderer videoRenderer; final MediaCodecVideoTrackRenderer videoRenderer;
final TrackRenderer debugRenderer;
if (videoTrackIndices == null || videoTrackIndices.length == 0) { if (videoTrackIndices == null || videoTrackIndices.length == 0) {
videoRenderer = null; videoRenderer = null;
debugRenderer = null;
} else { } else {
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher, ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
...@@ -173,8 +166,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -173,8 +166,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true, videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, drmSessionManager, true,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, mainHandler, player, 50);
debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, player, videoRenderer, bandwidthMeter) : null;
} }
// Build the audio renderer. // Build the audio renderer.
...@@ -252,25 +243,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -252,25 +243,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer; renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer; renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_TEXT] = textRenderer; renderers[DemoPlayer.TYPE_TEXT] = textRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer; callback.onRenderers(trackNames, multiTrackChunkSources, renderers, bandwidthMeter);
callback.onRenderers(trackNames, multiTrackChunkSources, renderers);
}
@TargetApi(18)
private static class V18Compat {
public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e);
}
}
} }
} }
...@@ -72,7 +72,7 @@ android.libraryVariants.all { variant -> ...@@ -72,7 +72,7 @@ android.libraryVariants.all { variant ->
classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath()) classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath())
options { options {
links "http://docs.oracle.com/javase/7/docs/api/" links "http://docs.oracle.com/javase/7/docs/api/"
linksOffline "https://d.android.com/reference","${android.sdkDirectory}/docs/reference" linksOffline "https://developer.android.com/reference","${android.sdkDirectory}/docs/reference"
} }
exclude '**/BuildConfig.java' exclude '**/BuildConfig.java'
exclude '**/R.java' exclude '**/R.java'
......
...@@ -134,8 +134,7 @@ public class MediaCodecUtil { ...@@ -134,8 +134,7 @@ public class MediaCodecUtil {
for (int i = 0; i < numberOfCodecs; i++) { for (int i = 0; i < numberOfCodecs; i++) {
MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i);
String codecName = info.getName(); String codecName = info.getName();
if (!info.isEncoder() && codecName.startsWith("OMX.") if (isCodecUsableDecoder(info, codecName, secureDecodersExplicit)) {
&& (secureDecodersExplicit || !codecName.endsWith(".secure"))) {
String[] supportedTypes = info.getSupportedTypes(); String[] supportedTypes = info.getSupportedTypes();
for (int j = 0; j < supportedTypes.length; j++) { for (int j = 0; j < supportedTypes.length; j++) {
String supportedType = supportedTypes[j]; String supportedType = supportedTypes[j];
...@@ -166,6 +165,28 @@ public class MediaCodecUtil { ...@@ -166,6 +165,28 @@ public class MediaCodecUtil {
return null; return null;
} }
/**
* Returns whether the specified codec is usable for decoding on the current device.
*/
private static boolean isCodecUsableDecoder(MediaCodecInfo info, String name,
boolean secureDecodersExplicit) {
if (info.isEncoder() || !name.startsWith("OMX.")
|| (!secureDecodersExplicit && name.endsWith(".secure"))) {
return false;
}
// Workaround an issue where creating a particular MP3 decoder on some HTC devices on platform
// API version 16 crashes mediaserver.
if (Util.SDK_INT == 16
&& ("dlxu".equals(Util.PRODUCT) // HTC Butterfly
|| "protou".equals(Util.PRODUCT)) // HTC Desire X
&& name.equals("OMX.qcom.audio.decoder.mp3")) {
return false;
}
return true;
}
private static boolean isAdaptive(CodecCapabilities capabilities) { private static boolean isAdaptive(CodecCapabilities capabilities) {
if (Util.SDK_INT >= 19) { if (Util.SDK_INT >= 19) {
return isAdaptiveV19(capabilities); return isAdaptiveV19(capabilities);
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.dash; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.BehindLiveWindowException; import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackInfo; import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
...@@ -51,8 +50,6 @@ import com.google.android.exoplayer.util.ManifestFetcher; ...@@ -51,8 +50,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.SystemClock; import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -67,20 +64,6 @@ import java.util.List; ...@@ -67,20 +64,6 @@ import java.util.List;
public class DashChunkSource implements ChunkSource { public class DashChunkSource implements ChunkSource {
/** /**
* Interface definition for a callback to be notified of {@link DashChunkSource} events.
*/
public interface EventListener {
/**
* Invoked when the available seek range of the stream has changed.
*
* @param seekRange The range which specifies available content that can be seeked to.
*/
public void onSeekRangeChanged(TimeRange seekRange);
}
/**
* Thrown when an AdaptationSet is missing from the MPD. * Thrown when an AdaptationSet is missing from the MPD.
*/ */
public static class NoAdaptationSetException extends IOException { public static class NoAdaptationSetException extends IOException {
...@@ -96,9 +79,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -96,9 +79,6 @@ public class DashChunkSource implements ChunkSource {
*/ */
public static final int USE_ALL_TRACKS = -1; public static final int USE_ALL_TRACKS = -1;
private final Handler eventHandler;
private final EventListener eventListener;
private final TrackInfo trackInfo; private final TrackInfo trackInfo;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator formatEvaluator; private final FormatEvaluator formatEvaluator;
...@@ -121,11 +101,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -121,11 +101,6 @@ public class DashChunkSource implements ChunkSource {
private boolean finishedCurrentManifest; private boolean finishedCurrentManifest;
private DrmInitData drmInitData; private DrmInitData drmInitData;
private TimeRange seekRange;
private long[] seekRangeValues;
private int firstAvailableSegmentNum;
private int lastAvailableSegmentNum;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
private IOException fatalError; private IOException fatalError;
...@@ -167,7 +142,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -167,7 +142,7 @@ public class DashChunkSource implements ChunkSource {
public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex, public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex,
int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator) { int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator) {
this(null, manifest, adaptationSetIndex, representationIndices, dataSource, formatEvaluator, this(null, manifest, adaptationSetIndex, representationIndices, dataSource, formatEvaluator,
new SystemClock(), 0, 0, null, null); new SystemClock(), 0, 0);
} }
/** /**
...@@ -192,24 +167,19 @@ public class DashChunkSource implements ChunkSource { ...@@ -192,24 +167,19 @@ public class DashChunkSource implements ChunkSource {
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. It unknown, set to 0. * as the server's unix time minus the local elapsed time. It unknown, set to 0.
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/ */
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher, public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
int adaptationSetIndex, int[] representationIndices, DataSource dataSource, int adaptationSetIndex, int[] representationIndices, DataSource dataSource,
FormatEvaluator formatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, FormatEvaluator formatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs) {
Handler eventHandler, EventListener eventListener) {
this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetIndex, representationIndices, this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetIndex, representationIndices,
dataSource, formatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000, dataSource, formatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000,
elapsedRealtimeOffsetMs * 1000, eventHandler, eventListener); elapsedRealtimeOffsetMs * 1000);
} }
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher, /* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
MediaPresentationDescription initialManifest, int adaptationSetIndex, MediaPresentationDescription initialManifest, int adaptationSetIndex,
int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator, int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator,
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs, Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs) {
Handler eventHandler, EventListener eventListener) {
this.manifestFetcher = manifestFetcher; this.manifestFetcher = manifestFetcher;
this.currentManifest = initialManifest; this.currentManifest = initialManifest;
this.adaptationSetIndex = adaptationSetIndex; this.adaptationSetIndex = adaptationSetIndex;
...@@ -219,11 +189,8 @@ public class DashChunkSource implements ChunkSource { ...@@ -219,11 +189,8 @@ public class DashChunkSource implements ChunkSource {
this.systemClock = systemClock; this.systemClock = systemClock;
this.liveEdgeLatencyUs = liveEdgeLatencyUs; this.liveEdgeLatencyUs = liveEdgeLatencyUs;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs; this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
this.headerBuilder = new StringBuilder(); this.headerBuilder = new StringBuilder();
this.seekRangeValues = new long[2];
drmInitData = getDrmInitData(currentManifest, adaptationSetIndex); drmInitData = getDrmInitData(currentManifest, adaptationSetIndex);
Representation[] representations = getFilteredRepresentations(currentManifest, Representation[] representations = getFilteredRepresentations(currentManifest,
...@@ -262,11 +229,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -262,11 +229,6 @@ public class DashChunkSource implements ChunkSource {
return trackInfo; return trackInfo;
} }
// VisibleForTesting
/* package */ TimeRange getSeekRange() {
return seekRange;
}
@Override @Override
public void enable() { public void enable() {
fatalError = null; fatalError = null;
...@@ -274,16 +236,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -274,16 +236,6 @@ public class DashChunkSource implements ChunkSource {
if (manifestFetcher != null) { if (manifestFetcher != null) {
manifestFetcher.enable(); manifestFetcher.enable();
} }
DashSegmentIndex segmentIndex =
representationHolders.get(formats[0].id).representation.getIndex();
if (segmentIndex == null) {
seekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, 0, currentManifest.duration * 1000);
notifySeekRangeChanged(seekRange);
} else {
long nowUs = getNowUs();
updateAvailableSegmentBounds(segmentIndex, nowUs);
updateSeekRange(segmentIndex, nowUs);
}
} }
@Override @Override
...@@ -292,7 +244,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -292,7 +244,6 @@ public class DashChunkSource implements ChunkSource {
if (manifestFetcher != null) { if (manifestFetcher != null) {
manifestFetcher.disable(); manifestFetcher.disable();
} }
seekRange = null;
} }
@Override @Override
...@@ -309,19 +260,31 @@ public class DashChunkSource implements ChunkSource { ...@@ -309,19 +260,31 @@ public class DashChunkSource implements ChunkSource {
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders.get(representation.format.id); representationHolders.get(representation.format.id);
DashSegmentIndex oldIndex = representationHolder.segmentIndex; DashSegmentIndex oldIndex = representationHolder.segmentIndex;
int oldIndexLastSegmentNum = oldIndex.getLastSegmentNum();
long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum)
+ oldIndex.getDurationUs(oldIndexLastSegmentNum);
DashSegmentIndex newIndex = representation.getIndex(); DashSegmentIndex newIndex = representation.getIndex();
int newFirstSegmentNum = newIndex.getFirstSegmentNum(); int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum();
int segmentNumShift = oldIndex.getSegmentNum(newIndex.getTimeUs(newFirstSegmentNum)) long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum);
- newFirstSegmentNum; if (oldIndexEndTimeUs < newIndexStartTimeUs) {
// There's a gap between the old manifest and the new one which means we've slipped behind
// the live window and can't proceed.
fatalError = new BehindLiveWindowException();
return;
}
int segmentNumShift;
if (oldIndexEndTimeUs == newIndexStartTimeUs) {
// The new manifest continues where the old one ended, with no overlap.
segmentNumShift = oldIndex.getLastSegmentNum() + 1 - newIndexFirstSegmentNum;
} else {
// The new manifest overlaps with the old one.
segmentNumShift = oldIndex.getSegmentNum(newIndexStartTimeUs) - newIndexFirstSegmentNum;
}
representationHolder.segmentNumShift += segmentNumShift; representationHolder.segmentNumShift += segmentNumShift;
representationHolder.segmentIndex = newIndex; representationHolder.segmentIndex = newIndex;
} }
currentManifest = newManifest; currentManifest = newManifest;
finishedCurrentManifest = false; finishedCurrentManifest = false;
long nowUs = getNowUs();
updateAvailableSegmentBounds(newRepresentations[0].getIndex(), nowUs);
updateSeekRange(newRepresentations[0].getIndex(), nowUs);
} }
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
...@@ -391,21 +354,36 @@ public class DashChunkSource implements ChunkSource { ...@@ -391,21 +354,36 @@ public class DashChunkSource implements ChunkSource {
return; return;
} }
long nowUs;
if (elapsedRealtimeOffsetUs != 0) {
nowUs = (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
} else {
nowUs = System.currentTimeMillis() * 1000;
}
int firstAvailableSegmentNum = segmentIndex.getFirstSegmentNum();
int lastAvailableSegmentNum = segmentIndex.getLastSegmentNum();
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
if (indexUnbounded) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
segmentIndex.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = segmentIndex.getSegmentNum(liveEdgeTimestampUs) - 1;
}
int segmentNum; int segmentNum;
boolean indexUnbounded = segmentIndex.getLastSegmentNum() == DashSegmentIndex.INDEX_UNBOUNDED;
if (queue.isEmpty()) { if (queue.isEmpty()) {
if (currentManifest.dynamic) { if (currentManifest.dynamic) {
seekRangeValues = seekRange.getCurrentBoundsUs(seekRangeValues); seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded, segmentIndex.isExplicit());
seekPositionUs = Math.max(seekPositionUs, seekRangeValues[0]);
seekPositionUs = Math.min(seekPositionUs, seekRangeValues[1]);
} }
segmentNum = segmentIndex.getSegmentNum(seekPositionUs); segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
// if the index is unbounded then the result of getSegmentNum isn't clamped to ensure that
// it doesn't exceed the last available segment. Clamp it here.
if (indexUnbounded) {
segmentNum = Math.min(segmentNum, lastAvailableSegmentNum);
}
} else { } else {
MediaChunk previous = queue.get(out.queueSize - 1); MediaChunk previous = queue.get(out.queueSize - 1);
segmentNum = previous.isLastChunk ? -1 segmentNum = previous.isLastChunk ? -1
...@@ -474,59 +452,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -474,59 +452,6 @@ public class DashChunkSource implements ChunkSource {
// Do nothing. // Do nothing.
} }
private void updateAvailableSegmentBounds(DashSegmentIndex segmentIndex, long nowUs) {
int indexFirstAvailableSegmentNum = segmentIndex.getFirstSegmentNum();
int indexLastAvailableSegmentNum = segmentIndex.getLastSegmentNum();
if (indexLastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
indexFirstAvailableSegmentNum = Math.max(indexFirstAvailableSegmentNum,
segmentIndex.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
indexLastAvailableSegmentNum = segmentIndex.getSegmentNum(liveEdgeTimestampUs) - 1;
}
firstAvailableSegmentNum = indexFirstAvailableSegmentNum;
lastAvailableSegmentNum = indexLastAvailableSegmentNum;
}
private void updateSeekRange(DashSegmentIndex segmentIndex, long nowUs) {
long earliestSeekPosition = segmentIndex.getTimeUs(firstAvailableSegmentNum);
long latestSeekPosition = segmentIndex.getTimeUs(lastAvailableSegmentNum)
+ segmentIndex.getDurationUs(lastAvailableSegmentNum);
if (currentManifest.dynamic) {
long liveEdgeTimestampUs;
if (segmentIndex.getLastSegmentNum() == DashSegmentIndex.INDEX_UNBOUNDED) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
} else {
liveEdgeTimestampUs = segmentIndex.getTimeUs(segmentIndex.getLastSegmentNum())
+ segmentIndex.getDurationUs(segmentIndex.getLastSegmentNum());
if (!segmentIndex.isExplicit()) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
}
// it's possible that the live edge latency actually puts our latest position before
// the earliest position in the case of a DVR-like stream that's just starting up, so
// in that case just return the earliest position instead
latestSeekPosition = Math.max(earliestSeekPosition, liveEdgeTimestampUs - liveEdgeLatencyUs);
}
TimeRange newSeekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, earliestSeekPosition,
latestSeekPosition);
if (seekRange == null || !seekRange.equals(newSeekRange)) {
seekRange = newSeekRange;
notifySeekRangeChanged(seekRange);
}
}
private static boolean mimeTypeIsWebm(String mimeType) { private static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM); return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM);
} }
...@@ -587,12 +512,36 @@ public class DashChunkSource implements ChunkSource { ...@@ -587,12 +512,36 @@ public class DashChunkSource implements ChunkSource {
} }
} }
private long getNowUs() { /**
if (elapsedRealtimeOffsetUs != 0) { * For live playbacks, determines the seek position that snaps playback to be
return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs; * {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
*
* @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @param indexExplicit True if the segment index is explicit. False otherwise.
* @return The seek position in microseconds.
*/
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded, boolean indexExplicit) {
long liveEdgeTimestampUs;
if (indexUnbounded) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
} else { } else {
return System.currentTimeMillis() * 1000; liveEdgeTimestampUs = Long.MIN_VALUE;
for (RepresentationHolder representationHolder : representationHolders.values()) {
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
int lastSegmentNum = segmentIndex.getLastSegmentNum();
long indexLiveEdgeTimestampUs = segmentIndex.getTimeUs(lastSegmentNum)
+ segmentIndex.getDurationUs(lastSegmentNum);
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
} }
if (!indexExplicit) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
}
return liveEdgeTimestampUs - liveEdgeLatencyUs;
} }
private static Representation[] getFilteredRepresentations(MediaPresentationDescription manifest, private static Representation[] getFilteredRepresentations(MediaPresentationDescription manifest,
...@@ -643,17 +592,6 @@ public class DashChunkSource implements ChunkSource { ...@@ -643,17 +592,6 @@ public class DashChunkSource implements ChunkSource {
Collections.singletonList(period)); Collections.singletonList(period));
} }
private void notifySeekRangeChanged(final TimeRange seekRange) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onSeekRangeChanged(seekRange);
}
});
}
}
private static class RepresentationHolder { private static class RepresentationHolder {
public final Representation representation; public final Representation representation;
......
...@@ -110,11 +110,11 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -110,11 +110,11 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedSchemeException If the specified DRM scheme is not supported. * @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/ */
public static StreamingDrmSessionManager newWidevineInstance(Looper playbackLooper, public static StreamingDrmSessionManager newWidevineInstance(Looper playbackLooper,
MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters,
Handler eventHandler, EventListener eventListener) throws UnsupportedSchemeException { Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException {
return new StreamingDrmSessionManager(WIDEVINE_UUID, playbackLooper, callback, return new StreamingDrmSessionManager(WIDEVINE_UUID, playbackLooper, callback,
optionalKeyRequestParameters, eventHandler, eventListener); optionalKeyRequestParameters, eventHandler, eventListener);
} }
...@@ -132,11 +132,11 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -132,11 +132,11 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedSchemeException If the specified DRM scheme is not supported. * @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/ */
public static StreamingDrmSessionManager newPlayReadyInstance(Looper playbackLooper, public static StreamingDrmSessionManager newPlayReadyInstance(Looper playbackLooper,
MediaDrmCallback callback, String customData, Handler eventHandler, MediaDrmCallback callback, String customData, Handler eventHandler,
EventListener eventListener) throws UnsupportedSchemeException { EventListener eventListener) throws UnsupportedDrmException {
HashMap<String, String> optionalKeyRequestParameters; HashMap<String, String> optionalKeyRequestParameters;
if (!TextUtils.isEmpty(customData)) { if (!TextUtils.isEmpty(customData)) {
optionalKeyRequestParameters = new HashMap<>(); optionalKeyRequestParameters = new HashMap<>();
...@@ -158,17 +158,23 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -158,17 +158,23 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedSchemeException If the specified DRM scheme is not supported. * @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/ */
public StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback, public StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback,
HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler, HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler,
EventListener eventListener) throws UnsupportedSchemeException { EventListener eventListener) throws UnsupportedDrmException {
this.uuid = uuid; this.uuid = uuid;
this.callback = callback; this.callback = callback;
this.optionalKeyRequestParameters = optionalKeyRequestParameters; this.optionalKeyRequestParameters = optionalKeyRequestParameters;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
try {
mediaDrm = new MediaDrm(uuid); mediaDrm = new MediaDrm(uuid);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_INSTANTIATION_ERROR, e);
}
mediaDrm.setOnEventListener(new MediaDrmEventListener()); mediaDrm.setOnEventListener(new MediaDrmEventListener());
mediaDrmHandler = new MediaDrmHandler(playbackLooper); mediaDrmHandler = new MediaDrmHandler(playbackLooper);
postResponseHandler = new PostResponseHandler(playbackLooper); postResponseHandler = new PostResponseHandler(playbackLooper);
...@@ -176,12 +182,12 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -176,12 +182,12 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
} }
@Override @Override
public int getState() { public final int getState() {
return state; return state;
} }
@Override @Override
public MediaCrypto getMediaCrypto() { public final MediaCrypto getMediaCrypto() {
if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
...@@ -197,7 +203,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -197,7 +203,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
} }
@Override @Override
public Exception getError() { public final Exception getError() {
return state == STATE_ERROR ? lastException : null; return state == STATE_ERROR ? lastException : null;
} }
...@@ -250,7 +256,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -250,7 +256,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
} }
@Override @Override
public void open(DrmInitData drmInitData) { public final void open(DrmInitData drmInitData) {
if (++openCount != 1) { if (++openCount != 1) {
return; return;
} }
...@@ -272,7 +278,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { ...@@ -272,7 +278,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
} }
@Override @Override
public void close() { public final void close() {
if (--openCount != 0) { if (--openCount != 0) {
return; return;
} }
......
...@@ -13,16 +13,22 @@ ...@@ -13,16 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.drm;
/** /**
* Exception thrown when the required level of DRM is not supported. * Thrown when the requested DRM scheme is not supported.
*/ */
public final class UnsupportedDrmException extends Exception { public final class UnsupportedDrmException extends Exception {
public static final int REASON_NO_DRM = 0; /**
* The requested DRM scheme is unsupported by the device.
*/
public static final int REASON_UNSUPPORTED_SCHEME = 1; public static final int REASON_UNSUPPORTED_SCHEME = 1;
public static final int REASON_UNKNOWN = 2; /**
* There device advertises support for the requested DRM scheme, but there was an error
* instantiating it. The cause can be retrieved using {@link #getCause()}.
*/
public static final int REASON_INSTANTIATION_ERROR = 2;
public final int reason; public final int reason;
......
...@@ -39,7 +39,8 @@ import java.util.List; ...@@ -39,7 +39,8 @@ import java.util.List;
public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp"); public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp");
public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1"); public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1");
public static final int TYPE_avc3 = Util.getIntegerCodeForString("avc3"); public static final int TYPE_avc3 = Util.getIntegerCodeForString("avc3");
public static final int TYPE_esds = Util.getIntegerCodeForString("esds"); public static final int TYPE_hvc1 = Util.getIntegerCodeForString("hvc1");
public static final int TYPE_hev1 = Util.getIntegerCodeForString("hev1");
public static final int TYPE_mdat = Util.getIntegerCodeForString("mdat"); public static final int TYPE_mdat = Util.getIntegerCodeForString("mdat");
public static final int TYPE_mp4a = Util.getIntegerCodeForString("mp4a"); public static final int TYPE_mp4a = Util.getIntegerCodeForString("mp4a");
public static final int TYPE_ac_3 = Util.getIntegerCodeForString("ac-3"); public static final int TYPE_ac_3 = Util.getIntegerCodeForString("ac-3");
...@@ -58,6 +59,8 @@ import java.util.List; ...@@ -58,6 +59,8 @@ import java.util.List;
public static final int TYPE_minf = Util.getIntegerCodeForString("minf"); public static final int TYPE_minf = Util.getIntegerCodeForString("minf");
public static final int TYPE_stbl = Util.getIntegerCodeForString("stbl"); public static final int TYPE_stbl = Util.getIntegerCodeForString("stbl");
public static final int TYPE_avcC = Util.getIntegerCodeForString("avcC"); public static final int TYPE_avcC = Util.getIntegerCodeForString("avcC");
public static final int TYPE_hvcC = Util.getIntegerCodeForString("hvcC");
public static final int TYPE_esds = Util.getIntegerCodeForString("esds");
public static final int TYPE_moof = Util.getIntegerCodeForString("moof"); public static final int TYPE_moof = Util.getIntegerCodeForString("moof");
public static final int TYPE_traf = Util.getIntegerCodeForString("traf"); public static final int TYPE_traf = Util.getIntegerCodeForString("traf");
public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex"); public static final int TYPE_mvex = Util.getIntegerCodeForString("mvex");
......
...@@ -20,8 +20,8 @@ import com.google.android.exoplayer.MediaFormat; ...@@ -20,8 +20,8 @@ import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.util.Ac3Util; import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.H264Util;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -331,25 +331,22 @@ import java.util.List; ...@@ -331,25 +331,22 @@ import java.util.List;
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = stsd.readInt(); int childAtomType = stsd.readInt();
if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
|| childAtomType == Atom.TYPE_encv) { || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v
parseAvcFromParent(stsd, childStartPosition, childAtomSize, durationUs, holder, i); || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1) {
parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, holder, i);
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|| childAtomType == Atom.TYPE_ac_3) { || childAtomType == Atom.TYPE_ac_3) {
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs,
holder, i); holder, i);
} else if (childAtomType == Atom.TYPE_TTML) { } else if (childAtomType == Atom.TYPE_TTML) {
holder.mediaFormat = MediaFormat.createTtmlFormat(); holder.mediaFormat = MediaFormat.createTtmlFormat();
} else if (childAtomType == Atom.TYPE_mp4v) {
holder.mediaFormat = parseMp4vFromParent(stsd, childStartPosition, childAtomSize,
durationUs);
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
} }
return holder; return holder;
} }
/** Returns the media format for an avc1 box. */ private static void parseVideoSampleEntry(ParsableByteArray parent, int position, int size,
private static void parseAvcFromParent(ParsableByteArray parent, int position, int size,
long durationUs, StsdDataHolder out, int entryIndex) { long durationUs, StsdDataHolder out, int entryIndex) {
parent.setPosition(position + Atom.HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE);
...@@ -361,6 +358,7 @@ import java.util.List; ...@@ -361,6 +358,7 @@ import java.util.List;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
int childPosition = parent.getPosition(); int childPosition = parent.getPosition();
String mimeType = null;
while (childPosition - position < size) { while (childPosition - position < size) {
parent.setPosition(childPosition); parent.setPosition(childPosition);
int childStartPosition = parent.getPosition(); int childStartPosition = parent.getPosition();
...@@ -372,9 +370,22 @@ import java.util.List; ...@@ -372,9 +370,22 @@ import java.util.List;
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
int childAtomType = parent.readInt(); int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_avcC) { if (childAtomType == Atom.TYPE_avcC) {
Assertions.checkState(mimeType == null);
mimeType = MimeTypes.VIDEO_H264;
Pair<List<byte[]>, Integer> avcCData = parseAvcCFromParent(parent, childStartPosition); Pair<List<byte[]>, Integer> avcCData = parseAvcCFromParent(parent, childStartPosition);
initializationData = avcCData.first; initializationData = avcCData.first;
out.nalUnitLengthFieldLength = avcCData.second; out.nalUnitLengthFieldLength = avcCData.second;
} else if (childAtomType == Atom.TYPE_hvcC) {
Assertions.checkState(mimeType == null);
mimeType = MimeTypes.VIDEO_H265;
Pair<List<byte[]>, Integer> hvcCData = parseHvcCFromParent(parent, childStartPosition);
initializationData = hvcCData.first;
out.nalUnitLengthFieldLength = hvcCData.second;
} else if (childAtomType == Atom.TYPE_esds) {
Assertions.checkState(mimeType == null);
mimeType = MimeTypes.VIDEO_MP4V;
initializationData =
Collections.singletonList(parseEsdsFromParent(parent, childStartPosition));
} else if (childAtomType == Atom.TYPE_sinf) { } else if (childAtomType == Atom.TYPE_sinf) {
out.trackEncryptionBoxes[entryIndex] = out.trackEncryptionBoxes[entryIndex] =
parseSinfFromParent(parent, childStartPosition, childAtomSize); parseSinfFromParent(parent, childStartPosition, childAtomSize);
...@@ -383,9 +394,8 @@ import java.util.List; ...@@ -383,9 +394,8 @@ import java.util.List;
} }
childPosition += childAtomSize; childPosition += childAtomSize;
} }
out.mediaFormat = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE, durationUs,
out.mediaFormat = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, width, height, pixelWidthHeightRatio, initializationData);
durationUs, width, height, pixelWidthHeightRatio, initializationData);
} }
private static Pair<List<byte[]>, Integer> parseAvcCFromParent(ParsableByteArray parent, private static Pair<List<byte[]>, Integer> parseAvcCFromParent(ParsableByteArray parent,
...@@ -401,15 +411,58 @@ import java.util.List; ...@@ -401,15 +411,58 @@ import java.util.List;
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat. // expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F; int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
for (int j = 0; j < numSequenceParameterSets; j++) { for (int j = 0; j < numSequenceParameterSets; j++) {
initializationData.add(H264Util.parseChildNalUnit(parent)); initializationData.add(NalUnitUtil.parseChildNalUnit(parent));
} }
int numPictureParameterSets = parent.readUnsignedByte(); int numPictureParameterSets = parent.readUnsignedByte();
for (int j = 0; j < numPictureParameterSets; j++) { for (int j = 0; j < numPictureParameterSets; j++) {
initializationData.add(H264Util.parseChildNalUnit(parent)); initializationData.add(NalUnitUtil.parseChildNalUnit(parent));
} }
return Pair.create(initializationData, nalUnitLengthFieldLength); return Pair.create(initializationData, nalUnitLengthFieldLength);
} }
private static Pair<List<byte[]>, Integer> parseHvcCFromParent(ParsableByteArray parent,
int position) {
// Skip to the NAL unit length size field.
parent.setPosition(position + Atom.HEADER_SIZE + 21);
int lengthSizeMinusOne = parent.readUnsignedByte() & 0x03;
// Calculate the combined size of all VPS/SPS/PPS bitstreams.
int numberOfArrays = parent.readUnsignedByte();
int csdLength = 0;
int csdStartPosition = parent.getPosition();
for (int i = 0; i < numberOfArrays; i++) {
parent.skipBytes(1); // completeness (1), nal_unit_type (7)
int numberOfNalUnits = parent.readUnsignedShort();
for (int j = 0; j < numberOfNalUnits; j++) {
int nalUnitLength = parent.readUnsignedShort();
csdLength += 4 + nalUnitLength; // Start code and NAL unit.
parent.skipBytes(nalUnitLength);
}
}
// Concatenate the codec-specific data into a single buffer.
parent.setPosition(csdStartPosition);
byte[] buffer = new byte[csdLength];
int bufferPosition = 0;
for (int i = 0; i < numberOfArrays; i++) {
parent.skipBytes(1); // completeness (1), nal_unit_type (7)
int numberOfNalUnits = parent.readUnsignedShort();
for (int j = 0; j < numberOfNalUnits; j++) {
int nalUnitLength = parent.readUnsignedShort();
System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition,
NalUnitUtil.NAL_START_CODE.length);
bufferPosition += NalUnitUtil.NAL_START_CODE.length;
System.arraycopy(parent.data, parent.getPosition(), buffer, bufferPosition, nalUnitLength);
bufferPosition += nalUnitLength;
parent.skipBytes(nalUnitLength);
}
}
List<byte[]> initializationData = csdLength == 0 ? Collections.<byte[]>emptyList()
: Collections.singletonList(buffer);
return Pair.create(initializationData, lengthSizeMinusOne + 1);
}
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position, private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
int size) { int size) {
int childPosition = position + Atom.HEADER_SIZE; int childPosition = position + Atom.HEADER_SIZE;
...@@ -462,34 +515,6 @@ import java.util.List; ...@@ -462,34 +515,6 @@ import java.util.List;
return null; return null;
} }
/** Returns the media format for an mp4v box. */
private static MediaFormat parseMp4vFromParent(ParsableByteArray parent, int position, int size,
long durationUs) {
parent.setPosition(position + Atom.HEADER_SIZE);
parent.skipBytes(24);
int width = parent.readUnsignedShort();
int height = parent.readUnsignedShort();
parent.skipBytes(50);
List<byte[]> 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, durationUs, width, height, initializationData);
}
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
int size, long durationUs, StsdDataHolder out, int entryIndex) { int size, long durationUs, StsdDataHolder out, int entryIndex) {
parent.setPosition(position + Atom.HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE);
......
...@@ -26,8 +26,8 @@ import com.google.android.exoplayer.extractor.TrackOutput; ...@@ -26,8 +26,8 @@ import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.H264Util;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -106,7 +106,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -106,7 +106,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public FragmentedMp4Extractor(int workaroundFlags) { public FragmentedMp4Extractor(int workaroundFlags) {
this.workaroundFlags = workaroundFlags; this.workaroundFlags = workaroundFlags;
atomHeader = new ParsableByteArray(Atom.HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.HEADER_SIZE);
nalStartCode = new ParsableByteArray(H264Util.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4); nalLength = new ParsableByteArray(4);
encryptionSignalByte = new ParsableByteArray(1); encryptionSignalByte = new ParsableByteArray(1);
extendedTypeScratch = new byte[16]; extendedTypeScratch = new byte[16];
......
...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.extractor.SeekMap; ...@@ -23,7 +23,7 @@ import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.H264Util; import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
...@@ -72,7 +72,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -72,7 +72,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
public Mp4Extractor() { public Mp4Extractor() {
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
containerAtoms = new Stack<>(); containerAtoms = new Stack<>();
nalStartCode = new ParsableByteArray(H264Util.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4); nalLength = new ParsableByteArray(4);
parserState = STATE_READING_ATOM_HEADER; parserState = STATE_READING_ATOM_HEADER;
} }
......
...@@ -19,8 +19,8 @@ import com.google.android.exoplayer.C; ...@@ -19,8 +19,8 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.H264Util;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -104,7 +104,7 @@ import java.util.List; ...@@ -104,7 +104,7 @@ import java.util.List;
@Override @Override
public void seek() { public void seek() {
seiReader.seek(); seiReader.seek();
H264Util.clearPrefixFlags(prefixFlags); NalUnitUtil.clearPrefixFlags(prefixFlags);
sps.reset(); sps.reset();
pps.reset(); pps.reset();
sei.reset(); sei.reset();
...@@ -128,7 +128,7 @@ import java.util.List; ...@@ -128,7 +128,7 @@ import java.util.List;
// Scan the appended data, processing NAL units as they are encountered // Scan the appended data, processing NAL units as they are encountered
while (offset < limit) { while (offset < limit) {
int nextNalUnitOffset = H264Util.findNalUnit(dataArray, offset, limit, prefixFlags); int nextNalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);
if (nextNalUnitOffset < limit) { if (nextNalUnitOffset < limit) {
// We've seen the start of a NAL unit. // We've seen the start of a NAL unit.
...@@ -139,7 +139,7 @@ import java.util.List; ...@@ -139,7 +139,7 @@ import java.util.List;
feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset); feedNalUnitTargetBuffersData(dataArray, offset, nextNalUnitOffset);
} }
int nalUnitType = H264Util.getNalUnitType(dataArray, nextNalUnitOffset); int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nextNalUnitOffset);
int bytesWrittenPastNalUnit = limit - nextNalUnitOffset; int bytesWrittenPastNalUnit = limit - nextNalUnitOffset;
switch (nalUnitType) { switch (nalUnitType) {
case NAL_UNIT_TYPE_IDR: case NAL_UNIT_TYPE_IDR:
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
...@@ -25,8 +26,6 @@ import com.google.android.exoplayer.TrackRenderer; ...@@ -25,8 +26,6 @@ import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DefaultAllocator;
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 com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
...@@ -52,23 +51,22 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -52,23 +51,22 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
*/ */
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private static final int BUFFER_FRAGMENT_LENGTH = 256 * 1024;
private static final int NO_RESET_PENDING = -1; private static final int NO_RESET_PENDING = -1;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final LinkedList<HlsExtractorWrapper> extractors; private final LinkedList<HlsExtractorWrapper> extractors;
private final Allocator allocator;
private final boolean frameAccurateSeeking; private final boolean frameAccurateSeeking;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final int requestedBufferSize; private final int bufferSizeContribution;
private final long requestedBufferDurationUs;
private final int eventSourceId; private final int eventSourceId;
private final LoadControl loadControl;
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private int remainingReleaseCount; private int remainingReleaseCount;
private boolean prepared; private boolean prepared;
private boolean loadControlRegistered;
private int trackCount; private int trackCount;
private int enabledTrackCount; private int enabledTrackCount;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
...@@ -92,36 +90,35 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -92,36 +90,35 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private long currentLoadableExceptionTimestamp; private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs; private long currentLoadStartTimeMs;
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs) { int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking,
requestedBufferDurationMs, null, null, 0); downstreamRendererCount, null, null, 0);
} }
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount,
Handler eventHandler, EventListener eventListener, int eventSourceId) { Handler eventHandler, EventListener eventListener, int eventSourceId) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking,
requestedBufferDurationMs, eventHandler, eventListener, eventSourceId, downstreamRendererCount, eventHandler, eventListener, eventSourceId,
DEFAULT_MIN_LOADABLE_RETRY_COUNT); DEFAULT_MIN_LOADABLE_RETRY_COUNT);
} }
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount,
Handler eventHandler, EventListener eventListener, Handler eventHandler, EventListener eventListener, int eventSourceId,
int eventSourceId, int minLoadableRetryCount) { int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution;
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
this.requestedBufferSize = requestedBufferSize;
this.requestedBufferDurationUs = requestedBufferDurationMs * 1000;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.eventSourceId = eventSourceId; this.eventSourceId = eventSourceId;
this.pendingResetPositionUs = NO_RESET_PENDING; this.pendingResetPositionUs = NO_RESET_PENDING;
extractors = new LinkedList<>(); extractors = new LinkedList<>();
allocator = new DefaultAllocator(BUFFER_FRAGMENT_LENGTH);
} }
@Override @Override
...@@ -150,6 +147,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -150,6 +147,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (loader == null) { if (loader == null) {
loader = new Loader("Loader:HLS"); loader = new Loader("Loader:HLS");
} }
if (!loadControlRegistered) {
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
if (!loader.isLoading()) { if (!loader.isLoading()) {
// We're going to have to start loading a chunk to get what we need for preparation. We should // We're going to have to start loading a chunk to get what we need for preparation. We should
// attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk // attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk
...@@ -182,6 +183,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -182,6 +183,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
downstreamMediaFormats[track] = null; downstreamMediaFormats[track] = null;
downstreamFormat = null; downstreamFormat = null;
if (!loadControlRegistered) {
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
if (enabledTrackCount == 1) { if (enabledTrackCount == 1) {
seekToUs(positionUs); seekToUs(positionUs);
} }
...@@ -194,12 +199,16 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -194,12 +199,16 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
enabledTrackCount--; enabledTrackCount--;
trackEnabledStates[track] = false; trackEnabledStates[track] = false;
pendingDiscontinuities[track] = false; pendingDiscontinuities[track] = false;
if (loadControlRegistered) {
loadControl.unregister(this);
loadControlRegistered = false;
}
if (enabledTrackCount == 0) { if (enabledTrackCount == 0) {
if (loader.isLoading()) { if (loader.isLoading()) {
loader.cancelLoading(); loader.cancelLoading();
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
} }
...@@ -357,7 +366,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -357,7 +366,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
maybeStartLoading(); maybeStartLoading();
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
...@@ -368,7 +377,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -368,7 +377,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
restartFrom(pendingResetPositionUs); restartFrom(pendingResetPositionUs);
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
...@@ -464,13 +473,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -464,13 +473,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
private void maybeStartLoading() { private void maybeStartLoading() {
if (currentLoadableExceptionFatal || loadingFinished || loader.isLoading()) { if (currentLoadableExceptionFatal) {
// We've failed, but we still need to update the control with our current state.
loadControl.update(this, downstreamPositionUs, -1, false, true);
return; return;
} }
long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null; boolean isBackedOff = currentLoadableException != null;
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
// Update the control with our current state, and determine whether we're the next loader.
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
loadingOrBackedOff, false);
if (isBackedOff) { if (isBackedOff) {
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp; long elapsedMillis = now - currentLoadableExceptionTimestamp;
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
currentLoadableException = null; currentLoadableException = null;
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
...@@ -478,20 +497,17 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -478,20 +497,17 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return; return;
} }
if (previousTsLoadable != null if (loader.isLoading() || !nextLoader) {
&& (previousTsLoadable.endTimeUs - downstreamPositionUs >= requestedBufferDurationUs
|| allocator.getTotalBytesAllocated() >= requestedBufferSize)) {
// We already have the target amount of data or time buffered.
return; return;
} }
Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs,
pendingResetPositionUs, downstreamPositionUs); downstreamPositionUs);
if (nextLoadable == null) { if (nextLoadable == null) {
return; return;
} }
currentLoadStartTimeMs = SystemClock.elapsedRealtime(); currentLoadStartTimeMs = now;
currentLoadable = nextLoadable; currentLoadable = nextLoadable;
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
TsChunk tsChunk = (TsChunk) currentLoadable; TsChunk tsChunk = (TsChunk) currentLoadable;
...@@ -500,7 +516,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -500,7 +516,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper;
if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) {
extractorWrapper.init(allocator); extractorWrapper.init(loadControl.getAllocator());
extractors.addLast(extractorWrapper); extractors.addLast(extractorWrapper);
} }
notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format,
...@@ -513,6 +529,18 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -513,6 +529,18 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
} }
/**
* Gets the next load time, assuming that the next load starts where the previous chunk ended (or
* from the pending reset time, if there is one).
*/
private long getNextLoadPositionUs() {
if (isPendingReset()) {
return pendingResetPositionUs;
} else {
return previousTsLoadable.isLastChunk ? -1 : previousTsLoadable.endTimeUs;
}
}
private boolean isTsChunk(Chunk chunk) { private boolean isTsChunk(Chunk chunk) {
return chunk instanceof TsChunk; return chunk instanceof TsChunk;
} }
......
...@@ -217,11 +217,22 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -217,11 +217,22 @@ public class SmoothStreamingChunkSource implements ChunkSource {
SmoothStreamingManifest newManifest = manifestFetcher.getManifest(); SmoothStreamingManifest newManifest = manifestFetcher.getManifest();
if (currentManifest != newManifest && newManifest != null) { if (currentManifest != newManifest && newManifest != null) {
StreamElement currentElement = getElement(currentManifest); StreamElement currentElement = getElement(currentManifest);
int currentElementChunkCount = currentElement.chunkCount;
StreamElement newElement = getElement(newManifest); StreamElement newElement = getElement(newManifest);
if (newElement.chunkCount == 0) { if (currentElementChunkCount == 0 || newElement.chunkCount == 0) {
currentManifestChunkOffset += currentElement.chunkCount; // There's no overlap between the old and new elements because at least one is empty.
} else if (currentElement.chunkCount > 0) { currentManifestChunkOffset += currentElementChunkCount;
currentManifestChunkOffset += currentElement.getChunkIndex(newElement.getStartTimeUs(0)); } else {
long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1)
+ currentElement.getChunkDurationUs(currentElementChunkCount - 1);
long newElementStartTimeUs = newElement.getStartTimeUs(0);
if (currentElementEndTimeUs <= newElementStartTimeUs) {
// There's no overlap between the old and new elements.
currentManifestChunkOffset += currentElementChunkCount;
} else {
// The new element overlaps with the old one.
currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs);
}
} }
currentManifest = newManifest; currentManifest = newManifest;
finishedCurrentManifest = false; finishedCurrentManifest = false;
......
/*
* 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.util;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import android.widget.TextView;
/**
* A helper class for periodically updating debug information displayed by a {@link TextView}.
*/
public final class DebugTextViewHelper implements Runnable {
/**
* Provides debug information about an ongoing playback.
*/
public interface Provider {
/**
* Returns the current playback position, in milliseconds.
*/
long getCurrentPosition();
/**
* Returns a format whose information should be displayed, or null.
*/
Format getFormat();
/**
* Returns a {@link BandwidthMeter} whose estimate should be displayed, or null.
*/
BandwidthMeter getBandwidthMeter();
/**
* Returns a {@link CodecCounters} whose information should be displayed, or null.
*/
CodecCounters getCodecCounters();
}
private static final int REFRESH_INTERVAL_MS = 1000;
private final TextView textView;
private final Provider debuggable;
/**
* @param debuggable The {@link Provider} from which debug information should be obtained.
* @param textView The {@link TextView} that should be updated to display the information.
*/
public DebugTextViewHelper(Provider debuggable, TextView textView) {
this.debuggable = debuggable;
this.textView = textView;
}
/**
* Starts periodic updates of the {@link TextView}.
* <p>
* Should be called from the application's main thread.
*/
public void start() {
stop();
run();
}
/**
* Stops periodic updates of the {@link TextView}.
* <p>
* Should be called from the application's main thread.
*/
public void stop() {
textView.removeCallbacks(this);
}
@Override
public void run() {
textView.setText(getRenderString());
textView.postDelayed(this, REFRESH_INTERVAL_MS);
}
private String getRenderString() {
return getTimeString() + " " + getQualityString() + " " + getBandwidthString() + " "
+ getVideoCodecCountersString();
}
private String getTimeString() {
return "ms(" + debuggable.getCurrentPosition() + ")";
}
private String getQualityString() {
Format format = debuggable.getFormat();
return format == null ? "id:? br:? h:?"
: "id:" + format.id + " br:" + format.bitrate + " h:" + format.height;
}
private String getBandwidthString() {
BandwidthMeter bandwidthMeter = debuggable.getBandwidthMeter();
if (bandwidthMeter == null
|| bandwidthMeter.getBitrateEstimate() == BandwidthMeter.NO_ESTIMATE) {
return "bw:?";
} else {
return "bw:" + (bandwidthMeter.getBitrateEstimate() / 1000);
}
}
private String getVideoCodecCountersString() {
CodecCounters codecCounters = debuggable.getCodecCounters();
return codecCounters == null ? "" : codecCounters.getDebugString();
}
}
...@@ -33,6 +33,7 @@ public class MimeTypes { ...@@ -33,6 +33,7 @@ public class MimeTypes {
public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4";
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; 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_H264 = BASE_TYPE_VIDEO + "/avc";
public static final String VIDEO_H265 = BASE_TYPE_VIDEO + "/hevc";
public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8"; public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8";
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; 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 VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
......
...@@ -18,11 +18,11 @@ package com.google.android.exoplayer.util; ...@@ -18,11 +18,11 @@ package com.google.android.exoplayer.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Utility methods for handling H264 data. * Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
*/ */
public final class H264Util { public final class NalUnitUtil {
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */ /** Four initial bytes that must prefix NAL units for decoding. */
public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; public static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
/** /**
...@@ -173,7 +173,7 @@ public final class H264Util { ...@@ -173,7 +173,7 @@ public final class H264Util {
return result; return result;
} }
private H264Util() { private NalUnitUtil() {
// Prevent instantiation. // Prevent instantiation.
} }
......
...@@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException; ...@@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
...@@ -40,6 +41,7 @@ import java.util.Collections; ...@@ -40,6 +41,7 @@ import java.util.Collections;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
...@@ -59,6 +61,12 @@ public final class Util { ...@@ -59,6 +61,12 @@ public final class Util {
*/ */
public static final int SDK_INT = android.os.Build.VERSION.SDK_INT; public static final int SDK_INT = android.os.Build.VERSION.SDK_INT;
/**
* Like {@link android.os.Build#PRODUCT}, but in a place where it can be conveniently overridden
* for local testing.
*/
public static final String PRODUCT = android.os.Build.PRODUCT;
private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile(
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
+ "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?" + "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?"
...@@ -577,4 +585,56 @@ public final class Util { ...@@ -577,4 +585,56 @@ public final class Util {
+ ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION; + ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION;
} }
/**
* Executes a post request using {@link HttpURLConnection}.
*
* @param url The request URL.
* @param data The request body, or null.
* @param requestProperties Request properties, or null.
* @return The response body.
* @throws IOException If an error occurred making the request.
*/
// TODO: Remove this and use HttpDataSource once DataSpec supports inclusion of a POST body.
public static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws IOException {
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(data != null);
urlConnection.setDoInput(true);
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
}
}
// Write the request body, if there is one.
if (data != null) {
OutputStream out = urlConnection.getOutputStream();
try {
out.write(data);
} finally {
out.close();
}
}
// Read and return the response body.
InputStream inputStream = urlConnection.getInputStream();
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte scratch[] = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(scratch)) != -1) {
byteArrayOutputStream.write(scratch, 0, bytesRead);
}
return byteArrayOutputStream.toByteArray();
} finally {
inputStream.close();
}
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
} }
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<projectDescription> <projectDescription>
<name>ExoPlayerTests</name> <name>ExoPlayerLibTests</name>
<comment></comment> <comment></comment>
<projects> <projects>
<project>ExoPlayerLib</project> <project>ExoPlayerLib</project>
......
...@@ -15,11 +15,9 @@ ...@@ -15,11 +15,9 @@
*/ */
package com.google.android.exoplayer.dash; package com.google.android.exoplayer.dash;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkOperationHolder; import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
...@@ -57,19 +55,12 @@ public class DashChunkSourceTest extends InstrumentationTestCase { ...@@ -57,19 +55,12 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private static final FormatEvaluator EVALUATOR = new FixedEvaluator(); private static final FormatEvaluator EVALUATOR = new FixedEvaluator();
private static final long VOD_DURATION = 30000; private static final long AVAILABILITY_START_TIME = 0;
private static final long AVAILABILITY_LATENCY = 5000;
private static final long LIVE_SEGMENT_COUNT = 5; private static final long AVAILABILITY_REALTIME_OFFSET = 1000;
private static final long LIVE_SEGMENT_DURATION_MS = 1000; private static final long AVAILABILITY_CURRENT_TIME =
private static final long LIVE_TIMESHIFT_BUFFER_DEPTH_MS = AVAILABILITY_START_TIME + AVAILABILITY_LATENCY - AVAILABILITY_REALTIME_OFFSET;
LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS; private static final FakeClock AVAILABILITY_CLOCK = new FakeClock(AVAILABILITY_CURRENT_TIME);
private static final long AVAILABILITY_START_TIME_MS = 60000;
private static final long AVAILABILITY_REALTIME_OFFSET_MS = 1000;
private static final long AVAILABILITY_CURRENT_TIME_MS =
AVAILABILITY_START_TIME_MS + LIVE_TIMESHIFT_BUFFER_DEPTH_MS - AVAILABILITY_REALTIME_OFFSET_MS;
private static final long LIVE_SEEK_BEYOND_EDGE_MS = 60000;
private static final int TALL_HEIGHT = 200; private static final int TALL_HEIGHT = 200;
private static final int WIDE_WIDTH = 400; private static final int WIDE_WIDTH = 400;
...@@ -99,21 +90,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase { ...@@ -99,21 +90,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals(TALL_HEIGHT, out.getMaxVideoHeight()); assertEquals(TALL_HEIGHT, out.getMaxVideoHeight());
} }
public void testGetSeekRangeOnVod() {
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, mock(FormatEvaluator.class));
chunkSource.enable();
TimeRange seekRange = chunkSource.getSeekRange();
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(0, seekRangeValuesUs[0]);
assertEquals(VOD_DURATION * 1000, seekRangeValuesUs[1]);
long[] seekRangeValuesMs = seekRange.getCurrentBoundsMs(null);
assertEquals(0, seekRangeValuesMs[0]);
assertEquals(VOD_DURATION, seekRangeValuesMs[1]);
}
public void testMaxVideoDimensionsLegacy() { public void testMaxVideoDimensionsLegacy() {
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4"); SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
Representation representation1 = Representation representation1 =
...@@ -131,254 +107,147 @@ public class DashChunkSourceTest extends InstrumentationTestCase { ...@@ -131,254 +107,147 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals(TALL_HEIGHT, out.getMaxVideoHeight()); assertEquals(TALL_HEIGHT, out.getMaxVideoHeight());
} }
public void testLiveEdgeNoLatency() { public void testLiveEdgeNoLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(0L);
long liveEdgeLatencyMs = 0; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
}
public void testLiveEdgeAlmostNoLatency() { assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long startTimeMs = 0; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
long liveEdgeLatencyMs = 1;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge500msLatency() { public void testLiveEdge500msLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(500L);
long liveEdgeLatencyMs = 500; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000; assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 5000; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge1000msLatency() { public void testLiveEdge1000msLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(1000L);
long liveEdgeLatencyMs = 1000; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000; assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 5000; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge1001msLatency() { public void testLiveEdge1001msLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(1001L);
long liveEdgeLatencyMs = 1001; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 3000; assertEquals(3000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 4000; assertEquals(4000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge2500msLatency() { public void testLiveEdge2500msLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(2500L);
long liveEdgeLatencyMs = 2500; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 2000; assertEquals(2000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 3000; assertEquals(3000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdgeVeryHighLatency() { public void testLiveEdgeVeryHighLatencyWithTimeline() {
long startTimeMs = 0; DashChunkSource chunkSource = setupLiveEdgeTimelineTest(10000L);
long liveEdgeLatencyMs = 10000; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 0; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 0;
long chunkStartTimeMs = 0; assertEquals(0L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 1000; assertEquals(1000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdgeNoLatencyInProgress() { public void testLiveEdgeNoLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(0L);
long liveEdgeLatencyMs = 0; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000; // this should actually return the "5th" segment, but it currently returns the "6th", which
long chunkEndTimeMs = 8000; // doesn't actually exist yet; this will be resolved in a subsequent cl (cl/87518875).
//assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs, //assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdgeAlmostNoLatencyInProgress() { public void testLiveEdgeAlmostNoLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1L);
long liveEdgeLatencyMs = 1; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000; assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 8000; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge500msLatencyInProgress() { public void testLiveEdge500msLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(500L);
long liveEdgeLatencyMs = 500; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000; assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 8000; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge1000msLatencyInProgress() { public void testLiveEdge1000msLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1000L);
long liveEdgeLatencyMs = 1000; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000; assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 8000; assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge1001msLatencyInProgress() { public void testLiveEdge1001msLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1001L);
long liveEdgeLatencyMs = 1001; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 6000; assertEquals(3000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 7000; assertEquals(4000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdge2500msLatencyInProgress() { public void testLiveEdge2500msLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(2500L);
long liveEdgeLatencyMs = 2500; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 5000; assertEquals(2000000L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 6000; assertEquals(3000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
public void testLiveEdgeVeryHighLatencyInProgress() { public void testLiveEdgeVeryHighLatencyWithTemplate() {
long startTimeMs = 3000; DashChunkSource chunkSource = setupLiveEdgeTemplateTest(10000L);
long liveEdgeLatencyMs = 10000; List<MediaChunk> queue = new ArrayList<>();
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS; ChunkOperationHolder out = new ChunkOperationHolder();
long seekRangeStartMs = 3000; chunkSource.getChunkOperation(queue, 0, 0, out);
long seekRangeEndMs = 3000;
long chunkStartTimeMs = 3000; assertEquals(0L, ((MediaChunk) out.chunk).startTimeUs);
long chunkEndTimeMs = 4000; assertEquals(1000000L, ((MediaChunk) out.chunk).endTimeUs);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, 0, 0, 1000);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
} }
private static MediaPresentationDescription generateMpd(boolean live, private static MediaPresentationDescription generateMpd(boolean live,
List<Representation> representations, boolean limitTimeshiftBuffer) { List<Representation> representations) {
Representation firstRepresentation = representations.get(0); Representation firstRepresentation = representations.get(0);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations);
Period period = new Period(null, firstRepresentation.periodStartMs, Period period = new Period(null, firstRepresentation.periodStartMs,
firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet)); firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet));
long duration = (live) ? TrackRenderer.UNKNOWN_TIME_US long duration = (live) ? TrackRenderer.UNKNOWN_TIME_US
: firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs; : firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs;
return new MediaPresentationDescription(AVAILABILITY_START_TIME_MS, duration, -1, live, -1, return new MediaPresentationDescription(AVAILABILITY_START_TIME, duration, -1, live, -1, -1,
(limitTimeshiftBuffer) ? LIVE_TIMESHIFT_BUFFER_DEPTH_MS : -1,
null, Collections.singletonList(period)); null, Collections.singletonList(period));
} }
...@@ -387,126 +256,72 @@ public class DashChunkSourceTest extends InstrumentationTestCase { ...@@ -387,126 +256,72 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4"); SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
Representation representation1 = Representation representation1 =
Representation.newInstance(0, VOD_DURATION, null, 0, TALL_VIDEO, segmentBase1); Representation.newInstance(0, 0, null, 0, TALL_VIDEO, segmentBase1);
representations.add(representation1); representations.add(representation1);
SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4"); SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4");
Representation representation2 = Representation representation2 =
Representation.newInstance(0, VOD_DURATION, null, 0, WIDE_VIDEO, segmentBase2); Representation.newInstance(0, 0, null, 0, WIDE_VIDEO, segmentBase2);
representations.add(representation2); representations.add(representation2);
return generateMpd(false, representations, false); return generateMpd(false, representations);
} }
private static MediaPresentationDescription generateLiveMpdWithTimeline(long startTime) { private static MediaPresentationDescription generateLiveMpdWithTimeline() {
List<Representation> representations = new ArrayList<>(); List<Representation> representations = new ArrayList<>();
List<SegmentTimelineElement> segmentTimeline = new ArrayList<>(); List<SegmentTimelineElement> segmentTimeline = new ArrayList<>();
segmentTimeline.add(new SegmentTimelineElement(0L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(1000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(2000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(3000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(4000L, 1000L));
List<RangedUri> mediaSegments = new ArrayList<>(); List<RangedUri> mediaSegments = new ArrayList<>();
long byteStart = 0; mediaSegments.add(new RangedUri("", "", 0L, 500L));
for (int i = 0; i < LIVE_SEGMENT_COUNT; i++) { mediaSegments.add(new RangedUri("", "", 500L, 500L));
segmentTimeline.add(new SegmentTimelineElement(startTime, LIVE_SEGMENT_DURATION_MS)); mediaSegments.add(new RangedUri("", "", 1000L, 500L));
mediaSegments.add(new RangedUri("", "", byteStart, 500L)); mediaSegments.add(new RangedUri("", "", 1500L, 500L));
startTime += LIVE_SEGMENT_DURATION_MS; mediaSegments.add(new RangedUri("", "", 2000L, 500L));
byteStart += 500;
}
MultiSegmentBase segmentBase = new SegmentList(null, 1000, 0, MultiSegmentBase segmentBase = new SegmentList(null, 1000, 0,
TrackRenderer.UNKNOWN_TIME_US, 0, TrackRenderer.UNKNOWN_TIME_US, segmentTimeline, TrackRenderer.UNKNOWN_TIME_US, 1, TrackRenderer.UNKNOWN_TIME_US, segmentTimeline,
mediaSegments); mediaSegments);
Representation representation = Representation.newInstance(startTime, Representation representation = Representation.newInstance(0, TrackRenderer.UNKNOWN_TIME_US,
TrackRenderer.UNKNOWN_TIME_US, null, 0, REGULAR_VIDEO, segmentBase); null, 0, REGULAR_VIDEO, segmentBase);
representations.add(representation); representations.add(representation);
return generateMpd(true, representations, false); return generateMpd(true, representations);
} }
private static MediaPresentationDescription generateLiveMpdWithTemplate( private static MediaPresentationDescription generateLiveMpdWithTemplate() {
boolean limitTimeshiftBuffer) {
List<Representation> representations = new ArrayList<>(); List<Representation> representations = new ArrayList<>();
UrlTemplate initializationTemplate = null; UrlTemplate initializationTemplate = null;
UrlTemplate mediaTemplate = UrlTemplate.compile("$RepresentationID$/$Number$"); UrlTemplate mediaTemplate = UrlTemplate.compile("$RepresentationID$/$Number$");
MultiSegmentBase segmentBase = new SegmentTemplate(null, 1000, 0, MultiSegmentBase segmentBase = new SegmentTemplate(null, 1000, 0,
TrackRenderer.UNKNOWN_TIME_US, 0, LIVE_SEGMENT_DURATION_MS, null, TrackRenderer.UNKNOWN_TIME_US, 1, 1000, null,
initializationTemplate, mediaTemplate, "http://www.youtube.com"); initializationTemplate, mediaTemplate, "http://www.youtube.com");
Representation representation = Representation.newInstance(0, TrackRenderer.UNKNOWN_TIME_US, Representation representation = Representation.newInstance(0, TrackRenderer.UNKNOWN_TIME_US,
null, 0, REGULAR_VIDEO, segmentBase); null, 0, REGULAR_VIDEO, segmentBase);
representations.add(representation); representations.add(representation);
return generateMpd(true, representations, limitTimeshiftBuffer); return generateMpd(true, representations);
} }
private DashChunkSource setupLiveEdgeTimelineTest(long startTime, long liveEdgeLatencyMs) { private DashChunkSource setupLiveEdgeTimelineTest(long liveEdgeLatencyMs) {
MediaPresentationDescription manifest = generateLiveMpdWithTimeline(startTime); MediaPresentationDescription manifest = generateLiveMpdWithTimeline();
when(mockManifestFetcher.getManifest()).thenReturn(manifest); when(mockManifestFetcher.getManifest()).thenReturn(manifest);
DashChunkSource chunkSource = new DashChunkSource(mockManifestFetcher, manifest, return new DashChunkSource(mockManifestFetcher, manifest, AdaptationSet.TYPE_VIDEO, null,
AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR, mockDataSource, EVALUATOR, AVAILABILITY_CLOCK, liveEdgeLatencyMs * 1000,
new FakeClock(AVAILABILITY_CURRENT_TIME_MS + startTime), liveEdgeLatencyMs * 1000, AVAILABILITY_REALTIME_OFFSET * 1000);
AVAILABILITY_REALTIME_OFFSET_MS * 1000, null, null);
chunkSource.enable();
return chunkSource;
} }
private DashChunkSource setupLiveEdgeTemplateTest(long startTime, long liveEdgeLatencyMs, private DashChunkSource setupLiveEdgeTemplateTest(long liveEdgeLatencyMs) {
boolean limitTimeshiftBuffer) { MediaPresentationDescription manifest = generateLiveMpdWithTemplate();
MediaPresentationDescription manifest = generateLiveMpdWithTemplate(limitTimeshiftBuffer);
when(mockManifestFetcher.getManifest()).thenReturn(manifest); when(mockManifestFetcher.getManifest()).thenReturn(manifest);
DashChunkSource chunkSource = new DashChunkSource(mockManifestFetcher, manifest, return new DashChunkSource(mockManifestFetcher, manifest, AdaptationSet.TYPE_VIDEO, null,
AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR, mockDataSource, EVALUATOR, AVAILABILITY_CLOCK, liveEdgeLatencyMs * 1000,
new FakeClock(AVAILABILITY_CURRENT_TIME_MS + startTime), liveEdgeLatencyMs * 1000, AVAILABILITY_REALTIME_OFFSET * 1000);
AVAILABILITY_REALTIME_OFFSET_MS * 1000, null, null);
chunkSource.enable();
return chunkSource;
}
private void checkLiveEdgeLatencyWithTimeline(long startTimeMs, long liveEdgeLatencyMs,
long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs, long chunkStartTimeMs,
long chunkEndTimeMs) {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(startTimeMs, liveEdgeLatencyMs);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, 0, out);
TimeRange seekRange = chunkSource.getSeekRange();
assertNotNull(out.chunk);
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(seekRangeStartMs * 1000, seekRangeValuesUs[0]);
assertEquals(seekRangeEndMs * 1000, seekRangeValuesUs[1]);
assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs);
}
private void checkLiveEdgeLatencyWithTemplate(long startTimeMs, long liveEdgeLatencyMs,
long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs, long chunkStartTimeMs,
long chunkEndTimeMs, boolean limitTimeshiftBuffer) {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(startTimeMs, liveEdgeLatencyMs,
limitTimeshiftBuffer);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, 0, out);
TimeRange seekRange = chunkSource.getSeekRange();
assertNotNull(out.chunk);
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(seekRangeStartMs * 1000, seekRangeValuesUs[0]);
assertEquals(seekRangeEndMs * 1000, seekRangeValuesUs[1]);
assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs);
}
private void checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(long startTimeMs,
long liveEdgeLatencyMs, long seekPositionMs, long seekRangeEndMs,
long chunkStartTimeMs, long chunkEndTimeMs) {
checkLiveEdgeLatencyWithTemplate(startTimeMs, liveEdgeLatencyMs, seekPositionMs, 0,
seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs, false);
}
private void checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(long startTimeMs,
long liveEdgeLatencyMs, long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs,
long chunkStartTimeMs, long chunkEndTimeMs) {
checkLiveEdgeLatencyWithTemplate(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs, true);
} }
} }
...@@ -20,9 +20,9 @@ import junit.framework.TestCase; ...@@ -20,9 +20,9 @@ import junit.framework.TestCase;
import java.util.Arrays; import java.util.Arrays;
/** /**
* Tests for {@link H264Util}. * Tests for {@link NalUnitUtil}.
*/ */
public class H264UtilTest extends TestCase { public class NalUnitUtilTest extends TestCase {
private static final int TEST_PARTIAL_NAL_POSITION = 4; private static final int TEST_PARTIAL_NAL_POSITION = 4;
private static final int TEST_NAL_POSITION = 10; private static final int TEST_NAL_POSITION = 10;
...@@ -31,19 +31,19 @@ public class H264UtilTest extends TestCase { ...@@ -31,19 +31,19 @@ public class H264UtilTest extends TestCase {
byte[] data = buildTestData(); byte[] data = buildTestData();
// Should find NAL unit. // Should find NAL unit.
int result = H264Util.findNalUnit(data, 0, data.length, null); int result = NalUnitUtil.findNalUnit(data, 0, data.length, null);
assertEquals(TEST_NAL_POSITION, result); assertEquals(TEST_NAL_POSITION, result);
// Should find NAL unit whose prefix ends one byte before the limit. // Should find NAL unit whose prefix ends one byte before the limit.
result = H264Util.findNalUnit(data, 0, TEST_NAL_POSITION + 4, null); result = NalUnitUtil.findNalUnit(data, 0, TEST_NAL_POSITION + 4, null);
assertEquals(TEST_NAL_POSITION, result); assertEquals(TEST_NAL_POSITION, result);
// Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive). // Shouldn't find NAL unit whose prefix ends at the limit (since the limit is exclusive).
result = H264Util.findNalUnit(data, 0, TEST_NAL_POSITION + 3, null); result = NalUnitUtil.findNalUnit(data, 0, TEST_NAL_POSITION + 3, null);
assertEquals(TEST_NAL_POSITION + 3, result); assertEquals(TEST_NAL_POSITION + 3, result);
// Should find NAL unit whose prefix starts at the offset. // Should find NAL unit whose prefix starts at the offset.
result = H264Util.findNalUnit(data, TEST_NAL_POSITION, data.length, null); result = NalUnitUtil.findNalUnit(data, TEST_NAL_POSITION, data.length, null);
assertEquals(TEST_NAL_POSITION, result); assertEquals(TEST_NAL_POSITION, result);
// Shouldn't find NAL unit whose prefix starts one byte past the offset. // Shouldn't find NAL unit whose prefix starts one byte past the offset.
result = H264Util.findNalUnit(data, TEST_NAL_POSITION + 1, data.length, null); result = NalUnitUtil.findNalUnit(data, TEST_NAL_POSITION + 1, data.length, null);
assertEquals(data.length, result); assertEquals(data.length, result);
} }
...@@ -54,9 +54,9 @@ public class H264UtilTest extends TestCase { ...@@ -54,9 +54,9 @@ public class H264UtilTest extends TestCase {
boolean[] prefixFlags = new boolean[3]; boolean[] prefixFlags = new boolean[3];
byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1); byte[] data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length); byte[] data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, data.length);
int result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags); int result = NalUnitUtil.findNalUnit(data1, 0, data1.length, prefixFlags);
assertEquals(data1.length, result); assertEquals(data1.length, result);
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags); result = NalUnitUtil.findNalUnit(data2, 0, data2.length, prefixFlags);
assertEquals(-1, result); assertEquals(-1, result);
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
...@@ -64,9 +64,9 @@ public class H264UtilTest extends TestCase { ...@@ -64,9 +64,9 @@ public class H264UtilTest extends TestCase {
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3); data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 3);
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length); data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 3, data.length);
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags); result = NalUnitUtil.findNalUnit(data1, 0, data1.length, prefixFlags);
assertEquals(data1.length, result); assertEquals(data1.length, result);
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags); result = NalUnitUtil.findNalUnit(data2, 0, data2.length, prefixFlags);
assertEquals(-3, result); assertEquals(-3, result);
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
...@@ -75,11 +75,11 @@ public class H264UtilTest extends TestCase { ...@@ -75,11 +75,11 @@ public class H264UtilTest extends TestCase {
data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1); data1 = Arrays.copyOfRange(data, 0, TEST_NAL_POSITION + 1);
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2); data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length); byte[] data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags); result = NalUnitUtil.findNalUnit(data1, 0, data1.length, prefixFlags);
assertEquals(data1.length, result); assertEquals(data1.length, result);
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags); result = NalUnitUtil.findNalUnit(data2, 0, data2.length, prefixFlags);
assertEquals(data2.length, result); assertEquals(data2.length, result);
result = H264Util.findNalUnit(data3, 0, data3.length, prefixFlags); result = NalUnitUtil.findNalUnit(data3, 0, data3.length, prefixFlags);
assertEquals(-2, result); assertEquals(-2, result);
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
...@@ -89,13 +89,13 @@ public class H264UtilTest extends TestCase { ...@@ -89,13 +89,13 @@ public class H264UtilTest extends TestCase {
data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2); data2 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 1, TEST_NAL_POSITION + 2);
data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3); data3 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, TEST_NAL_POSITION + 3);
byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length); byte[] data4 = Arrays.copyOfRange(data, TEST_NAL_POSITION + 2, data.length);
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags); result = NalUnitUtil.findNalUnit(data1, 0, data1.length, prefixFlags);
assertEquals(data1.length, result); assertEquals(data1.length, result);
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags); result = NalUnitUtil.findNalUnit(data2, 0, data2.length, prefixFlags);
assertEquals(data2.length, result); assertEquals(data2.length, result);
result = H264Util.findNalUnit(data3, 0, data3.length, prefixFlags); result = NalUnitUtil.findNalUnit(data3, 0, data3.length, prefixFlags);
assertEquals(data3.length, result); assertEquals(data3.length, result);
result = H264Util.findNalUnit(data4, 0, data4.length, prefixFlags); result = NalUnitUtil.findNalUnit(data4, 0, data4.length, prefixFlags);
assertEquals(-3, result); assertEquals(-3, result);
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
...@@ -103,9 +103,9 @@ public class H264UtilTest extends TestCase { ...@@ -103,9 +103,9 @@ public class H264UtilTest extends TestCase {
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2); data1 = Arrays.copyOfRange(data, 0, TEST_PARTIAL_NAL_POSITION + 2);
data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length); data2 = Arrays.copyOfRange(data, TEST_PARTIAL_NAL_POSITION + 2, data.length);
result = H264Util.findNalUnit(data1, 0, data1.length, prefixFlags); result = NalUnitUtil.findNalUnit(data1, 0, data1.length, prefixFlags);
assertEquals(data1.length, result); assertEquals(data1.length, result);
result = H264Util.findNalUnit(data2, 0, data2.length, prefixFlags); result = NalUnitUtil.findNalUnit(data2, 0, data2.length, prefixFlags);
assertEquals(4, result); assertEquals(4, result);
assertPrefixFlagsCleared(prefixFlags); assertPrefixFlagsCleared(prefixFlags);
} }
......
...@@ -12,4 +12,5 @@ ...@@ -12,4 +12,5 @@
# Project target. # Project target.
target=android-22 target=android-22
android.library=false
android.library.reference.1=../main android.library.reference.1=../main
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