mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-18 18:17:43 +03:00
Merge branch 'fixShuffle' into 'develop'
Fix shuffle Closes #876 and #877 See merge request ultrasonic/ultrasonic!966
This commit is contained in:
commit
08d3618eb3
@ -34,7 +34,7 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testCoverageEnabled true
|
testCoverageEnabled true
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix '.debug'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,6 @@ class TrackViewBinder(
|
|||||||
diffAdapter.isSelected(item.longId)
|
diffAdapter.isSelected(item.longId)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Timber.v("Setting listeners")
|
|
||||||
|
|
||||||
holder.itemView.setOnLongClickListener {
|
holder.itemView.setOnLongClickListener {
|
||||||
if (onContextMenuClick != null) {
|
if (onContextMenuClick != null) {
|
||||||
val popup = createContextMenu(holder.itemView, track)
|
val popup = createContextMenu(holder.itemView, track)
|
||||||
@ -116,8 +114,6 @@ class TrackViewBinder(
|
|||||||
|
|
||||||
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timber.v("Setting listeners done")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewRecycled(holder: TrackViewHolder) {
|
override fun onViewRecycled(holder: TrackViewHolder) {
|
||||||
|
@ -131,7 +131,7 @@ class TrackViewHolder(val view: View) :
|
|||||||
// Create new Disposable for the new Subscriptions
|
// Create new Disposable for the new Subscriptions
|
||||||
rxBusSubscription = CompositeDisposable()
|
rxBusSubscription = CompositeDisposable()
|
||||||
rxBusSubscription!! += RxBus.playerStateObservable.subscribe {
|
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 {
|
rxBusSubscription!! += RxBus.trackDownloadStateObservable.subscribe {
|
||||||
|
@ -148,10 +148,10 @@ class NowPlayingFragment : Fragment() {
|
|||||||
if (abs(deltaX) > MIN_DISTANCE) {
|
if (abs(deltaX) > MIN_DISTANCE) {
|
||||||
// left or right
|
// left or right
|
||||||
if (deltaX < 0) {
|
if (deltaX < 0) {
|
||||||
mediaPlayerController.previous()
|
mediaPlayerController.seekToPrevious()
|
||||||
}
|
}
|
||||||
if (deltaX > 0) {
|
if (deltaX > 0) {
|
||||||
mediaPlayerController.next()
|
mediaPlayerController.seekToNext()
|
||||||
}
|
}
|
||||||
} else if (abs(deltaY) > MIN_DISTANCE) {
|
} else if (abs(deltaY) > MIN_DISTANCE) {
|
||||||
if (deltaY < 0) {
|
if (deltaY < 0) {
|
||||||
|
@ -154,6 +154,8 @@ class PlayerFragment :
|
|||||||
private lateinit var pauseButton: View
|
private lateinit var pauseButton: View
|
||||||
private lateinit var stopButton: View
|
private lateinit var stopButton: View
|
||||||
private lateinit var playButton: View
|
private lateinit var playButton: View
|
||||||
|
private lateinit var previousButton: MaterialButton
|
||||||
|
private lateinit var nextButton: MaterialButton
|
||||||
private lateinit var shuffleButton: View
|
private lateinit var shuffleButton: View
|
||||||
private lateinit var repeatButton: MaterialButton
|
private lateinit var repeatButton: MaterialButton
|
||||||
private lateinit var progressBar: SeekBar
|
private lateinit var progressBar: SeekBar
|
||||||
@ -196,6 +198,8 @@ class PlayerFragment :
|
|||||||
pauseButton = view.findViewById(R.id.button_pause)
|
pauseButton = view.findViewById(R.id.button_pause)
|
||||||
stopButton = view.findViewById(R.id.button_stop)
|
stopButton = view.findViewById(R.id.button_stop)
|
||||||
playButton = view.findViewById(R.id.button_start)
|
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)
|
repeatButton = view.findViewById(R.id.button_repeat)
|
||||||
fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
|
fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
|
||||||
fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
|
fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
|
||||||
@ -259,9 +263,7 @@ class PlayerFragment :
|
|||||||
previousButton.setOnClickListener {
|
previousButton.setOnClickListener {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.previous()
|
mediaPlayerController.seekToPrevious()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,9 +274,7 @@ class PlayerFragment :
|
|||||||
nextButton.setOnClickListener {
|
nextButton.setOnClickListener {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.next()
|
mediaPlayerController.seekToNext()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,16 +285,12 @@ class PlayerFragment :
|
|||||||
pauseButton.setOnClickListener {
|
pauseButton.setOnClickListener {
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.pause()
|
mediaPlayerController.pause()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopButton.setOnClickListener {
|
stopButton.setOnClickListener {
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.reset()
|
mediaPlayerController.reset()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,8 +300,6 @@ class PlayerFragment :
|
|||||||
|
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.play()
|
mediaPlayerController.play()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +336,6 @@ class PlayerFragment :
|
|||||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
launch(CommunicationError.getHandler(context)) {
|
launch(CommunicationError.getHandler(context)) {
|
||||||
mediaPlayerController.seekTo(progressBar.progress)
|
mediaPlayerController.seekTo(progressBar.progress)
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,11 +360,13 @@ class PlayerFragment :
|
|||||||
// Observe playlist changes and update the UI
|
// Observe playlist changes and update the UI
|
||||||
rxBusSubscription += RxBus.playlistObservable.subscribe {
|
rxBusSubscription += RxBus.playlistObservable.subscribe {
|
||||||
onPlaylistChanged()
|
onPlaylistChanged()
|
||||||
onSliderProgressChanged()
|
updateSeekBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
rxBusSubscription += RxBus.playerStateObservable.subscribe {
|
rxBusSubscription += RxBus.playerStateObservable.subscribe {
|
||||||
update()
|
update()
|
||||||
|
updateTitle(it.state)
|
||||||
|
updateButtonStates(it.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the Jukebox state in an IO Context
|
// Query the Jukebox state in an IO Context
|
||||||
@ -432,7 +427,7 @@ class PlayerFragment :
|
|||||||
} else {
|
} else {
|
||||||
// Download list and Album art must be updated when resumed
|
// Download list and Album art must be updated when resumed
|
||||||
onPlaylistChanged()
|
onPlaylistChanged()
|
||||||
onCurrentChanged()
|
onTrackChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
@ -764,10 +759,9 @@ class PlayerFragment :
|
|||||||
if (cancel?.isCancellationRequested == true) return
|
if (cancel?.isCancellationRequested == true) return
|
||||||
val mediaPlayerController = mediaPlayerController
|
val mediaPlayerController = mediaPlayerController
|
||||||
if (currentSong?.id != mediaPlayerController.currentMediaItem?.mediaId) {
|
if (currentSong?.id != mediaPlayerController.currentMediaItem?.mediaId) {
|
||||||
onCurrentChanged()
|
onTrackChanged()
|
||||||
}
|
}
|
||||||
onSliderProgressChanged()
|
updateSeekBar()
|
||||||
requireActivity().invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun savePlaylistInBackground(playlistName: String) {
|
private fun savePlaylistInBackground(playlistName: String) {
|
||||||
@ -827,12 +821,9 @@ class PlayerFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create listener
|
// Create listener
|
||||||
val clickHandler: ((Track, Int) -> Unit) = { _, pos ->
|
val clickHandler: ((Track, Int) -> Unit) = { _, listPos ->
|
||||||
mediaPlayerController.seekTo(pos, 0)
|
val mediaIndex = mediaPlayerController.getUnshuffledIndexOf(listPos)
|
||||||
mediaPlayerController.prepare()
|
mediaPlayerController.play(mediaIndex)
|
||||||
mediaPlayerController.play()
|
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewAdapter.register(
|
viewAdapter.register(
|
||||||
@ -931,6 +922,7 @@ class PlayerFragment :
|
|||||||
if (actionState == ACTION_STATE_IDLE && dragging) {
|
if (actionState == ACTION_STATE_IDLE && dragging) {
|
||||||
dragging = false
|
dragging = false
|
||||||
// Move the item in the playlist separately
|
// Move the item in the playlist separately
|
||||||
|
Timber.i("Moving item %s to %s", startPosition, endPosition)
|
||||||
mediaPlayerController.moveItemInPlaylist(startPosition, endPosition)
|
mediaPlayerController.moveItemInPlaylist(startPosition, endPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1010,7 +1002,8 @@ class PlayerFragment :
|
|||||||
|
|
||||||
private fun onPlaylistChanged() {
|
private fun onPlaylistChanged() {
|
||||||
val mediaPlayerController = mediaPlayerController
|
val mediaPlayerController = mediaPlayerController
|
||||||
val list = mediaPlayerController.playlist
|
// Try to display playlist in play order
|
||||||
|
val list = mediaPlayerController.playlistInPlayOrder
|
||||||
emptyTextView.setText(R.string.playlist_empty)
|
emptyTextView.setText(R.string.playlist_empty)
|
||||||
|
|
||||||
viewAdapter.submitList(list.map(MediaItem::toTrack))
|
viewAdapter.submitList(list.map(MediaItem::toTrack))
|
||||||
@ -1020,7 +1013,7 @@ class PlayerFragment :
|
|||||||
updateRepeatButtonState(mediaPlayerController.repeatMode)
|
updateRepeatButtonState(mediaPlayerController.repeatMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCurrentChanged() {
|
private fun onTrackChanged() {
|
||||||
currentSong = mediaPlayerController.currentMediaItem?.toTrack()
|
currentSong = mediaPlayerController.currentMediaItem?.toTrack()
|
||||||
|
|
||||||
scrollToCurrent()
|
scrollToCurrent()
|
||||||
@ -1064,7 +1057,7 @@ class PlayerFragment :
|
|||||||
it.loadImage(albumArtImageView, currentSong, true, 0)
|
it.loadImage(albumArtImageView, currentSong, true, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
displaySongRating()
|
updateSongRating()
|
||||||
} else {
|
} else {
|
||||||
currentSong = null
|
currentSong = null
|
||||||
songTitleTextView.text = null
|
songTitleTextView.text = null
|
||||||
@ -1078,24 +1071,27 @@ class PlayerFragment :
|
|||||||
it.loadImage(albumArtImageView, null, true, 0)
|
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
|
@Synchronized
|
||||||
private fun onSliderProgressChanged() {
|
private fun updateSeekBar() {
|
||||||
|
Timber.i("Calling updateSeekBar")
|
||||||
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
|
val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled
|
||||||
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
|
val millisPlayed: Int = max(0, mediaPlayerController.playerPosition)
|
||||||
val duration: Int = mediaPlayerController.playerDuration
|
val duration: Int = mediaPlayerController.playerDuration
|
||||||
val playbackState: Int = mediaPlayerController.playbackState
|
val playbackState: Int = mediaPlayerController.playbackState
|
||||||
val isPlaying = mediaPlayerController.isPlaying
|
|
||||||
|
|
||||||
if (cancellationToken.isCancellationRequested) return
|
|
||||||
if (currentSong != null) {
|
if (currentSong != null) {
|
||||||
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
|
positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
|
||||||
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
|
durationTextView.text = Util.formatTotalDuration(duration.toLong(), true)
|
||||||
progressBar.max =
|
progressBar.max = if (duration == 0) 100 else duration // Work-around for apparent bug.
|
||||||
if (duration == 0) 100 else duration // Work-around for apparent bug.
|
|
||||||
progressBar.progress = millisPlayed
|
progressBar.progress = millisPlayed
|
||||||
progressBar.isEnabled = mediaPlayerController.isPlaying || isJukeboxEnabled
|
progressBar.isEnabled = mediaPlayerController.isPlaying || isJukeboxEnabled
|
||||||
} else {
|
} else {
|
||||||
@ -1107,18 +1103,18 @@ class PlayerFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
val progress = mediaPlayerController.bufferedPercentage
|
val progress = mediaPlayerController.bufferedPercentage
|
||||||
|
updateBufferProgress(playbackState, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTitle(playbackState: Int) {
|
||||||
when (playbackState) {
|
when (playbackState) {
|
||||||
Player.STATE_BUFFERING -> {
|
Player.STATE_BUFFERING -> {
|
||||||
|
|
||||||
val downloadStatus = resources.getString(
|
val downloadStatus = resources.getString(
|
||||||
R.string.download_playerstate_loading
|
R.string.download_playerstate_loading
|
||||||
)
|
)
|
||||||
progressBar.secondaryProgress = progress
|
|
||||||
setTitle(this@PlayerFragment, downloadStatus)
|
setTitle(this@PlayerFragment, downloadStatus)
|
||||||
}
|
}
|
||||||
Player.STATE_READY -> {
|
Player.STATE_READY -> {
|
||||||
progressBar.secondaryProgress = progress
|
|
||||||
if (mediaPlayerController.isShufflePlayEnabled) {
|
if (mediaPlayerController.isShufflePlayEnabled) {
|
||||||
setTitle(
|
setTitle(
|
||||||
this@PlayerFragment,
|
this@PlayerFragment,
|
||||||
@ -1128,13 +1124,22 @@ class PlayerFragment :
|
|||||||
setTitle(this@PlayerFragment, R.string.common_appname)
|
setTitle(this@PlayerFragment, R.string.common_appname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Player.STATE_IDLE,
|
Player.STATE_IDLE, Player.STATE_ENDED -> {}
|
||||||
Player.STATE_ENDED,
|
|
||||||
-> {
|
|
||||||
}
|
|
||||||
else -> setTitle(this@PlayerFragment, R.string.common_appname)
|
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) {
|
when (playbackState) {
|
||||||
Player.STATE_READY -> {
|
Player.STATE_READY -> {
|
||||||
pauseButton.isVisible = isPlaying
|
pauseButton.isVisible = isPlaying
|
||||||
@ -1152,10 +1157,6 @@ class PlayerFragment :
|
|||||||
playButton.isVisible = true
|
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) {
|
private fun seek(forward: Boolean) {
|
||||||
@ -1189,18 +1190,14 @@ class PlayerFragment :
|
|||||||
// Right to Left swipe
|
// Right to Left swipe
|
||||||
if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
|
if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
mediaPlayerController.next()
|
mediaPlayerController.seekToNext()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left to Right swipe
|
// Left to Right swipe
|
||||||
if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
|
if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
mediaPlayerController.previous()
|
mediaPlayerController.seekToPrevious()
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,7 +1205,6 @@ class PlayerFragment :
|
|||||||
if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
|
if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
|
mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
|
||||||
onSliderProgressChanged()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1216,7 +1212,6 @@ class PlayerFragment :
|
|||||||
if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
|
if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||||
mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
|
mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
|
||||||
onSliderProgressChanged()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -1237,7 +1232,7 @@ class PlayerFragment :
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displaySongRating() {
|
private fun updateSongRating() {
|
||||||
var rating = 0
|
var rating = 0
|
||||||
|
|
||||||
if (currentSong?.userRating != null) {
|
if (currentSong?.userRating != null) {
|
||||||
@ -1253,7 +1248,7 @@ class PlayerFragment :
|
|||||||
|
|
||||||
private fun setSongRating(rating: Int) {
|
private fun setSongRating(rating: Int) {
|
||||||
if (currentSong == null) return
|
if (currentSong == null) return
|
||||||
displaySongRating()
|
updateSongRating()
|
||||||
mediaPlayerController.setSongRating(rating)
|
mediaPlayerController.setSongRating(rating)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +26,12 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
|
|||||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
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.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import java.util.Random
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -175,6 +178,27 @@ class PlaybackService :
|
|||||||
player.setWakeMode(getWakeModeFlag())
|
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
|
// Listen to the shutdown command
|
||||||
rxBusSubscription += RxBus.shutdownCommandObservable.subscribe {
|
rxBusSubscription += RxBus.shutdownCommandObservable.subscribe {
|
||||||
Timber.i("Received destroy command via Rx")
|
Timber.i("Received destroy command via Rx")
|
||||||
@ -185,6 +209,24 @@ class PlaybackService :
|
|||||||
isStarted = true
|
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 {
|
private val listener: Player.Listener = object : Player.Listener {
|
||||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||||
cacheNextSongs()
|
cacheNextSongs()
|
||||||
|
@ -11,11 +11,14 @@ import android.content.Context
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.IntRange
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.HeartRating
|
import androidx.media3.common.HeartRating
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
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.MEDIA_ITEM_TRANSITION_REASON_AUTO
|
||||||
import androidx.media3.common.Player.REPEAT_MODE_OFF
|
import androidx.media3.common.Player.REPEAT_MODE_OFF
|
||||||
import androidx.media3.common.Timeline
|
import androidx.media3.common.Timeline
|
||||||
@ -30,6 +33,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.app.UApp
|
import org.moire.ultrasonic.app.UApp
|
||||||
@ -60,6 +64,7 @@ class MediaPlayerController(
|
|||||||
private val externalStorageMonitor: ExternalStorageMonitor,
|
private val externalStorageMonitor: ExternalStorageMonitor,
|
||||||
val context: Context
|
val context: Context
|
||||||
) : KoinComponent {
|
) : KoinComponent {
|
||||||
|
|
||||||
private val activeServerProvider: ActiveServerProvider by inject()
|
private val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
|
||||||
private var created = false
|
private var created = false
|
||||||
@ -96,6 +101,14 @@ class MediaPlayerController(
|
|||||||
* We run the event through RxBus in order to throttle them
|
* We run the event through RxBus in order to throttle them
|
||||||
*/
|
*/
|
||||||
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
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))
|
RxBus.playlistPublisher.onNext(playlist.map(MediaItem::toTrack))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,19 +163,21 @@ class MediaPlayerController(
|
|||||||
|
|
||||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||||
val timeline: Timeline = controller!!.currentTimeline
|
val timeline: Timeline = controller!!.currentTimeline
|
||||||
var windowIndex = timeline.getFirstWindowIndex( /* shuffleModeEnabled= */true)
|
var windowIndex = timeline.getFirstWindowIndex(true)
|
||||||
var count = 0
|
var count = 0
|
||||||
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
|
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
|
||||||
while (windowIndex != C.INDEX_UNSET) {
|
while (windowIndex != C.INDEX_UNSET) {
|
||||||
count++
|
count++
|
||||||
windowIndex = timeline.getNextWindowIndex(
|
windowIndex = timeline.getNextWindowIndex(
|
||||||
windowIndex, REPEAT_MODE_OFF, /* shuffleModeEnabled= */true
|
windowIndex, REPEAT_MODE_OFF, true
|
||||||
)
|
)
|
||||||
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
|
Timber.d("Shuffle: windowIndex: $windowIndex, at: $count")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var deferredPlay: (() -> Unit)? = null
|
||||||
|
|
||||||
private var cachedMediaItem: MediaItem? = null
|
private var cachedMediaItem: MediaItem? = null
|
||||||
|
|
||||||
fun onCreate(onCreated: () -> Unit) {
|
fun onCreate(onCreated: () -> Unit) {
|
||||||
@ -259,7 +274,7 @@ class MediaPlayerController(
|
|||||||
private fun publishPlaybackState() {
|
private fun publishPlaybackState() {
|
||||||
val newState = RxBus.StateWithTrack(
|
val newState = RxBus.StateWithTrack(
|
||||||
track = currentMediaItem?.toTrack(),
|
track = currentMediaItem?.toTrack(),
|
||||||
index = currentMediaItemIndex,
|
index = if (isShufflePlayEnabled) getCurrentShuffleIndex() else currentMediaItemIndex,
|
||||||
isPlaying = isPlaying,
|
isPlaying = isPlaying,
|
||||||
state = playbackState
|
state = playbackState
|
||||||
)
|
)
|
||||||
@ -316,6 +331,8 @@ class MediaPlayerController(
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun play(index: Int) {
|
fun play(index: Int) {
|
||||||
controller?.seekTo(index, 0L)
|
controller?.seekTo(index, 0L)
|
||||||
|
// FIXME CHECK ITS NOT MAKING PROBLEMS
|
||||||
|
controller?.prepare()
|
||||||
controller?.play()
|
controller?.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,6 +421,7 @@ class MediaPlayerController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shuffle) isShufflePlayEnabled = true
|
if (shuffle) isShufflePlayEnabled = true
|
||||||
|
Timber.w("Adding ${mediaItems.size} media items")
|
||||||
controller?.addMediaItems(insertAt, mediaItems)
|
controller?.addMediaItems(insertAt, mediaItems)
|
||||||
|
|
||||||
prepare()
|
prepare()
|
||||||
@ -411,10 +429,19 @@ class MediaPlayerController(
|
|||||||
// Playback doesn't start correctly when the player is in STATE_ENDED.
|
// 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.
|
// 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.
|
// 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) {
|
if (autoPlay) {
|
||||||
val start = controller?.currentTimeline?.getFirstWindowIndex(isShufflePlayEnabled) ?: 0
|
if (isShufflePlayEnabled) {
|
||||||
play(start)
|
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
|
var isShufflePlayEnabled: Boolean
|
||||||
get() = controller?.shuffleModeEnabled == true
|
get() = controller?.shuffleModeEnabled == true
|
||||||
set(enabled) {
|
set(enabled) {
|
||||||
|
Timber.i("Shuffle is now enabled: %s", enabled)
|
||||||
|
RxBus.shufflePlayPublisher.onNext(enabled)
|
||||||
controller?.shuffleModeEnabled = enabled
|
controller?.shuffleModeEnabled = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,11 +460,17 @@ class MediaPlayerController(
|
|||||||
return isShufflePlayEnabled
|
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
|
val bufferedPercentage: Int
|
||||||
get() = controller?.bufferedPercentage ?: 0
|
get() = controller?.bufferedPercentage ?: 0
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun moveItemInPlaylist(oldPos: Int, newPos: Int) {
|
fun moveItemInPlaylist(oldPos: Int, newPos: Int) {
|
||||||
|
// TODO: This currently does not care about shuffle position.
|
||||||
controller?.moveMediaItem(oldPos, newPos)
|
controller?.moveMediaItem(oldPos, newPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,15 +529,25 @@ class MediaPlayerController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun previous() {
|
fun seekToPrevious() {
|
||||||
controller?.seekToPrevious()
|
controller?.seekToPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
operator fun next() {
|
fun canSeekToPrevious(): Boolean {
|
||||||
|
return controller?.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun seekToNext() {
|
||||||
controller?.seekToNext()
|
controller?.seekToNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun canSeekToNext(): Boolean {
|
||||||
|
return controller?.isCommandAvailable(COMMAND_SEEK_TO_NEXT) == true
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun reset() {
|
fun reset() {
|
||||||
controller?.clearMediaItems()
|
controller?.clearMediaItems()
|
||||||
@ -693,15 +738,15 @@ class MediaPlayerController(
|
|||||||
if (currentMediaItem == null) return
|
if (currentMediaItem == null) return
|
||||||
val song = currentMediaItem!!.toTrack()
|
val song = currentMediaItem!!.toTrack()
|
||||||
song.userRating = rating
|
song.userRating = rating
|
||||||
Thread {
|
mainScope.launch {
|
||||||
try {
|
withContext(Dispatchers.IO) {
|
||||||
getMusicService().setRating(song.id, rating)
|
try {
|
||||||
} catch (e: Exception) {
|
getMusicService().setRating(song.id, rating)
|
||||||
Timber.e(e)
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}
|
||||||
// TODO this would be better handled with a Rx command
|
|
||||||
// updateNotification()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentMediaItem: MediaItem?
|
val currentMediaItem: MediaItem?
|
||||||
@ -710,9 +755,65 @@ class MediaPlayerController(
|
|||||||
val currentMediaItemIndex: Int
|
val currentMediaItemIndex: Int
|
||||||
get() = controller?.currentMediaItemIndex ?: -1
|
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
|
val mediaItemCount: Int
|
||||||
get() = controller?.mediaItemCount ?: 0
|
get() = controller?.mediaItemCount ?: 0
|
||||||
|
|
||||||
|
fun getMediaItemAt(index: Int): MediaItem? {
|
||||||
|
return controller?.getMediaItemAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
val playlistSize: Int
|
val playlistSize: Int
|
||||||
get() = controller?.currentTimeline?.windowCount ?: 0
|
get() = controller?.currentTimeline?.windowCount ?: 0
|
||||||
|
|
||||||
@ -721,10 +822,6 @@ class MediaPlayerController(
|
|||||||
return Util.getPlayListFromTimeline(controller?.currentTimeline, false)
|
return Util.getPlayListFromTimeline(controller?.currentTimeline, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMediaItemAt(index: Int): MediaItem? {
|
|
||||||
return controller?.getMediaItemAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
val playlistInPlayOrder: List<MediaItem>
|
val playlistInPlayOrder: List<MediaItem>
|
||||||
get() {
|
get() {
|
||||||
return Util.getPlayListFromTimeline(
|
return Util.getPlayListFromTimeline(
|
||||||
|
@ -182,8 +182,8 @@ class MediaPlayerLifecycleSupport : KoinComponent {
|
|||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
|
||||||
KeyEvent.KEYCODE_HEADSETHOOK -> mediaPlayerController.togglePlayPause()
|
KeyEvent.KEYCODE_HEADSETHOOK -> mediaPlayerController.togglePlayPause()
|
||||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> mediaPlayerController.previous()
|
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> mediaPlayerController.seekToPrevious()
|
||||||
KeyEvent.KEYCODE_MEDIA_NEXT -> mediaPlayerController.next()
|
KeyEvent.KEYCODE_MEDIA_NEXT -> mediaPlayerController.seekToNext()
|
||||||
KeyEvent.KEYCODE_MEDIA_STOP -> mediaPlayerController.stop()
|
KeyEvent.KEYCODE_MEDIA_STOP -> mediaPlayerController.stop()
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY -> mediaPlayerController.play()
|
KeyEvent.KEYCODE_MEDIA_PLAY -> mediaPlayerController.play()
|
||||||
KeyEvent.KEYCODE_MEDIA_PAUSE -> mediaPlayerController.pause()
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> mediaPlayerController.pause()
|
||||||
@ -226,8 +226,8 @@ class MediaPlayerLifecycleSupport : KoinComponent {
|
|||||||
// no need to call anything
|
// no need to call anything
|
||||||
if (isRunning) mediaPlayerController.resumeOrPlay()
|
if (isRunning) mediaPlayerController.resumeOrPlay()
|
||||||
|
|
||||||
Constants.CMD_NEXT -> mediaPlayerController.next()
|
Constants.CMD_NEXT -> mediaPlayerController.seekToNext()
|
||||||
Constants.CMD_PREVIOUS -> mediaPlayerController.previous()
|
Constants.CMD_PREVIOUS -> mediaPlayerController.seekToPrevious()
|
||||||
Constants.CMD_TOGGLEPAUSE -> mediaPlayerController.togglePlayPause()
|
Constants.CMD_TOGGLEPAUSE -> mediaPlayerController.togglePlayPause()
|
||||||
Constants.CMD_STOP -> mediaPlayerController.stop()
|
Constants.CMD_STOP -> mediaPlayerController.stop()
|
||||||
Constants.CMD_PAUSE -> mediaPlayerController.pause()
|
Constants.CMD_PAUSE -> mediaPlayerController.pause()
|
||||||
|
@ -20,9 +20,13 @@ class RxBus {
|
|||||||
|
|
||||||
private fun mainThread() = AndroidSchedulers.from(Looper.getMainLooper())
|
private fun mainThread() = AndroidSchedulers.from(Looper.getMainLooper())
|
||||||
|
|
||||||
|
val shufflePlayPublisher: PublishSubject<Boolean> =
|
||||||
|
PublishSubject.create()
|
||||||
|
val shufflePlayObservable: Observable<Boolean> =
|
||||||
|
shufflePlayPublisher
|
||||||
|
|
||||||
var activeServerChangingPublisher: PublishSubject<Int> =
|
var activeServerChangingPublisher: PublishSubject<Int> =
|
||||||
PublishSubject.create()
|
PublishSubject.create()
|
||||||
|
|
||||||
// Subscribers should be called synchronously, not on another thread
|
// Subscribers should be called synchronously, not on another thread
|
||||||
var activeServerChangingObservable: Observable<Int> =
|
var activeServerChangingObservable: Observable<Int> =
|
||||||
activeServerChangingPublisher
|
activeServerChangingPublisher
|
||||||
|
@ -757,7 +757,7 @@ object Util {
|
|||||||
|
|
||||||
fun getPlayListFromTimeline(
|
fun getPlayListFromTimeline(
|
||||||
timeline: Timeline?,
|
timeline: Timeline?,
|
||||||
shuffle: Boolean,
|
isShuffled: Boolean,
|
||||||
firstIndex: Int? = null,
|
firstIndex: Int? = null,
|
||||||
count: Int? = null
|
count: Int? = null
|
||||||
): List<MediaItem> {
|
): List<MediaItem> {
|
||||||
@ -765,13 +765,13 @@ object Util {
|
|||||||
if (timeline.windowCount < 1) return emptyList()
|
if (timeline.windowCount < 1) return emptyList()
|
||||||
|
|
||||||
val playlist: MutableList<MediaItem> = mutableListOf()
|
val playlist: MutableList<MediaItem> = mutableListOf()
|
||||||
var i = firstIndex ?: timeline.getFirstWindowIndex(false)
|
var i = firstIndex ?: timeline.getFirstWindowIndex(isShuffled)
|
||||||
if (i == C.INDEX_UNSET) return emptyList()
|
if (i == C.INDEX_UNSET) return emptyList()
|
||||||
|
|
||||||
while (i != C.INDEX_UNSET && (count != playlist.count())) {
|
while (i != C.INDEX_UNSET && (count != playlist.count())) {
|
||||||
val window = timeline.getWindow(i, Timeline.Window())
|
val window = timeline.getWindow(i, Timeline.Window())
|
||||||
playlist.add(window.mediaItem)
|
playlist.add(window.mediaItem)
|
||||||
i = timeline.getNextWindowIndex(i, Player.REPEAT_MODE_OFF, shuffle)
|
i = timeline.getNextWindowIndex(i, Player.REPEAT_MODE_OFF, isShuffled)
|
||||||
}
|
}
|
||||||
return playlist
|
return playlist
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user