Skip to content

Commit

Permalink
Add SampleSourceProvider as a factory for SampleSources (playlists #2).
Browse files Browse the repository at this point in the history
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
  • Loading branch information
andrewlewis authored and ojw28 committed Jun 15, 2016
1 parent c650ab6 commit c2b89d6
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 85 deletions.
4 changes: 4 additions & 0 deletions demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
*
* <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>
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit c2b89d6

Please sign in to comment.