Media2: Remove MediaSourceFactory + DataSourceCallback

- Applications should generally use DefaultMediaSourceFactory,
  or their own custom implementation if they need one. Having the
  media2 extension implement its own version directly doesn't seem
  that useful.
- Remove support for CallbackMediaItem. This type of MediaItem
  doesn't go cross-process, and it seems like there would never
  be a use case where an app would need to generate one locally.
  If an app needs to provide data from a custom source, it should
  hook into ExoPlayer's way of doing this (i.e., use a UriMediaItem
  with a custom scheme, and inject a custom DataSource that can
  handle this scheme).

PiperOrigin-RevId: 326914465
This commit is contained in:
olly 2020-08-16 19:28:46 +01:00 committed by kim-vde
parent 42cf213aea
commit 49bf83a169
9 changed files with 24 additions and 379 deletions

View File

@ -1,28 +0,0 @@
# Proguard rules specific to the media2 extension.
# Constructors and methods accessed via reflection in ExoPlayerUtils.
-dontnote com.google.android.exoplayer2.source.dash.DashMediaSource$Factory
-keepclasseswithmembers class com.google.android.exoplayer2.source.dash.DashMediaSource$Factory {
public <init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
public com.google.android.exoplayer2.source.dash.DashMediaSource$Factory setTag(java.lang.Object);
}
-dontnote com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory
-keepclasseswithmembers class com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory {
public <init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
public com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory setTag(java.lang.Object);
}
-dontnote com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory
-keepclasseswithmembers class com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory {
public <init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
public com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory setTag(java.lang.Object);
}
# Don't warn about checkerframework and Kotlin annotations
-dontwarn org.checkerframework.**
-dontwarn kotlin.annotations.jvm.**
-dontwarn javax.annotation.**
# Work around [internal: b/151134701]: keep non-public versionedparcelable
# classes.
-keep class * implements androidx.versionedparcelable.VersionedParcelable
-keep class androidx.media2.common.MediaParcelUtils$MediaItemParcelImpl

View File

@ -75,8 +75,8 @@ import org.junit.rules.ExternalResource;
.setLooper(Looper.myLooper())
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory, null))
.build();
DefaultMediaItemConverter converter = new DefaultMediaItemConverter(context);
sessionPlayerConnector = new SessionPlayerConnector(exoPlayer, converter);
sessionPlayerConnector =
new SessionPlayerConnector(exoPlayer, new DefaultMediaItemConverter());
});
}

View File

@ -183,7 +183,6 @@ public class SessionPlayerConnectorTest {
return false;
}
};
DefaultMediaItemConverter converter = new DefaultMediaItemConverter(context);
SimpleExoPlayer simpleExoPlayer = null;
try {
simpleExoPlayer =
@ -191,7 +190,8 @@ public class SessionPlayerConnectorTest {
.setLooper(Looper.myLooper())
.build();
try (SessionPlayerConnector player =
new SessionPlayerConnector(simpleExoPlayer, converter, controlDispatcher)) {
new SessionPlayerConnector(
simpleExoPlayer, new DefaultMediaItemConverter(), controlDispatcher)) {
assertPlayerResult(player.play(), RESULT_INFO_SKIPPED);
}
} finally {

View File

@ -1,110 +0,0 @@
/*
* Copyright 2019 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.exoplayer2.ext.media2;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media2.common.DataSourceCallback;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import java.io.EOFException;
import java.io.IOException;
/** An ExoPlayer {@link DataSource} for reading from a {@link DataSourceCallback}. */
/* package */ final class DataSourceCallbackDataSource extends BaseDataSource {
/**
* Returns a factory for {@link DataSourceCallbackDataSource}s.
*
* @return A factory for data sources that read from the data source callback.
*/
public static DataSource.Factory getFactory(DataSourceCallback dataSourceCallback) {
Assertions.checkNotNull(dataSourceCallback);
return () -> new DataSourceCallbackDataSource(dataSourceCallback);
}
private final DataSourceCallback dataSourceCallback;
@Nullable private Uri uri;
private long position;
private long bytesRemaining;
private boolean opened;
public DataSourceCallbackDataSource(DataSourceCallback dataSourceCallback) {
super(/* isNetwork= */ false);
this.dataSourceCallback = Assertions.checkNotNull(dataSourceCallback);
}
@Override
public long open(DataSpec dataSpec) throws IOException {
uri = dataSpec.uri;
position = dataSpec.position;
transferInitializing(dataSpec);
long dataSourceCallbackSize = dataSourceCallback.getSize();
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else if (dataSourceCallbackSize != -1) {
bytesRemaining = dataSourceCallbackSize - position;
} else {
bytesRemaining = C.LENGTH_UNSET;
}
opened = true;
transferStarted(dataSpec);
return bytesRemaining;
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
int bytesToRead =
bytesRemaining == C.LENGTH_UNSET ? readLength : (int) Math.min(bytesRemaining, readLength);
int bytesRead = dataSourceCallback.readAt(position, buffer, offset, bytesToRead);
if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
throw new EOFException();
}
return C.RESULT_END_OF_INPUT;
}
position += bytesRead;
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
bytesTransferred(bytesRead);
return bytesRead;
}
@Override
@Nullable
public Uri getUri() {
return uri;
}
@Override
public void close() {
uri = null;
if (opened) {
opened = false;
transferEnded();
}
}
}

View File

@ -16,82 +16,44 @@
package com.google.android.exoplayer2.ext.media2;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.FileMediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.UriMediaItem;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.source.ClippingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/** Default implementation of both {@link MediaItemConverter} and {@link MediaSourceFactory}. */
public final class DefaultMediaItemConverter implements MediaItemConverter, MediaSourceFactory {
private static final int[] SUPPORTED_TYPES =
new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
private final Context context;
private final DataSource.Factory dataSourceFactory;
/**
* Default constructor with {@link DefaultDataSourceFactory}.
*
* @param context The context.
*/
public DefaultMediaItemConverter(Context context) {
this(
context,
new DefaultDataSourceFactory(
context, Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY)));
}
/**
* Default constructor with {@link DataSource.Factory}.
*
* @param context The {@link Context}.
* @param dataSourceFactory The {@link DataSource.Factory} to create {@link MediaSource} from
* {@link MediaItem ExoPlayer MediaItem}.
*/
public DefaultMediaItemConverter(Context context, DataSource.Factory dataSourceFactory) {
this.context = Assertions.checkNotNull(context);
this.dataSourceFactory = Assertions.checkNotNull(dataSourceFactory);
}
// Implements MediaItemConverter
/** Default implementation of {@link MediaItemConverter}. */
public final class DefaultMediaItemConverter implements MediaItemConverter {
@Override
public MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
if (androidXMediaItem instanceof FileMediaItem) {
throw new IllegalStateException("FileMediaItem isn't supported");
}
if (androidXMediaItem instanceof CallbackMediaItem) {
throw new IllegalStateException("CallbackMediaItem isn't supported");
}
com.google.android.exoplayer2.MediaItem.Builder exoplayerMediaItemBuilder =
new com.google.android.exoplayer2.MediaItem.Builder();
MediaItem.Builder exoplayerMediaItemBuilder = new MediaItem.Builder();
// Set mediaItem as tag for creating MediaSource via MediaSourceFactory methods.
exoplayerMediaItemBuilder.setTag(androidXMediaItem);
// Media id or Uri must be present. Get it from androidx.MediaItem if possible.
Uri uri = null;
String mediaId = null;
// Media ID or URI must be present. Get it from androidx.MediaItem if possible.
@Nullable Uri uri = null;
@Nullable String mediaId = null;
if (androidXMediaItem instanceof UriMediaItem) {
UriMediaItem uriMediaItem = (UriMediaItem) androidXMediaItem;
uri = uriMediaItem.getUri();
}
MediaMetadata metadata = androidXMediaItem.getMetadata();
@Nullable MediaMetadata metadata = androidXMediaItem.getMetadata();
if (metadata != null) {
mediaId = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
String uriString = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_URI);
if (uriString != null) {
@Nullable String uriString = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_URI);
if (uri == null && uriString != null) {
uri = Uri.parse(uriString);
}
}
@ -102,8 +64,6 @@ public final class DefaultMediaItemConverter implements MediaItemConverter, Medi
exoplayerMediaItemBuilder.setUri(uri);
exoplayerMediaItemBuilder.setMediaId(mediaId);
// These are actually aren't needed, because MediaSource will be generated only via tag.
// However, fills in the exoplayer2.MediaItem's fields as much as possible just in case.
if (androidXMediaItem.getStartPosition() != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
exoplayerMediaItemBuilder.setClipStartPositionMs(androidXMediaItem.getStartPosition());
exoplayerMediaItemBuilder.setClipRelativeToDefaultPosition(true);
@ -121,77 +81,11 @@ public final class DefaultMediaItemConverter implements MediaItemConverter, Medi
Assertions.checkNotNull(exoplayerMediaItem);
MediaItem.PlaybackProperties playbackProperties =
Assertions.checkNotNull(exoplayerMediaItem.playbackProperties);
Object tag = playbackProperties.tag;
@Nullable Object tag = playbackProperties.tag;
if (!(tag instanceof androidx.media2.common.MediaItem)) {
throw new IllegalStateException(
"DefaultMediaItemConverter cannot understand "
+ exoplayerMediaItem
+ ". Unexpected tag "
+ tag
+ " in PlaybackProperties");
"MediaItem tag must be an instance of androidx.media2.common.MediaItem");
}
return (androidx.media2.common.MediaItem) tag;
}
// Implements MediaSourceFactory
@Override
public MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
// No-op
return this;
}
@Override
public MediaSourceFactory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
// No-op
return this;
}
@Override
public int[] getSupportedTypes() {
return SUPPORTED_TYPES;
}
@Override
public MediaSource createMediaSource(com.google.android.exoplayer2.MediaItem exoplayerMediaItem) {
Assertions.checkNotNull(
exoplayerMediaItem.playbackProperties,
"DefaultMediaItemConverter cannot understand "
+ exoplayerMediaItem
+ ". PlaybackProperties is missing.");
Object tag = exoplayerMediaItem.playbackProperties.tag;
if (!(tag instanceof androidx.media2.common.MediaItem)) {
throw new IllegalStateException(
"DefaultMediaItemConverter cannot understand "
+ exoplayerMediaItem
+ ". Unexpected tag "
+ tag
+ " in PlaybackProperties");
}
androidx.media2.common.MediaItem androidXMediaItem = (androidx.media2.common.MediaItem) tag;
// Create a source for the item.
MediaSource mediaSource =
Utils.createUnclippedMediaSource(context, dataSourceFactory, androidXMediaItem);
// Apply clipping if needed.
long startPosition = androidXMediaItem.getStartPosition();
long endPosition = androidXMediaItem.getEndPosition();
if (startPosition != 0L || endPosition != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
if (endPosition == androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
endPosition = C.TIME_END_OF_SOURCE;
}
// Disable the initial discontinuity to give seamless transitions to clips.
mediaSource =
new ClippingMediaSource(
mediaSource,
C.msToUs(startPosition),
C.msToUs(endPosition),
/* enableInitialDiscontinuity= */ false,
/* allowDynamicClippingUpdates= */ false,
/* relativeToDefaultPosition= */ true);
}
return mediaSource;
}
}

View File

@ -19,15 +19,15 @@ import android.annotation.SuppressLint;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.media2.session.MediaSession;
/** Utility methods to use {@link MediaSession} with other existing Exo modules. */
/** Utility methods to use {@link MediaSession} with other ExoPlayer modules. */
public final class MediaSessionUtil {
/** Gets the {@link MediaSessionCompat.Token} from the {@link MediaSession}. */
// TODO(b/152764014): Deprecate this API when MediaSession#getSessionCompatToken() is released.
public static MediaSessionCompat.Token getSessionCompatToken(MediaSession session2) {
public static MediaSessionCompat.Token getSessionCompatToken(MediaSession mediaSession) {
@SuppressLint("RestrictedApi")
@SuppressWarnings("RestrictTo")
MediaSessionCompat sessionCompat = session2.getSessionCompat();
MediaSessionCompat sessionCompat = mediaSession.getSessionCompat();
return sessionCompat.getSessionToken();
}

View File

@ -126,7 +126,7 @@ import java.util.List;
* @param mediaItemConverter The {@link MediaItemConverter}.
* @param controlDispatcher A {@link ControlDispatcher}.
*/
PlayerWrapper(
public PlayerWrapper(
Listener listener,
Player player,
MediaItemConverter mediaItemConverter,

View File

@ -15,81 +15,14 @@
*/
package com.google.android.exoplayer2.ext.media2;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.MediaItem;
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.UriMediaItem;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
/**
* Utility methods for the media2 extension (primarily for translating between the media2 and
* ExoPlayer {@link Player} APIs).
*/
/** Utility methods for translating between the media2 and ExoPlayer APIs. */
/* package */ final class Utils {
private static final ExtractorsFactory sExtractorsFactory =
new DefaultExtractorsFactory()
.setAdtsExtractorFlags(AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING);
/**
* Returns an ExoPlayer media source for the given media item. The given {@link MediaItem} is set
* as the tag of the source.
*/
public static MediaSource createUnclippedMediaSource(
Context context, DataSource.Factory dataSourceFactory, MediaItem androidXMediaItem) {
if (androidXMediaItem instanceof UriMediaItem) {
Uri uri = ((UriMediaItem) androidXMediaItem).getUri();
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
String path = Assertions.checkNotNull(uri.getPath());
int resourceIdentifier;
if (uri.getPathSegments().size() == 1 && uri.getPathSegments().get(0).matches("\\d+")) {
resourceIdentifier = Integer.parseInt(uri.getPathSegments().get(0));
} else {
if (path.startsWith("/")) {
path = path.substring(1);
}
@Nullable String host = uri.getHost();
String resourceName = (TextUtils.isEmpty(host) ? "" : (host + ":")) + path;
resourceIdentifier =
context.getResources().getIdentifier(resourceName, "raw", context.getPackageName());
}
Assertions.checkState(resourceIdentifier != 0);
uri = RawResourceDataSource.buildRawResourceUri(resourceIdentifier);
}
return createMediaSource(uri, dataSourceFactory, /* tag= */ androidXMediaItem);
} else if (androidXMediaItem instanceof CallbackMediaItem) {
CallbackMediaItem callbackMediaItem = (CallbackMediaItem) androidXMediaItem;
dataSourceFactory =
DataSourceCallbackDataSource.getFactory(callbackMediaItem.getDataSourceCallback());
return new ProgressiveMediaSource.Factory(dataSourceFactory, sExtractorsFactory)
.createMediaSource(
new com.google.android.exoplayer2.MediaItem.Builder()
.setUri(Uri.EMPTY)
.setTag(androidXMediaItem)
.build());
} else {
throw new IllegalStateException();
}
}
/** Returns ExoPlayer audio attributes for the given audio attributes. */
public static AudioAttributes getAudioAttributes(AudioAttributesCompat audioAttributesCompat) {
return new AudioAttributes.Builder()
@ -156,49 +89,6 @@ import com.google.android.exoplayer2.util.Util;
}
}
private static MediaSource createMediaSource(
Uri uri, DataSource.Factory dataSourceFactory, Object tag) {
// TODO: Deduplicate with DefaultMediaSource once MediaItem support in ExoPlayer has been
// released. See [Internal: b/150857202].
@Nullable Class<? extends MediaSourceFactory> factoryClazz = null;
try {
// LINT.IfChange
switch (Util.inferContentType(uri)) {
case C.TYPE_DASH:
factoryClazz =
Class.forName("com.google.android.exoplayer2.source.dash.DashMediaSource$Factory")
.asSubclass(MediaSourceFactory.class);
break;
case C.TYPE_HLS:
factoryClazz =
Class.forName("com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory")
.asSubclass(MediaSourceFactory.class);
break;
case C.TYPE_SS:
factoryClazz =
Class.forName(
"com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory")
.asSubclass(MediaSourceFactory.class);
break;
case C.TYPE_OTHER:
default:
break;
}
if (factoryClazz != null) {
MediaSourceFactory mediaSourceFactory =
factoryClazz.getConstructor(DataSource.Factory.class).newInstance(dataSourceFactory);
factoryClazz.getMethod("setTag", Object.class).invoke(mediaSourceFactory, tag);
return mediaSourceFactory.createMediaSource(
com.google.android.exoplayer2.MediaItem.fromUri(uri));
}
// LINT.ThenChange(../../../../../../../../../proguard-rules.txt)
} catch (Exception e) {
// Expected if the app was built without the corresponding module.
}
return new ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(
new com.google.android.exoplayer2.MediaItem.Builder().setUri(uri).setTag(tag).build());
}
private Utils() {
// Prevent instantiation.

View File

@ -1 +0,0 @@
../../proguard-rules.txt