Merge branch 'fixShuffle' into 'develop'

Fix shuffle

Closes #876 and #877

See merge request ultrasonic/ultrasonic!966
This commit is contained in:
birdbird 2023-04-20 11:25:25 +00:00
commit 08d3618eb3
10 changed files with 225 additions and 91 deletions

View File

@ -34,7 +34,7 @@ android {
minifyEnabled false
multiDexEnabled true
testCoverageEnabled true
applicationIdSuffix ".debug"
applicationIdSuffix '.debug'
}
}

View File

@ -62,8 +62,6 @@ class TrackViewBinder(
diffAdapter.isSelected(item.longId)
)
// Timber.v("Setting listeners")
holder.itemView.setOnLongClickListener {
if (onContextMenuClick != null) {
val popup = createContextMenu(holder.itemView, track)
@ -116,8 +114,6 @@ class TrackViewBinder(
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
}
// Timber.v("Setting listeners done")
}
override fun onViewRecycled(holder: TrackViewHolder) {

View File

@ -131,7 +131,7 @@ class TrackViewHolder(val view: View) :
// Create new Disposable for the new Subscriptions
rxBusSubscription = CompositeDisposable()
rxBusSubscription!! += RxBus.playerStateObservable.subscribe {
setPlayIcon(it.index == bindingAdapterPosition && it.track?.id == song.id)
setPlayIcon(it.track?.id == song.id && it.index == bindingAdapterPosition)
}
rxBusSubscription!! += RxBus.trackDownloadStateObservable.subscribe {

View File

@ -148,10 +148,10 @@ class NowPlayingFragment : Fragment() {
if (abs(deltaX) > MIN_DISTANCE) {
// left or right
if (deltaX < 0) {
mediaPlayerController.previous()
mediaPlayerController.seekToPrevious()
}
if (deltaX > 0) {
mediaPlayerController.next()
mediaPlayerController.seekToNext()
}
} else if (abs(deltaY) > MIN_DISTANCE) {
if (deltaY < 0) {

View File

@ -154,6 +154,8 @@ class PlayerFragment :
private lateinit var pauseButton: View
private lateinit var stopButton: View
private lateinit var playButton: View
private lateinit var previousButton: MaterialButton
private lateinit var nextButton: MaterialButton
private lateinit var shuffleButton: View
private lateinit var repeatButton: MaterialButton
private lateinit var progressBar: SeekBar
@ -196,6 +198,8 @@ class PlayerFragment :
pauseButton = view.findViewById(R.id.button_pause)
stopButton = view.findViewById(R.id.button_stop)
playButton = view.findViewById(R.id.button_start)
nextButton = view.findViewById(R.id.button_next)
previousButton = view.findViewById(R.id.button_previous)
repeatButton = view.findViewById(R.id.button_repeat)
fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
@ -259,9 +263,7 @@ class PlayerFragment :
previousButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
mediaPlayerController.seekToPrevious()
}
}
@ -272,9 +274,7 @@ class PlayerFragment :
nextButton.setOnClickListener {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
mediaPlayerController.seekToNext()
}
}
@ -285,16 +285,12 @@ class PlayerFragment :
pauseButton.setOnClickListener {
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.pause()
onCurrentChanged()
onSliderProgressChanged()
}
}
stopButton.setOnClickListener {
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.reset()
onCurrentChanged()
onSliderProgressChanged()
}
}
@ -304,8 +300,6 @@ class PlayerFragment :
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.play()
onCurrentChanged()
onSliderProgressChanged()
}
}
@ -342,7 +336,6 @@ class PlayerFragment :
override fun onStopTrackingTouch(seekBar: SeekBar) {
launch(CommunicationError.getHandler(context)) {
mediaPlayerController.seekTo(progressBar.progress)
onSliderProgressChanged()
}
}
@ -367,11 +360,13 @@ class PlayerFragment :
// Observe playlist changes and update the UI
rxBusSubscription += RxBus.playlistObservable.subscribe {
onPlaylistChanged()
onSliderProgressChanged()
updateSeekBar()
}
rxBusSubscription += RxBus.playerStateObservable.subscribe {
update()
updateTitle(it.state)
updateButtonStates(it.state)
}
// Query the Jukebox state in an IO Context
@ -432,7 +427,7 @@ class PlayerFragment :
} else {
// Download list and Album art must be updated when resumed
onPlaylistChanged()
onCurrentChanged()
onTrackChanged()
}
val handler = Handler(Looper.getMainLooper())
@ -764,10 +759,9 @@ class PlayerFragment :
if (cancel?.isCancellationRequested == true) return
val mediaPlayerController = mediaPlayerController
if (currentSong?.id != mediaPlayerController.currentMediaItem?.mediaId) {
onCurrentChanged()
onTrackChanged()
}
onSliderProgressChanged()
requireActivity().invalidateOptionsMenu()
updateSeekBar()
}
private fun savePlaylistInBackground(playlistName: String) {
@ -827,12 +821,9 @@ class PlayerFragment :
}
// Create listener
val clickHandler: ((Track, Int) -> Unit) = { _, pos ->
mediaPlayerController.seekTo(pos, 0)
mediaPlayerController.prepare()
mediaPlayerController.play()
onCurrentChanged()
onSliderProgressChanged()
val clickHandler: ((Track, Int) -> Unit) = { _, listPos ->
val mediaIndex = mediaPlayerController.getUnshuffledIndexOf(listPos)
mediaPlayerController.play(mediaIndex)
}
viewAdapter.register(
@ -931,6 +922,7 @@ class PlayerFragment :
if (actionState == ACTION_STATE_IDLE && dragging) {
dragging = false
// Move the item in the playlist separately
Timber.i("Moving item %s to %s", startPosition, endPosition)
mediaPlayerController.moveItemInPlaylist(startPosition, endPosition)
}
}
@ -1010,7 +1002,8 @@ class PlayerFragment :
private fun onPlaylistChanged() {
val mediaPlayerController = mediaPlayerController
val list = mediaPlayerController.playlist
// Try to display playlist in play order
val list = mediaPlayerController.playlistInPlayOrder
emptyTextView.setText(R.string.playlist_empty)
viewAdapter.submitList(list.map(MediaItem::toTrack))
@ -1020,7 +1013,7 @@ class PlayerFragment :
updateRepeatButtonState(mediaPlayerController.repeatMode)
}
private fun onCurrentChanged() {
private fun onTrackChanged() {
currentSong = mediaPlayerController.currentMediaItem?.toTrack()
scrollToCurrent()
@ -1064,7 +1057,7 @@ class PlayerFragment :
it.loadImage(albumArtImageView, currentSong, true, 0)
}
displaySongRating()
updateSongRating()
} else {
currentSong = null
songTitleTextView.text = null
@ -1078,24 +1071,27 @@ class PlayerFragment :
it.loadImage(albumArtImageView, null, true, 0)
}
}
// TODO: It would be a lot nicer if MediaPlayerController would send an event
// when this is necessary instead of updating every time
updateSongRating()
nextButton.isEnabled = mediaPlayerController.canSeekToNext()
previousButton.isEnabled = mediaPlayerController.canSeekToPrevious()
}
@Suppress("LongMethod")
@Synchronized
private fun onSliderProgressChanged() {
private fun updateSeekBar() {
Timber.i("Calling updateSeekBar")
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
val duration: Int = mediaPlayerController.playerDuration
val playbackState: Int = mediaPlayerController.playbackState
val isPlaying = mediaPlayerController.isPlaying
if (cancellationToken.isCancellationRequested) return
if (currentSong != null) {
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
progressBar.max =
if (duration == 0) 100 else duration // Work-around for apparent bug.
progressBar.max = if (duration == 0) 100 else duration // Work-around for apparent bug.
progressBar.progress = millisPlayed
progressBar.isEnabled = mediaPlayerController.isPlaying || isJukeboxEnabled
} else {
@ -1107,18 +1103,18 @@ class PlayerFragment :
}
val progress = mediaPlayerController.bufferedPercentage
updateBufferProgress(playbackState, progress)
}
private fun updateTitle(playbackState: Int) {
when (playbackState) {
Player.STATE_BUFFERING -> {
val downloadStatus = resources.getString(
R.string.download_playerstate_loading
)
progressBar.secondaryProgress = progress
setTitle(this@PlayerFragment, downloadStatus)
}
Player.STATE_READY -> {
progressBar.secondaryProgress = progress
if (mediaPlayerController.isShufflePlayEnabled) {
setTitle(
this@PlayerFragment,
@ -1128,13 +1124,22 @@ class PlayerFragment :
setTitle(this@PlayerFragment, R.string.common_appname)
}
}
Player.STATE_IDLE,
Player.STATE_ENDED,
-> {
}
Player.STATE_IDLE, Player.STATE_ENDED -> {}
else -> setTitle(this@PlayerFragment, R.string.common_appname)
}
}
private fun updateBufferProgress(playbackState: Int, progress: Int) {
when (playbackState) {
Player.STATE_BUFFERING, Player.STATE_READY -> {
progressBar.secondaryProgress = progress
}
else -> { }
}
}
private fun updateButtonStates(playbackState: Int) {
val isPlaying = mediaPlayerController.isPlaying
when (playbackState) {
Player.STATE_READY -> {
pauseButton.isVisible = isPlaying
@ -1152,10 +1157,6 @@ class PlayerFragment :
playButton.isVisible = true
}
}
// TODO: It would be a lot nicer if MediaPlayerController would send an event
// when this is necessary instead of updating every time
displaySongRating()
}
private fun seek(forward: Boolean) {
@ -1189,18 +1190,14 @@ class PlayerFragment :
// Right to Left swipe
if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.next()
onCurrentChanged()
onSliderProgressChanged()
mediaPlayerController.seekToNext()
return true
}
// Left to Right swipe
if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.previous()
onCurrentChanged()
onSliderProgressChanged()
mediaPlayerController.seekToPrevious()
return true
}
@ -1208,7 +1205,6 @@ class PlayerFragment :
if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
onSliderProgressChanged()
return true
}
@ -1216,7 +1212,6 @@ class PlayerFragment :
if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
onSliderProgressChanged()
return true
}
return false
@ -1237,7 +1232,7 @@ class PlayerFragment :
return false
}
private fun displaySongRating() {
private fun updateSongRating() {
var rating = 0
if (currentSong?.userRating != null) {
@ -1253,7 +1248,7 @@ class PlayerFragment :
private fun setSongRating(rating: Int) {
if (currentSong == null) return
displaySongRating()
updateSongRating()
mediaPlayerController.setSongRating(rating)
}

View File

@ -26,9 +26,12 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder
import androidx.media3.exoplayer.source.ShuffleOrder.UnshuffledShuffleOrder
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.Random
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -175,6 +178,27 @@ class PlaybackService :
player.setWakeMode(getWakeModeFlag())
}
// Set a listener to reset the ShuffleOrder
rxBusSubscription += RxBus.shufflePlayObservable.subscribe { shuffle ->
val len = player.currentTimeline.windowCount
Timber.i("Resetting shuffle order, isShuffled: %s", shuffle)
// If disabling Shuffle return early
if (!shuffle) {
return@subscribe player.setShuffleOrder(UnshuffledShuffleOrder(len))
}
// Get the position of the current track in the unshuffled order
val cur = player.currentMediaItemIndex
val seed = System.currentTimeMillis()
val random = Random(seed)
val list = createShuffleListFromCurrentIndex(cur, len, random)
Timber.i("New Shuffle order: %s", list.joinToString { it.toString() })
player.setShuffleOrder(DefaultShuffleOrder(list, seed))
}
// Listen to the shutdown command
rxBusSubscription += RxBus.shutdownCommandObservable.subscribe {
Timber.i("Received destroy command via Rx")
@ -185,6 +209,24 @@ class PlaybackService :
isStarted = true
}
fun createShuffleListFromCurrentIndex(
currentIndex: Int,
length: Int,
random: Random
): IntArray {
val list = IntArray(length) { it }
// Shuffle the remaining items using a swapping algorithm
for (i in currentIndex + 1 until length) {
val swapIndex = (currentIndex + 1) + random.nextInt(i - currentIndex)
val swapItem = list[i]
list[i] = list[swapIndex]
list[swapIndex] = swapItem
}
return list
}
private val listener: Player.Listener = object : Player.Listener {
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
cacheNextSongs()

View File

@ -11,11 +11,14 @@ import android.content.Context
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.annotation.IntRange
import androidx.media3.common.C
import androidx.media3.common.HeartRating
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT
import androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS
import androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO
import androidx.media3.common.Player.REPEAT_MODE_OFF
import androidx.media3.common.Timeline
@ -30,6 +33,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
@ -60,6 +64,7 @@ class MediaPlayerController(
private val externalStorageMonitor: ExternalStorageMonitor,
val context: Context
) : KoinComponent {
private val activeServerProvider: ActiveServerProvider by inject()
private var created = false
@ -96,6 +101,14 @@ class MediaPlayerController(
* We run the event through RxBus in order to throttle them
*/
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
val start = controller?.currentTimeline?.getFirstWindowIndex(isShufflePlayEnabled)
Timber.w("On timeline changed. First shuffle play at index: %s", start)
deferredPlay?.let {
Timber.w("Executing deferred shuffle play")
it()
deferredPlay = null
}
RxBus.playlistPublisher.onNext(playlist.map(MediaItem::toTrack))
}
@ -150,19 +163,21 @@ class MediaPlayerController(
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
val timeline: Timeline = controller!!.currentTimeline
var windowIndex = timeline.getFirstWindowIndex( /* shuffleModeEnabled= */true)
var windowIndex = timeline.getFirstWindowIndex(true)
var count = 0
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
while (windowIndex != C.INDEX_UNSET) {
count++
windowIndex = timeline.getNextWindowIndex(
windowIndex, REPEAT_MODE_OFF, /* shuffleModeEnabled= */true
windowIndex, REPEAT_MODE_OFF, true
)
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
}
}
}
private var deferredPlay: (() -> Unit)? = null
private var cachedMediaItem: MediaItem? = null
fun onCreate(onCreated: () -> Unit) {
@ -259,7 +274,7 @@ class MediaPlayerController(
private fun publishPlaybackState() {
val newState = RxBus.StateWithTrack(
track = currentMediaItem?.toTrack(),
index = currentMediaItemIndex,
index = if (isShufflePlayEnabled) getCurrentShuffleIndex() else currentMediaItemIndex,
isPlaying = isPlaying,
state = playbackState
)
@ -316,6 +331,8 @@ class MediaPlayerController(
@Synchronized
fun play(index: Int) {
controller?.seekTo(index, 0L)
// FIXME CHECK ITS NOT MAKING PROBLEMS
controller?.prepare()
controller?.play()
}
@ -404,6 +421,7 @@ class MediaPlayerController(
}
if (shuffle) isShufflePlayEnabled = true
Timber.w("Adding ${mediaItems.size} media items")
controller?.addMediaItems(insertAt, mediaItems)
prepare()
@ -411,10 +429,19 @@ class MediaPlayerController(
// Playback doesn't start correctly when the player is in STATE_ENDED.
// So we need to call seek before (this is what play(0,0)) does.
// We can't just use play(0,0) then all random playlists will start with the first track.
// This means that we need to generate the random first track ourselves.
// Additionally the shuffle order becomes clear on after some time, so we need to wait for
// the right event, and can start playback only then.
if (autoPlay) {
val start = controller?.currentTimeline?.getFirstWindowIndex(isShufflePlayEnabled) ?: 0
play(start)
if (isShufflePlayEnabled) {
deferredPlay = {
val start = controller?.currentTimeline
?.getFirstWindowIndex(isShufflePlayEnabled) ?: 0
Timber.i("Deferred shuffle play starting now at index: %s", start)
play(start)
}
} else {
play(0)
}
}
}
@ -422,6 +449,8 @@ class MediaPlayerController(
var isShufflePlayEnabled: Boolean
get() = controller?.shuffleModeEnabled == true
set(enabled) {
Timber.i("Shuffle is now enabled: %s", enabled)
RxBus.shufflePlayPublisher.onNext(enabled)
controller?.shuffleModeEnabled = enabled
}
@ -431,11 +460,17 @@ class MediaPlayerController(
return isShufflePlayEnabled
}
/**
* Returns an estimate of the percentage in the current content up to which data is
* buffered, or 0 if no estimate is available.
*/
@get:IntRange(from = 0, to = 100)
val bufferedPercentage: Int
get() = controller?.bufferedPercentage ?: 0
@Synchronized
fun moveItemInPlaylist(oldPos: Int, newPos: Int) {
// TODO: This currently does not care about shuffle position.
controller?.moveMediaItem(oldPos, newPos)
}
@ -494,15 +529,25 @@ class MediaPlayerController(
}
@Synchronized
fun previous() {
fun seekToPrevious() {
controller?.seekToPrevious()
}
@Synchronized
operator fun next() {
fun canSeekToPrevious(): Boolean {
return controller?.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS) == true
}
@Synchronized
fun seekToNext() {
controller?.seekToNext()
}
@Synchronized
fun canSeekToNext(): Boolean {
return controller?.isCommandAvailable(COMMAND_SEEK_TO_NEXT) == true
}
@Synchronized
fun reset() {
controller?.clearMediaItems()
@ -693,15 +738,15 @@ class MediaPlayerController(
if (currentMediaItem == null) return
val song = currentMediaItem!!.toTrack()
song.userRating = rating
Thread {
try {
getMusicService().setRating(song.id, rating)
} catch (e: Exception) {
Timber.e(e)
mainScope.launch {
withContext(Dispatchers.IO) {
try {
getMusicService().setRating(song.id, rating)
} catch (e: Exception) {
Timber.e(e)
}
}
}.start()
// TODO this would be better handled with a Rx command
// updateNotification()
}
}
val currentMediaItem: MediaItem?
@ -710,9 +755,65 @@ class MediaPlayerController(
val currentMediaItemIndex: Int
get() = controller?.currentMediaItemIndex ?: -1
fun getCurrentShuffleIndex(): Int {
val currentMediaItemIndex = controller?.currentMediaItemIndex ?: return -1
return getShuffledIndexOf(currentMediaItemIndex)
}
/**
* Loops over the timeline windows to find the entry which matches the given closure.
*
* @param searchClosure Determines the condition which the searched for window needs to match.
* @param timeline the timeline to search in.
* @return the index of the window that satisfies the search condition,
* or [C.INDEX_UNSET] if not found.
*/
private fun getWindowIndexWhere(searchClosure: (Int, Int) -> Boolean): Int {
val timeline = controller?.currentTimeline!!
var windowIndex = timeline.getFirstWindowIndex(true)
var count = 0
while (windowIndex != C.INDEX_UNSET) {
if (searchClosure(count, windowIndex)) return count
count++
windowIndex = timeline.getNextWindowIndex(
windowIndex, REPEAT_MODE_OFF, true
)
}
return C.INDEX_UNSET
}
/**
* Returns the index of the shuffled position of the current playback item given its original
* position in the unshuffled timeline.
*
* @param searchPosition The index of the item in the unshuffled timeline to search for
* in the shuffled timeline.
* @return The index of the item in the shuffled timeline, or [C.INDEX_UNSET] if not found.
*/
fun getShuffledIndexOf(searchPosition: Int): Int {
return getWindowIndexWhere { _, windowIndex -> windowIndex == searchPosition }
}
/**
* Returns the index of the unshuffled position of the current playback item given its shuffled
* position in the shuffled timeline.
*
* @param shufflePosition the index of the item in the shuffled timeline to search for in the
* unshuffled timeline.
* @return the index of the item in the unshuffled timeline, or [C.INDEX_UNSET] if not found.
*/
fun getUnshuffledIndexOf(shufflePosition: Int): Int {
return getWindowIndexWhere { count, _ -> count == shufflePosition }
}
val mediaItemCount: Int
get() = controller?.mediaItemCount ?: 0
fun getMediaItemAt(index: Int): MediaItem? {
return controller?.getMediaItemAt(index)
}
val playlistSize: Int
get() = controller?.currentTimeline?.windowCount ?: 0
@ -721,10 +822,6 @@ class MediaPlayerController(
return Util.getPlayListFromTimeline(controller?.currentTimeline, false)
}
fun getMediaItemAt(index: Int): MediaItem? {
return controller?.getMediaItemAt(index)
}
val playlistInPlayOrder: List<MediaItem>
get() {
return Util.getPlayListFromTimeline(

View File

@ -182,8 +182,8 @@ class MediaPlayerLifecycleSupport : KoinComponent {
when (keyCode) {
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
KeyEvent.KEYCODE_HEADSETHOOK -> mediaPlayerController.togglePlayPause()
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> mediaPlayerController.previous()
KeyEvent.KEYCODE_MEDIA_NEXT -> mediaPlayerController.next()
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> mediaPlayerController.seekToPrevious()
KeyEvent.KEYCODE_MEDIA_NEXT -> mediaPlayerController.seekToNext()
KeyEvent.KEYCODE_MEDIA_STOP -> mediaPlayerController.stop()
KeyEvent.KEYCODE_MEDIA_PLAY -> mediaPlayerController.play()
KeyEvent.KEYCODE_MEDIA_PAUSE -> mediaPlayerController.pause()
@ -226,8 +226,8 @@ class MediaPlayerLifecycleSupport : KoinComponent {
// no need to call anything
if (isRunning) mediaPlayerController.resumeOrPlay()
Constants.CMD_NEXT -> mediaPlayerController.next()
Constants.CMD_PREVIOUS -> mediaPlayerController.previous()
Constants.CMD_NEXT -> mediaPlayerController.seekToNext()
Constants.CMD_PREVIOUS -> mediaPlayerController.seekToPrevious()
Constants.CMD_TOGGLEPAUSE -> mediaPlayerController.togglePlayPause()
Constants.CMD_STOP -> mediaPlayerController.stop()
Constants.CMD_PAUSE -> mediaPlayerController.pause()

View File

@ -20,9 +20,13 @@ class RxBus {
private fun mainThread() = AndroidSchedulers.from(Looper.getMainLooper())
val shufflePlayPublisher: PublishSubject<Boolean> =
PublishSubject.create()
val shufflePlayObservable: Observable<Boolean> =
shufflePlayPublisher
var activeServerChangingPublisher: PublishSubject<Int> =
PublishSubject.create()
// Subscribers should be called synchronously, not on another thread
var activeServerChangingObservable: Observable<Int> =
activeServerChangingPublisher

View File

@ -757,7 +757,7 @@ object Util {
fun getPlayListFromTimeline(
timeline: Timeline?,
shuffle: Boolean,
isShuffled: Boolean,
firstIndex: Int? = null,
count: Int? = null
): List<MediaItem> {
@ -765,13 +765,13 @@ object Util {
if (timeline.windowCount < 1) return emptyList()
val playlist: MutableList<MediaItem> = mutableListOf()
var i = firstIndex ?: timeline.getFirstWindowIndex(false)
var i = firstIndex ?: timeline.getFirstWindowIndex(isShuffled)
if (i == C.INDEX_UNSET) return emptyList()
while (i != C.INDEX_UNSET && (count != playlist.count())) {
val window = timeline.getWindow(i, Timeline.Window())
playlist.add(window.mediaItem)
i = timeline.getNextWindowIndex(i, Player.REPEAT_MODE_OFF, shuffle)
i = timeline.getNextWindowIndex(i, Player.REPEAT_MODE_OFF, isShuffled)
}
return playlist
}