diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 0c54dadcbf4..d3293adcd5c 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -66,6 +66,10 @@ + + + + diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index dbbd5694ad9..6ca6c0d3ecc 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -25,27 +25,19 @@ import com.google.android.exoplayer.ExoPlayerFactory; import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; -import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SimpleExoPlayer; import com.google.android.exoplayer.TrackGroupArray; -import com.google.android.exoplayer.dash.DashSampleSource; import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.UnsupportedDrmException; -import com.google.android.exoplayer.extractor.ExtractorSampleSource; -import com.google.android.exoplayer.hls.HlsSampleSource; import com.google.android.exoplayer.metadata.id3.GeobFrame; import com.google.android.exoplayer.metadata.id3.Id3Frame; import com.google.android.exoplayer.metadata.id3.PrivFrame; import com.google.android.exoplayer.metadata.id3.TxxxFrame; -import com.google.android.exoplayer.smoothstreaming.SmoothStreamingSampleSource; import com.google.android.exoplayer.text.CaptionStyleCompat; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleLayout; -import com.google.android.exoplayer.upstream.Allocator; -import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSourceFactory; -import com.google.android.exoplayer.upstream.DefaultAllocator; import com.google.android.exoplayer.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer.util.DebugTextViewHelper; import com.google.android.exoplayer.util.PlayerControl; @@ -60,7 +52,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -99,7 +90,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, public static final String USE_EXTENSION_DECODERS = "use_extension_decoders"; // For use when launching the demo app using adb. + public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW"; + private static final String ACTION_VIEW_LIST = + "com.google.android.exoplayer.demo.action.VIEW_LIST"; private static final String CONTENT_EXT_EXTRA = "type"; + private static final String URIS_LIST_EXTRA = "uris"; private static final String TAG = "PlayerActivity"; @@ -246,40 +241,10 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, } } - // Permission management methods - - /** - * Checks whether it is necessary to ask for permission to read storage. If necessary, it also - * requests permission. - * - * @param uri A URI that may require {@link permission#READ_EXTERNAL_STORAGE} to play. - * @return True if a permission request is made. False if it is not necessary. - */ - @TargetApi(23) - private boolean maybeRequestPermission(Uri uri) { - if (requiresPermission(uri)) { - requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); - return true; - } else { - return false; - } - } - - @TargetApi(23) - private boolean requiresPermission(Uri uri) { - return Util.SDK_INT >= 23 - && Util.isLocalFileUri(uri) - && checkSelfPermission(permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED; - } - // Internal methods private void initializePlayer() { Intent intent = getIntent(); - Uri uri = intent.getData(); - int type = intent.getIntExtra(CONTENT_TYPE_EXTRA, - inferContentType(uri, intent.getStringExtra(CONTENT_EXT_EXTRA))); if (player == null) { boolean useExtensionDecoders = intent.getBooleanExtra(USE_EXTENSION_DECODERS, false); UUID drmSchemeUuid = (UUID) intent.getSerializableExtra(DRM_SCHEME_UUID_EXTRA); @@ -318,11 +283,31 @@ private void initializePlayer() { playerNeedsSource = true; } if (playerNeedsSource) { - if (maybeRequestPermission(uri)) { + String action = intent.getAction(); + Uri[] uris; + UriSampleSourceProvider sourceProvider; + if (ACTION_VIEW.equals(action)) { + uris = new Uri[] {intent.getData()}; + sourceProvider = new UriSampleSourceProvider(player, dataSourceFactory, intent.getData(), + intent.getIntExtra(CONTENT_TYPE_EXTRA, UriSampleSourceProvider.UNKNOWN_TYPE), + intent.getStringExtra(CONTENT_EXT_EXTRA), mainHandler, eventLogger); + } else if (ACTION_VIEW_LIST.equals(action)) { + String[] uriStrings = intent.getStringArrayExtra(URIS_LIST_EXTRA); + uris = new Uri[uriStrings.length]; + for (int i = 0; i < uriStrings.length; i++) { + uris[i] = Uri.parse(uriStrings[i]); + } + sourceProvider = new UriSampleSourceProvider(player, dataSourceFactory, uris, mainHandler, + eventLogger); + } else { + Log.w(TAG, "Unexpected intent action: " + action); + return; + } + if (maybeRequestPermission(uris)) { // The player will be reinitialized if permission is granted. return; } - player.setSource(buildSource(type, uri)); + player.setSourceProvider(sourceProvider); playerNeedsSource = false; updateButtonVisibilities(); } @@ -351,25 +336,28 @@ private void onUnsupportedDrmError(UnsupportedDrmException e) { Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_LONG).show(); } - private SampleSource buildSource(int type, Uri uri) { - switch (type) { - case Util.TYPE_SS: - return new SmoothStreamingSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), - mainHandler, eventLogger); - case Util.TYPE_DASH: - return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), mainHandler, - eventLogger); - case Util.TYPE_HLS: - return new HlsSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), mainHandler, - eventLogger); - case Util.TYPE_OTHER: - Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); - DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter()); - return new ExtractorSampleSource(uri, dataSource, allocator, C.DEFAULT_MUXED_BUFFER_SIZE, - mainHandler, eventLogger, 0, ExtractorSampleSource.newDefaultExtractors()); - default: - throw new IllegalStateException("Unsupported type: " + type); + /** + * Checks whether it is necessary to ask for permission to read storage. If necessary, it also + * requests permission. + * + * @param uris URIs that may require {@link permission#READ_EXTERNAL_STORAGE} to play. + * @return True if a permission request is made. False if it is not necessary. + */ + @TargetApi(23) + private boolean maybeRequestPermission(Uri... uris) { + if (Util.SDK_INT >= 23) { + for (Uri uri : uris) { + if (Util.isLocalFileUri(uri)) { + if (checkSelfPermission(permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); + return true; + } + break; + } + } } + return false; } private void releasePlayer() { @@ -585,20 +573,6 @@ private CaptionStyleCompat getUserCaptionStyleV19() { return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); } - /** - * Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file - * extension. - * - * @param uri The {@link Uri} of the media. - * @param fileExtension An overriding file extension. - * @return The inferred type. - */ - private static int inferContentType(Uri uri, String fileExtension) { - String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension - : uri.getLastPathSegment(); - return Util.inferContentType(lastPathSegment); - } - private static final class KeyCompatibleMediaController extends MediaController { private MediaController.MediaPlayerControl playerControl; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java index 3b747762448..4c4f637e63e 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java @@ -91,6 +91,7 @@ public boolean onChildClick(ExpandableListView parent, View view, int groupPosit private void onSampleSelected(Sample sample) { Intent intent = new Intent(this, PlayerActivity.class) + .setAction(PlayerActivity.ACTION_VIEW) .setData(Uri.parse(sample.uri)) .putExtra(PlayerActivity.CONTENT_TYPE_EXTRA, sample.type) .putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, sample.drmSchemeUuid) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java b/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java new file mode 100644 index 00000000000..51037917ae4 --- /dev/null +++ b/demo/src/main/java/com/google/android/exoplayer/demo/UriSampleSourceProvider.java @@ -0,0 +1,142 @@ +/* + * 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 com.google.android.exoplayer.C; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.SampleSourceProvider; +import com.google.android.exoplayer.SimpleExoPlayer; +import com.google.android.exoplayer.dash.DashSampleSource; +import com.google.android.exoplayer.extractor.ExtractorSampleSource; +import com.google.android.exoplayer.hls.HlsSampleSource; +import com.google.android.exoplayer.smoothstreaming.SmoothStreamingSampleSource; +import com.google.android.exoplayer.upstream.Allocator; +import com.google.android.exoplayer.upstream.DataSource; +import com.google.android.exoplayer.upstream.DataSourceFactory; +import com.google.android.exoplayer.upstream.DefaultAllocator; +import com.google.android.exoplayer.util.Util; + +import android.net.Uri; +import android.os.Handler; +import android.text.TextUtils; + +/** + * Provides {@link SampleSource}s to play back media loaded from one or more URI/URIs. + */ +public final class UriSampleSourceProvider implements SampleSourceProvider { + + public static final int UNKNOWN_TYPE = -1; + + private final SimpleExoPlayer player; + private final DataSourceFactory dataSourceFactory; + private final Uri[] uris; + private final String overrideExtension; + private final int type; + private final Handler handler; + private final EventLogger eventLogger; + + /** + * Constructs a source provider for {@link SampleSource} to play back media at the specified + * URI, using the specified type. + * + * @param player The demo player, which will listen to source events. + * @param dataSourceFactory A data source factory. + * @param uri The URI to play back. + * @param type A {@code PlayerActivity.TYPE_*} constant specifying the type of the source, or + * {@link #UNKNOWN_TYPE}, in which case it is inferred from the URI's extension. + * @param overrideExtension An overriding file extension used when inferring the source's type, + * or {@code null}. + * @param handler A handler to use for logging events. + * @param eventLogger An event logger. + */ + public UriSampleSourceProvider(SimpleExoPlayer player, DataSourceFactory dataSourceFactory, + Uri uri, int type, String overrideExtension, Handler handler, EventLogger eventLogger) { + this.player = player; + this.dataSourceFactory = dataSourceFactory; + this.overrideExtension = overrideExtension; + this.type = type; + this.handler = handler; + this.eventLogger = eventLogger; + + uris = new Uri[] {uri}; + } + + /** + * Constructs a source provider for {@link SampleSource}s to play back media at one or more + * {@link Uri}s. The content type of each URI is inferred based on its last path segment. + * + * @param player The demo player, which will listen to source events. + * @param dataSourceFactory A data source factory. + * @param uris The URIs to play back. + * @param handler A handler to use for logging events. + * @param eventLogger An event logger. + */ + public UriSampleSourceProvider(SimpleExoPlayer player, DataSourceFactory dataSourceFactory, + Uri[] uris, Handler handler, EventLogger eventLogger) { + this.player = player; + this.dataSourceFactory = dataSourceFactory; + this.uris = uris; + this.handler = handler; + this.eventLogger = eventLogger; + + overrideExtension = null; + type = UNKNOWN_TYPE; + } + + @Override + public int getSourceCount() { + return uris.length; + } + + @Override + public SampleSource createSource(int index) { + Uri uri = uris[index]; + int type = this.type == UNKNOWN_TYPE ? inferContentType(uri, overrideExtension) : this.type; + switch (type) { + case Util.TYPE_SS: + return new SmoothStreamingSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), + handler, eventLogger); + case Util.TYPE_DASH: + return new DashSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), handler, + eventLogger); + case Util.TYPE_HLS: + return new HlsSampleSource(uri, dataSourceFactory, player.getBandwidthMeter(), handler, + eventLogger); + case Util.TYPE_OTHER: + Allocator allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE); + DataSource dataSource = dataSourceFactory.createDataSource(player.getBandwidthMeter()); + return new ExtractorSampleSource(uri, dataSource, allocator, C.DEFAULT_MUXED_BUFFER_SIZE, + handler, eventLogger, 0, ExtractorSampleSource.newDefaultExtractors()); + default: + throw new IllegalStateException("Unsupported type: " + type); + } + } + + /** + * Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file + * extension. + * + * @param uri The {@link Uri} of the media. + * @param fileExtension An overriding file extension. + * @return The inferred type. + */ + private static int inferContentType(Uri uri, String fileExtension) { + String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension + : uri.getLastPathSegment(); + return Util.inferContentType(lastPathSegment); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index f975a0db399..affb59a7640 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -84,7 +84,8 @@ * *

The possible playback state transitions are shown below. Transitions can be triggered either * by changes in the state of the {@link TrackRenderer}s being used, or as a result of - * {@link #setSource(SampleSource)}, {@link #stop()} or {@link #release()} being invoked.

+ * {@link #setSource(SampleSource)}, {@link #setSourceProvider(SampleSourceProvider)}, + * {@link #stop()} or {@link #release()} being invoked.

*

ExoPlayer playback state transitions

@@ -225,6 +226,14 @@ public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object messa */ void setSource(SampleSource sampleSource); + /** + * Sets the player's source provider. The player will transition to {@link #STATE_BUFFERING} until + * it is ready to play the first source. + * + * @param sourceProvider The provider of {@link SampleSource}s to play. + */ + void setSourceProvider(SampleSourceProvider sourceProvider); + /** * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. * If the player is already in this state, then this method can be used to pause and resume diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java index f8b2e486fe4..d98689d4264 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -86,8 +86,13 @@ public int getPlaybackState() { } @Override - public void setSource(SampleSource source) { - internalPlayer.setSource(source); + public void setSource(final SampleSource sampleSource) { + internalPlayer.setSourceProvider(new SingleSampleSourceProvider(sampleSource)); + } + + @Override + public void setSourceProvider(SampleSourceProvider sourceProvider) { + internalPlayer.setSourceProvider(sourceProvider); } @Override @@ -190,4 +195,25 @@ public int getBufferedPercentage() { } } + private static final class SingleSampleSourceProvider implements SampleSourceProvider { + + private final SampleSource sampleSource; + + public SingleSampleSourceProvider(SampleSource sampleSource) { + this.sampleSource = sampleSource; + } + + @Override + public int getSourceCount() { + return 1; + } + + @Override + public SampleSource createSource(int index) { + // The source will only be created once. + return sampleSource; + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index 04635ec41f1..f609ad71912 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -48,7 +48,7 @@ public static final int MSG_ERROR = 3; // Internal messages - private static final int MSG_SET_SOURCE = 0; + private static final int MSG_SET_SOURCE_PROVIDER = 0; private static final int MSG_SET_PLAY_WHEN_READY = 1; private static final int MSG_DO_SOME_WORK = 2; private static final int MSG_SEEK_TO = 3; @@ -135,8 +135,8 @@ public long getDuration() { return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : durationUs / 1000; } - public void setSource(SampleSource sampleSource) { - handler.obtainMessage(MSG_SET_SOURCE, sampleSource).sendToTarget(); + public void setSourceProvider(SampleSourceProvider sourceProvider) { + handler.obtainMessage(MSG_SET_SOURCE_PROVIDER, sourceProvider).sendToTarget(); } public void setPlayWhenReady(boolean playWhenReady) { @@ -203,8 +203,8 @@ public void onTrackSelectionsInvalidated() { public boolean handleMessage(Message msg) { try { switch (msg.what) { - case MSG_SET_SOURCE: { - setSourceInternal((SampleSource) msg.obj); + case MSG_SET_SOURCE_PROVIDER: { + setSourceProviderInternal((SampleSourceProvider) msg.obj); return true; } case MSG_SET_PLAY_WHEN_READY: { @@ -277,9 +277,10 @@ private boolean haveSufficientBuffer() { || (durationUs != C.UNSET_TIME_US && bufferedPositionUs >= durationUs); } - private void setSourceInternal(SampleSource source) { + private void setSourceProviderInternal(SampleSourceProvider sourceProvider) { resetInternal(); - this.source = source; + // TODO[playlists]: Create and use sources after the first one. + this.source = sourceProvider.createSource(0); setState(ExoPlayer.STATE_BUFFERING); handler.sendEmptyMessage(MSG_DO_SOME_WORK); } diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSourceProvider.java b/library/src/main/java/com/google/android/exoplayer/SampleSourceProvider.java new file mode 100644 index 00000000000..5e2700fd950 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/SampleSourceProvider.java @@ -0,0 +1,46 @@ +/* + * 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; + +// TODO[playlists]: Rename this and maybe change the interface once we support multi-period DASH. +/** + * Provides a sequence of {@link SampleSource}s to play back. + */ +public interface SampleSourceProvider { + + /** + * Returned by {@link #getSourceCount()} if the number of sources is not known. + */ + int UNKNOWN_SOURCE_COUNT = -1; + + /** + * Returns the number of sources in the sequence, or {@link #UNKNOWN_SOURCE_COUNT} if the number + * of sources is not yet known. + */ + int getSourceCount(); + + /** + * Returns a new {@link SampleSource} providing media at the specified index in the sequence, or + * {@code null} if the source at the specified index is not yet available. + * + * @param index The index of the source to create, which must be less than the count returned by + * {@link #getSourceCount()}. + * @return A new {@link SampleSource}, or {@code null} if the source at the specified index is not + * yet available. + */ + SampleSource createSource(int index); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java index f9ab7f38a05..c635f9349ae 100644 --- a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -287,8 +287,13 @@ public int getPlaybackState() { } @Override - public void setSource(SampleSource source) { - player.setSource(source); + public void setSource(SampleSource sampleSource) { + player.setSource(sampleSource); + } + + @Override + public void setSourceProvider(SampleSourceProvider sourceProvider) { + player.setSourceProvider(sourceProvider); } @Override