Add support for gapless playback

This commit is contained in:
Naveen 2023-04-29 17:30:12 +05:30
parent a23da671aa
commit 3bc8c06eab
6 changed files with 395 additions and 209 deletions

View file

@ -42,8 +42,10 @@ class EqualizerActivity : SimpleActivity() {
@SuppressLint("SetTextI18n")
private fun initMediaPlayer() {
val player = MusicService.mPlayer ?: MediaPlayer()
val equalizer = MusicService.mEqualizer ?: Equalizer(0, player.audioSessionId)
val equalizer = MusicService.mEqualizer ?: run {
val audioSessionId = MusicService.mPlayer?.getAudioSessionId() ?: MediaPlayer().audioSessionId
Equalizer(0, audioSessionId)
}
try {
if (!equalizer.enabled) {
equalizer.enabled = true

View file

@ -0,0 +1,288 @@
package com.simplemobiletools.musicplayer.helpers
import android.app.Application
import android.content.IntentFilter
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.PowerManager
import androidx.core.content.getSystemService
import androidx.media.AudioAttributesCompat
import androidx.media.AudioFocusRequestCompat
import androidx.media.AudioManagerCompat
import com.simplemobiletools.musicplayer.extensions.config
import com.simplemobiletools.musicplayer.receivers.HeadsetPlugReceiver
class MultiPlayer(private val app: Application, private val callbacks: PlaybackCallbacks) : MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
private var mCurrentMediaPlayer = MediaPlayer()
private var mNextMediaPlayer: MediaPlayer? = null
var isInitialized: Boolean = false
private set
private var becomingNoisyReceiverRegistered = false
private val becomingNoisyReceiver = HeadsetPlugReceiver()
private val becomingNoisyReceiverIntentFilter = IntentFilter(AudioManager.ACTION_HEADSET_PLUG).apply {
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
}
private val audioManager: AudioManager? = app.getSystemService()
private var isPausedByTransientLossOfFocus = false
private val audioFocusListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
if (!isPlaying() && isPausedByTransientLossOfFocus) {
start()
callbacks.onPlayStateChanged()
isPausedByTransientLossOfFocus = false
}
setVolume(Volume.NORMAL)
}
AudioManager.AUDIOFOCUS_LOSS -> {
pause()
callbacks.onPlayStateChanged()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
val wasPlaying = isPlaying()
pause()
callbacks.onPlayStateChanged()
isPausedByTransientLossOfFocus = wasPlaying
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
setVolume(Volume.DUCK)
}
}
}
private val audioFocusRequest: AudioFocusRequestCompat =
AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN).setOnAudioFocusChangeListener(audioFocusListener).setAudioAttributes(
AudioAttributesCompat.Builder().setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC).build()
).build()
init {
mCurrentMediaPlayer.setWakeMode(app, PowerManager.PARTIAL_WAKE_LOCK)
}
fun getAudioSessionId(): Int = mCurrentMediaPlayer.audioSessionId
fun isPlaying(): Boolean = isInitialized && mCurrentMediaPlayer.isPlaying
fun setDataSource(trackUri: Uri) {
isInitialized = false
setDataSourceImpl(mCurrentMediaPlayer, trackUri) { result ->
if (result.isFailure) {
throw result.exceptionOrNull()!!
}
isInitialized = true
setNextDataSource(null)
callbacks.onPrepared()
}
}
fun setNextDataSource(trackUri: Uri?, onPrepared: (() -> Unit)? = null) {
try {
mCurrentMediaPlayer.setNextMediaPlayer(null)
} catch (e: IllegalArgumentException) {
// Next media player is current one, continuing
} catch (e: IllegalStateException) {
return
}
if (mNextMediaPlayer != null) {
mNextMediaPlayer?.release()
mNextMediaPlayer = null
}
if (trackUri == null) {
return
}
if (app.config.gapLessPlayback) {
mNextMediaPlayer = MediaPlayer()
mNextMediaPlayer?.setWakeMode(app, PowerManager.PARTIAL_WAKE_LOCK)
mNextMediaPlayer?.audioSessionId = mCurrentMediaPlayer.audioSessionId
setDataSourceImpl(mNextMediaPlayer!!, trackUri) { result ->
if (result.isSuccess) {
try {
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer)
onPrepared?.invoke()
} catch (e: IllegalArgumentException) {
if (mNextMediaPlayer != null) {
mNextMediaPlayer?.release()
mNextMediaPlayer = null
}
} catch (e: IllegalStateException) {
if (mNextMediaPlayer != null) {
mNextMediaPlayer?.release()
mNextMediaPlayer = null
}
}
} else {
if (mNextMediaPlayer != null) {
mNextMediaPlayer?.release()
mNextMediaPlayer = null
}
throw result.exceptionOrNull()!!
}
}
}
}
fun start(): Boolean {
requestFocus()
registerBecomingNoisyReceiver()
return try {
mCurrentMediaPlayer.start()
true
} catch (e: IllegalStateException) {
false
}
}
fun stop() {
abandonFocus()
unregisterBecomingNoisyReceiver()
mCurrentMediaPlayer.reset()
isInitialized = false
}
fun release() {
stop()
mCurrentMediaPlayer.release()
mNextMediaPlayer?.release()
}
fun pause(): Boolean {
unregisterBecomingNoisyReceiver()
return try {
mCurrentMediaPlayer.pause()
true
} catch (e: IllegalStateException) {
false
}
}
fun duration(): Int {
return if (!this.isInitialized) {
-1
} else try {
mCurrentMediaPlayer.duration
} catch (e: IllegalStateException) {
-1
}
}
fun position(): Int {
return if (!this.isInitialized) {
-1
} else try {
mCurrentMediaPlayer.currentPosition
} catch (e: IllegalStateException) {
-1
}
}
fun seek(whereto: Int): Int {
return try {
mCurrentMediaPlayer.seekTo(whereto)
whereto
} catch (e: IllegalStateException) {
-1
}
}
fun setVolume(vol: Float): Boolean {
return try {
mCurrentMediaPlayer.setVolume(vol, vol)
true
} catch (e: IllegalStateException) {
false
}
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
isInitialized = false
mCurrentMediaPlayer.reset()
return false
}
override fun onCompletion(mp: MediaPlayer) {
if (app.config.gapLessPlayback && mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
isInitialized = false
mCurrentMediaPlayer.reset()
mCurrentMediaPlayer.release()
mCurrentMediaPlayer = mNextMediaPlayer!!
isInitialized = true
mNextMediaPlayer = null
callbacks.onTrackWentToNext()
} else {
callbacks.onTrackEnded()
}
}
private fun setDataSourceImpl(player: MediaPlayer, trackUri: Uri, onPrepared: (success: Result<Boolean>) -> Unit) {
player.reset()
try {
player.setDataSource(app, trackUri)
player.setAudioAttributes(
AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()
)
try {
player.playbackParams = player.playbackParams.setSpeed(app.config.playbackSpeed)
} catch (ignored: Exception) {
}
player.setOnPreparedListener {
player.setOnPreparedListener(null)
onPrepared(Result.success(true))
}
player.prepareAsync()
} catch (e: Exception) {
onPrepared(Result.failure(e))
e.printStackTrace()
}
player.setOnCompletionListener(this)
player.setOnErrorListener(this)
}
private fun unregisterBecomingNoisyReceiver() {
if (becomingNoisyReceiverRegistered) {
app.unregisterReceiver(becomingNoisyReceiver)
becomingNoisyReceiverRegistered = false
}
}
private fun registerBecomingNoisyReceiver() {
if (!becomingNoisyReceiverRegistered) {
app.registerReceiver(
becomingNoisyReceiver, becomingNoisyReceiverIntentFilter
)
becomingNoisyReceiverRegistered = true
}
}
private fun requestFocus(): Boolean {
return AudioManagerCompat.requestAudioFocus(audioManager!!, audioFocusRequest) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
}
private fun abandonFocus() {
AudioManagerCompat.abandonAudioFocusRequest(audioManager!!, audioFocusRequest)
}
fun setPlaybackSpeed(speed: Float) {
mCurrentMediaPlayer.playbackParams = mCurrentMediaPlayer.playbackParams.setSpeed(speed)
}
interface PlaybackCallbacks {
fun onPrepared()
fun onTrackEnded()
fun onTrackWentToNext()
fun onPlayStateChanged()
}
object Volume {
const val DUCK = 0.2f
const val NORMAL = 1.0f
}
}

View file

@ -35,7 +35,8 @@ class NotificationHelper(private val context: Context, private val mediaSessionT
var usesChronometer = false
var ongoing = false
if (isPlaying) {
postTime = System.currentTimeMillis() - (MusicService.mPlayer?.currentPosition ?: 0)
val position = MusicService.mPlayer!!.position()
postTime = System.currentTimeMillis() - maxOf(position, 0)
showWhen = true
usesChronometer = true
ongoing = true

View file

@ -1,39 +0,0 @@
package com.simplemobiletools.musicplayer.helpers
import android.annotation.TargetApi
import android.app.Application
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
@TargetApi(Build.VERSION_CODES.O)
class OreoAudioFocusHandler constructor(val app: Application) {
private var audioFocusRequest: AudioFocusRequest? = null
private var audioManager: AudioManager? = null
init {
audioManager = app.getSystemService(Context.AUDIO_SERVICE) as AudioManager
}
fun abandonAudioFocus() {
if (audioFocusRequest != null) {
audioManager?.abandonAudioFocusRequest(audioFocusRequest!!)
}
}
fun requestAudioFocus(audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.setAudioAttributes(audioAttributes)
.build()
audioManager?.requestAudioFocus(audioFocusRequest!!)
}
}

View file

@ -1,5 +1,7 @@
package com.simplemobiletools.musicplayer.models
import android.content.ContentUris
import android.net.Uri
import android.provider.MediaStore
import androidx.room.ColumnInfo
import androidx.room.Entity
@ -10,6 +12,7 @@ import com.simplemobiletools.commons.extensions.getFormattedDuration
import com.simplemobiletools.commons.helpers.AlphanumericComparator
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.musicplayer.helpers.*
import java.io.File
import java.io.Serializable
@Entity(tableName = "tracks", indices = [Index(value = ["media_store_id", "playlist_id"], unique = true)])
@ -83,4 +86,10 @@ data class Track(
else -> path.getFilenameFromPath()
}
}
fun getUri(): Uri = if (mediaStoreId == 0L) {
Uri.fromFile(File(path))
} else {
ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mediaStoreId)
}
}

View file

@ -1,23 +1,18 @@
package com.simplemobiletools.musicplayer.services
import android.annotation.SuppressLint
import android.app.Application
import android.app.Service
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ServiceInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.AudioManager
import android.media.AudioManager.*
import android.media.MediaMetadataRetriever
import android.media.MediaPlayer
import android.media.audiofx.Equalizer
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.provider.MediaStore.Audio
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
@ -28,7 +23,6 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isSPlus
import com.simplemobiletools.musicplayer.R
@ -36,44 +30,43 @@ import com.simplemobiletools.musicplayer.databases.SongsDatabase
import com.simplemobiletools.musicplayer.extensions.*
import com.simplemobiletools.musicplayer.helpers.*
import com.simplemobiletools.musicplayer.helpers.NotificationHelper.Companion.NOTIFICATION_ID
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting.REPEAT_OFF
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting.REPEAT_PLAYLIST
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting.REPEAT_TRACK
import com.simplemobiletools.musicplayer.helpers.PlaybackSetting.STOP_AFTER_CURRENT_TRACK
import com.simplemobiletools.musicplayer.inlines.indexOfFirstOrNull
import com.simplemobiletools.musicplayer.models.Events
import com.simplemobiletools.musicplayer.models.QueueItem
import com.simplemobiletools.musicplayer.models.Track
import com.simplemobiletools.musicplayer.receivers.HeadsetPlugReceiver
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.IOException
import kotlin.math.roundToInt
class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, OnAudioFocusChangeListener {
class MusicService : Service(), MultiPlayer.PlaybackCallbacks {
companion object {
private const val PROGRESS_UPDATE_INTERVAL = 1000L
private const val MAX_CLICK_DURATION = 700L
private const val FAST_FORWARD_SKIP_MS = 10000
var mCurrTrack: Track? = null
var mNextTrack: Track? = null
var mTracks = ArrayList<Track>()
var mPlayer: MediaPlayer? = null
var mPlayer: MultiPlayer? = null
var mEqualizer: Equalizer? = null
private var mCurrTrackCover: Bitmap? = null
private var mHeadsetPlaceholder: Bitmap? = null
private var mHeadsetPlugReceiver = HeadsetPlugReceiver()
private var mProgressHandler = Handler()
private var mSleepTimer: CountDownTimer? = null
private var mAudioManager: AudioManager? = null
private var mCoverArtHeight = 0
private var mRetriedTrackCount = 0
private var mPlaybackSpeed = 1f
private var mOreoFocusHandler: OreoAudioFocusHandler? = null
private var mWasPlayingAtFocusLost = false
private var mPlayOnPrepare = true
private var mIsThirdPartyIntent = false
private var mIntentUri: Uri? = null
private var mMediaSession: MediaSessionCompat? = null
var mIsServiceInitialized = false
private var mPrevAudioFocusState = 0
private var mSetProgressOnPrepare = 0
private const val mMediaSessionActions =
PlaybackStateCompat.ACTION_STOP or
@ -85,11 +78,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
PlaybackStateCompat.ACTION_PLAY_PAUSE
fun isPlaying(): Boolean {
return try {
mPlayer?.isPlaying == true
} catch (e: Exception) {
false
}
return mPlayer != null && mPlayer!!.isPlaying()
}
}
@ -114,16 +103,12 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
override fun onCreate() {
super.onCreate()
mCoverArtHeight = resources.getDimension(R.dimen.top_art_height).toInt()
initMediaPlayerIfNeeded()
createMediaSession()
notificationHelper = NotificationHelper.createInstance(context = this, mMediaSession!!)
startForegroundAndNotify()
mAudioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (isOreoPlus()) {
mOreoFocusHandler = OreoAudioFocusHandler(application)
}
if (!isQPlus() && !hasPermission(getPermissionToRequest())) {
EventBus.getDefault().post(Events.NoStoragePermission())
}
@ -153,8 +138,6 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
return START_NOT_STICKY
}
notifyFocusGained()
val action = intent.action
when (action) {
INIT -> handleInit(intent)
@ -188,11 +171,6 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
return START_NOT_STICKY
}
private fun notifyFocusGained() {
mWasPlayingAtFocusLost = false
mPrevAudioFocusState = AUDIOFOCUS_GAIN
}
private fun initService(intent: Intent?) {
mTracks.clear()
mCurrTrack = null
@ -238,7 +216,6 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
checkTrackOrder()
}
mWasPlayingAtFocusLost = false
initMediaPlayerIfNeeded()
startForegroundAndNotify()
mIsServiceInitialized = true
@ -281,7 +258,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
checkTrackOrder()
val currentQueueItem = queuedItems.firstOrNull { it.isCurrent } ?: queuedItems.firstOrNull()
if (currentQueueItem != null) {
mCurrTrack = mTracks.firstOrNull { it.mediaStoreId == currentQueueItem.trackId } ?: return@ensureBackgroundThread
mCurrTrack = getTrackWithId(currentQueueItem.trackId) ?: return@ensureBackgroundThread
mPlayOnPrepare = false
mSetProgressOnPrepare = currentQueueItem.lastPosition
setTrack(mCurrTrack!!.mediaStoreId)
@ -350,16 +327,9 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
private fun setupTrack() {
if (mIsThirdPartyIntent) {
initMediaPlayerIfNeeded()
mPlayOnPrepare = true
try {
mPlayer!!.apply {
reset()
setDataSource(applicationContext, mIntentUri!!)
prepare()
start()
}
requestAudioFocus()
mPlayer!!.setDataSource(mIntentUri!!)
val track = mTracks.first()
mTracks.clear()
mTracks.add(track)
@ -391,23 +361,15 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
val secs = getPosition()!! / 1000
broadcastTrackProgress(secs)
}
trackStateChanged(isPlaying())
trackStateChanged()
}
}
private fun initMediaPlayerIfNeeded() {
if (mPlayer != null) {
return
if (mPlayer == null) {
mPlayer = MultiPlayer(app = applicationContext as Application, callbacks = this)
setupEqualizer()
}
mPlayer = MediaPlayer().apply {
setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
setAudioStreamType(STREAM_MUSIC)
setOnPreparedListener(this@MusicService)
setOnCompletionListener(this@MusicService)
setOnErrorListener(this@MusicService)
}
setupEqualizer()
}
private fun setupEqualizer() {
@ -417,7 +379,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
try {
val preset = config.equalizerPreset
mEqualizer = Equalizer(0, mPlayer!!.audioSessionId)
mEqualizer = Equalizer(0, mPlayer!!.getAudioSessionId())
if (!mEqualizer!!.enabled) {
mEqualizer!!.enabled = true
}
@ -503,6 +465,10 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
notificationHelper?.cancel(NOTIFICATION_ID)
}
private fun getTrackWithId(trackId: Long): Track? {
return mTracks.firstOrNull { it.mediaStoreId == trackId }
}
private fun getNextQueueItem(): QueueItem {
return when (mTracks.size) {
0 -> QueueItem.from(-1L)
@ -569,7 +535,6 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
setupNextTrack()
} else {
mPlayer!!.start()
requestAudioFocus()
}
setupEqualizer()
@ -615,18 +580,11 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
}
initMediaPlayerIfNeeded()
mPlayer?.reset() ?: return
mCurrTrack = mTracks.firstOrNull { it.mediaStoreId == wantedTrackId } ?: return
mCurrTrack = getTrackWithId(wantedTrackId) ?: return
try {
val trackUri = if (mCurrTrack!!.mediaStoreId == 0L) {
Uri.fromFile(File(mCurrTrack!!.path))
} else {
ContentUris.withAppendedId(Audio.Media.EXTERNAL_CONTENT_URI, mCurrTrack!!.mediaStoreId)
}
mPlayer!!.setDataSource(applicationContext, trackUri)
mPlayer!!.prepareAsync()
val trackUri = mCurrTrack!!.getUri()
mPlayer!!.setDataSource(trackUri)
trackChanged()
} catch (e: IOException) {
if (mCurrTrack != null) {
@ -644,9 +602,45 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
}
}
private fun maybePrepareNext() {
val isGapLess = config.gapLessPlayback
val playbackSetting = config.playbackSetting
val isPlayerInitialized = mPlayer != null && mPlayer!!.isInitialized
val playOnce = playbackSetting == STOP_AFTER_CURRENT_TRACK
if (!isGapLess || !isPlayerInitialized || playOnce) {
return
}
if (playbackSetting == REPEAT_OFF && isEndOfPlaylist()) {
mPlayer!!.setNextDataSource(null)
trackChanged()
} else if (playbackSetting == REPEAT_TRACK) {
prepareNext(nextTrack = mCurrTrack)
} else {
prepareNext()
}
}
private fun prepareNext(nextTrack: Track? = null) {
mNextTrack = if (nextTrack != null) {
nextTrack
} else {
val queueItem = getNextQueueItem()
getTrackWithId(queueItem.trackId) ?: return
}
try {
val trackUri = mNextTrack!!.getUri()
mPlayer!!.setNextDataSource(trackUri) {
trackChanged()
}
} catch (ignored: Exception) {
}
}
private fun handleEmptyPlaylist() {
mPlayer?.pause()
abandonAudioFocus()
mCurrTrack = null
trackChanged()
trackStateChanged(false)
@ -658,7 +652,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
override fun onBind(intent: Intent?): IBinder? = null
override fun onCompletion(mp: MediaPlayer) {
override fun onTrackEnded() {
if (!config.autoplay) {
return
}
@ -666,13 +660,13 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
val playbackSetting = config.playbackSetting
mPlayOnPrepare = when (playbackSetting) {
PlaybackSetting.REPEAT_OFF -> !isEndOfPlaylist()
PlaybackSetting.REPEAT_PLAYLIST, PlaybackSetting.REPEAT_TRACK -> true
PlaybackSetting.STOP_AFTER_CURRENT_TRACK -> false
REPEAT_OFF -> !isEndOfPlaylist()
REPEAT_PLAYLIST, REPEAT_TRACK -> true
STOP_AFTER_CURRENT_TRACK -> false
}
when (config.playbackSetting) {
PlaybackSetting.REPEAT_OFF -> {
when (playbackSetting) {
REPEAT_OFF -> {
if (isEndOfPlaylist()) {
broadcastTrackProgress(0)
setupNextTrack()
@ -680,42 +674,41 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
setupNextTrack()
}
}
PlaybackSetting.REPEAT_PLAYLIST -> setupNextTrack()
PlaybackSetting.REPEAT_TRACK -> restartTrack()
PlaybackSetting.STOP_AFTER_CURRENT_TRACK -> {
REPEAT_PLAYLIST -> setupNextTrack()
REPEAT_TRACK -> restartTrack()
STOP_AFTER_CURRENT_TRACK -> {
broadcastTrackProgress(0)
restartTrack()
}
}
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
mPlayer!!.reset()
return false
override fun onTrackWentToNext() {
mCurrTrack = mNextTrack
maybePrepareNext()
trackStateChanged()
}
override fun onPrepared(mp: MediaPlayer) {
override fun onPrepared() {
mRetriedTrackCount = 0
if (mPlayOnPrepare) {
mp.start()
requestAudioFocus()
try {
mp.playbackParams = mp.playbackParams.setSpeed(config.playbackSpeed)
} catch (ignored: Exception) {
}
mPlayer!!.start()
if (mIsThirdPartyIntent) {
trackChanged()
}
}
if (mSetProgressOnPrepare > 0) {
mp.seekTo(mSetProgressOnPrepare)
mPlayer!!.seek(mSetProgressOnPrepare)
broadcastTrackProgress(mSetProgressOnPrepare / 1000)
mSetProgressOnPrepare = 0
}
trackStateChanged(isPlaying())
maybePrepareNext()
trackStateChanged()
}
override fun onPlayStateChanged() {
trackStateChanged()
}
private fun trackChanged() {
@ -781,9 +774,9 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
private fun setPlaybackSpeed() {
if (mPlayer != null) {
mPlaybackSpeed = config.playbackSpeed
if (mPlayer!!.isPlaying) {
if (isPlaying()) {
try {
mPlayer!!.playbackParams = mPlayer!!.playbackParams.setSpeed(config.playbackSpeed)
mPlayer!!.setPlaybackSpeed(mPlaybackSpeed)
} catch (ignored: Exception) {
}
}
@ -829,7 +822,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
}
private fun getPosition(): Int? {
return mPlayer?.currentPosition
return mPlayer?.position()
}
// do not just return the album cover, but also a boolean to indicate if it a real cover, or just the placeholder
@ -923,7 +916,6 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
mCurrTrack = null
}
mPlayer?.stop()
mPlayer?.release()
mPlayer = null
@ -934,94 +926,27 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
stopSelf()
mIsThirdPartyIntent = false
mIsServiceInitialized = false
abandonAudioFocus()
}
private fun requestAudioFocus() {
if (isOreoPlus()) {
mOreoFocusHandler?.requestAudioFocus(this)
} else {
mAudioManager?.requestAudioFocus(this, STREAM_MUSIC, AUDIOFOCUS_GAIN)
}
}
private fun abandonAudioFocus() {
if (isOreoPlus()) {
mOreoFocusHandler?.abandonAudioFocus()
} else {
mAudioManager?.abandonAudioFocus(this)
}
}
override fun onAudioFocusChange(focusChange: Int) {
when (focusChange) {
AUDIOFOCUS_GAIN -> audioFocusGained()
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> duckAudio()
AUDIOFOCUS_LOSS, AUDIOFOCUS_LOSS_TRANSIENT -> audioFocusLost()
}
mPrevAudioFocusState = focusChange
}
private fun audioFocusLost() {
if (isPlaying()) {
mWasPlayingAtFocusLost = true
pauseTrack()
} else {
mWasPlayingAtFocusLost = false
}
}
private fun audioFocusGained() {
if (mWasPlayingAtFocusLost) {
if (mPrevAudioFocusState == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
unduckAudio()
} else {
resumeTrack()
}
}
mWasPlayingAtFocusLost = false
}
private fun duckAudio() {
mPlayer?.setVolume(0.3f, 0.3f)
mWasPlayingAtFocusLost = isPlaying()
}
private fun unduckAudio() {
mPlayer?.setVolume(1f, 1f)
}
fun updateProgress(progress: Int) {
mPlayer!!.seekTo(progress * 1000)
mPlayer!!.seek(progress * 1000)
saveTrackProgress()
resumeTrack()
}
private fun trackStateChanged(isPlaying: Boolean, notify: Boolean = true) {
private fun trackStateChanged(isPlaying: Boolean = isPlaying(), notify: Boolean = true) {
handleProgressHandler(isPlaying)
broadcastTrackStateChange(isPlaying)
if (notify) {
startForegroundAndNotify()
}
if (isPlaying) {
val filter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
filter.addAction(ACTION_AUDIO_BECOMING_NOISY)
registerReceiver(mHeadsetPlugReceiver, filter)
} else {
try {
unregisterReceiver(mHeadsetPlugReceiver)
} catch (ignored: IllegalArgumentException) {
}
}
}
private fun handleProgressHandler(isPlaying: Boolean) {
if (isPlaying) {
mProgressHandler.post(object : Runnable {
override fun run() {
if (mPlayer?.isPlaying == true) {
if (isPlaying()) {
val secs = getPosition()!! / 1000
broadcastTrackProgress(secs)
}
@ -1037,7 +962,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
private fun skip(forward: Boolean) {
val curr = getPosition() ?: return
val newProgress = if (forward) curr + FAST_FORWARD_SKIP_MS else curr - FAST_FORWARD_SKIP_MS
mPlayer!!.seekTo(newProgress)
mPlayer!!.seek(newProgress)
resumeTrack()
}
@ -1066,7 +991,7 @@ class MusicService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
// used at updating the widget at create or resize
private fun broadcastPlayerStatus() {
broadcastTrackStateChange(mPlayer?.isPlaying ?: false)
broadcastTrackStateChange(isPlaying())
broadcastTrackChange()
broadcastNextTrackChange()
broadcastTrackProgress((getPosition() ?: 0) / 1000)