Fixed server change

This commit is contained in:
Nite 2022-09-24 20:43:18 +00:00 committed by birdbird
parent 972d81ae53
commit c294c446a3
12 changed files with 117 additions and 108 deletions

View File

@ -10,7 +10,6 @@ dependencies {
}
implementation libs.kotlinReflect // for jackson kotlin, but to use the same version
implementation libs.okhttpLogging
implementation libs.timber
testImplementation libs.kotlinJunit
testImplementation libs.mockito

View File

@ -83,15 +83,7 @@ public class ChatFragment extends Fragment {
messageEditText = view.findViewById(R.id.chat_edittext);
sendButton = view.findViewById(R.id.chat_send);
sendButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
sendMessage();
}
});
sendButton.setOnClickListener(view1 -> sendMessage());
chatListView = view.findViewById(R.id.chat_entries_list);
chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
@ -125,20 +117,14 @@ public class ChatFragment extends Fragment {
}
});
messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener()
{
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
messageEditText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN))
{
if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN))
{
sendMessage();
return true;
}
return false;
sendMessage();
return true;
}
return false;
});
load();
@ -215,14 +201,7 @@ public class ChatFragment extends Fragment {
@Override
public void run()
{
getActivity().runOnUiThread(new Runnable()
{
@Override
public void run()
{
load();
}
});
getActivity().runOnUiThread(() -> load());
}
}, refreshInterval, refreshInterval);
}

View File

@ -211,7 +211,7 @@ class NavigationActivity : AppCompatActivity() {
recreate()
}
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
rxBusSubscription += RxBus.activeServerChangedObservable.subscribe {
updateNavigationHeaderForServer()
}

View File

@ -7,6 +7,8 @@
package org.moire.ultrasonic.data
import android.os.Handler
import android.os.Looper
import androidx.room.Room
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -234,12 +236,22 @@ class ActiveServerProvider(
* @param serverId: The id of the desired server
*/
fun setActiveServerById(serverId: Int) {
resetMusicService()
val oldServerId = Settings.activeServer
if (oldServerId == serverId) return
Settings.activeServer = serverId
// Notify components about the change before actually resetting the MusicService
// so they can react by e.g. stopping playback on the old server
RxBus.activeServerChangingPublisher.onNext(oldServerId)
Timber.i("setActiveServerById done, new id: %s", serverId)
RxBus.activeServerChangePublisher.onNext(serverId)
// Post the server change to the end of the message queue,
// so the cleanup have time to finish
Handler(Looper.getMainLooper()).post {
resetMusicService()
Settings.activeServer = serverId
RxBus.activeServerChangedPublisher.onNext(serverId)
Timber.i("setActiveServerById done, new id: %s", serverId)
}
}
/**

View File

@ -17,8 +17,6 @@ import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.OFFLINE_DB_ID
import org.moire.ultrasonic.data.ServerSetting
import org.moire.ultrasonic.model.ServerSettingsModel
import org.moire.ultrasonic.service.DownloadService
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.util.ErrorDialog
import org.moire.ultrasonic.util.Util
import timber.log.Timber
@ -30,7 +28,6 @@ class ServerSelectorFragment : Fragment() {
private var listView: ListView? = null
private val serverSettingsModel: ServerSettingsModel by viewModel()
private val controller: MediaPlayerController by inject()
private val activeServerProvider: ActiveServerProvider by inject()
private var serverRowAdapter: ServerRowAdapter? = null
@ -68,7 +65,7 @@ class ServerSelectorFragment : Fragment() {
listView?.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val server = parent.getItemAtPosition(position) as ServerSetting
setActiveServerById(server.id)
ActiveServerProvider.setActiveServerById(server.id)
findNavController().popBackStack(R.id.mainFragment, false)
}
@ -88,26 +85,6 @@ class ServerSelectorFragment : Fragment() {
}
}
/**
* Sets the active server when a list item is clicked
*/
private fun setActiveServerById(id: Int) {
val oldId = activeServerProvider.getActiveServer().id
// Check if there is a change
if (oldId == id)
return
// Remove incomplete tracks if we are going offline, or changing between servers.
// If we are coming from offline there is no need to clear downloads etc.
if (oldId != OFFLINE_DB_ID) {
controller.removeIncompleteTracksFromPlaylist()
DownloadService.requestStop()
}
ActiveServerProvider.setActiveServerById(id)
}
/**
* This Callback handles the deletion of a Server Setting
*/
@ -122,7 +99,7 @@ class ServerSelectorFragment : Fragment() {
val activeServerId = ActiveServerProvider.getActiveServerId()
// If the currently active server is deleted, go offline
if (id == activeServerId) setActiveServerById(OFFLINE_DB_ID)
if (id == activeServerId) ActiveServerProvider.setActiveServerById(OFFLINE_DB_ID)
serverSettingsModel.deleteItemById(id)

View File

@ -37,6 +37,7 @@ import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.stopForegroundRemoveNotification
import org.moire.ultrasonic.util.toTrack
import timber.log.Timber
@ -86,12 +87,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
mediaLibrarySession.release()
rxBusSubscription.dispose()
isStarted = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
stopForegroundRemoveNotification()
stopSelf()
}
@ -155,7 +151,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
.build()
// Set a listener to update the API client when the active server has changed
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
rxBusSubscription += RxBus.activeServerChangedObservable.subscribe {
// Set the player wake mode
player.setWakeMode(getWakeModeFlag())
}

View File

@ -38,6 +38,7 @@ import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.SimpleServiceBinder
import org.moire.ultrasonic.util.Storage
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.stopForegroundRemoveNotification
import timber.log.Timber
private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic"
@ -94,7 +95,7 @@ class DownloadService : Service(), KoinComponent {
isShuttingDown = true
isInForeground = false
stopForeground(true)
stopForegroundRemoveNotification()
wifiLock?.release()
wifiLock = null

View File

@ -60,11 +60,13 @@ import org.moire.ultrasonic.playback.MediaNotificationProvider
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.Util.getPendingIntentToShowPlayer
import org.moire.ultrasonic.util.Util.sleepQuietly
import org.moire.ultrasonic.util.Util.stopForegroundRemoveNotification
import timber.log.Timber
private const val STATUS_UPDATE_INTERVAL_SECONDS = 5L
private const val SEEK_INCREMENT_SECONDS = 5L
private const val SEEK_START_AFTER_SECONDS = 5
private const val QUEUE_POLL_INTERVAL_SECONDS = 1L
/**
* Provides an asynchronous interface to the remote jukebox on the Subsonic server.
@ -159,17 +161,15 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
}
override fun onDestroy() {
tasks.clear()
stop()
if (!running.get()) return
running.set(false)
sleepQuietly(1000)
if (serviceThread != null) {
serviceThread!!.interrupt()
}
serviceThread!!.join()
tasks.clear()
stop()
stopForeground(true)
stopForegroundRemoveNotification()
mediaSession.release()
super.onDestroy()
@ -417,9 +417,8 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
if (playlist.isEmpty()) return 0
if (currentIndex < 0 || currentIndex >= playlist.size) return 0
return (
playlist[currentIndex].mediaMetadata.extras?.getInt("duration") ?: 0
).toLong() * 1000
return (playlist[currentIndex].mediaMetadata.extras?.getInt("duration") ?: 0)
.toLong() * 1000
}
override fun getContentDuration(): Long {
@ -449,6 +448,17 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
seekTo(currentIndex, 0)
}
override fun setMediaItems(
mediaItems: MutableList<MediaItem>,
startIndex: Int,
startPositionMs: Long
) {
playlist.clear()
playlist.addAll(mediaItems)
updatePlaylist()
seekTo(startIndex, startPositionMs)
}
private fun startProcessTasks() {
serviceThread = object : Thread() {
override fun run() {
@ -481,22 +491,28 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
}
}
@Suppress("LoopWithTooManyJumpStatements")
private fun processTasks() {
while (running.get()) {
Timber.d("JukeboxMediaPlayer processTasks starting")
while (true) {
// Sleep a bit to spare processor time if we loop a lot
sleepQuietly(10)
// This is only necessary if Ultrasonic goes offline sooner than the thread stops
if (isOffline()) continue
var task: JukeboxTask? = null
try {
if (!isOffline()) {
task = tasks.take()
val status = task.execute()
onStatusUpdate(status)
}
} catch (ignored: InterruptedException) {
task = tasks.poll()
// If running is false, exit when the queue is empty
if (task == null && !running.get()) break
if (task == null) continue
Timber.v("JukeBoxMediaPlayer processTasks processes Task %s", task::class)
val status = task.execute()
onStatusUpdate(status)
} catch (x: Throwable) {
onError(task, x)
}
}
Timber.d("JukeboxMediaPlayer processTasks stopped")
}
private fun onStatusUpdate(jukeboxStatus: JukeboxStatus) {
@ -621,9 +637,8 @@ class JukeboxMediaPlayer : JukeboxUnimplementedFunctions(), Player {
queue.add(jukeboxTask)
}
@Throws(InterruptedException::class)
fun take(): JukeboxTask {
return queue.take()
fun poll(): JukeboxTask? {
return queue.poll(QUEUE_POLL_INTERVAL_SECONDS, TimeUnit.SECONDS)
}
fun remove(taskClass: Class<out JukeboxTask?>) {

View File

@ -36,14 +36,6 @@ abstract class JukeboxUnimplementedFunctions : Service(), Player {
TODO("Not yet implemented")
}
override fun setMediaItems(
mediaItems: MutableList<MediaItem>,
startIndex: Int,
startPositionMs: Long
) {
TODO("Not yet implemented")
}
override fun setMediaItem(mediaItem: MediaItem) {
TODO("Not yet implemented")
}

View File

@ -33,6 +33,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.OFFLINE_DB_ID
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.playback.PlaybackService
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1
@ -162,9 +163,28 @@ class MediaPlayerController(
switchToLocalPlayer(onCreated)
}
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
// Update the Jukebox state when the active server has changed
isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault
rxBusSubscription += RxBus.activeServerChangingObservable.subscribe { oldServer ->
if (oldServer != OFFLINE_DB_ID) {
// When the server changes, the playlist can retain the downloaded songs.
// Incomplete songs should be removed as the new server won't recognise them.
removeIncompleteTracksFromPlaylist()
DownloadService.requestStop()
}
if (controller is JukeboxMediaPlayer) {
// When the server changes, the Jukebox should be released.
// The new server won't understand the jukebox requests of the old one.
releaseJukebox(controller)
controller = null
}
}
rxBusSubscription += RxBus.activeServerChangedObservable.subscribe {
val jukebox = activeServerProvider.getActiveServer().jukeboxByDefault
// Remove all songs when changing servers before turning on Jukebox.
// Jukebox wouldn't find the songs on the new server.
if (jukebox) controller?.clearMediaItems()
// Update the Jukebox state as the new server requires
isJukeboxEnabled = jukebox
}
rxBusSubscription += RxBus.throttledPlaylistObservable.subscribe {
@ -550,7 +570,7 @@ class MediaPlayerController(
}
private fun switchToJukebox(onCreated: () -> Unit) {
if (JukeboxMediaPlayer.running.get()) return
if (controller is JukeboxMediaPlayer) return
val currentPlaylist = playlist
val currentIndex = controller?.currentMediaItemIndex ?: 0
val currentPosition = controller?.currentPosition ?: 0
@ -564,14 +584,14 @@ class MediaPlayerController(
Handler(Looper.getMainLooper()).postDelayed({
if (oldController != null) releaseLocalPlayer(oldController)
setupJukebox {
controller?.addMediaItems(0, currentPlaylist)
controller?.seekTo(currentIndex, currentPosition)
controller?.setMediaItems(currentPlaylist, currentIndex, currentPosition)
onCreated()
}
}, CONTROLLER_SWITCH_DELAY)
}
private fun switchToLocalPlayer(onCreated: () -> Unit) {
if (controller is MediaController) return
val currentPlaylist = playlist
val currentIndex = controller?.currentMediaItemIndex ?: 0
val currentPosition = controller?.currentPosition ?: 0
@ -582,8 +602,7 @@ class MediaPlayerController(
Handler(Looper.getMainLooper()).postDelayed({
if (oldController != null) releaseJukebox(oldController)
setupLocalPlayer {
controller?.addMediaItems(0, currentPlaylist)
controller?.seekTo(currentIndex, currentPosition)
controller?.setMediaItems(currentPlaylist, currentIndex, currentPosition)
onCreated()
}
}, CONTROLLER_SWITCH_DELAY)

View File

@ -20,10 +20,17 @@ class RxBus {
private fun mainThread() = AndroidSchedulers.from(Looper.getMainLooper())
var activeServerChangePublisher: PublishSubject<Int> =
var activeServerChangingPublisher: PublishSubject<Int> =
PublishSubject.create()
var activeServerChangeObservable: Observable<Int> =
activeServerChangePublisher.observeOn(mainThread())
// Subscribers should be called synchronously, not on another thread
var activeServerChangingObservable: Observable<Int> =
activeServerChangingPublisher
var activeServerChangedPublisher: PublishSubject<Int> =
PublishSubject.create()
var activeServerChangedObservable: Observable<Int> =
activeServerChangedPublisher.observeOn(mainThread())
val themeChangedEventPublisher: PublishSubject<Unit> =
PublishSubject.create()

View File

@ -11,6 +11,8 @@ import android.Manifest.permission.POST_NOTIFICATIONS
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.app.Service
import android.app.Service.STOP_FOREGROUND_REMOVE
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@ -33,6 +35,7 @@ import android.util.TypedValue
import android.view.Gravity
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.AnyRes
import androidx.appcompat.app.AppCompatActivity
@ -512,7 +515,7 @@ object Util {
}
}
fun ensurePermissionToPostNotification(fragment: AppCompatActivity) {
fun ensurePermissionToPostNotification(fragment: ComponentActivity) {
if (ContextCompat.checkSelfPermission(
applicationContext(),
POST_NOTIFICATIONS,
@ -751,4 +754,13 @@ object Util {
// Ignored
}
}
fun Service.stopForegroundRemoveNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
}
}