Add SampleSourceProvider as a factory for SampleSources (playlists #2).

Initially only the first source index is used. In a later change,
ExoPlayerImplInternal will create SampleSources for different playlist item
indices as necessary.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123312595
This commit is contained in:
andrewlewis 2016-05-26 06:13:04 -07:00 committed by Oliver Woodman
parent c650ab64bc
commit c2b89d6285
9 changed files with 293 additions and 85 deletions

View File

@ -66,6 +66,10 @@
<data android:scheme="asset"/>
<data android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW_LIST"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>

View File

@ -25,27 +25,19 @@ import com.google.android.exoplayer.ExoPlayer;
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.content.pm.PackageManager;
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 class PlayerActivity extends Activity implements SurfaceHolder.Callback,
}
}
// 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 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
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 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
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 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
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;

View File

@ -91,6 +91,7 @@ public class SampleChooserActivity extends Activity {
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)

View File

@ -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);
}
}

View File

@ -84,7 +84,8 @@ package com.google.android.exoplayer;
*
* <p>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.</p>
* {@link #setSource(SampleSource)}, {@link #setSourceProvider(SampleSourceProvider)},
* {@link #stop()} or {@link #release()} being invoked.</p>
* <p align="center"><img src="../../../../../images/exoplayer_playbackstate.png"
* alt="ExoPlayer playback state transitions"
* border="0"/></p>
@ -225,6 +226,14 @@ public interface ExoPlayer {
*/
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

View File

@ -86,8 +86,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@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 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
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;
}
}
}

View File

@ -48,7 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger;
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 @@ import java.util.concurrent.atomic.AtomicInteger;
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 @@ import java.util.concurrent.atomic.AtomicInteger;
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 @@ import java.util.concurrent.atomic.AtomicInteger;
|| (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);
}

View File

@ -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);
}

View File

@ -287,8 +287,13 @@ public final class SimpleExoPlayer implements ExoPlayer {
}
@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