blob: b16c0695098c632be92938bac8d2820dd29a88ab [file] [log] [blame]
/*
* 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 androidx.media2.player;
import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_608;
import static android.media.MediaFormat.MIMETYPE_TEXT_CEA_708;
import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO;
import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_METADATA;
import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_UNKNOWN;
import static androidx.media2.common.SessionPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO;
import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_IO;
import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_MALFORMED;
import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_TIMED_OUT;
import static androidx.media2.player.MediaPlayer2.MEDIA_ERROR_UNKNOWN;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaFormat;
import android.net.Uri;
import androidx.core.util.Preconditions;
import androidx.media.AudioAttributesCompat;
import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.FileMediaItem;
import androidx.media2.common.MediaItem;
import androidx.media2.common.UriMediaItem;
import androidx.media2.exoplayer.external.C;
import androidx.media2.exoplayer.external.ExoPlaybackException;
import androidx.media2.exoplayer.external.Format;
import androidx.media2.exoplayer.external.ParserException;
import androidx.media2.exoplayer.external.PlaybackParameters;
import androidx.media2.exoplayer.external.SeekParameters;
import androidx.media2.exoplayer.external.audio.AudioAttributes;
import androidx.media2.exoplayer.external.extractor.DefaultExtractorsFactory;
import androidx.media2.exoplayer.external.extractor.ExtractorsFactory;
import androidx.media2.exoplayer.external.extractor.ts.AdtsExtractor;
import androidx.media2.exoplayer.external.mediacodec.MediaFormatUtil;
import androidx.media2.exoplayer.external.source.ExtractorMediaSource;
import androidx.media2.exoplayer.external.source.MediaSource;
import androidx.media2.exoplayer.external.source.hls.HlsMediaSource;
import androidx.media2.exoplayer.external.upstream.DataSource;
import androidx.media2.exoplayer.external.upstream.HttpDataSource;
import androidx.media2.exoplayer.external.upstream.RawResourceDataSource;
import androidx.media2.exoplayer.external.util.MimeTypes;
import androidx.media2.exoplayer.external.util.Util;
import java.io.IOException;
import java.net.SocketTimeoutException;
/**
* Utility methods for translating between the MediaPlayer2 and ExoPlayer APIs.
*/
@SuppressLint("RestrictedApi") // TODO(b/68398926): Remove once RestrictedApi checks are fixed.
/* package */ class ExoPlayerUtils {
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 mediaItem) {
if (mediaItem instanceof UriMediaItem) {
Uri uri = ((UriMediaItem) mediaItem).getUri();
if (Util.inferContentType(uri) == C.TYPE_HLS) {
return new HlsMediaSource.Factory(dataSourceFactory)
.setTag(mediaItem)
.createMediaSource(uri);
} else {
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
String path = Preconditions.checkNotNull(uri.getPath());
int resourceIdentifier;
if (uri.getPathSegments().size() == 1
&& uri.getPathSegments().get(0).matches("\\d+")) {
resourceIdentifier = Integer.parseInt(uri.getPathSegments().get(0));
} else {
path = path.replaceAll("^/", "");
String host = uri.getHost();
String resourceName = (host != null ? host + ":" : "") + path;
resourceIdentifier = context.getResources()
.getIdentifier(resourceName, "raw", context.getPackageName());
}
Preconditions.checkState(resourceIdentifier != 0);
uri = RawResourceDataSource.buildRawResourceUri(resourceIdentifier);
}
return new ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(sExtractorsFactory)
.setTag(mediaItem)
.createMediaSource(uri);
}
} else if (mediaItem instanceof FileMediaItem) {
return new ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(sExtractorsFactory)
.setTag(mediaItem)
.createMediaSource(Uri.EMPTY);
} else if (mediaItem instanceof CallbackMediaItem) {
CallbackMediaItem callbackMediaItem = (CallbackMediaItem) mediaItem;
dataSourceFactory = DataSourceCallbackDataSource.getFactory(
callbackMediaItem.getDataSourceCallback());
return new ExtractorMediaSource.Factory(dataSourceFactory)
.setExtractorsFactory(sExtractorsFactory)
.setTag(mediaItem)
.createMediaSource(Uri.EMPTY);
} else {
throw new IllegalStateException();
}
}
/** Returns ExoPlayer audio attributes for the given audio attributes. */
public static AudioAttributes getAudioAttributes(AudioAttributesCompat audioAttributesCompat) {
return new AudioAttributes.Builder()
.setContentType(audioAttributesCompat.getContentType())
.setFlags(audioAttributesCompat.getFlags())
.setUsage(audioAttributesCompat.getUsage())
.build();
}
/** Returns audio attributes for the given ExoPlayer audio attributes. */
public static AudioAttributesCompat getAudioAttributesCompat(AudioAttributes audioAttributes) {
return new AudioAttributesCompat.Builder()
.setContentType(audioAttributes.contentType)
.setFlags(audioAttributes.flags)
.setUsage(audioAttributes.usage)
.build();
}
/** Returns ExoPlayer playback parameters for the given playback params. */
public static PlaybackParameters getPlaybackParameters(PlaybackParams playbackParams2) {
Float speed = playbackParams2.getSpeed();
Float pitch = playbackParams2.getPitch();
return new PlaybackParameters(speed != null ? speed : 1f, pitch != null ? pitch : 1f);
}
/** Returns the ExoPlayer seek parameters corresponding to the given seek mode. */
public static SeekParameters getSeekParameters(int seekMode) {
switch (seekMode) {
case MediaPlayer2.SEEK_CLOSEST:
return SeekParameters.EXACT;
case MediaPlayer2.SEEK_CLOSEST_SYNC:
return SeekParameters.CLOSEST_SYNC;
case MediaPlayer2.SEEK_NEXT_SYNC:
return SeekParameters.NEXT_SYNC;
case MediaPlayer2.SEEK_PREVIOUS_SYNC:
return SeekParameters.PREVIOUS_SYNC;
default:
throw new IllegalArgumentException();
}
}
/** Returns the MEDIA_ERROR_* constant for an ExoPlayer player exception. */
public static int getError(ExoPlaybackException exception) {
if (exception.type == ExoPlaybackException.TYPE_SOURCE) {
IOException sourceException = exception.getSourceException();
if (sourceException instanceof ParserException) {
return MEDIA_ERROR_MALFORMED;
} else {
if (sourceException instanceof HttpDataSource.HttpDataSourceException
&& sourceException.getCause() instanceof SocketTimeoutException) {
return MEDIA_ERROR_TIMED_OUT;
}
return MEDIA_ERROR_IO;
}
}
return MEDIA_ERROR_UNKNOWN;
}
/** Returns the ExoPlayer track type for the given track type. */
public static int getExoPlayerTrackType(int trackType) {
switch (trackType) {
case MEDIA_TRACK_TYPE_AUDIO:
return C.TRACK_TYPE_AUDIO;
case MEDIA_TRACK_TYPE_VIDEO:
return C.TRACK_TYPE_VIDEO;
case MEDIA_TRACK_TYPE_SUBTITLE:
return C.TRACK_TYPE_TEXT;
case MEDIA_TRACK_TYPE_METADATA:
return C.TRACK_TYPE_METADATA;
case MEDIA_TRACK_TYPE_UNKNOWN:
default:
return C.TRACK_TYPE_UNKNOWN;
}
}
/** Returns the track type corresponding to the given ExoPlayer track type. */
public static int getTrackType(int exoPlayerTrackType) {
switch (exoPlayerTrackType) {
case C.TRACK_TYPE_AUDIO:
return MEDIA_TRACK_TYPE_AUDIO;
case C.TRACK_TYPE_VIDEO:
return MEDIA_TRACK_TYPE_VIDEO;
case C.TRACK_TYPE_TEXT:
return MEDIA_TRACK_TYPE_SUBTITLE;
case C.TRACK_TYPE_METADATA:
return MEDIA_TRACK_TYPE_METADATA;
case C.TRACK_TYPE_NONE:
case C.TRACK_TYPE_CAMERA_MOTION:
case C.TRACK_TYPE_DEFAULT:
case C.TRACK_TYPE_UNKNOWN:
default:
return MEDIA_TRACK_TYPE_UNKNOWN;
}
}
/** Returns the media format corresponding to an ExoPlayer format. */
@SuppressLint("InlinedApi")
public static MediaFormat getMediaFormat(Format format) {
MediaFormat mediaFormat = new MediaFormat();
String mimeType = format.sampleMimeType;
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
int trackType = MimeTypes.getTrackType(mimeType);
if (trackType == C.TRACK_TYPE_AUDIO) {
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
if (format.language != null) {
mediaFormat.setString(MediaFormat.KEY_LANGUAGE, format.language);
}
} else if (trackType == C.TRACK_TYPE_VIDEO) {
MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_WIDTH, format.width);
MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_HEIGHT, format.height);
MediaFormatUtil.maybeSetFloat(
mediaFormat, MediaFormat.KEY_FRAME_RATE, format.frameRate);
MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo);
} else if (trackType == C.TRACK_TYPE_TEXT) {
boolean isAutoselect = format.selectionFlags == C.SELECTION_FLAG_AUTOSELECT;
boolean isDefault = format.selectionFlags == C.SELECTION_FLAG_DEFAULT;
boolean isForced = format.selectionFlags == C.SELECTION_FLAG_FORCED;
mediaFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, isAutoselect ? 1 : 0);
mediaFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, isDefault ? 1 : 0);
mediaFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, isForced ? 1 : 0);
if (format.language == null) {
mediaFormat.setString(MediaFormat.KEY_LANGUAGE, C.LANGUAGE_UNDETERMINED);
} else {
mediaFormat.setString(MediaFormat.KEY_LANGUAGE, format.language);
}
// MediaPlayer2 uses text/* instead of application/* MIME types.
if (MimeTypes.APPLICATION_CEA608.equals(mimeType)) {
mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_608);
} else if (MimeTypes.APPLICATION_CEA708.equals(mimeType)) {
mediaFormat.setString(MediaFormat.KEY_MIME, MIMETYPE_TEXT_CEA_708);
}
}
return mediaFormat;
}
private ExoPlayerUtils() {
// Prevent instantiation.
}
}