2013-04-06 21:47:24 +02:00
|
|
|
/*
|
2021-05-21 22:35:22 +02:00
|
|
|
* MediaPlayerController.kt
|
|
|
|
* Copyright (C) 2009-2021 Ultrasonic developers
|
|
|
|
*
|
|
|
|
* Distributed under terms of the GNU GPLv3 license.
|
2013-04-06 21:47:24 +02:00
|
|
|
*/
|
2021-05-21 22:35:22 +02:00
|
|
|
package org.moire.ultrasonic.service
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
import android.content.ComponentName
|
|
|
|
import android.content.Context
|
2021-05-21 22:35:22 +02:00
|
|
|
import android.content.Intent
|
2022-04-03 23:57:50 +02:00
|
|
|
import androidx.core.net.toUri
|
|
|
|
import androidx.media3.common.MediaItem
|
|
|
|
import androidx.media3.common.MediaMetadata
|
|
|
|
import androidx.media3.common.Player
|
|
|
|
import androidx.media3.common.Timeline
|
|
|
|
import androidx.media3.session.MediaController
|
|
|
|
import androidx.media3.session.SessionToken
|
|
|
|
import com.google.common.util.concurrent.MoreExecutors
|
2022-04-08 18:08:56 +02:00
|
|
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
2022-04-22 21:03:57 +02:00
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.launch
|
2021-05-27 12:15:56 +02:00
|
|
|
import org.koin.core.component.KoinComponent
|
|
|
|
import org.koin.core.component.inject
|
2021-05-21 22:35:22 +02:00
|
|
|
import org.moire.ultrasonic.app.UApp
|
|
|
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
2022-03-27 14:57:07 +02:00
|
|
|
import org.moire.ultrasonic.domain.Track
|
2022-04-03 23:57:50 +02:00
|
|
|
import org.moire.ultrasonic.playback.LegacyPlaylistManager
|
|
|
|
import org.moire.ultrasonic.playback.PlaybackService
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
|
2021-05-21 22:35:22 +02:00
|
|
|
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
2022-04-03 23:57:50 +02:00
|
|
|
import org.moire.ultrasonic.util.FileUtil
|
2021-09-24 18:20:53 +02:00
|
|
|
import org.moire.ultrasonic.util.Settings
|
2021-05-21 22:35:22 +02:00
|
|
|
import timber.log.Timber
|
2021-05-21 18:50:21 +02:00
|
|
|
|
2013-04-06 21:47:24 +02:00
|
|
|
/**
|
2021-05-21 18:50:21 +02:00
|
|
|
* The implementation of the Media Player Controller.
|
|
|
|
* This class contains everything that is necessary for the Application UI
|
2020-06-26 15:18:14 +02:00
|
|
|
* to control the Media Player implementation.
|
2013-04-06 21:47:24 +02:00
|
|
|
*/
|
2021-05-21 22:35:22 +02:00
|
|
|
@Suppress("TooManyFunctions")
|
|
|
|
class MediaPlayerController(
|
2021-10-23 16:37:19 +02:00
|
|
|
private val playbackStateSerializer: PlaybackStateSerializer,
|
2021-05-21 22:35:22 +02:00
|
|
|
private val externalStorageMonitor: ExternalStorageMonitor,
|
|
|
|
private val downloader: Downloader,
|
2022-04-03 23:57:50 +02:00
|
|
|
private val legacyPlaylistManager: LegacyPlaylistManager,
|
|
|
|
val context: Context
|
2021-05-27 12:15:56 +02:00
|
|
|
) : KoinComponent {
|
2021-05-21 22:35:22 +02:00
|
|
|
|
|
|
|
private var created = false
|
|
|
|
var suggestedPlaylistName: String? = null
|
|
|
|
var keepScreenOn = false
|
|
|
|
var showVisualization = false
|
|
|
|
private var autoPlayStart = false
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
private val scrobbler = Scrobbler()
|
|
|
|
|
2021-05-27 12:15:56 +02:00
|
|
|
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
|
|
|
|
private val activeServerProvider: ActiveServerProvider by inject()
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-08 18:08:56 +02:00
|
|
|
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
|
|
|
|
|
2022-04-22 21:03:57 +02:00
|
|
|
private var mainScope = CoroutineScope(Dispatchers.Main)
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
private var sessionToken =
|
|
|
|
SessionToken(context, ComponentName(context, PlaybackService::class.java))
|
|
|
|
|
|
|
|
private var mediaControllerFuture = MediaController.Builder(
|
|
|
|
context,
|
|
|
|
sessionToken
|
|
|
|
).buildAsync()
|
|
|
|
|
|
|
|
var controller: MediaController? = null
|
|
|
|
|
2022-04-20 21:40:22 +02:00
|
|
|
fun onCreate(onCreated: () -> Unit) {
|
2021-05-21 22:35:22 +02:00
|
|
|
if (created) return
|
|
|
|
externalStorageMonitor.onCreate { reset() }
|
|
|
|
isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault
|
2022-04-03 23:57:50 +02:00
|
|
|
|
|
|
|
mediaControllerFuture.addListener({
|
|
|
|
controller = mediaControllerFuture.get()
|
|
|
|
|
2022-04-20 21:40:22 +02:00
|
|
|
Timber.i("MediaController Instance received")
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.addListener(object : Player.Listener {
|
2022-04-18 09:56:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Log all events
|
|
|
|
*/
|
2022-04-20 21:40:22 +02:00
|
|
|
override fun onEvents(player: Player, events: Player.Events) {
|
|
|
|
for (i in 0 until events.size()) {
|
|
|
|
Timber.i("Media3 Event, event type: %s", events[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
/*
|
|
|
|
* This will be called everytime the playlist has changed.
|
2022-04-22 21:03:57 +02:00
|
|
|
* We run the event through RxBus in order to throttle them
|
2022-04-03 23:57:50 +02:00
|
|
|
*/
|
|
|
|
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
|
|
|
legacyPlaylistManager.rebuildPlaylist(controller!!)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
|
|
playerStateChangedHandler()
|
|
|
|
publishPlaybackState()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
|
|
playerStateChangedHandler()
|
|
|
|
publishPlaybackState()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
2022-04-05 21:56:13 +02:00
|
|
|
onTrackCompleted()
|
2022-04-03 23:57:50 +02:00
|
|
|
legacyPlaylistManager.updateCurrentPlaying(mediaItem)
|
|
|
|
publishPlaybackState()
|
|
|
|
}
|
2022-04-21 10:45:37 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If the same item is contained in a playlist multiple times directly after each
|
|
|
|
* other, Media3 on emits a PositionDiscontinuity event.
|
|
|
|
* Can be removed if https://github.com/androidx/media/issues/68 is fixed.
|
|
|
|
*/
|
|
|
|
override fun onPositionDiscontinuity(
|
|
|
|
oldPosition: Player.PositionInfo,
|
|
|
|
newPosition: Player.PositionInfo,
|
|
|
|
reason: Int
|
|
|
|
) {
|
|
|
|
playerStateChangedHandler()
|
|
|
|
publishPlaybackState()
|
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
})
|
|
|
|
|
2022-04-20 21:40:22 +02:00
|
|
|
onCreated()
|
|
|
|
|
|
|
|
Timber.i("MediaPlayerController creation complete")
|
|
|
|
|
2022-04-04 17:59:12 +02:00
|
|
|
// controller?.play()
|
2022-04-03 23:57:50 +02:00
|
|
|
}, MoreExecutors.directExecutor())
|
|
|
|
|
2022-04-08 18:08:56 +02:00
|
|
|
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
|
|
|
|
// Update the Jukebox state when the active server has changed
|
|
|
|
isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault
|
|
|
|
}
|
|
|
|
|
2022-04-22 21:03:57 +02:00
|
|
|
rxBusSubscription += RxBus.throttledPlaylistObservable.subscribe {
|
|
|
|
// Even though Rx should launch on the main thread it doesn't always :(
|
|
|
|
mainScope.launch {
|
|
|
|
serializeCurrentSession()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rxBusSubscription += RxBus.throttledPlayerStateObservable.subscribe {
|
|
|
|
// Even though Rx should launch on the main thread it doesn't always :(
|
|
|
|
mainScope.launch {
|
|
|
|
serializeCurrentSession()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
created = true
|
2022-04-20 21:40:22 +02:00
|
|
|
Timber.i("MediaPlayerController started")
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
private fun playerStateChangedHandler() {
|
|
|
|
|
|
|
|
val currentPlaying = legacyPlaylistManager.currentPlaying
|
|
|
|
|
2022-04-18 09:26:13 +02:00
|
|
|
when (playbackState) {
|
|
|
|
Player.STATE_READY -> {
|
|
|
|
if (isPlaying) {
|
|
|
|
scrobbler.scrobble(currentPlaying, false)
|
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
}
|
2022-04-18 09:26:13 +02:00
|
|
|
Player.STATE_ENDED -> {
|
2022-04-03 23:57:50 +02:00
|
|
|
scrobbler.scrobble(currentPlaying, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-04 17:59:12 +02:00
|
|
|
// Update widget
|
2022-04-03 23:57:50 +02:00
|
|
|
if (currentPlaying != null) {
|
2022-04-18 09:26:13 +02:00
|
|
|
updateWidget(currentPlaying.track)
|
2022-04-03 23:57:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-05 21:56:13 +02:00
|
|
|
private fun onTrackCompleted() {
|
2022-04-04 18:18:52 +02:00
|
|
|
// This method is called before we update the currentPlaying,
|
|
|
|
// so in fact currentPlaying will refer to the track that has just finished.
|
|
|
|
if (legacyPlaylistManager.currentPlaying != null) {
|
|
|
|
val song = legacyPlaylistManager.currentPlaying!!.track
|
|
|
|
if (song.bookmarkPosition > 0 && Settings.shouldClearBookmark) {
|
|
|
|
val musicService = getMusicService()
|
|
|
|
try {
|
|
|
|
musicService.deleteBookmark(song.id)
|
|
|
|
} catch (ignored: Exception) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
private fun publishPlaybackState() {
|
2022-04-18 09:26:13 +02:00
|
|
|
val newState = RxBus.StateWithTrack(
|
|
|
|
track = legacyPlaylistManager.currentPlaying,
|
|
|
|
index = currentMediaItemIndex,
|
|
|
|
isPlaying = isPlaying,
|
|
|
|
state = playbackState
|
2022-04-03 23:57:50 +02:00
|
|
|
)
|
2022-04-18 09:26:13 +02:00
|
|
|
RxBus.playerStatePublisher.onNext(newState)
|
|
|
|
Timber.i("New PlaybackState: %s", newState)
|
2022-04-03 23:57:50 +02:00
|
|
|
}
|
|
|
|
|
2022-04-18 09:26:13 +02:00
|
|
|
private fun updateWidget(song: Track?) {
|
2022-04-03 23:57:50 +02:00
|
|
|
val context = UApp.applicationContext()
|
|
|
|
|
2022-04-18 09:26:13 +02:00
|
|
|
UltrasonicAppWidgetProvider4X1.instance?.notifyChange(context, song, isPlaying, false)
|
|
|
|
UltrasonicAppWidgetProvider4X2.instance?.notifyChange(context, song, isPlaying, true)
|
|
|
|
UltrasonicAppWidgetProvider4X3.instance?.notifyChange(context, song, isPlaying, false)
|
|
|
|
UltrasonicAppWidgetProvider4X4.instance?.notifyChange(context, song, isPlaying, false)
|
2022-04-03 23:57:50 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
fun onDestroy() {
|
|
|
|
if (!created) return
|
|
|
|
val context = UApp.applicationContext()
|
|
|
|
externalStorageMonitor.onDestroy()
|
2022-04-03 23:57:50 +02:00
|
|
|
context.stopService(Intent(context, DownloadService::class.java))
|
|
|
|
legacyPlaylistManager.onDestroy()
|
2021-05-21 22:35:22 +02:00
|
|
|
downloader.onDestroy()
|
|
|
|
created = false
|
|
|
|
Timber.i("MediaPlayerController destroyed")
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun restore(
|
2022-04-18 09:56:36 +02:00
|
|
|
songs: List<Track>,
|
2021-05-21 22:35:22 +02:00
|
|
|
currentPlayingIndex: Int,
|
|
|
|
currentPlayingPosition: Int,
|
|
|
|
autoPlay: Boolean,
|
|
|
|
newPlaylist: Boolean
|
|
|
|
) {
|
2022-04-18 09:56:36 +02:00
|
|
|
val insertionMode = if (newPlaylist) InsertionMode.CLEAR
|
|
|
|
else InsertionMode.APPEND
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
addToPlaylist(
|
2021-05-21 22:35:22 +02:00
|
|
|
songs,
|
2022-04-03 23:57:50 +02:00
|
|
|
cachePermanently = false,
|
2021-05-21 22:35:22 +02:00
|
|
|
autoPlay = false,
|
|
|
|
shuffle = false,
|
2022-04-18 09:56:36 +02:00
|
|
|
insertionMode = insertionMode
|
2021-05-21 22:35:22 +02:00
|
|
|
)
|
2022-04-03 23:57:50 +02:00
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
if (currentPlayingIndex != -1) {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.skip(
|
|
|
|
currentPlayingIndex,
|
|
|
|
currentPlayingPosition / 1000
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
seekTo(currentPlayingIndex, currentPlayingPosition)
|
|
|
|
}
|
|
|
|
|
2022-04-20 21:40:22 +02:00
|
|
|
prepare()
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
if (autoPlay) {
|
|
|
|
play()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
|
|
|
|
autoPlayStart = false
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun play(index: Int) {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.seekTo(index, 0L)
|
|
|
|
controller?.play()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun play() {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.start()
|
|
|
|
} else {
|
2022-05-30 16:24:24 +02:00
|
|
|
controller?.prepare()
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.play()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun prepare() {
|
|
|
|
controller?.prepare()
|
|
|
|
}
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
@Synchronized
|
|
|
|
fun resumeOrPlay() {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.play()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun togglePlayPause() {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (playbackState == Player.STATE_IDLE) autoPlayStart = true
|
2022-04-08 21:24:04 +02:00
|
|
|
if (controller?.isPlaying == true) {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.pause()
|
|
|
|
} else {
|
|
|
|
controller?.play()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2022-04-03 23:57:50 +02:00
|
|
|
fun seekTo(position: Int) {
|
2022-04-16 00:04:52 +02:00
|
|
|
Timber.i("SeekTo: %s", position)
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.seekTo(position.toLong())
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2022-04-03 23:57:50 +02:00
|
|
|
fun seekTo(index: Int, position: Int) {
|
2022-04-16 00:04:52 +02:00
|
|
|
Timber.i("SeekTo: %s %s", index, position)
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.seekTo(index, position.toLong())
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun pause() {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.stop()
|
|
|
|
} else {
|
|
|
|
controller?.pause()
|
|
|
|
}
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun stop() {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.stop()
|
|
|
|
} else {
|
|
|
|
controller?.stop()
|
|
|
|
}
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun addToPlaylist(
|
|
|
|
songs: List<Track>,
|
|
|
|
cachePermanently: Boolean,
|
|
|
|
autoPlay: Boolean,
|
|
|
|
shuffle: Boolean,
|
|
|
|
insertionMode: InsertionMode
|
|
|
|
) {
|
|
|
|
var insertAt = 0
|
|
|
|
|
|
|
|
when (insertionMode) {
|
|
|
|
InsertionMode.CLEAR -> clear()
|
|
|
|
InsertionMode.APPEND -> insertAt = mediaItemCount
|
2022-04-05 10:10:24 +02:00
|
|
|
InsertionMode.AFTER_CURRENT -> insertAt = currentMediaItemIndex + 1
|
2022-04-03 23:57:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
val mediaItems: List<MediaItem> = songs.map {
|
|
|
|
val downloadFile = downloader.getDownloadFileForSong(it)
|
|
|
|
if (cachePermanently) downloadFile.shouldSave = true
|
|
|
|
val result = it.toMediaItem()
|
|
|
|
legacyPlaylistManager.addToCache(result, downloader.getDownloadFileForSong(it))
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
|
|
|
controller?.addMediaItems(insertAt, mediaItems)
|
|
|
|
|
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
|
|
|
|
|
|
|
if (shuffle) isShufflePlayEnabled = true
|
|
|
|
|
2022-04-21 10:12:05 +02:00
|
|
|
prepare()
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
if (autoPlay) {
|
|
|
|
play(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2022-03-27 14:57:07 +02:00
|
|
|
fun downloadBackground(songs: List<Track?>?, save: Boolean) {
|
2021-08-27 23:53:31 +02:00
|
|
|
if (songs == null) return
|
|
|
|
val filteredSongs = songs.filterNotNull()
|
|
|
|
downloader.downloadBackground(filteredSongs, save)
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fun stopJukeboxService() {
|
|
|
|
jukeboxMediaPlayer.stopJukeboxService()
|
|
|
|
}
|
|
|
|
|
|
|
|
@set:Synchronized
|
|
|
|
var isShufflePlayEnabled: Boolean
|
2022-04-03 23:57:50 +02:00
|
|
|
get() = controller?.shuffleModeEnabled == true
|
2021-05-21 22:35:22 +02:00
|
|
|
set(enabled) {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.shuffleModeEnabled = enabled
|
2021-05-21 22:35:22 +02:00
|
|
|
if (enabled) {
|
|
|
|
downloader.checkDownloads()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2022-04-05 21:41:27 +02:00
|
|
|
fun toggleShuffle(): Boolean {
|
2022-04-03 23:57:50 +02:00
|
|
|
isShufflePlayEnabled = !isShufflePlayEnabled
|
2022-04-05 21:41:27 +02:00
|
|
|
return isShufflePlayEnabled
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
val bufferedPercentage: Int
|
|
|
|
get() = controller?.bufferedPercentage ?: 0
|
|
|
|
|
2021-11-28 18:26:44 +01:00
|
|
|
@Synchronized
|
|
|
|
fun moveItemInPlaylist(oldPos: Int, newPos: Int) {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.moveMediaItem(oldPos, newPos)
|
2021-11-28 18:26:44 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
@set:Synchronized
|
2022-04-03 23:57:50 +02:00
|
|
|
var repeatMode: Int
|
|
|
|
get() = controller?.repeatMode ?: 0
|
|
|
|
set(newMode) {
|
|
|
|
controller?.repeatMode = newMode
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2021-11-01 14:14:12 +01:00
|
|
|
@JvmOverloads
|
|
|
|
fun clear(serialize: Boolean = true) {
|
2022-04-03 23:57:50 +02:00
|
|
|
|
|
|
|
controller?.clearMediaItems()
|
|
|
|
|
|
|
|
if (controller != null && serialize) {
|
|
|
|
playbackStateSerializer.serialize(
|
|
|
|
listOf(), -1, 0
|
|
|
|
)
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
|
|
|
}
|
|
|
|
|
2021-12-20 13:15:45 +01:00
|
|
|
@Synchronized
|
|
|
|
fun clearCaches() {
|
|
|
|
downloader.clearDownloadFileCache()
|
|
|
|
}
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
@Synchronized
|
|
|
|
fun clearIncomplete() {
|
|
|
|
reset()
|
2021-11-09 18:08:26 +01:00
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
downloader.clearActiveDownloads()
|
|
|
|
downloader.clearBackground()
|
2021-05-21 22:35:22 +02:00
|
|
|
|
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2022-04-05 21:41:27 +02:00
|
|
|
fun removeFromPlaylist(position: Int) {
|
|
|
|
|
|
|
|
controller?.removeMediaItem(position)
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-20 21:40:22 +02:00
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
private fun serializeCurrentSession() {
|
2021-10-23 16:37:19 +02:00
|
|
|
playbackStateSerializer.serialize(
|
2022-04-03 23:57:50 +02:00
|
|
|
legacyPlaylistManager.playlist,
|
2022-04-04 18:18:52 +02:00
|
|
|
currentMediaItemIndex,
|
2021-05-21 22:35:22 +02:00
|
|
|
playerPosition
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2021-08-27 23:53:31 +02:00
|
|
|
// TODO: Make it require not null
|
2022-03-27 14:57:07 +02:00
|
|
|
fun delete(songs: List<Track?>) {
|
2021-08-27 23:53:31 +02:00
|
|
|
for (song in songs.filterNotNull()) {
|
2021-05-21 22:35:22 +02:00
|
|
|
downloader.getDownloadFileForSong(song).delete()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
2021-08-27 23:53:31 +02:00
|
|
|
// TODO: Make it require not null
|
2022-03-27 14:57:07 +02:00
|
|
|
fun unpin(songs: List<Track?>) {
|
2021-08-27 23:53:31 +02:00
|
|
|
for (song in songs.filterNotNull()) {
|
2021-05-21 22:35:22 +02:00
|
|
|
downloader.getDownloadFileForSong(song).unpin()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun previous() {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.seekToPrevious()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
operator fun next() {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.seekToNext()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
|
|
fun reset() {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.clearMediaItems()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@get:Synchronized
|
|
|
|
val playerPosition: Int
|
|
|
|
get() {
|
2022-04-03 23:57:50 +02:00
|
|
|
return if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.positionSeconds * 1000
|
|
|
|
} else {
|
|
|
|
controller?.currentPosition?.toInt() ?: 0
|
|
|
|
}
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@get:Synchronized
|
|
|
|
val playerDuration: Int
|
|
|
|
get() {
|
2022-04-03 23:57:50 +02:00
|
|
|
return controller?.duration?.toInt() ?: return 0
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
val playbackState: Int
|
|
|
|
get() = controller?.playbackState ?: 0
|
|
|
|
|
|
|
|
val isPlaying: Boolean
|
|
|
|
get() = controller?.isPlaying ?: false
|
2021-05-21 22:35:22 +02:00
|
|
|
|
|
|
|
@set:Synchronized
|
|
|
|
var isJukeboxEnabled: Boolean
|
|
|
|
get() = jukeboxMediaPlayer.isEnabled
|
|
|
|
set(jukeboxEnabled) {
|
|
|
|
jukeboxMediaPlayer.isEnabled = jukeboxEnabled
|
2022-04-03 23:57:50 +02:00
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
if (jukeboxEnabled) {
|
|
|
|
jukeboxMediaPlayer.startJukeboxService()
|
|
|
|
reset()
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
// Cancel current downloads
|
2021-08-27 23:53:31 +02:00
|
|
|
downloader.clearActiveDownloads()
|
2021-05-21 22:35:22 +02:00
|
|
|
} else {
|
|
|
|
jukeboxMediaPlayer.stopJukeboxService()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 19:07:18 +01:00
|
|
|
/**
|
|
|
|
* This function calls the music service directly and
|
|
|
|
* therefore can't be called from the main thread
|
|
|
|
*/
|
2021-05-21 22:35:22 +02:00
|
|
|
val isJukeboxAvailable: Boolean
|
|
|
|
get() {
|
|
|
|
try {
|
|
|
|
val username = activeServerProvider.getActiveServer().userName
|
2021-05-26 23:21:56 +02:00
|
|
|
return getMusicService().getUser(username).jukeboxRole
|
2021-10-13 20:49:35 +02:00
|
|
|
} catch (all: Exception) {
|
|
|
|
Timber.w(all, "Error getting user information")
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fun adjustJukeboxVolume(up: Boolean) {
|
|
|
|
jukeboxMediaPlayer.adjustVolume(up)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setVolume(volume: Float) {
|
2022-04-03 23:57:50 +02:00
|
|
|
controller?.volume = volume
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fun toggleSongStarred() {
|
2022-04-03 23:57:50 +02:00
|
|
|
if (legacyPlaylistManager.currentPlaying == null) return
|
|
|
|
val song = legacyPlaylistManager.currentPlaying!!.track
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2021-06-16 20:27:22 +02:00
|
|
|
Thread {
|
|
|
|
val musicService = getMusicService()
|
|
|
|
try {
|
|
|
|
if (song.starred) {
|
|
|
|
musicService.unstar(song.id, null, null)
|
|
|
|
} else {
|
|
|
|
musicService.star(song.id, null, null)
|
|
|
|
}
|
2021-06-19 23:25:02 +02:00
|
|
|
} catch (all: Exception) {
|
|
|
|
Timber.e(all)
|
2021-06-16 20:27:22 +02:00
|
|
|
}
|
|
|
|
}.start()
|
|
|
|
|
2021-05-21 22:35:22 +02:00
|
|
|
// Trigger an update
|
2022-04-03 23:57:50 +02:00
|
|
|
// TODO Update Metadata of MediaItem...
|
2022-04-04 17:59:12 +02:00
|
|
|
// localMediaPlayer.setCurrentPlaying(localMediaPlayer.currentPlaying)
|
2021-05-21 22:35:22 +02:00
|
|
|
song.starred = !song.starred
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions
|
|
|
|
fun setSongRating(rating: Int) {
|
2021-12-07 13:10:15 +01:00
|
|
|
if (!Settings.useFiveStarRating) return
|
2022-04-03 23:57:50 +02:00
|
|
|
if (legacyPlaylistManager.currentPlaying == null) return
|
|
|
|
val song = legacyPlaylistManager.currentPlaying!!.track
|
2021-05-21 22:35:22 +02:00
|
|
|
song.userRating = rating
|
|
|
|
Thread {
|
|
|
|
try {
|
|
|
|
getMusicService().setRating(song.id, rating)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Timber.e(e)
|
|
|
|
}
|
|
|
|
}.start()
|
2021-11-02 17:45:01 +01:00
|
|
|
// TODO this would be better handled with a Rx command
|
2022-04-04 17:59:12 +02:00
|
|
|
// updateNotification()
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
val currentMediaItem: MediaItem?
|
|
|
|
get() = controller?.currentMediaItem
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
val currentMediaItemIndex: Int
|
|
|
|
get() = controller?.currentMediaItemIndex ?: -1
|
|
|
|
|
|
|
|
@Deprecated("Use currentMediaItem")
|
|
|
|
val currentPlayingLegacy: DownloadFile?
|
|
|
|
get() = legacyPlaylistManager.currentPlaying
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
val mediaItemCount: Int
|
|
|
|
get() = controller?.mediaItemCount ?: 0
|
|
|
|
|
|
|
|
@Deprecated("Use mediaItemCount")
|
|
|
|
val playlistSize: Int
|
|
|
|
get() = legacyPlaylistManager.playlist.size
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
@Deprecated("Use native APIs")
|
2021-05-21 22:35:22 +02:00
|
|
|
val playList: List<DownloadFile>
|
2022-04-03 23:57:50 +02:00
|
|
|
get() = legacyPlaylistManager.playlist
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-04-03 23:57:50 +02:00
|
|
|
@Deprecated("Use timeline")
|
2021-05-21 22:35:22 +02:00
|
|
|
val playListDuration: Long
|
2022-04-03 23:57:50 +02:00
|
|
|
get() = legacyPlaylistManager.playlistDuration
|
2021-05-21 22:35:22 +02:00
|
|
|
|
2022-03-27 14:57:07 +02:00
|
|
|
fun getDownloadFileForSong(song: Track): DownloadFile {
|
2021-05-21 22:35:22 +02:00
|
|
|
return downloader.getDownloadFileForSong(song)
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
2022-04-20 21:40:22 +02:00
|
|
|
Timber.i("MediaPlayerController instance initiated")
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
|
|
|
|
enum class InsertionMode {
|
|
|
|
CLEAR, APPEND, AFTER_CURRENT
|
|
|
|
}
|
2021-05-21 22:35:22 +02:00
|
|
|
}
|
2022-04-03 23:57:50 +02:00
|
|
|
|
|
|
|
fun Track.toMediaItem(): MediaItem {
|
|
|
|
|
|
|
|
val filePath = FileUtil.getSongFile(this)
|
|
|
|
val bitrate = Settings.maxBitRate
|
|
|
|
val uri = "$id|$bitrate|$filePath"
|
|
|
|
|
|
|
|
val metadata = MediaMetadata.Builder()
|
|
|
|
metadata.setTitle(title)
|
|
|
|
.setArtist(artist)
|
|
|
|
.setAlbumTitle(album)
|
|
|
|
.setMediaUri(uri.toUri())
|
|
|
|
.setAlbumArtist(artist)
|
|
|
|
|
|
|
|
val mediaItem = MediaItem.Builder()
|
|
|
|
.setUri(uri)
|
|
|
|
.setMediaId(id)
|
|
|
|
.setMediaMetadata(metadata.build())
|
|
|
|
|
|
|
|
return mediaItem.build()
|
2022-04-04 17:59:12 +02:00
|
|
|
}
|