Swich playback to an ExoPlayer based backend
This commit is contained in:
parent
13ea8dd04d
commit
4648e56b15
7 changed files with 246 additions and 55 deletions
|
@ -161,6 +161,8 @@ dependencies {
|
|||
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||
implementation 'cat.ereza:customactivityoncrash:2.3.0'
|
||||
debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6'
|
||||
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.16.1'
|
||||
}
|
||||
|
||||
apply from: '../spotless.gradle'
|
||||
|
|
|
@ -82,18 +82,18 @@ class RealSongRepository(private val context: Context) : SongRepository {
|
|||
return listOf(
|
||||
Song(
|
||||
1,
|
||||
"example",
|
||||
"river-lake convergence",
|
||||
1,
|
||||
2021,
|
||||
23723478,
|
||||
"uuh idk what goes here yet, i think the file path or something",
|
||||
"https://versary.town/river-lake-convergence.mp3",
|
||||
1639589623,
|
||||
1,
|
||||
"example",
|
||||
"river-lake convergence",
|
||||
1,
|
||||
"example",
|
||||
"example",
|
||||
"example"
|
||||
"annieversary",
|
||||
null,
|
||||
"annieversary"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
|||
player.setAudioAttributes(
|
||||
AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()
|
||||
)
|
||||
player.prepare()
|
||||
player.prepareAsync()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
|
|
|
@ -23,10 +23,8 @@ import android.net.Uri;
|
|||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.service.playback.Playback;
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
|
@ -83,7 +81,7 @@ public class MultiPlayer
|
|||
player.setDataSource(path);
|
||||
}
|
||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
player.prepare();
|
||||
player.prepareAsync();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -14,27 +14,11 @@
|
|||
|
||||
package code.name.monkey.retromusic.service;
|
||||
|
||||
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
||||
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT;
|
||||
import static org.koin.java.KoinJavaComponent.get;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
|
||||
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.*;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Bitmap;
|
||||
|
@ -42,16 +26,10 @@ import android.graphics.Point;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.*;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
|
@ -62,30 +40,14 @@ import android.telephony.PhoneStateListener;
|
|||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.activities.LockScreenActivity;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetBig;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetCard;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetClassic;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetMD3;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetSmall;
|
||||
import code.name.monkey.retromusic.appwidgets.AppWidgetText;
|
||||
import code.name.monkey.retromusic.appwidgets.*;
|
||||
import code.name.monkey.retromusic.auto.AutoMediaIDHelper;
|
||||
import code.name.monkey.retromusic.auto.AutoMusicProvider;
|
||||
import code.name.monkey.retromusic.glide.BlurTransformation;
|
||||
|
@ -108,7 +70,18 @@ import code.name.monkey.retromusic.util.PreferenceUtil;
|
|||
import code.name.monkey.retromusic.util.RetroUtil;
|
||||
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
|
||||
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import kotlin.Unit;
|
||||
import software.lavender.music.player.ExoPlayerPlayback;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
||||
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT;
|
||||
import static code.name.monkey.retromusic.ConstantsKt.*;
|
||||
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
|
||||
import static org.koin.java.KoinJavaComponent.get;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
||||
|
@ -162,6 +135,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
public static final int DUCK = 7;
|
||||
public static final int UNDUCK = 8;
|
||||
public static final int RESTORE_QUEUES = 9;
|
||||
|
||||
public static final int SHUFFLE_MODE_NONE = 0;
|
||||
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
||||
public static final int REPEAT_MODE_NONE = 0;
|
||||
|
@ -384,7 +358,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
}
|
||||
|
||||
private static String getTrackUri(@NonNull Song song) {
|
||||
return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
|
||||
return song.getData();//MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -407,8 +381,9 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper());
|
||||
|
||||
// Set MultiPlayer when crossfade duration is 0 i.e. off
|
||||
// TODO: do crossfading in exoplayer or remove the feature entirely for now, crossfadeplayer will NOT work right now
|
||||
if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) {
|
||||
playback = new MultiPlayer(this);
|
||||
playback = new ExoPlayerPlayback(this);
|
||||
} else {
|
||||
playback = new CrossFadePlayer(this);
|
||||
}
|
||||
|
@ -904,7 +879,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
|||
playback.release();
|
||||
}
|
||||
playback = null;
|
||||
playback = new MultiPlayer(this);
|
||||
playback = new ExoPlayerPlayback(this);
|
||||
playback.setCallbacks(this);
|
||||
if (openTrackAndPrepareNextAt(position)) {
|
||||
seek(progress);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package software.lavender.music.extensions
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Looper
|
||||
|
||||
inline val Looper.isCurrentThreadCompat: Boolean
|
||||
get() = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) isCurrentThread else Thread.currentThread() == thread
|
|
@ -0,0 +1,209 @@
|
|||
package software.lavender.music.player
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.audiofx.AudioEffect
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.PowerManager
|
||||
import android.widget.Toast
|
||||
import code.name.monkey.retromusic.service.playback.Playback
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.PlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import software.lavender.music.extensions.isCurrentThreadCompat
|
||||
|
||||
// TODO: implement audio offloading, see: https://exoplayer.dev/battery-consumption.html
|
||||
class ExoPlayerPlayback(private val context: Context) : Playback, Player.Listener {
|
||||
private val handler = Handler(HandlerThread("ExoPlayerHandler").apply { start() }.looper)
|
||||
private val dispatcher = handler.asCoroutineDispatcher("ExoPlayerDispatcher")
|
||||
private val player =
|
||||
ExoPlayer.Builder(context).setWakeMode(PowerManager.PARTIAL_WAKE_LOCK).setLooper(handler.looper).build()
|
||||
|
||||
private var initialized: Boolean = false
|
||||
override val isInitialized: Boolean
|
||||
get() = initialized
|
||||
private var playing = false
|
||||
override val isPlaying: Boolean
|
||||
get() = isInitialized && ensurePlayerThread { player.isPlaying }
|
||||
private var sessionId = -1
|
||||
override val audioSessionId: Int
|
||||
get() = ensurePlayerThread { player.audioSessionId }
|
||||
|
||||
private var callbacks: Playback.PlaybackCallbacks? = null
|
||||
private var hasNext = false
|
||||
|
||||
override fun setDataSource(path: String): Boolean {
|
||||
Toast.makeText(
|
||||
context,
|
||||
path,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
val success = ensurePlayerThread {
|
||||
initialized = false
|
||||
try {
|
||||
player.stop()
|
||||
player.clearMediaItems()
|
||||
player.setMediaItem(MediaItem.fromUri(path))
|
||||
player.prepare()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
e.message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
e.printStackTrace()
|
||||
initialized = false
|
||||
false
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
return initialized
|
||||
}
|
||||
// TODO: listeners?
|
||||
ensurePlayerThread {
|
||||
player.addListener(this)
|
||||
}
|
||||
val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId)
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
|
||||
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||
context.sendBroadcast(intent)
|
||||
initialized = true
|
||||
return initialized
|
||||
}
|
||||
|
||||
override fun setNextDataSource(path: String?) = ensurePlayerThread {
|
||||
if (path == null) {
|
||||
return@ensurePlayerThread
|
||||
}
|
||||
player.addMediaItem(MediaItem.fromUri(path))
|
||||
hasNext = true
|
||||
|
||||
// TODO: i think we need to do more stuff here
|
||||
// TODO: gapless?
|
||||
}
|
||||
|
||||
override fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
||||
this.callbacks = callbacks
|
||||
}
|
||||
|
||||
override fun start() = ensurePlayerThread {
|
||||
try {
|
||||
player.play()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() = ensurePlayerThread {
|
||||
player.stop()
|
||||
player.clearMediaItems()
|
||||
initialized = false
|
||||
}
|
||||
|
||||
override fun release() = ensurePlayerThread {
|
||||
player.stop()
|
||||
player.clearMediaItems()
|
||||
initialized = false
|
||||
player.release()
|
||||
}
|
||||
|
||||
override fun pause() = ensurePlayerThread {
|
||||
try {
|
||||
player.pause()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun duration() = if (!initialized) -1 else ensurePlayerThread {
|
||||
try {
|
||||
player.duration.toInt()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun position() = if (!initialized) -1 else ensurePlayerThread {
|
||||
try {
|
||||
player.currentPosition.toInt()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do seeking via
|
||||
override fun seek(whereto: Int) = ensurePlayerThread {
|
||||
try {
|
||||
player.seekTo(whereto.toLong())
|
||||
player.currentPosition.toInt()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun setVolume(vol: Float) = ensurePlayerThread {
|
||||
try {
|
||||
player.volume = vol
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAudioSessionId(sessionId: Int) = ensurePlayerThread {
|
||||
try {
|
||||
player.audioSessionId = sessionId
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCrossFadeDuration(duration: Int) {
|
||||
// TODO: can we do crossfading in exoplayer directly??
|
||||
}
|
||||
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
error.message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
// TODO: tbh with first class playlist support in exoplayer we should probably get rid of the weird track switching behaviour asap
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
if (hasNext) {
|
||||
hasNext = false
|
||||
callbacks?.onTrackWentToNext()
|
||||
} else {
|
||||
callbacks?.onTrackEnded()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
playing = isPlaying
|
||||
}
|
||||
|
||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||
sessionId = audioSessionId
|
||||
}
|
||||
|
||||
private fun <T> ensurePlayerThread(block: () -> T): T =
|
||||
if (player.playbackLooper.isCurrentThreadCompat) block() else runBlocking(dispatcher) { block() }
|
||||
}
|
Loading…
Reference in a new issue