Rework ActiveServer handling.

Remove blocking call on setting the server.
Implement offline server display more cleanly.
Reconfigure the SourceFactory when the active server has changed
This commit is contained in:
tzugen 2022-04-08 18:08:56 +02:00
parent 6da83db9df
commit 3ca25ed1c6
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
11 changed files with 147 additions and 123 deletions

View File

@ -2,8 +2,6 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComplexCondition:DownloadHandler.kt$DownloadHandler.&lt;no name provided>$!append &amp;&amp; !playNext &amp;&amp; !unpin &amp;&amp; !background</ID>
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.&lt;no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file)</ID>
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name)</ID>
@ -12,7 +10,7 @@
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array&lt;ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array&lt;ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$0.05f</ID>
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$50</ID>

View File

@ -40,7 +40,7 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.navigation.NavigationView
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R
@ -54,6 +54,7 @@ import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.InfoDialog
@ -83,8 +84,8 @@ class NavigationActivity : AppCompatActivity() {
private var headerBackgroundImage: ImageView? = null
private lateinit var appBarConfiguration: AppBarConfiguration
private var themeChangedEventSubscription: Disposable? = null
private var playerStateSubscription: Disposable? = null
private var rxBusSubscription: CompositeDisposable = CompositeDisposable()
private val serverSettingsModel: ServerSettingsModel by viewModel()
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
@ -181,25 +182,25 @@ class NavigationActivity : AppCompatActivity() {
hideNowPlaying()
}
playerStateSubscription = RxBus.playerStateObservable.subscribe {
rxBusSubscription += RxBus.playerStateObservable.subscribe {
if (it.state === PlayerState.STARTED || it.state === PlayerState.PAUSED)
showNowPlaying()
else
hideNowPlaying()
}
themeChangedEventSubscription = RxBus.themeChangedEventObservable.subscribe {
rxBusSubscription += RxBus.themeChangedEventObservable.subscribe {
recreate()
}
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
updateNavigationHeaderForServer()
}
serverRepository.liveServerCount().observe(this) { count ->
cachedServerCount = count ?: 0
updateNavigationHeaderForServer()
}
ActiveServerProvider.liveActiveServerId.observe(this) {
updateNavigationHeaderForServer()
}
}
private fun updateNavigationHeaderForServer() {
@ -239,8 +240,7 @@ class NavigationActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
themeChangedEventSubscription?.dispose()
playerStateSubscription?.dispose()
rxBusSubscription.dispose()
imageLoaderProvider.clearImageLoader()
}

View File

@ -30,7 +30,7 @@ import org.moire.ultrasonic.util.Util
*/
internal class ServerRowAdapter(
private var context: Context,
private var data: Array<ServerSetting>,
passedData: Array<ServerSetting>,
private val model: ServerSettingsModel,
private val activeServerProvider: ActiveServerProvider,
private val manageMode: Boolean,
@ -38,6 +38,12 @@ internal class ServerRowAdapter(
private val serverEditRequestedCallback: ((Int) -> Unit)
) : BaseAdapter() {
private var data: MutableList<ServerSetting> = mutableListOf()
init {
setData(passedData)
}
companion object {
private const val MENU_ID_EDIT = 1
private const val MENU_ID_DELETE = 2
@ -49,12 +55,19 @@ internal class ServerRowAdapter(
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
fun setData(data: Array<ServerSetting>) {
this.data = data
this.data.clear()
// In read mode show the offline server as well
if (!manageMode) {
this.data.add(ActiveServerProvider.OFFLINE_DB)
}
this.data.addAll(data)
notifyDataSetChanged()
}
override fun getCount(): Int {
return if (manageMode) data.size else data.size + 1
return data.size
}
override fun getItem(position: Int): Any {
@ -69,11 +82,11 @@ internal class ServerRowAdapter(
* Creates the Row representation of a Server Setting
*/
@Suppress("LongMethod")
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
var index = position
override fun getView(pos: Int, convertView: View?, parent: ViewGroup?): View? {
var position = pos
// Skip "Offline" in manage mode
if (manageMode) index++
if (manageMode) position++
var vi: View? = convertView
if (vi == null) vi = inflater.inflate(R.layout.server_row, parent, false)
@ -83,22 +96,17 @@ internal class ServerRowAdapter(
val layout = vi?.findViewById<ConstraintLayout>(R.id.server_layout)
val image = vi?.findViewById<ImageView>(R.id.server_image)
val serverMenu = vi?.findViewById<ImageButton>(R.id.server_menu)
val setting = data.singleOrNull { t -> t.index == index }
val setting = data.singleOrNull { t -> t.index == position }
if (index == 0) {
text?.text = context.getString(R.string.main_offline)
description?.text = ""
} else {
text?.text = setting?.name ?: ""
description?.text = setting?.url ?: ""
if (setting == null) serverMenu?.visibility = View.INVISIBLE
}
text?.text = setting?.name ?: ""
description?.text = setting?.url ?: ""
if (setting == null) serverMenu?.visibility = View.INVISIBLE
val icon: Drawable?
val background: Drawable?
// Configure icons for the row
if (index == 0) {
if (setting?.id == ActiveServerProvider.OFFLINE_DB_ID) {
serverMenu?.visibility = View.INVISIBLE
icon = Util.getDrawableFromAttribute(context, R.attr.screen_on_off)
background = ContextCompat.getDrawable(context, R.drawable.circle)
@ -116,7 +124,7 @@ internal class ServerRowAdapter(
image?.background = background
// Highlight the Active Server's row by changing its background
if (index == activeServerProvider.getActiveServer().index) {
if (position == activeServerProvider.getActiveServer().index) {
layout?.background = ContextCompat.getDrawable(context, R.drawable.select_ripple)
} else {
layout?.background = ContextCompat.getDrawable(context, R.drawable.default_ripple)
@ -128,7 +136,7 @@ internal class ServerRowAdapter(
R.drawable.select_ripple_circle
)
serverMenu?.setOnClickListener { view -> serverMenuClick(view, index) }
serverMenu?.setOnClickListener { view -> serverMenuClick(view, position) }
return vi
}
@ -192,7 +200,8 @@ internal class ServerRowAdapter(
return true
}
MENU_ID_DELETE -> {
serverDeletedCallback.invoke(position)
val server = getItem(position) as ServerSetting
serverDeletedCallback.invoke(server.id)
return true
}
MENU_ID_UP -> {

View File

@ -1,6 +1,5 @@
package org.moire.ultrasonic.data
import androidx.lifecycle.MutableLiveData
import androidx.room.Room
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -11,6 +10,7 @@ import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.di.DB_FILENAME
import org.moire.ultrasonic.service.MusicServiceFactory.resetMusicService
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
@ -52,12 +52,32 @@ class ActiveServerProvider(
}
// Fallback to Offline
setActiveServerId(OFFLINE_DB_ID)
setActiveServerById(OFFLINE_DB_ID)
}
return OFFLINE_DB
}
/**
* Resolves the index (sort order) of a server to its id (unique)
* @param index: The index of the server in the server selector
* @return id: The unique id of the server
*/
fun getServerIdFromIndex(index: Int): Int {
if (index <= OFFLINE_DB_INDEX) {
// Offline mode is selected
return OFFLINE_DB_ID
}
var id: Int
runBlocking {
id = repository.findByIndex(index)?.id ?: 0
}
return id
}
/**
* Sets the Active Server by the Server Index in the Server Selector List
* @param index: The index of the Active Server in the Server Selector List
@ -66,13 +86,13 @@ class ActiveServerProvider(
Timber.d("setActiveServerByIndex $index")
if (index <= OFFLINE_DB_INDEX) {
// Offline mode is selected
setActiveServerId(OFFLINE_DB_ID)
setActiveServerById(OFFLINE_DB_ID)
return
}
launch {
val serverId = repository.findByIndex(index)?.id ?: 0
setActiveServerId(serverId)
setActiveServerById(serverId)
}
}
@ -180,8 +200,6 @@ class ActiveServerProvider(
minimumApiVersion = null
)
val liveActiveServerId: MutableLiveData<Int> = MutableLiveData(getActiveServerId())
/**
* Queries if the Active Server is the "Offline" mode of Ultrasonic
* @return True, if the "Offline" mode is selected
@ -198,13 +216,16 @@ class ActiveServerProvider(
}
/**
* Sets the Id of the Active Server
* Sets the Active Server by its unique id
* @param serverId: The id of the desired server
*/
fun setActiveServerId(serverId: Int) {
fun setActiveServerById(serverId: Int) {
resetMusicService()
Settings.activeServer = serverId
liveActiveServerId.postValue(serverId)
Timber.i("setActiveServerById done, new id: %s", serverId)
RxBus.activeServerChangePublisher.onNext(serverId)
}
/**

View File

@ -9,14 +9,12 @@ import android.widget.ListView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.ServerRowAdapter
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ServerSetting
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
import org.moire.ultrasonic.model.ServerSettingsModel
import org.moire.ultrasonic.service.MediaPlayerController
@ -26,6 +24,8 @@ import timber.log.Timber
/**
* Displays the list of configured servers, they can be selected or edited
*
* TODO: Manage mode is unused. Remove it...
*/
class ServerSelectorFragment : Fragment() {
companion object {
@ -59,6 +59,7 @@ class ServerSelectorFragment : Fragment() {
SERVER_SELECTOR_MANAGE_MODE,
false
) ?: false
if (manageMode) {
FragmentTitle.setTitle(this, R.string.settings_server_manage_servers)
} else {
@ -72,31 +73,26 @@ class ServerSelectorFragment : Fragment() {
serverSettingsModel,
activeServerProvider,
manageMode,
{
i ->
onServerDeleted(i)
},
{
i ->
editServer(i)
}
::deleteServerById,
::editServerByIndex
)
listView?.adapter = serverRowAdapter
listView?.onItemClickListener = AdapterView.OnItemClickListener {
_, _, position, _ ->
listView?.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val server = parent.getItemAtPosition(position) as ServerSetting
if (manageMode) {
editServer(position + 1)
editServerByIndex(position + 1)
} else {
setActiveServer(position)
setActiveServerById(server.id)
findNavController().popBackStack(R.id.mainFragment, false)
}
}
val fab = view.findViewById<FloatingActionButton>(R.id.server_add_fab)
fab.setOnClickListener {
editServer(-1)
editServerByIndex(-1)
}
}
@ -113,44 +109,37 @@ class ServerSelectorFragment : Fragment() {
/**
* Sets the active server when a list item is clicked
*/
private fun setActiveServer(index: Int) {
// TODO this is still a blocking call - we shouldn't leave this activity before the active server is updated.
// Maybe this can be refactored by using LiveData, or this can be made more user friendly with a ProgressDialog
runBlocking {
controller.clearIncomplete()
withContext(Dispatchers.IO) {
if (activeServerProvider.getActiveServer().index != index) {
activeServerProvider.setActiveServerByIndex(index)
}
}
controller.isJukeboxEnabled =
activeServerProvider.getActiveServer().jukeboxByDefault
private fun setActiveServerById(id: Int) {
controller.clearIncomplete()
if (activeServerProvider.getActiveServer().id != id) {
ActiveServerProvider.setActiveServerById(id)
}
Timber.i("Active server was set to: $index")
}
/**
* This Callback handles the deletion of a Server Setting
*/
private fun onServerDeleted(index: Int) {
private fun deleteServerById(id: Int) {
ErrorDialog.Builder(context)
.setTitle(R.string.server_menu_delete)
.setMessage(R.string.server_selector_delete_confirmation)
.setPositiveButton(R.string.common_delete) { dialog, _ ->
dialog.dismiss()
val activeServerIndex = activeServerProvider.getActiveServer().index
val id = ActiveServerProvider.getActiveServerId()
// Get the id of the current active server
val activeServerId = ActiveServerProvider.getActiveServerId()
// If the currently active server is deleted, go offline
if (index == activeServerIndex) setActiveServer(-1)
if (id == activeServerId) setActiveServerById(ActiveServerProvider.OFFLINE_DB_ID)
serverSettingsModel.deleteItem(index)
serverSettingsModel.deleteItemById(id)
// Clear the metadata cache
activeServerProvider.deleteMetaDatabase(id)
activeServerProvider.deleteMetaDatabase(activeServerId)
Timber.i("Server deleted: $index")
Timber.i("Server deleted, id: $id")
}
.setNegativeButton(R.string.common_cancel) { dialog, _ ->
dialog.dismiss()
@ -161,7 +150,7 @@ class ServerSelectorFragment : Fragment() {
/**
* Starts the Edit Server Fragment to edit the details of a server
*/
private fun editServer(index: Int) {
private fun editServerByIndex(index: Int) {
val bundle = Bundle()
bundle.putInt(EDIT_SERVER_INTENT_INDEX, index)
findNavController().navigate(R.id.serverSelectorToEditServer, bundle)

View File

@ -12,6 +12,7 @@ import kotlinx.coroutines.runBlocking
import org.moire.ultrasonic.R
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.data.ServerSetting
import org.moire.ultrasonic.data.ServerSettingDao
import timber.log.Timber
@ -30,6 +31,8 @@ class ServerSettingsModel(
/**
* Retrieves the list of the configured servers from the database.
* This function is asynchronous, uses LiveData to provide the Setting.
*
* It does not include the Offline "server".
*/
fun getServerList(): LiveData<List<ServerSetting>> {
// This check should run before returning any result
@ -92,14 +95,14 @@ class ServerSettingsModel(
/**
* Removes a Setting from the database
*/
fun deleteItem(index: Int) {
if (index == 0) return
fun deleteItemById(id: Int) {
if (id == OFFLINE_DB_ID) return
viewModelScope.launch {
val itemToBeDeleted = repository.findByIndex(index)
val itemToBeDeleted = repository.findById(id)
if (itemToBeDeleted != null) {
repository.delete(itemToBeDeleted)
Timber.d("deleteItem deleted index: $index")
Timber.d("deleteItem deleted id: $id")
reindexSettings()
activeServerProvider.invalidateCache()
}

View File

@ -7,12 +7,12 @@
package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.net.Uri
import androidx.core.net.toUri
import androidx.media3.common.C
import androidx.media3.common.PlaybackException
import androidx.media3.common.util.Assertions
import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util
import androidx.media3.datasource.BaseDataSource
import androidx.media3.datasource.DataSourceException
@ -43,15 +43,15 @@ import timber.log.Timber
* priority) the `dataSpec`, [.setRequestProperty] and the default parameters used to
* construct the instance.
*/
@SuppressLint("UnsafeOptInUsageError")
@Suppress("MagicNumber")
@UnstableApi
open class APIDataSource private constructor(
subsonicAPIClient: SubsonicAPIClient
) : BaseDataSource(true),
HttpDataSource {
/** [DataSource.Factory] for [APIDataSource] instances. */
class Factory(private val subsonicAPIClient: SubsonicAPIClient) : HttpDataSource.Factory {
class Factory(private var subsonicAPIClient: SubsonicAPIClient) : HttpDataSource.Factory {
private val defaultRequestProperties: RequestProperties = RequestProperties()
private var transferListener: TransferListener? = null
@ -75,6 +75,10 @@ open class APIDataSource private constructor(
return this
}
fun setAPIClient(newClient: SubsonicAPIClient) {
this.subsonicAPIClient = newClient
}
override fun createDataSource(): APIDataSource {
val dataSource = APIDataSource(
subsonicAPIClient
@ -318,6 +322,7 @@ open class APIDataSource private constructor(
return C.RESULT_END_OF_INPUT
}
bytesRead += read.toLong()
// TODO
// bytesTransferred(read)
return read
}

View File

@ -29,21 +29,27 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
class PlaybackService : MediaLibraryService(), KoinComponent {
private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var apiDataSource: APIDataSource.Factory
private lateinit var dataSourceFactory: DataSource.Factory
private lateinit var librarySessionCallback: MediaLibrarySession.MediaLibrarySessionCallback
private var rxBusSubscription = CompositeDisposable()
/*
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
* and thereby customarily it is required to rebuild it..
@ -64,11 +70,18 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
override fun onCreate() {
super.onCreate()
initializeSessionAndPlayer()
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
// Update the API endpoint when the active server has changed
val newClient: SubsonicAPIClient by inject()
apiDataSource.setAPIClient(newClient)
}
}
override fun onDestroy() {
player.release()
mediaLibrarySession.release()
rxBusSubscription.dispose()
super.onDestroy()
}
@ -88,8 +101,10 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
val subsonicAPIClient: SubsonicAPIClient by inject()
// Create a MediaSource which passes calls through our OkHttp Stack
apiDataSource = APIDataSource.Factory(subsonicAPIClient)
dataSourceFactory = APIDataSource.Factory(subsonicAPIClient)
val cacheDataSourceFactory: DataSource.Factory = CachedDataSource.Factory(dataSourceFactory)
val cacheDataSourceFactory: DataSource.Factory = CachedDataSource.Factory(apiDataSource)
// Create a renderer with HW rendering support
val renderer = DefaultRenderersFactory(this)

View File

@ -18,6 +18,7 @@ import androidx.media3.common.Timeline
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.MoreExecutors
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
@ -61,6 +62,8 @@ class MediaPlayerController(
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
private val activeServerProvider: ActiveServerProvider by inject()
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
private var sessionToken =
SessionToken(context, ComponentName(context, PlaybackService::class.java))
@ -109,6 +112,11 @@ class MediaPlayerController(
// controller?.play()
}, MoreExecutors.directExecutor())
rxBusSubscription += RxBus.activeServerChangeObservable.subscribe {
// Update the Jukebox state when the active server has changed
isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault
}
created = true
Timber.i("MediaPlayerController created")
}

View File

@ -27,8 +27,14 @@ import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.di.OFFLINE_MUSIC_SERVICE
import org.moire.ultrasonic.di.ONLINE_MUSIC_SERVICE
import org.moire.ultrasonic.di.musicServiceModule
import timber.log.Timber
// TODO Refactor everywhere to use DI way to get MusicService, and then remove this class
/*
* TODO: When resetMusicService is called, a large number of classes are completely newly instantiated,
* which take quite a bit of time.
*
* Instead it would probably be faster to listen to Rx
*/
object MusicServiceFactory : KoinComponent {
@JvmStatic
fun getMusicService(): MusicService {
@ -45,6 +51,7 @@ object MusicServiceFactory : KoinComponent {
*/
@JvmStatic
fun resetMusicService() {
Timber.i("Regenerating Koin Music Service Module")
unloadKoinModules(musicServiceModule)
loadKoinModules(musicServiceModule)
}

View File

@ -1,23 +1,19 @@
package org.moire.ultrasonic.service
import android.os.Bundle
import android.support.v4.media.session.MediaSessionCompat
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.subjects.PublishSubject
import java.util.concurrent.TimeUnit
import org.moire.ultrasonic.domain.PlayerState
class RxBus {
companion object {
var mediaSessionTokenPublisher: PublishSubject<MediaSessionCompat.Token> =
var activeServerChangePublisher: PublishSubject<Int> =
PublishSubject.create()
val mediaSessionTokenObservable: Observable<MediaSessionCompat.Token> =
mediaSessionTokenPublisher.observeOn(AndroidSchedulers.mainThread())
.replay(1)
.autoConnect(0)
var activeServerChangeObservable: Observable<Int> =
activeServerChangePublisher.observeOn(AndroidSchedulers.mainThread())
val themeChangedEventPublisher: PublishSubject<Unit> =
PublishSubject.create()
@ -43,38 +39,11 @@ class RxBus {
.replay(1)
.autoConnect(0)
val playbackPositionPublisher: PublishSubject<Int> =
PublishSubject.create()
val playbackPositionObservable: Observable<Int> =
playbackPositionPublisher.observeOn(AndroidSchedulers.mainThread())
.throttleFirst(1, TimeUnit.SECONDS)
.replay(1)
.autoConnect(0)
// Commands
val dismissNowPlayingCommandPublisher: PublishSubject<Unit> =
PublishSubject.create()
val dismissNowPlayingCommandObservable: Observable<Unit> =
dismissNowPlayingCommandPublisher.observeOn(AndroidSchedulers.mainThread())
val playFromMediaIdCommandPublisher: PublishSubject<Pair<String?, Bundle?>> =
PublishSubject.create()
val playFromMediaIdCommandObservable: Observable<Pair<String?, Bundle?>> =
playFromMediaIdCommandPublisher.observeOn(AndroidSchedulers.mainThread())
val playFromSearchCommandPublisher: PublishSubject<Pair<String?, Bundle?>> =
PublishSubject.create()
val playFromSearchCommandObservable: Observable<Pair<String?, Bundle?>> =
playFromSearchCommandPublisher.observeOn(AndroidSchedulers.mainThread())
val skipToQueueItemCommandPublisher: PublishSubject<Long> =
PublishSubject.create()
val skipToQueueItemCommandObservable: Observable<Long> =
skipToQueueItemCommandPublisher.observeOn(AndroidSchedulers.mainThread())
fun releaseMediaSessionToken() {
mediaSessionTokenPublisher = PublishSubject.create()
}
}
data class StateWithTrack(val state: PlayerState, val track: DownloadFile?, val index: Int = -1)