Commit 876fa41b by ojw28

Merge pull request #283 from google/dev

dev -> dev-webm-vp9-opus
parents 3338a09c 147bbe6d
Showing with 393 additions and 982 deletions
...@@ -42,12 +42,7 @@ ...@@ -42,12 +42,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.google.android.exoplayer.demo.simple.SimplePlayerActivity" <activity android:name="com.google.android.exoplayer.demo.PlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/application_name"
android:theme="@style/PlayerTheme"/>
<activity android:name="com.google.android.exoplayer.demo.full.FullPlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/application_name" android:label="@string/application_name"
android:theme="@style/PlayerTheme"/> android:theme="@style/PlayerTheme"/>
......
...@@ -44,16 +44,11 @@ public class DemoUtil { ...@@ -44,16 +44,11 @@ public class DemoUtil {
public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id";
public static final int TYPE_DASH = 0; public static final int TYPE_DASH = 0;
public static final int TYPE_SS = 1; public static final int TYPE_SS = 1;
public static final int TYPE_OTHER = 2; public static final int TYPE_OTHER = 2;
public static final int TYPE_HLS = 3; public static final int TYPE_HLS = 3;
public static final boolean EXPOSE_EXPERIMENTAL_FEATURES = false;
private static final CookieManager defaultCookieManager; private static final CookieManager defaultCookieManager;
static { static {
......
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
* 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.full; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.demo.full.player.DemoPlayer; import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.util.VerboseLogUtil; import com.google.android.exoplayer.util.VerboseLogUtil;
import android.media.MediaCodec.CryptoException; import android.media.MediaCodec.CryptoException;
...@@ -63,8 +63,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -63,8 +63,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override @Override
public void onStateChanged(boolean playWhenReady, int state) { public void onStateChanged(boolean playWhenReady, int state) {
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " + Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
getStateString(state) + "]"); + getStateString(state) + "]");
} }
@Override @Override
...@@ -81,8 +81,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -81,8 +81,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override @Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) { public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
", " + getTimeString(elapsedMs) + ", " + bitrateEstimate + "]"); + getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
} }
@Override @Override
...@@ -104,21 +104,21 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -104,21 +104,21 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
public void onLoadCompleted(int sourceId, long bytesLoaded) { public void onLoadCompleted(int sourceId, long bytesLoaded) {
if (VerboseLogUtil.isTagEnabled(TAG)) { if (VerboseLogUtil.isTagEnabled(TAG)) {
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId]; long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
downloadTime + "]"); + "]");
} }
} }
@Override @Override
public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) { public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " + Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", "
Integer.toString(trigger) + "]"); + Integer.toString(trigger) + "]");
} }
@Override @Override
public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) { public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " + Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", "
Integer.toString(trigger) + "]"); + Integer.toString(trigger) + "]");
} }
// DemoPlayer.InternalErrorListener // DemoPlayer.InternalErrorListener
......
...@@ -13,21 +13,19 @@ ...@@ -13,21 +13,19 @@
* 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.full; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.VideoSurfaceView; import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
import com.google.android.exoplayer.demo.DemoUtil; import com.google.android.exoplayer.demo.player.DashRendererBuilder;
import com.google.android.exoplayer.demo.R; import com.google.android.exoplayer.demo.player.DefaultRendererBuilder;
import com.google.android.exoplayer.demo.full.player.DashRendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer; import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.demo.full.player.HlsRendererBuilder; import com.google.android.exoplayer.demo.player.UnsupportedDrmException;
import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.demo.full.player.UnsupportedDrmException;
import com.google.android.exoplayer.metadata.TxxxMetadata; 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.SubtitleView; import com.google.android.exoplayer.text.SubtitleView;
...@@ -65,11 +63,14 @@ import java.util.Map; ...@@ -65,11 +63,14 @@ import java.util.Map;
/** /**
* An activity that plays media using {@link DemoPlayer}. * An activity that plays media using {@link DemoPlayer}.
*/ */
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener, public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener, DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener,
AudioCapabilitiesReceiver.Listener { AudioCapabilitiesReceiver.Listener {
private static final String TAG = "FullPlayerActivity"; public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id";
private static final String TAG = "PlayerActivity";
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f; private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
private static final int MENU_GROUP_TRACKS = 1; private static final int MENU_GROUP_TRACKS = 1;
...@@ -110,10 +111,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba ...@@ -110,10 +111,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
Intent intent = getIntent(); Intent intent = getIntent();
contentUri = intent.getData(); contentUri = intent.getData();
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER); contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA); contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
setContentView(R.layout.player_activity_full); setContentView(R.layout.player_activity);
View root = findViewById(R.id.root); View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() { root.setOnTouchListener(new OnTouchListener() {
@Override @Override
......
...@@ -15,15 +15,17 @@ ...@@ -15,15 +15,17 @@
*/ */
package com.google.android.exoplayer.demo; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.demo.Samples.Sample; import com.google.android.exoplayer.demo.Samples.Sample;
import com.google.android.exoplayer.demo.full.FullPlayerActivity; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -38,6 +40,8 @@ import android.widget.TextView; ...@@ -38,6 +40,8 @@ import android.widget.TextView;
*/ */
public class SampleChooserActivity extends Activity { public class SampleChooserActivity extends Activity {
private static final String TAG = "SampleChooserActivity";
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
...@@ -46,22 +50,26 @@ public class SampleChooserActivity extends Activity { ...@@ -46,22 +50,26 @@ public class SampleChooserActivity extends Activity {
ListView sampleList = (ListView) findViewById(R.id.sample_list); ListView sampleList = (ListView) findViewById(R.id.sample_list);
final SampleAdapter sampleAdapter = new SampleAdapter(this); final SampleAdapter sampleAdapter = new SampleAdapter(this);
sampleAdapter.add(new Header("Simple player"));
sampleAdapter.addAll((Object[]) Samples.SIMPLE);
sampleAdapter.add(new Header("YouTube DASH")); sampleAdapter.add(new Header("YouTube DASH"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4); sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4);
sampleAdapter.add(new Header("Widevine GTS DASH")); sampleAdapter.add(new Header("Widevine GTS DASH"));
sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS); sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS);
sampleAdapter.add(new Header("SmoothStreaming")); sampleAdapter.add(new Header("SmoothStreaming"));
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING); sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);
sampleAdapter.add(new Header("Misc"));
sampleAdapter.addAll((Object[]) Samples.MISC);
sampleAdapter.add(new Header("HLS")); sampleAdapter.add(new Header("HLS"));
sampleAdapter.addAll((Object[]) Samples.HLS); sampleAdapter.addAll((Object[]) Samples.HLS);
if (DemoUtil.EXPOSE_EXPERIMENTAL_FEATURES) { sampleAdapter.add(new Header("Misc"));
sampleAdapter.addAll((Object[]) Samples.MISC);
// Add WebM samples if the device has a VP9 decoder.
try {
if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false) != null) {
sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)")); sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM); sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
} }
} catch (DecoderQueryException e) {
Log.e(TAG, "Failed to query vp9 decoder", e);
}
sampleList.setAdapter(sampleAdapter); sampleList.setAdapter(sampleAdapter);
sampleList.setOnItemClickListener(new OnItemClickListener() { sampleList.setOnItemClickListener(new OnItemClickListener() {
...@@ -76,12 +84,10 @@ public class SampleChooserActivity extends Activity { ...@@ -76,12 +84,10 @@ public class SampleChooserActivity extends Activity {
} }
private void onSampleSelected(Sample sample) { private void onSampleSelected(Sample sample) {
Class<?> playerActivityClass = sample.fullPlayer ? FullPlayerActivity.class Intent mpdIntent = new Intent(this, PlayerActivity.class)
: SimplePlayerActivity.class;
Intent mpdIntent = new Intent(this, playerActivityClass)
.setData(Uri.parse(sample.uri)) .setData(Uri.parse(sample.uri))
.putExtra(DemoUtil.CONTENT_ID_EXTRA, sample.contentId) .putExtra(PlayerActivity.CONTENT_ID_EXTRA, sample.contentId)
.putExtra(DemoUtil.CONTENT_TYPE_EXTRA, sample.type); .putExtra(PlayerActivity.CONTENT_TYPE_EXTRA, sample.type);
startActivity(mpdIntent); startActivity(mpdIntent);
} }
......
...@@ -13,9 +13,8 @@ ...@@ -13,9 +13,8 @@
* 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.full; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
......
...@@ -13,9 +13,8 @@ ...@@ -13,9 +13,8 @@
* 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.full; package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback; import com.google.android.exoplayer.drm.MediaDrmCallback;
import android.annotation.TargetApi; import android.annotation.TargetApi;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer; import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.DefaultLoadControl;
...@@ -38,8 +38,8 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser; ...@@ -38,8 +38,8 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.dash.mpd.Period; import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.demo.DemoUtil; import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.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.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;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaCodecTrackRenderer; import com.google.android.exoplayer.MediaCodecTrackRenderer;
...@@ -82,8 +82,8 @@ import android.widget.TextView; ...@@ -82,8 +82,8 @@ import android.widget.TextView;
} }
private String getRenderString() { private String getRenderString() {
return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() + return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString()
", " + renderer.codecCounters.getDebugString(); + ", " + renderer.codecCounters.getDebugString();
} }
private String getQualityString() { private String getQualityString() {
......
...@@ -13,13 +13,13 @@ ...@@ -13,13 +13,13 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.source.DefaultSampleSource; import com.google.android.exoplayer.source.DefaultSampleSource;
import com.google.android.exoplayer.source.FrameworkSampleExtractor; import com.google.android.exoplayer.source.FrameworkSampleExtractor;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer; import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
import com.google.android.exoplayer.DummyTrackRenderer; import com.google.android.exoplayer.DummyTrackRenderer;
......
...@@ -13,13 +13,13 @@ ...@@ -13,13 +13,13 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsPlaylist; import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser; import com.google.android.exoplayer.hls.HlsPlaylistParser;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.DefaultLoadControl; import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.LoadControl;
...@@ -27,8 +27,8 @@ import com.google.android.exoplayer.chunk.ChunkSource; ...@@ -27,8 +27,8 @@ import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.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.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;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.full.player; package com.google.android.exoplayer.demo.player;
/** /**
* Exception thrown when the required level of DRM is not supported. * Exception thrown when the required level of DRM is not supported.
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.media.MediaCodec;
import android.os.Handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link RendererBuilder} for DASH.
*/
/* package */ class DashRendererBuilder implements RendererBuilder,
ManifestCallback<MediaPresentationDescription> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60;
private static final int LIVE_EDGE_LATENCY_MS = 30000;
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private RendererBuilderCallback callback;
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
public DashRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(parser, contentId, url,
userAgent);
manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
}
@Override
public void onManifestError(String contentId, IOException e) {
callback.onRenderersError(e);
}
@Override
public void onManifest(String contentId, MediaPresentationDescription manifest) {
Period period = manifest.periods.get(0);
Handler mainHandler = playerActivity.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Determine which video representations we should use for playback.
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}
int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
List<Representation> videoRepresentations =
period.adaptationSets.get(videoAdaptationSetIndex).representations;
ArrayList<Integer> videoRepresentationIndexList = new ArrayList<Integer>();
for (int i = 0; i < videoRepresentations.size(); i++) {
Format format = videoRepresentations.get(i).format;
if (format.width * format.height > maxDecodableFrameSize) {
// Filtering stream that device cannot play
} else if (!format.mimeType.equals(MimeTypes.VIDEO_MP4)
&& !format.mimeType.equals(MimeTypes.VIDEO_WEBM)) {
// Filtering unsupported mime type
} else {
videoRepresentationIndexList.add(i);
}
}
// Build the video renderer.
final MediaCodecVideoTrackRenderer videoRenderer;
if (videoRepresentationIndexList.isEmpty()) {
videoRenderer = null;
} else {
int[] videoRepresentationIndices = Util.toArray(videoRepresentationIndexList);
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex,
videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter),
LIVE_EDGE_LATENCY_MS);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
}
// Build the audio renderer.
int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {0}, audioDataSource, new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
audioSampleSource);
callback.onRenderers(videoRenderer, audioRenderer);
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.source.DefaultSampleSource;
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
import android.media.MediaCodec;
import android.net.Uri;
/**
* A {@link RendererBuilder} for streams that can be read using
* {@link android.media.MediaExtractor}.
*/
/* package */ class DefaultRendererBuilder implements RendererBuilder {
private final SimplePlayerActivity playerActivity;
private final Uri uri;
public DefaultRendererBuilder(SimplePlayerActivity playerActivity, Uri uri) {
this.playerActivity = playerActivity;
this.uri = uri;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
// Build the video and audio renderers.
DefaultSampleSource sampleSource =
new DefaultSampleSource(new FrameworkSampleExtractor(playerActivity, uri, null), 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
playerActivity, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
// Invoke the callback.
callback.onRenderers(videoRenderer, audioRenderer);
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsPlaylist;
import com.google.android.exoplayer.hls.HlsPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import android.media.MediaCodec;
import java.io.IOException;
/**
* A {@link RendererBuilder} for HLS.
*/
/* package */ class HlsRendererBuilder implements RendererBuilder,
ManifestCallback<HlsPlaylist> {
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private RendererBuilderCallback callback;
public HlsRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
HlsPlaylistParser parser = new HlsPlaylistParser();
ManifestFetcher<HlsPlaylist> playlistFetcher =
new ManifestFetcher<HlsPlaylist>(parser, contentId, url, userAgent);
playlistFetcher.singleLoad(playerActivity.getMainLooper(), this);
}
@Override
public void onManifestError(String contentId, IOException e) {
callback.onRenderersError(e);
}
@Override
public void onManifest(String contentId, HlsPlaylist manifest) {
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
playerActivity, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
callback.onRenderers(videoRenderer, audioRenderer);
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.R;
import com.google.android.exoplayer.util.PlayerControl;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaCodec.CryptoException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.MediaController;
import android.widget.Toast;
/**
* An activity that plays media using {@link ExoPlayer}.
*/
public class SimplePlayerActivity extends Activity implements SurfaceHolder.Callback,
ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener {
/**
* Builds renderers for the player.
*/
public interface RendererBuilder {
void buildRenderers(RendererBuilderCallback callback);
}
public static final int RENDERER_COUNT = 2;
public static final int TYPE_VIDEO = 0;
public static final int TYPE_AUDIO = 1;
private static final String TAG = "PlayerActivity";
private MediaController mediaController;
private Handler mainHandler;
private View shutterView;
private VideoSurfaceView surfaceView;
private ExoPlayer player;
private RendererBuilder builder;
private RendererBuilderCallback callback;
private MediaCodecVideoTrackRenderer videoRenderer;
private boolean autoPlay = true;
private long playerPosition;
private Uri contentUri;
private int contentType;
private String contentId;
// Activity lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
contentUri = intent.getData();
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
mainHandler = new Handler(getMainLooper());
builder = getRendererBuilder();
setContentView(R.layout.player_activity_simple);
View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
toggleControlsVisibility();
}
return true;
}
});
mediaController = new MediaController(this);
mediaController.setAnchorView(root);
shutterView = findViewById(R.id.shutter);
surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
DemoUtil.setDefaultCookieManager();
}
@Override
public void onResume() {
super.onResume();
// Setup the player
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
player.addListener(this);
player.seekTo(playerPosition);
// Build the player controls
mediaController.setMediaPlayer(new PlayerControl(player));
mediaController.setEnabled(true);
// Request the renderers
callback = new RendererBuilderCallback();
builder.buildRenderers(callback);
}
@Override
public void onPause() {
super.onPause();
// Release the player
if (player != null) {
playerPosition = player.getCurrentPosition();
player.release();
player = null;
}
callback = null;
videoRenderer = null;
shutterView.setVisibility(View.VISIBLE);
}
// Public methods
public Handler getMainHandler() {
return mainHandler;
}
// Internal methods
private void toggleControlsVisibility() {
if (mediaController.isShowing()) {
mediaController.hide();
} else {
mediaController.show(0);
}
}
private RendererBuilder getRendererBuilder() {
String userAgent = DemoUtil.getUserAgent(this);
switch (contentType) {
case DemoUtil.TYPE_SS:
return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
contentId);
case DemoUtil.TYPE_DASH:
return new DashRendererBuilder(this, userAgent, contentUri.toString(), contentId);
case DemoUtil.TYPE_HLS:
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), contentId);
default:
return new DefaultRendererBuilder(this, contentUri);
}
}
private void onRenderers(RendererBuilderCallback callback,
MediaCodecVideoTrackRenderer videoRenderer, MediaCodecAudioTrackRenderer audioRenderer) {
if (this.callback != callback) {
return;
}
this.callback = null;
this.videoRenderer = videoRenderer;
player.prepare(videoRenderer, audioRenderer);
maybeStartPlayback();
}
private void maybeStartPlayback() {
Surface surface = surfaceView.getHolder().getSurface();
if (videoRenderer == null || surface == null || !surface.isValid()) {
// We're not ready yet.
return;
}
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
if (autoPlay) {
player.setPlayWhenReady(true);
autoPlay = false;
}
}
private void onRenderersError(RendererBuilderCallback callback, Exception e) {
if (this.callback != callback) {
return;
}
this.callback = null;
onError(e);
}
private void onError(Exception e) {
Log.e(TAG, "Playback failed", e);
Toast.makeText(this, R.string.failed, Toast.LENGTH_SHORT).show();
finish();
}
// ExoPlayer.Listener implementation
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
// Do nothing.
}
@Override
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException e) {
onError(e);
}
// MediaCodecVideoTrackRenderer.Listener
@Override
public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
surfaceView.setVideoWidthHeightRatio(
height == 0 ? 1 : (pixelWidthHeightRatio * width) / height);
}
@Override
public void onDrawnToSurface(Surface surface) {
shutterView.setVisibility(View.GONE);
}
@Override
public void onDroppedFrames(int count, long elapsed) {
Log.d(TAG, "Dropped frames: " + count);
}
@Override
public void onDecoderInitializationError(DecoderInitializationException e) {
// This is for informational purposes only. Do nothing.
}
@Override
public void onCryptoError(CryptoException e) {
// This is for informational purposes only. Do nothing.
}
// SurfaceHolder.Callback implementation
@Override
public void surfaceCreated(SurfaceHolder holder) {
maybeStartPlayback();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Do nothing.
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (videoRenderer != null) {
player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
}
}
/* package */ final class RendererBuilderCallback {
public void onRenderers(MediaCodecVideoTrackRenderer videoRenderer,
MediaCodecAudioTrackRenderer audioRenderer) {
SimplePlayerActivity.this.onRenderers(this, videoRenderer, audioRenderer);
}
public void onRenderersError(Exception e) {
SimplePlayerActivity.this.onRenderersError(this, e);
}
}
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.Util;
import android.media.MediaCodec;
import android.os.Handler;
import java.io.IOException;
import java.util.ArrayList;
/**
* A {@link RendererBuilder} for SmoothStreaming.
*/
/* package */ class SmoothStreamingRendererBuilder implements RendererBuilder,
ManifestCallback<SmoothStreamingManifest> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60;
private static final int LIVE_EDGE_LATENCY_MS = 30000;
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private RendererBuilderCallback callback;
private ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
public SmoothStreamingRendererBuilder(SimplePlayerActivity playerActivity, String userAgent,
String url, String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
manifestFetcher = new ManifestFetcher<SmoothStreamingManifest>(parser, contentId,
url + "/Manifest", userAgent);
manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
}
@Override
public void onManifestError(String contentId, IOException e) {
callback.onRenderersError(e);
}
@Override
public void onManifest(String contentId, SmoothStreamingManifest manifest) {
Handler mainHandler = playerActivity.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Obtain stream elements for playback.
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}
int audioStreamElementIndex = -1;
int videoStreamElementIndex = -1;
ArrayList<Integer> videoTrackIndexList = new ArrayList<Integer>();
for (int i = 0; i < manifest.streamElements.length; i++) {
if (audioStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
audioStreamElementIndex = i;
} else if (videoStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
videoStreamElementIndex = i;
StreamElement streamElement = manifest.streamElements[i];
for (int j = 0; j < streamElement.tracks.length; j++) {
TrackElement trackElement = streamElement.tracks[j];
if (trackElement.maxWidth * trackElement.maxHeight <= maxDecodableFrameSize) {
videoTrackIndexList.add(j);
} else {
// The device isn't capable of playing this stream.
}
}
}
}
int[] videoTrackIndices = Util.toArray(videoTrackIndexList);
// Build the video renderer.
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
// Build the audio renderer.
DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
audioStreamElementIndex, new int[] {0}, audioDataSource,
new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
audioSampleSource);
callback.onRenderers(videoRenderer, audioRenderer);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</FrameLayout>
...@@ -375,10 +375,13 @@ import java.util.List; ...@@ -375,10 +375,13 @@ import java.util.List;
} }
private void updatePositionUs() { private void updatePositionUs() {
positionUs = timeSourceTrackRenderer != null && if (timeSourceTrackRenderer != null && enabledRenderers.contains(timeSourceTrackRenderer)
enabledRenderers.contains(timeSourceTrackRenderer) ? && !timeSourceTrackRenderer.isEnded()) {
timeSourceTrackRenderer.getCurrentPositionUs() : positionUs = timeSourceTrackRenderer.getCurrentPositionUs();
mediaClock.getPositionUs(); mediaClock.setPositionUs(positionUs);
} else {
positionUs = mediaClock.getPositionUs();
}
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.chunk.parser; package com.google.android.exoplayer.chunk.parser;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
...@@ -79,6 +80,11 @@ public interface Extractor { ...@@ -79,6 +80,11 @@ public interface Extractor {
public MediaFormat getFormat(); public MediaFormat getFormat();
/** /**
* Returns the duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public long getDurationUs();
/**
* Returns the pssh information parsed from the stream. * Returns the pssh information parsed from the stream.
* *
* @return The pssh information. May be null if pssh data has yet to be parsed, or if the stream * @return The pssh information. May be null if pssh data has yet to be parsed, or if the stream
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.chunk.parser.webm; package com.google.android.exoplayer.chunk.parser.webm;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
...@@ -185,6 +186,11 @@ public final class WebmExtractor implements Extractor { ...@@ -185,6 +186,11 @@ public final class WebmExtractor implements Extractor {
} }
@Override @Override
public long getDurationUs() {
return durationUs == UNKNOWN ? C.UNKNOWN_TIME_US : durationUs;
}
@Override
public Map<UUID, byte[]> getPsshInfo() { public Map<UUID, byte[]> getPsshInfo() {
// TODO: Parse pssh data from Webm streams. // TODO: Parse pssh data from Webm streams.
return null; return null;
......
...@@ -15,52 +15,64 @@ ...@@ -15,52 +15,64 @@
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.mp4;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class Atom { public abstract class Atom {
public static final int TYPE_avc1 = 0x61766331; public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
public static final int TYPE_avc3 = 0x61766333; public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
public static final int TYPE_esds = 0x65736473; public static final int TYPE_esds = getAtomTypeInteger("esds");
public static final int TYPE_mdat = 0x6D646174; public static final int TYPE_mdat = getAtomTypeInteger("mdat");
public static final int TYPE_mp4a = 0x6D703461; public static final int TYPE_mp4a = getAtomTypeInteger("mp4a");
public static final int TYPE_ac_3 = 0x61632D33; // ac-3 public static final int TYPE_ac_3 = getAtomTypeInteger("ac-3");
public static final int TYPE_dac3 = 0x64616333; public static final int TYPE_dac3 = getAtomTypeInteger("dac3");
public static final int TYPE_ec_3 = 0x65632D33; // ec-3 public static final int TYPE_ec_3 = getAtomTypeInteger("ec-3");
public static final int TYPE_dec3 = 0x64656333; public static final int TYPE_dec3 = getAtomTypeInteger("dec3");
public static final int TYPE_tfdt = 0x74666474; public static final int TYPE_tfdt = getAtomTypeInteger("tfdt");
public static final int TYPE_tfhd = 0x74666864; public static final int TYPE_tfhd = getAtomTypeInteger("tfhd");
public static final int TYPE_trex = 0x74726578; public static final int TYPE_trex = getAtomTypeInteger("trex");
public static final int TYPE_trun = 0x7472756E; public static final int TYPE_trun = getAtomTypeInteger("trun");
public static final int TYPE_sidx = 0x73696478; public static final int TYPE_sidx = getAtomTypeInteger("sidx");
public static final int TYPE_moov = 0x6D6F6F76; public static final int TYPE_moov = getAtomTypeInteger("moov");
public static final int TYPE_trak = 0x7472616B; public static final int TYPE_trak = getAtomTypeInteger("trak");
public static final int TYPE_mdia = 0x6D646961; public static final int TYPE_mdia = getAtomTypeInteger("mdia");
public static final int TYPE_minf = 0x6D696E66; public static final int TYPE_minf = getAtomTypeInteger("minf");
public static final int TYPE_stbl = 0x7374626C; public static final int TYPE_stbl = getAtomTypeInteger("stbl");
public static final int TYPE_avcC = 0x61766343; public static final int TYPE_avcC = getAtomTypeInteger("avcC");
public static final int TYPE_moof = 0x6D6F6F66; public static final int TYPE_moof = getAtomTypeInteger("moof");
public static final int TYPE_traf = 0x74726166; public static final int TYPE_traf = getAtomTypeInteger("traf");
public static final int TYPE_mvex = 0x6D766578; public static final int TYPE_mvex = getAtomTypeInteger("mvex");
public static final int TYPE_tkhd = 0x746B6864; public static final int TYPE_tkhd = getAtomTypeInteger("tkhd");
public static final int TYPE_mdhd = 0x6D646864; public static final int TYPE_mdhd = getAtomTypeInteger("mdhd");
public static final int TYPE_hdlr = 0x68646C72; public static final int TYPE_hdlr = getAtomTypeInteger("hdlr");
public static final int TYPE_stsd = 0x73747364; public static final int TYPE_stsd = getAtomTypeInteger("stsd");
public static final int TYPE_pssh = 0x70737368; public static final int TYPE_pssh = getAtomTypeInteger("pssh");
public static final int TYPE_sinf = 0x73696E66; public static final int TYPE_sinf = getAtomTypeInteger("sinf");
public static final int TYPE_schm = 0x7363686D; public static final int TYPE_schm = getAtomTypeInteger("schm");
public static final int TYPE_schi = 0x73636869; public static final int TYPE_schi = getAtomTypeInteger("schi");
public static final int TYPE_tenc = 0x74656E63; public static final int TYPE_tenc = getAtomTypeInteger("tenc");
public static final int TYPE_encv = 0x656E6376; public static final int TYPE_encv = getAtomTypeInteger("encv");
public static final int TYPE_enca = 0x656E6361; public static final int TYPE_enca = getAtomTypeInteger("enca");
public static final int TYPE_frma = 0x66726D61; public static final int TYPE_frma = getAtomTypeInteger("frma");
public static final int TYPE_saiz = 0x7361697A; public static final int TYPE_saiz = getAtomTypeInteger("saiz");
public static final int TYPE_uuid = 0x75756964; public static final int TYPE_uuid = getAtomTypeInteger("uuid");
public static final int TYPE_senc = 0x73656E63; public static final int TYPE_senc = getAtomTypeInteger("senc");
public static final int TYPE_pasp = 0x70617370; public static final int TYPE_pasp = getAtomTypeInteger("pasp");
public static final int TYPE_TTML = 0x54544D4C; public static final int TYPE_TTML = getAtomTypeInteger("TTML");
public static final int TYPE_vmhd = getAtomTypeInteger("vmhd");
public static final int TYPE_smhd = getAtomTypeInteger("smhd");
public static final int TYPE_mp4v = getAtomTypeInteger("mp4v");
public static final int TYPE_stts = getAtomTypeInteger("stts");
public static final int TYPE_stss = getAtomTypeInteger("stss");
public static final int TYPE_stsc = getAtomTypeInteger("stsc");
public static final int TYPE_stsz = getAtomTypeInteger("stsz");
public static final int TYPE_stco = getAtomTypeInteger("stco");
public static final int TYPE_co64 = getAtomTypeInteger("co64");
public final int type; public final int type;
...@@ -68,6 +80,12 @@ public abstract class Atom { ...@@ -68,6 +80,12 @@ public abstract class Atom {
this.type = type; this.type = type;
} }
@Override
public String toString() {
return getAtomTypeString(type);
}
/** An MP4 atom that is a leaf. */
public static final class LeafAtom extends Atom { public static final class LeafAtom extends Atom {
public final ParsableByteArray data; public final ParsableByteArray data;
...@@ -79,43 +97,75 @@ public abstract class Atom { ...@@ -79,43 +97,75 @@ public abstract class Atom {
} }
/** An MP4 atom that has child atoms. */
public static final class ContainerAtom extends Atom { public static final class ContainerAtom extends Atom {
public final ArrayList<Atom> children; public final long endByteOffset;
public final int endByteOffset; public final List<LeafAtom> leafChildren;
public final List<ContainerAtom> containerChildren;
public ContainerAtom(int type, int endByteOffset) { public ContainerAtom(int type, long endByteOffset) {
super(type); super(type);
leafChildren = new ArrayList<LeafAtom>();
containerChildren = new ArrayList<ContainerAtom>();
this.endByteOffset = endByteOffset; this.endByteOffset = endByteOffset;
children = new ArrayList<Atom>();
} }
public void add(Atom atom) { public void add(LeafAtom atom) {
children.add(atom); leafChildren.add(atom);
}
public void add(ContainerAtom atom) {
containerChildren.add(atom);
} }
public LeafAtom getLeafAtomOfType(int type) { public LeafAtom getLeafAtomOfType(int type) {
int childrenSize = children.size(); int childrenSize = leafChildren.size();
for (int i = 0; i < childrenSize; i++) { for (int i = 0; i < childrenSize; i++) {
Atom atom = children.get(i); LeafAtom atom = leafChildren.get(i);
if (atom.type == type) { if (atom.type == type) {
return (LeafAtom) atom; return atom;
} }
} }
return null; return null;
} }
public ContainerAtom getContainerAtomOfType(int type) { public ContainerAtom getContainerAtomOfType(int type) {
int childrenSize = children.size(); int childrenSize = containerChildren.size();
for (int i = 0; i < childrenSize; i++) { for (int i = 0; i < childrenSize; i++) {
Atom atom = children.get(i); ContainerAtom atom = containerChildren.get(i);
if (atom.type == type) { if (atom.type == type) {
return (ContainerAtom) atom; return atom;
} }
} }
return null; return null;
} }
@Override
public String toString() {
return getAtomTypeString(type)
+ " leaves: " + Arrays.toString(leafChildren.toArray(new LeafAtom[0]))
+ " containers: " + Arrays.toString(containerChildren.toArray(new ContainerAtom[0]));
}
}
private static String getAtomTypeString(int type) {
return "" + (char) (type >> 24)
+ (char) ((type >> 16) & 0xFF)
+ (char) ((type >> 8) & 0xFF)
+ (char) (type & 0xFF);
}
private static int getAtomTypeInteger(String typeName) {
Assertions.checkArgument(typeName.length() == 4);
int result = 0;
for (int i = 0; i < 4; i++) {
result <<= 8;
result |= typeName.charAt(i);
}
return result;
} }
} }
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.mp4;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.nio.ByteBuffer;
/**
* Utility methods and constants for parsing fragmented and unfragmented MP4 files.
*/
public final class Mp4Util {
/** Size of an atom header, in bytes. */
public static final int ATOM_HEADER_SIZE = 8;
/** Size of a long atom header, in bytes. */
public static final int LONG_ATOM_HEADER_SIZE = 16;
/** Size of a full atom header, in bytes. */
public static final int FULL_ATOM_HEADER_SIZE = 12;
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
/** Parses the version number out of the additional integer component of a full atom. */
public static int parseFullAtomVersion(int fullAtomInt) {
return 0x000000FF & (fullAtomInt >> 24);
}
/** Parses the atom flags out of the additional integer component of a full atom. */
public static int parseFullAtomFlags(int fullAtomInt) {
return 0x00FFFFFF & fullAtomInt;
}
/**
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* assumed that the top bit will always be set to zero.
*
* @throws IllegalArgumentException If the top bit of the input data is set.
*/
public static int readUnsignedIntToInt(ByteBuffer data) {
int result = 0xFF & data.get();
for (int i = 1; i < 4; i++) {
result <<= 8;
result |= 0xFF & data.get();
}
if (result < 0) {
throw new IllegalArgumentException("Top bit not zero: " + result);
}
return result;
}
/**
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
* {@code size} bytes preceding the buffer's position.
*/
public static void replaceLengthPrefixesWithAvcStartCodes(ByteBuffer buffer, int size) {
int sampleOffset = buffer.position() - size;
int position = sampleOffset;
while (position < sampleOffset + size) {
buffer.position(position);
int length = readUnsignedIntToInt(buffer);
buffer.position(position);
buffer.put(NAL_START_CODE);
position += length + 4;
}
buffer.position(sampleOffset + size);
}
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
public static byte[] parseChildNalUnit(ParsableByteArray atom) {
int length = atom.readUnsignedShort();
int offset = atom.getPosition();
atom.skip(length);
return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
}
}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.mp4; package com.google.android.exoplayer.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox; import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
...@@ -43,6 +44,10 @@ public final class Track { ...@@ -43,6 +44,10 @@ public final class Track {
* Type of a meta track. * Type of a meta track.
*/ */
public static final int TYPE_META = 0x6D657461; public static final int TYPE_META = 0x6D657461;
/**
* Type of a time-code track.
*/
public static final int TYPE_TIME_CODE = 0x746D6364;
/** /**
* The track identifier. * The track identifier.
...@@ -50,7 +55,8 @@ public final class Track { ...@@ -50,7 +55,8 @@ public final class Track {
public final int id; public final int id;
/** /**
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT} and {@link #TYPE_META}. * One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT}, {@link #TYPE_META} and
* {@link #TYPE_TIME_CODE}.
*/ */
public final int type; public final int type;
...@@ -60,6 +66,11 @@ public final class Track { ...@@ -60,6 +66,11 @@ public final class Track {
public final long timescale; public final long timescale;
/** /**
* The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public final long durationUs;
/**
* The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise. * The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise.
*/ */
public final MediaFormat mediaFormat; public final MediaFormat mediaFormat;
...@@ -69,11 +80,12 @@ public final class Track { ...@@ -69,11 +80,12 @@ public final class Track {
*/ */
public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes;
public Track(int id, int type, long timescale, MediaFormat mediaFormat, public Track(int id, int type, long timescale, long durationUs, MediaFormat mediaFormat,
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes) { TrackEncryptionBox[] sampleDescriptionEncryptionBoxes) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.timescale = timescale; this.timescale = timescale;
this.durationUs = durationUs;
this.mediaFormat = mediaFormat; this.mediaFormat = mediaFormat;
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes; this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
} }
......
...@@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
: Track.TYPE_AUDIO; : Track.TYPE_AUDIO;
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME); FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat, extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale,
trackEncryptionBoxes)); initialManifest.durationUs, mediaFormat, trackEncryptionBoxes));
extractors.put(trackIndex, extractor); extractors.put(trackIndex, extractor);
} }
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
......
...@@ -123,7 +123,7 @@ public class SubtitleView extends View { ...@@ -123,7 +123,7 @@ public class SubtitleView extends View {
@Override @Override
public void setBackgroundColor(int color) { public void setBackgroundColor(int color) {
backgroundColor = color; backgroundColor = color;
invalidate(); forceUpdate(false);
} }
/** /**
...@@ -134,8 +134,7 @@ public class SubtitleView extends View { ...@@ -134,8 +134,7 @@ public class SubtitleView extends View {
public void setText(CharSequence text) { public void setText(CharSequence text) {
textBuilder.setLength(0); textBuilder.setLength(0);
textBuilder.append(text); textBuilder.append(text);
hasMeasurements = false; forceUpdate(true);
requestLayout();
} }
/** /**
...@@ -147,9 +146,7 @@ public class SubtitleView extends View { ...@@ -147,9 +146,7 @@ public class SubtitleView extends View {
if (textPaint.getTextSize() != size) { if (textPaint.getTextSize() != size) {
textPaint.setTextSize(size); textPaint.setTextSize(size);
innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
hasMeasurements = false; forceUpdate(true);
requestLayout();
invalidate();
} }
} }
...@@ -165,17 +162,22 @@ public class SubtitleView extends View { ...@@ -165,17 +162,22 @@ public class SubtitleView extends View {
edgeColor = style.edgeColor; edgeColor = style.edgeColor;
setTypeface(style.typeface); setTypeface(style.typeface);
super.setBackgroundColor(style.windowColor); super.setBackgroundColor(style.windowColor);
hasMeasurements = false; forceUpdate(true);
requestLayout();
} }
private void setTypeface(Typeface typeface) { private void setTypeface(Typeface typeface) {
if (textPaint.getTypeface() != typeface) { if (textPaint.getTypeface() != typeface) {
textPaint.setTypeface(typeface); textPaint.setTypeface(typeface);
forceUpdate(true);
}
}
private void forceUpdate(boolean needsLayout) {
if (needsLayout) {
hasMeasurements = false; hasMeasurements = false;
requestLayout(); requestLayout();
invalidate();
} }
invalidate();
} }
@Override @Override
......
...@@ -56,20 +56,28 @@ public class WebvttParser implements SubtitleParser { ...@@ -56,20 +56,28 @@ public class WebvttParser implements SubtitleParser {
private static final Pattern WEBVTT_METADATA_HEADER = private static final Pattern WEBVTT_METADATA_HEADER =
Pattern.compile(WEBVTT_METADATA_HEADER_STRING); Pattern.compile(WEBVTT_METADATA_HEADER_STRING);
private static final String WEBVTT_CUE_IDENTIFIER_STRING = "^(?!.*(-->)).*$";
private static final Pattern WEBVTT_CUE_IDENTIFIER =
Pattern.compile(WEBVTT_CUE_IDENTIFIER_STRING);
private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}"; private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}";
private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING); private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING);
private static final Pattern MEDIA_TIMESTAMP_OFFSET = Pattern.compile(OFFSET + "\\d+"); private static final Pattern MEDIA_TIMESTAMP_OFFSET = Pattern.compile(OFFSET + "\\d+");
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+"); private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+");
private static final String WEBVTT_CUE_TAG_STRING = "\\<.*?>";
private final boolean strictParsing; private final boolean strictParsing;
private final boolean filterTags;
public WebvttParser() { public WebvttParser() {
this(true); this(true, true);
} }
public WebvttParser(boolean strictParsing) { public WebvttParser(boolean strictParsing, boolean filterTags) {
this.strictParsing = strictParsing; this.strictParsing = strictParsing;
this.filterTags = filterTags;
} }
@Override @Override
...@@ -137,8 +145,15 @@ public class WebvttParser implements SubtitleParser { ...@@ -137,8 +145,15 @@ public class WebvttParser implements SubtitleParser {
// process the cues and text // process the cues and text
while ((line = webvttData.readLine()) != null) { while ((line = webvttData.readLine()) != null) {
// parse the cue identifier (if present) {
Matcher matcher = WEBVTT_CUE_IDENTIFIER.matcher(line);
if (matcher.find()) {
// ignore the identifier (we currently don't use it) and read the next line
line = webvttData.readLine();
}
// parse the cue timestamps // parse the cue timestamps
Matcher matcher = WEBVTT_TIMESTAMP.matcher(line); matcher = WEBVTT_TIMESTAMP.matcher(line);
long startTime; long startTime;
long endTime; long endTime;
String text = ""; String text = "";
...@@ -159,7 +174,7 @@ public class WebvttParser implements SubtitleParser { ...@@ -159,7 +174,7 @@ public class WebvttParser implements SubtitleParser {
// parse text // parse text
while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) { while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
text += line.trim() + "\n"; text += processCueText(line.trim()) + "\n";
} }
WebvttCue cue = new WebvttCue(startTime, endTime, text); WebvttCue cue = new WebvttCue(startTime, endTime, text);
...@@ -193,6 +208,19 @@ public class WebvttParser implements SubtitleParser { ...@@ -193,6 +208,19 @@ public class WebvttParser implements SubtitleParser {
return startTimeUs; return startTimeUs;
} }
protected String processCueText(String line) {
if (filterTags) {
line = line.replaceAll(WEBVTT_CUE_TAG_STRING, "");
line = line.replaceAll("&lt;", "<");
line = line.replaceAll("&gt;", ">");
line = line.replaceAll("&nbsp;", " ");
line = line.replaceAll("&amp;", "&");
return line;
} else {
return line;
}
}
protected void handleNoncompliantLine(String line) throws ParserException { protected void handleNoncompliantLine(String line) throws ParserException {
if (strictParsing) { if (strictParsing) {
throw new ParserException("Unexpected line: " + line); throw new ParserException("Unexpected line: " + line);
......
...@@ -27,24 +27,27 @@ public class ByteArrayDataSource implements DataSource { ...@@ -27,24 +27,27 @@ public class ByteArrayDataSource implements DataSource {
private final byte[] data; private final byte[] data;
private int readPosition; private int readPosition;
private int remainingBytes;
/** /**
* @param data The data to be read. * @param data The data to be read.
*/ */
public ByteArrayDataSource(byte[] data) { public ByteArrayDataSource(byte[] data) {
this.data = Assertions.checkNotNull(data); Assertions.checkNotNull(data);
Assertions.checkArgument(data.length > 0);
this.data = data;
} }
@Override @Override
public long open(DataSpec dataSpec) throws IOException { public long open(DataSpec dataSpec) throws IOException {
if (dataSpec.length == C.LENGTH_UNBOUNDED) {
Assertions.checkArgument(dataSpec.position < data.length);
} else {
Assertions.checkArgument(dataSpec.position + dataSpec.length <= data.length);
}
readPosition = (int) dataSpec.position; readPosition = (int) dataSpec.position;
return (dataSpec.length == C.LENGTH_UNBOUNDED) ? (data.length - dataSpec.position) remainingBytes = (int) ((dataSpec.length == C.LENGTH_UNBOUNDED)
: dataSpec.length; ? (data.length - dataSpec.position) : dataSpec.length);
if (remainingBytes <= 0 || readPosition + remainingBytes > data.length) {
throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length
+ "], length: " + data.length);
}
return remainingBytes;
} }
@Override @Override
...@@ -54,8 +57,13 @@ public class ByteArrayDataSource implements DataSource { ...@@ -54,8 +57,13 @@ public class ByteArrayDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int length) throws IOException { public int read(byte[] buffer, int offset, int length) throws IOException {
if (remainingBytes == 0) {
return -1;
}
length = Math.min(length, remainingBytes);
System.arraycopy(data, readPosition, buffer, offset, length); System.arraycopy(data, readPosition, buffer, offset, length);
readPosition += length; readPosition += length;
remainingBytes -= length;
return length; return length;
} }
} }
......
...@@ -29,6 +29,7 @@ public class MimeTypes { ...@@ -29,6 +29,7 @@ public class MimeTypes {
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_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 AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
......
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