Commit 02f8cdf1 by jaewan Committed by Jaewan Kim

Release media2 extension

PiperOrigin-RevId: 320351394
parent 9a42141e
Showing with 1376 additions and 0 deletions
...@@ -231,6 +231,9 @@ ...@@ -231,6 +231,9 @@
([#7357](https://github.com/google/ExoPlayer/issues/7357)). ([#7357](https://github.com/google/ExoPlayer/issues/7357)).
* Metadata: Add minimal DVB Application Information Table (AIT) support * Metadata: Add minimal DVB Application Information Table (AIT) support
([#6922](https://github.com/google/ExoPlayer/pull/6922)). ([#6922](https://github.com/google/ExoPlayer/pull/6922)).
* Media2 extension: Publish media2 extension for integrating ExoPlayer with
`androidx.media2.common.SessionPlayer` and
`androidx.media2.session.MediaSession`.
* Cast extension: Implement playlist API and deprecate the old queue * Cast extension: Implement playlist API and deprecate the old queue
manipulation API. manipulation API.
* Demo app: Retain previous position in list of samples. * Demo app: Retain previous position in list of samples.
......
...@@ -39,6 +39,7 @@ include modulePrefix + 'extension-ima' ...@@ -39,6 +39,7 @@ include modulePrefix + 'extension-ima'
include modulePrefix + 'extension-cast' include modulePrefix + 'extension-cast'
include modulePrefix + 'extension-cronet' include modulePrefix + 'extension-cronet'
include modulePrefix + 'extension-mediasession' include modulePrefix + 'extension-mediasession'
include modulePrefix + 'extension-media2'
include modulePrefix + 'extension-okhttp' include modulePrefix + 'extension-okhttp'
include modulePrefix + 'extension-opus' include modulePrefix + 'extension-opus'
include modulePrefix + 'extension-vp9' include modulePrefix + 'extension-vp9'
...@@ -65,6 +66,7 @@ project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensio ...@@ -65,6 +66,7 @@ project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensio
project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast') project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast')
project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet') project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet')
project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession') project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession')
project(modulePrefix + 'extension-media2').projectDir = new File(rootDir, 'extensions/media2')
project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp') project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp')
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus') project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9') project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
......
# ExoPlayer Media2 extension #
The Media2 extension provides builders for [SessionPlayer][] and [MediaSession.SessionCallback][] in
the [Media2 library][].
Compared to [MediaSessionConnector][] that uses [MediaSessionCompat][], this provides finer grained
control for incoming calls, so you can selectively allow/reject commands per controller.
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
```gradle
implementation 'com.google.android.exoplayer:extension-media2:2.X.X'
```
where `2.X.X` is the version, which must match the version of the ExoPlayer
library being used.
Alternatively, you can clone the ExoPlayer repository and depend on the module
locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Using the extension ##
### Using `SessionPlayerConnector` ###
`SessionPlayerConnector` is a [SessionPlayer][] implementation wrapping a given `Player`.
You can use a [SessionPlayer][] instance to build a [MediaSession][], or to set the player
associated with a [VideoView][] or [MediaControlView][]
### Using `SessionCallbackBuilder` ###
`SessionCallbackBuilder` lets you build a [MediaSession.SessionCallback][] instance given its
collaborators. You can use a [MediaSession.SessionCallback][] to build a [MediaSession][].
## Links ##
* [Javadoc][]: Classes matching
`com.google.android.exoplayer2.ext.media2.*` belong to this module.
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
[SessionPlayer]: https://developer.android.com/reference/androidx/media2/common/SessionPlayer
[MediaSession]: https://developer.android.com/reference/androidx/media2/session/MediaSession
[MediaSession.SessionCallback]: https://developer.android.com/reference/androidx/media2/session/MediaSession.SessionCallback
[Media2 library]: https://developer.android.com/jetpack/androidx/releases/media2
[MediaSessionCompat]: https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat
[MediaSessionConnector]: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.html
[VideoView]: https://developer.android.com/reference/androidx/media2/widget/VideoView
[MediaControlView]: https://developer.android.com/reference/androidx/media2/widget/MediaControlView
// 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.
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android.defaultConfig.minSdkVersion 19
dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'androidx.collection:collection:' + androidxCollectionVersion
implementation 'androidx.concurrent:concurrent-futures:1.0.0'
implementation 'com.google.guava:guava:' + guavaVersion
api 'androidx.media2:media2-session:1.0.3'
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
androidTestImplementation 'androidx.test:core:' + androidxTestCoreVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
androidTestImplementation 'com.google.truth:truth:' + truthVersion
}
ext {
javadocTitle = 'Media2 extension'
}
apply from: '../../javadoc_library.gradle'
ext {
releaseArtifact = 'extension-media2'
releaseDescription = 'Media2 extension for ExoPlayer.'
}
apply from: '../../publish.gradle'
# 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
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.media2.test">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk/>
<application
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<activity android:name="com.google.android.exoplayer2.ext.media2.MediaStubActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="false"
android:label="MediaStubActivity"/>
</application>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.media2.test"
android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest>
/*
* Copyright 2020 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 static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Looper;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import androidx.annotation.NonNull;
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.SessionPlayer.PlayerResult;
import androidx.media2.session.MediaSession;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.android.exoplayer2.ext.media2.test.R;
import com.google.android.exoplayer2.util.Assertions;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link MediaSessionUtil} */
@RunWith(AndroidJUnit4.class)
public class MediaSessionUtilTest {
private static final int PLAYER_STATE_CHANGE_WAIT_TIME_MS = 5_000;
@Rule public final PlayerTestRule playerTestRule = new PlayerTestRule();
@Test
public void getSessionCompatToken_withMediaControllerCompat_returnsValidToken() throws Exception {
// Workaround to instantiate MediaSession with public androidx.media dependency.
// TODO(b/146536708): Remove this workaround when the relevant change is released via
// androidx.media 1.2.0.
if (Looper.myLooper() == null) {
Looper.prepare();
}
Context context = ApplicationProvider.getApplicationContext();
SessionPlayerConnector sessionPlayerConnector = playerTestRule.getSessionPlayerConnector();
MediaSession.SessionCallback sessionCallback =
new SessionCallbackBuilder(context, sessionPlayerConnector).build();
TestUtils.loadResource(context, R.raw.testmp3_2, sessionPlayerConnector);
ListenableFuture<PlayerResult> prepareResult = sessionPlayerConnector.prepare();
CountDownLatch latch = new CountDownLatch(1);
sessionPlayerConnector.registerPlayerCallback(
playerTestRule.getExecutor(),
new SessionPlayer.PlayerCallback() {
@Override
public void onPlayerStateChanged(@NonNull SessionPlayer player, int playerState) {
if (playerState == SessionPlayer.PLAYER_STATE_PLAYING) {
latch.countDown();
}
}
});
MediaSession session2 =
new MediaSession.Builder(context, sessionPlayerConnector)
.setSessionCallback(playerTestRule.getExecutor(), sessionCallback)
.build();
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
try {
MediaSessionCompat.Token token =
Assertions.checkNotNull(MediaSessionUtil.getSessionCompatToken(session2));
MediaControllerCompat controllerCompat = new MediaControllerCompat(context, token);
controllerCompat.getTransportControls().play();
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
assertThat(
prepareResult
.get(PLAYER_STATE_CHANGE_WAIT_TIME_MS, TimeUnit.MILLISECONDS)
.getResultCode())
.isEqualTo(PlayerResult.RESULT_SUCCESS);
assertThat(latch.await(PLAYER_STATE_CHANGE_WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isTrue();
}
}
/*
* 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.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import com.google.android.exoplayer2.ext.media2.test.R;
import com.google.android.exoplayer2.util.Util;
/** Stub activity to play media contents on. */
public class MediaStubActivity extends Activity {
private static final String TAG = "MediaStubActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mediaplayer);
// disable enter animation.
overridePendingTransition(0, 0);
if (Util.SDK_INT >= 27) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setTurnScreenOn(true);
setShowWhenLocked(true);
KeyguardManager keyguardManager =
(KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
keyguardManager.requestDismissKeyguard(this, null);
} else {
getWindow()
.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
@Override
public void finish() {
super.finish();
// disable exit animation.
overridePendingTransition(0, 0);
}
@Override
protected void onResume() {
Log.i(TAG, "onResume");
super.onResume();
}
@Override
protected void onPause() {
Log.i(TAG, "onPause");
super.onPause();
}
public SurfaceHolder getSurfaceHolder() {
SurfaceView surface = findViewById(R.id.surface);
return surface.getHolder();
}
}
/*
* 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.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.rules.ExternalResource;
/** Rule for tests that use {@link SessionPlayerConnector}. */
public class PlayerTestRule extends ExternalResource {
private Context context;
private ExecutorService executor;
private SessionPlayerConnector sessionPlayerConnector;
private SimpleExoPlayer exoPlayer;
@Override
protected void before() {
context = ApplicationProvider.getApplicationContext();
executor = Executors.newFixedThreadPool(1);
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
// Initialize AudioManager on the main thread to workaround b/78617702 that
// audio focus listener is called on the thread where the AudioManager was
// originally initialized.
// Without posting this, audio focus listeners wouldn't be called because the
// listeners would be posted to the test thread (here) where it waits until the
// tests are finished.
AudioManager audioManager =
(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
exoPlayer = new SimpleExoPlayer.Builder(context).setLooper(Looper.myLooper()).build();
ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource();
TimelinePlaylistManager manager =
new TimelinePlaylistManager(context, concatenatingMediaSource);
ConcatenatingMediaSourcePlaybackPreparer playbackPreparer =
new ConcatenatingMediaSourcePlaybackPreparer(exoPlayer, concatenatingMediaSource);
sessionPlayerConnector =
new SessionPlayerConnector(exoPlayer, manager, playbackPreparer);
});
}
@Override
protected void after() {
if (sessionPlayerConnector != null) {
sessionPlayerConnector.close();
sessionPlayerConnector = null;
}
if (exoPlayer != null) {
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
exoPlayer.release();
exoPlayer = null;
});
}
if (executor != null) {
executor.shutdown();
executor = null;
}
}
public ExecutorService getExecutor() {
return executor;
}
public SessionPlayerConnector getSessionPlayerConnector() {
return sessionPlayerConnector;
}
public SimpleExoPlayer getSimpleExoPlayer() {
return exoPlayer;
}
}
/*
* 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.content.res.AssetFileDescriptor;
import android.util.Log;
import androidx.media2.common.DataSourceCallback;
import java.io.IOException;
import java.io.InputStream;
/** A DataSourceCallback that reads from a byte array for use in tests. */
public class TestDataSourceCallback extends DataSourceCallback {
private static final String TAG = "TestDataSourceCallback";
private byte[] data;
// Read an asset fd into a new byte array media item. Closes afd.
public static TestDataSourceCallback fromAssetFd(AssetFileDescriptor afd) throws IOException {
try {
InputStream in = afd.createInputStream();
int size = (int) afd.getDeclaredLength();
byte[] data = new byte[size];
int writeIndex = 0;
int numRead;
do {
numRead = in.read(data, writeIndex, size - writeIndex);
writeIndex += numRead;
} while (numRead >= 0);
return new TestDataSourceCallback(data);
} finally {
afd.close();
}
}
public TestDataSourceCallback(byte[] data) {
this.data = data;
}
@Override
public synchronized int readAt(long position, byte[] buffer, int offset, int size) {
// Clamp reads past the end of the source.
if (position >= data.length) {
return -1; // -1 indicates EOF
}
if (position + size > data.length) {
size -= (position + size) - data.length;
}
System.arraycopy(data, (int) position, buffer, offset, size);
return size;
}
@Override
public synchronized long getSize() {
Log.v(TAG, "getSize: " + data.length);
return data.length;
}
// Note: it's fine to keep using this media item after closing it.
@Override
public synchronized void close() {
Log.v(TAG, "close()");
}
}
/*
* 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 static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
import static com.google.common.truth.Truth.assertThat;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.SessionPlayer.PlayerResult;
import androidx.media2.common.UriMediaItem;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/** Utilities for tests. */
public final class TestUtils {
private static final long PLAYER_STATE_CHANGE_WAIT_TIME_MS = 5_000;
public static Uri createResourceUri(Context context, int resId) {
Resources resources = context.getResources();
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(resources.getResourcePackageName(resId))
.appendPath(resources.getResourceTypeName(resId))
.appendPath(resources.getResourceEntryName(resId))
.build();
}
public static MediaItem createMediaItem(Context context) {
return createMediaItem(context, com.google.android.exoplayer2.ext.media2.test.R.raw.testvideo);
}
public static MediaItem createMediaItem(Context context, int resId) {
Uri testVideoUri = createResourceUri(context, resId);
String resourceName = context.getResources().getResourceName(resId);
MediaMetadata metadata =
new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, resourceName)
.build();
return new UriMediaItem.Builder(testVideoUri).setMetadata(metadata).build();
}
public static List<MediaItem> createPlaylist(Context context, int size) {
List<MediaItem> items = new ArrayList<>();
for (int i = 0; i < size; ++i) {
items.add(createMediaItem(context));
}
return items;
}
public static void loadResource(Context context, int resId, SessionPlayer sessionPlayer)
throws Exception {
Uri testUri = TestUtils.createResourceUri(context, resId);
MediaItem mediaItem = createMediaItem(context, resId);
assertPlayerResultSuccess(sessionPlayer.setMediaItem(mediaItem));
}
public static void assertPlayerResultSuccess(Future<PlayerResult> future) throws Exception {
assertPlayerResult(future, RESULT_SUCCESS);
}
public static void assertPlayerResult(
Future<PlayerResult> future, /* @PlayerResult.ResultCode */ int playerResult)
throws Exception {
assertThat(future).isNotNull();
PlayerResult result = future.get(PLAYER_STATE_CHANGE_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getResultCode()).isEqualTo(playerResult);
}
private TestUtils() {
// Prevent from instantiation.
}
}
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:keepScreenOn="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView android:id="@+id/surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
</SurfaceView>
<SurfaceView android:id="@+id/surface2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
</SurfaceView>
<SurfaceView android:id="@+id/surface3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
</SurfaceView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest package="com.google.android.exoplayer2.ext.media2"/>
/*
* 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 com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.util.Assertions;
/** Prepares an {@link ExoPlayer} instance with a {@link ConcatenatingMediaSource}. */
public final class ConcatenatingMediaSourcePlaybackPreparer implements PlaybackPreparer {
private final ExoPlayer exoPlayer;
private final ConcatenatingMediaSource concatenatingMediaSource;
/**
* Creates a concatenating media source playback preparer.
*
* @param exoPlayer The player to prepare.
* @param concatenatingMediaSource The concatenating media source with which to prepare the
* player.
*/
public ConcatenatingMediaSourcePlaybackPreparer(
ExoPlayer exoPlayer, ConcatenatingMediaSource concatenatingMediaSource) {
this.exoPlayer = exoPlayer;
this.concatenatingMediaSource = Assertions.checkNotNull(concatenatingMediaSource);
}
@Override
public void preparePlayback() {
exoPlayer.prepare(concatenatingMediaSource);
}
}
/*
* 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();
}
}
}
/*
* Copyright 2020 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.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. */
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) {
@SuppressLint("RestrictedApi")
@SuppressWarnings("RestrictTo")
MediaSessionCompat sessionCompat = session2.getSessionCompat();
return sessionCompat.getSessionToken();
}
private MediaSessionUtil() {
// Prevent from instantiation.
}
}
/*
* 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.os.Handler;
import android.os.Looper;
/** A {@link Handler} that provides {@link #postOrRun(Runnable)}. */
/* package */ final class PlayerHandler extends Handler {
public PlayerHandler(Looper looper) {
super(looper);
}
/**
* Posts the {@link Runnable} if the calling thread differs with the {@link Looper} of this
* handler. Otherwise, runs the runnable directly.
*
* @param r A runnable to either post or run.
* @return {@code true} if it's successfully run. {@code false} otherwise.
*/
public boolean postOrRun(Runnable r) {
if (Thread.currentThread() != getLooper().getThread()) {
return post(r);
}
r.run();
return true;
}
}
/*
* 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 androidx.annotation.Nullable;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.SessionPlayer;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.Player;
import java.util.List;
/** Interface that handles playlist edit and navigation operations. */
public interface PlaylistManager {
/**
* See {@link SessionPlayer#setPlaylist(List, MediaMetadata)}.
*
* @param player The player used to build SessionPlayer together.
* @param playlist A list of {@link MediaItem} objects to set as a play list.
* @param metadata The metadata of the playlist.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean setPlaylist(Player player, List<MediaItem> playlist, @Nullable MediaMetadata metadata);
/**
* See {@link SessionPlayer#addPlaylistItem(int, MediaItem)}.
*
* @param player The player used to build SessionPlayer together.
* @param index The index of the item you want to add in the playlist.
* @param mediaItem The media item you want to add.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean addPlaylistItem(Player player, int index, MediaItem mediaItem);
/**
* See {@link SessionPlayer#removePlaylistItem(int)}.
*
* @param player The player used to build SessionPlayer together.
* @param index The index of the item you want to remove in the playlist.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean removePlaylistItem(Player player, int index);
/**
* See {@link SessionPlayer#replacePlaylistItem(int, MediaItem)}.
*
* @param player The player used to build SessionPlayer together.
* @param mediaItem The media item you want to replace with.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean replacePlaylistItem(Player player, int index, MediaItem mediaItem);
/**
* See {@link SessionPlayer#setMediaItem(MediaItem)}.
*
* @param player The player used to build SessionPlayer together.
* @param mediaItem The media item you want to set.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean setMediaItem(Player player, MediaItem mediaItem);
/**
* See {@link SessionPlayer#updatePlaylistMetadata(MediaMetadata)}.
*
* @param player The player used to build SessionPlayer together.
* @param metadata The metadata of the playlist.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean updatePlaylistMetadata(Player player, @Nullable MediaMetadata metadata);
/**
* See {@link SessionPlayer#skipToNextPlaylistItem()}.
*
* @param player The player used to build SessionPlayer together.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean skipToNextPlaylistItem(Player player, ControlDispatcher controlDispatcher);
/**
* See {@link SessionPlayer#skipToPreviousPlaylistItem()}.
*
* @param player The player used to build SessionPlayer together.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean skipToPreviousPlaylistItem(Player player, ControlDispatcher controlDispatcher);
/**
* See {@link SessionPlayer#skipToPlaylistItem(int)}.
*
* @param player The player used to build SessionPlayer together.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
* @return true if the operation was dispatched. False if suppressed.
*/
boolean skipToPlaylistItem(Player player, ControlDispatcher controlDispatcher, int index);
/**
* See {@link SessionPlayer#getCurrentMediaItemIndex()}.
*
* @param player The player used to build SessionPlayer together.
* @return The current media item index
*/
int getCurrentMediaItemIndex(Player player);
/**
* See {@link SessionPlayer#getCurrentMediaItem()}.
*
* @param player The player used to build SessionPlayer together.
* @return The current media item index
*/
@Nullable
MediaItem getCurrentMediaItem(Player player);
/**
* See {@link SessionPlayer#setPlaylist(List, MediaMetadata)}.
*
* @param player The player used to build SessionPlayer together.
* @return The playlist.
*/
@Nullable
List<MediaItem> getPlaylist(Player player);
/**
* See {@link SessionPlayer#getPlaylistMetadata()}.
*
* @param player The player used to build SessionPlayer together.
* @return The metadata of the playlist.
*/
@Nullable
MediaMetadata getPlaylistMetadata(Player player);
/**
* Called when the player's timeline is changed.
*
* @param player The player used to build SessionPlayer together.
*/
void onTimelineChanged(Player player);
}
/*
* 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 androidx.concurrent.futures.CallbackToFutureAdapter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* A replacement of com.google.common.util.concurrent.SettableFuture with CallbackToFutureAdapter to
* avoid the dependency on Guava.
*/
@SuppressWarnings("ShouldNotSubclass")
/* package */ class SettableFuture<V> implements ListenableFuture<V> {
static <V> SettableFuture<V> create() {
return new SettableFuture<>();
}
private final ListenableFuture<V> future;
private final CallbackToFutureAdapter.Completer<V> completer;
SettableFuture() {
AtomicReference<CallbackToFutureAdapter.Completer<V>> completerRef = new AtomicReference<>();
future =
CallbackToFutureAdapter.getFuture(
completer -> {
completerRef.set(completer);
return null;
});
completer = Assertions.checkNotNull(completerRef.get());
}
@Override
public void addListener(Runnable listener, Executor executor) {
future.addListener(listener, executor);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return future.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public V get() throws ExecutionException, InterruptedException {
return future.get();
}
@Override
public V get(long timeout, TimeUnit unit)
throws ExecutionException, InterruptedException, TimeoutException {
return future.get(timeout, unit);
}
void set(V value) {
completer.set(value);
}
void setException(Throwable throwable) {
completer.setException(throwable);
}
}
/*
* 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.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).
*/
/* 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 mediaItem) {
if (mediaItem instanceof UriMediaItem) {
Uri uri = ((UriMediaItem) mediaItem).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= */ mediaItem);
} else if (mediaItem instanceof CallbackMediaItem) {
CallbackMediaItem callbackMediaItem = (CallbackMediaItem) mediaItem;
dataSourceFactory =
DataSourceCallbackDataSource.getFactory(callbackMediaItem.getDataSourceCallback());
return new ProgressiveMediaSource.Factory(dataSourceFactory, 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 the SimpleExoPlayer's shuffle mode for the given shuffle mode. */
public static boolean getExoPlayerShuffleMode(int shuffleMode) {
switch (shuffleMode) {
case SessionPlayer.SHUFFLE_MODE_ALL:
case SessionPlayer.SHUFFLE_MODE_GROUP:
return true;
case SessionPlayer.SHUFFLE_MODE_NONE:
return false;
default:
throw new IllegalArgumentException();
}
}
/** Returns the shuffle mode for the given ExoPlayer's shuffle mode */
public static int getShuffleMode(boolean exoPlayerShuffleMode) {
return exoPlayerShuffleMode ? SessionPlayer.SHUFFLE_MODE_ALL : SessionPlayer.SHUFFLE_MODE_NONE;
}
/** Returns the ExoPlayer's repeat mode for the given repeat mode. */
@Player.RepeatMode
public static int getExoPlayerRepeatMode(int repeatMode) {
switch (repeatMode) {
case SessionPlayer.REPEAT_MODE_ALL:
case SessionPlayer.REPEAT_MODE_GROUP:
return Player.REPEAT_MODE_ALL;
case SessionPlayer.REPEAT_MODE_ONE:
return Player.REPEAT_MODE_ONE;
case SessionPlayer.REPEAT_MODE_NONE:
return Player.REPEAT_MODE_OFF;
default:
throw new IllegalArgumentException();
}
}
/** Returns the repeat mode for the given SimpleExoPlayer's repeat mode. */
public static int getRepeatMode(@Player.RepeatMode int exoPlayerRepeatMode) {
switch (exoPlayerRepeatMode) {
case Player.REPEAT_MODE_ALL:
return SessionPlayer.REPEAT_MODE_ALL;
case Player.REPEAT_MODE_ONE:
return SessionPlayer.REPEAT_MODE_ONE;
case Player.REPEAT_MODE_OFF:
return SessionPlayer.REPEAT_MODE_NONE;
default:
throw new IllegalArgumentException();
}
}
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(uri);
}
// LINT.ThenChange(../../../../../../../../../proguard-rules.txt)
} catch (Exception e) {
// Expected if the app was built without the corresponding module.
}
return new ProgressiveMediaSource.Factory(dataSourceFactory).setTag(tag).createMediaSource(uri);
}
private Utils() {
// Prevent instantiation.
}
}
/*
* 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.
*/
@NonNullApi
package com.google.android.exoplayer2.ext.media2;
import com.google.android.exoplayer2.util.NonNullApi;
../../proguard-rules.txt
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment