From 27530ea3164f13a403256d84dcf8deb33d225bc5 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 10 Jan 2024 21:29:33 +0100 Subject: [PATCH] ExoPlayer module migrated to androidx.media3 --- build.gradle | 6 +- depends/.gitignore | 1 - depends/build.sh | 118 ------------------ .../media/service/MediaSessionCallback.java | 6 +- .../fermata/ui/view/MediaItemMenuHandler.java | 3 +- modules/exoplayer/build.gradle | 9 +- .../engine/exoplayer/ExoPlayerEngine.java | 96 +++++++------- settings.gradle | 8 -- 8 files changed, 61 insertions(+), 186 deletions(-) delete mode 100644 depends/.gitignore delete mode 100755 depends/build.sh diff --git a/build.gradle b/build.gradle index cec2097d..b4ea10c1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ ext { def abi = project.properties['ABI'] - VERSION_CODE = 234 - VERSION_NAME = "1.9.6" + VERSION_CODE = 235 + VERSION_NAME = "1.9.7" SDK_MIN_VERSION = 23 SDK_TARGET_VERSION = 34 SDK_COMPILE_VERSION = 34 @@ -95,7 +95,6 @@ subprojects { compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 - if (name == "exoplayer") coreLibraryDesugaringEnabled true } testOptions { @@ -107,7 +106,6 @@ subprojects { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - if (name == "exoplayer") coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' } } diff --git a/depends/.gitignore b/depends/.gitignore deleted file mode 100644 index 9b566cdb..00000000 --- a/depends/.gitignore +++ /dev/null @@ -1 +0,0 @@ -ExoPlayer/ diff --git a/depends/build.sh b/depends/build.sh deleted file mode 100755 index 20fdffec..00000000 --- a/depends/build.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash -set -e - -: ${NDK_DIR:="$1"} - -if [ ! -d "$NDK_DIR" ];then - echo "NDK_DIR is not specified" - exit 1 -fi - -DEPENDS_DIR="$(cd "$(dirname "$0")" && pwd)" -FFMPEG_DIR="$DEPENDS_DIR/ffmpeg" -EXO_DIR="$DEPENDS_DIR/ExoPlayer" -EXO_EXT_DIR="$EXO_DIR/extensions" - -SDK_MIN_VERSION="$(grep 'SDK_MIN_VERSION = ' $DEPENDS_DIR/../build.gradle | cut -d= -f2)" -SDK_TARGET_VERSION="$(grep 'SDK_TARGET_VERSION = ' $DEPENDS_DIR/../build.gradle | cut -d= -f2)" -SDK_COMPILE_VERSION="$(grep 'SDK_COMPILE_VERSION = ' $DEPENDS_DIR/../build.gradle | cut -d= -f2)" -ANDROIDX_MEDIA_VERSION="$(grep 'ANDROIDX_MEDIA_VERSION = ' $DEPENDS_DIR/../build.gradle | cut -d= -f2)" -ANDROIDX_APPCOMPAT_VERSION="$(grep 'ANDROIDX_APPCOMPAT_VERSION = ' $DEPENDS_DIR/../build.gradle | cut -d= -f2)" - -: ${ABI:='armeabi-v7a arm64-v8a x86 x86_64'} - -if [ -z "$HOST_PLATFORM" ]; then - case "$OSTYPE" in - linux*) - HOST_PLATFORM='linux-x86_64' - ;; - darwin*) - HOST_PLATFORM='darwin-x86_x64' - ;; - win*|cygwin|msys) - HOST_PLATFORM='windows-x86_x64' - ;; - *) - echo 'Failed to detect host platform!' - echo 'Please, set the HOST_PLATFORM environment variable to one of the following: linux-x86_64, darwin-x86_x64, windows-x86_x64' - ;; - esac - - echo "Host platform: $HOST_PLATFORM" -fi - -# Clone or update FFmpeg -FFMPEG_VER="release/4.4" -if [ -d "$FFMPEG_DIR" ]; then - cd "$FFMPEG_DIR" - git clean -xfd && git reset --hard && git checkout $FFMPEG_VER && git reset --hard && git pull -else - git clone 'git://source.ffmpeg.org/ffmpeg' "$FFMPEG_DIR" - cd "$FFMPEG_DIR" - git checkout $FFMPEG_VER -fi - -# Clone or update ExoPlayer -if [ -d "$EXO_DIR" ]; then - cd "$EXO_DIR" - git clean -xfd && git reset --hard && git pull origin release-v2 -else - git clone 'https://github.com/google/ExoPlayer.git' "$EXO_DIR" - cd "$EXO_DIR" -fi -git checkout release-v2 - -echo > "$EXO_DIR/publish.gradle" -sed -i "s/minSdkVersion .*/minSdkVersion = $SDK_MIN_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/targetSdkVersion .*/targetSdkVersion = $SDK_TARGET_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/appTargetSdkVersion .*/appTargetSdkVersion = $SDK_TARGET_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/compileSdkVersion .*/compileSdkVersion = $SDK_COMPILE_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/androidxMediaVersion .*/androidxMediaVersion = $ANDROIDX_MEDIA_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/androidxAppCompatVersion .*/androidxAppCompatVersion = $ANDROIDX_APPCOMPAT_VERSION/" "$EXO_DIR/constants.gradle" -sed -i "s/minSdkVersion .*/minSdkVersion $SDK_MIN_VERSION/" "$EXO_DIR/extensions/leanback/build.gradle" - -# Move package name to the android.namespace property -for i in $(find "$EXO_DIR/" -name AndroidManifest.xml | grep /src/main/); do - pkg=$(grep -o ' package="[^ ]*"' "$i") - pkg="$(eval "$pkg; echo \$package")" - sed -i 's/ package="[^ ]*"//' "$i" - echo "android.namespace = '$pkg'" >> "$(dirname $i)/../../build.gradle" -done - -# Build ExoPlayer FFmpeg extension -FFMPEG_DECODERS=(vorbis alac mp3 aac ac3 eac3 mlp truehd) - -cd "$EXO_EXT_DIR/ffmpeg/src/main/jni" -[ -e ffmpeg ] || ln -s "$FFMPEG_DIR" ffmpeg -sed -ie 's|${TOOLCHAIN_PREFIX}/.*-linux-android.*-nm|${TOOLCHAIN_PREFIX}/llvm-nm|g' build_ffmpeg.sh -sed -ie 's|${TOOLCHAIN_PREFIX}/.*-linux-android.*-ar|${TOOLCHAIN_PREFIX}/llvm-ar|g' build_ffmpeg.sh -sed -ie 's|${TOOLCHAIN_PREFIX}/.*-linux-android.*-ranlib|${TOOLCHAIN_PREFIX}/llvm-ranlib|g' build_ffmpeg.sh -sed -ie 's|${TOOLCHAIN_PREFIX}/.*-linux-android.*-strip|${TOOLCHAIN_PREFIX}/llvm-strip|g' build_ffmpeg.sh -sed -ie 's|android16|android23|g' build_ffmpeg.sh -sed -ie 's|androideabi16|androideabi23|g' build_ffmpeg.sh - -./build_ffmpeg.sh "$(pwd)/.." "$NDK_DIR" "$HOST_PLATFORM" "${FFMPEG_DECODERS[@]}" - -# Build ExoPlayer Flac extension -FLAC_VERSION='1.3.3' - -cd "$EXO_EXT_DIR/flac/src/main/jni" -rm -rf flac -curl "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-${FLAC_VERSION}.tar.xz" | tar xJ -mv flac-${FLAC_VERSION} flac -"$NDK_DIR/ndk-build" APP_ABI="$ABI" -j4 - -# Build ExoPlayer Opus extension -cd "$EXO_EXT_DIR/opus/src/main/jni" - -if [ -d libopus ]; then - cd libopus - git clean -xfd && git reset --hard && git pull - cd - -else -# git clone 'https://git.xiph.org/opus.git' libopus - git clone 'https://github.com/xiph/opus.git' libopus -fi - -./convert_android_asm.sh -"$NDK_DIR/ndk-build" APP_ABI="$ABI" -j4 diff --git a/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java b/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java index ab7717b6..bfa20ff4 100644 --- a/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java +++ b/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java @@ -190,10 +190,10 @@ public MediaSessionCallback(FermataMediaService service, MediaSessionCompat sess ctx.getString(R.string.repeat), R.drawable.repeat).build(); customRepeatDisable = new PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_REPEAT_DISABLE, - ctx.getString(R.string.repeat_disable), R.drawable.repeat_filled).build(); + ctx.getString(R.string.repeat_disable), R.drawable.repeat_filled).build(); customShuffleEnable = new PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SHUFFLE_ENABLE, - ctx.getString(R.string.shuffle), R.drawable.shuffle).build(); + ctx.getString(R.string.shuffle), R.drawable.shuffle).build(); customShuffleDisable = new PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SHUFFLE_DISABLE, ctx.getString(R.string.shuffle_disable), R.drawable.shuffle_filled).build(); @@ -1234,6 +1234,8 @@ private float getSpeed(PlayableItem i) { } private void start(MediaEngine engine, float speed) { + Log.i("Start playing ", engine.getSource().getLocation(), " with ", + engine.getClass().getSimpleName()); engine.setSpeed(speed); engine.start(); } diff --git a/fermata/src/main/java/me/aap/fermata/ui/view/MediaItemMenuHandler.java b/fermata/src/main/java/me/aap/fermata/ui/view/MediaItemMenuHandler.java index d99ac1d3..2a7485db 100644 --- a/fermata/src/main/java/me/aap/fermata/ui/view/MediaItemMenuHandler.java +++ b/fermata/src/main/java/me/aap/fermata/ui/view/MediaItemMenuHandler.java @@ -181,7 +181,8 @@ protected void buildPlayableMenu(MainActivityDelegate a, OverlayMenu.Builder b, if (addMediaEngMenu()) { b.addItem(R.id.preferred_media_engine, R.drawable.media_engine, - R.string.preferred_media_engine).setSubmenu(this::buildVideoEngMenu); + R.string.preferred_media_engine) + .setSubmenu(pi.isVideo() ? this::buildVideoEngMenu : this::buildAudioEngMenu); } } diff --git a/modules/exoplayer/build.gradle b/modules/exoplayer/build.gradle index e9c3e385..7c791522 100644 --- a/modules/exoplayer/build.gradle +++ b/modules/exoplayer/build.gradle @@ -9,9 +9,8 @@ android { dependencies { implementation project(':utils') implementation project(':fermata') - implementation project(':exoplayer-library-core') - implementation project(':exoplayer-library-hls') - implementation project(':exoplayer-extension-ffmpeg') - implementation project(':exoplayer-extension-flac') - implementation project(':exoplayer-extension-opus') + implementation("androidx.media3:media3-exoplayer:1.2.0") + implementation("androidx.media3:media3-exoplayer-hls:1.2.0") + implementation("androidx.media3:media3-exoplayer-dash:1.2.0") + implementation("androidx.media3:media3-datasource-cronet:1.2.0") } diff --git a/modules/exoplayer/src/main/java/me/aap/fermata/engine/exoplayer/ExoPlayerEngine.java b/modules/exoplayer/src/main/java/me/aap/fermata/engine/exoplayer/ExoPlayerEngine.java index 5987c218..3e7e18b2 100644 --- a/modules/exoplayer/src/main/java/me/aap/fermata/engine/exoplayer/ExoPlayerEngine.java +++ b/modules/exoplayer/src/main/java/me/aap/fermata/engine/exoplayer/ExoPlayerEngine.java @@ -1,28 +1,39 @@ package me.aap.fermata.engine.exoplayer; -import static com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER; import static me.aap.utils.async.Completed.completed; import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoSize; +import androidx.annotation.NonNull; +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.MediaItem; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.common.VideoSize; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.datasource.cronet.CronetDataSource; +import androidx.media3.datasource.cronet.CronetUtil; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSource; + +import org.chromium.net.CronetEngine; + +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.util.concurrent.Executors; import me.aap.fermata.BuildConfig; +import me.aap.fermata.FermataApplication; import me.aap.fermata.media.engine.AudioEffects; import me.aap.fermata.media.engine.MediaEngineBase; import me.aap.fermata.media.lib.MediaLib.PlayableItem; @@ -33,13 +44,25 @@ /** * @author Andrey Pavlenko */ +@UnstableApi public class ExoPlayerEngine extends MediaEngineBase implements Player.Listener { - private final Context ctx; + private static final DataSource.Factory httpDsFactory; + + static { + CronetEngine cre = CronetUtil.buildCronetEngine(FermataApplication.get(), + "Fermata/" + BuildConfig.VERSION_NAME, true); + if (cre != null) { + httpDsFactory = new CronetDataSource.Factory(cre, Executors.newSingleThreadExecutor()); + } else { + CookieManager cookieManager = new CookieManager(); + cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); + CookieHandler.setDefault(cookieManager); + httpDsFactory = new DefaultHttpDataSource.Factory(); + } + } + private final ExoPlayer player; private final AudioEffects audioEffects; - private final DataSource.Factory dsFactory; - private ProgressiveMediaSource.Factory progressive; - private HlsMediaSource.Factory hls; private PlayableItem source; private boolean preparing; private boolean buffering; @@ -47,12 +70,12 @@ public class ExoPlayerEngine extends MediaEngineBase implements Player.Listener public ExoPlayerEngine(Context ctx, Listener listener) { super(listener); - this.ctx = ctx; - player = new ExoPlayer.Builder(ctx, new DefaultRenderersFactory(ctx).setExtensionRendererMode( - EXTENSION_RENDERER_MODE_PREFER)).build(); + DefaultDataSource.Factory dsFactory = new DefaultDataSource.Factory(ctx, httpDsFactory); + MediaSource.Factory msFactory = + new DefaultMediaSourceFactory(ctx).setDataSourceFactory(dsFactory); + player = new ExoPlayer.Builder(ctx).setMediaSourceFactory(msFactory).build(); player.addListener(this); audioEffects = AudioEffects.create(0, player.getAudioSessionId()); - dsFactory = new DefaultDataSourceFactory(ctx, "Fermata/" + BuildConfig.VERSION_NAME); } @Override @@ -70,32 +93,11 @@ public void prepare(PlayableItem source) { Uri uri = source.getLocation(); MediaItem m = MediaItem.fromUri(uri); - int type = Util.inferContentType(uri, null); - - switch (type) { - case C.TYPE_HLS -> { - if (hls == null) hls = new HlsMediaSource.Factory(getDsFactory(source)); - isHls = true; - player.setMediaSource(hls.createMediaSource(m), true); - } - case C.TYPE_OTHER -> { - if (progressive == null) - progressive = new ProgressiveMediaSource.Factory(getDsFactory(source)); - isHls = false; - player.setMediaSource(progressive.createMediaSource(m), true); - } - default -> - listener.onEngineError(this, new IllegalArgumentException("Unsupported type: " + type)); - } - + isHls = Util.inferContentType(uri) == C.CONTENT_TYPE_HLS; + player.setMediaItem(m); player.prepare(); } - private DataSource.Factory getDsFactory(PlayableItem source) { - String agent = source.getUserAgent(); - return (agent == null) ? dsFactory : new DefaultDataSourceFactory(ctx, agent); - } - @Override public void start() { player.setPlayWhenReady(true); @@ -230,7 +232,7 @@ public void onVideoSizeChanged(VideoSize videoSize) { } @Override - public void onPlayerError(PlaybackException error) { + public void onPlayerError(@NonNull PlaybackException error) { listener.onEngineError(this, error); } } diff --git a/settings.gradle b/settings.gradle index 66efa1fe..044a52f8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,6 @@ gradle.ext.localProps = new Properties() gradle.ext.enableGoogleServices = (System.getenv('NO_GS') == null) && file('fermata/google-services.json').isFile() def localPropsFile = file('local.properties') -def exoRoot = file('depends/ExoPlayer') if (localPropsFile.isFile()) { gradle.ext.localProps.load(localPropsFile.newDataInputStream()) @@ -15,12 +14,6 @@ if (localPropsFile.isFile()) { gradle.ext.localProps.setProperty('storePassword', 'fermata') } -if (exoRoot.isDirectory()) { - gradle.ext.exoplayerRoot = exoRoot - gradle.ext.exoplayerModulePrefix = 'exoplayer-' - apply from: new File(exoRoot, 'core_settings.gradle') -} - rootProject.name = 'Fermata Music Player' include ':fermata' include ':control' @@ -29,7 +22,6 @@ project(':utils').projectDir = file('depends/utils') file('modules').eachDir { def name = ':' + it.name - if ((':exoplayer' == name) && !exoRoot.isDirectory()) return if ((':gdrive' == name) && !gradle.ext.enableGoogleServices) return gradle.ext.modules.add(name) include name