diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 535a4028..205ff394 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -2,8 +2,6 @@ - ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background - ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) ) ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file) ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name) @@ -12,7 +10,7 @@ LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?) LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken ) - LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) + LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192 MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$0.05f MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$50 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index fac3e504..5eacc74a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -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() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt index 8790bf7c..1ec4fbe7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt @@ -30,7 +30,7 @@ import org.moire.ultrasonic.util.Util */ internal class ServerRowAdapter( private var context: Context, - private var data: Array, + passedData: Array, 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 = 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) { - 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(R.id.server_layout) val image = vi?.findViewById(R.id.server_image) val serverMenu = vi?.findViewById(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 -> { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt index e132c67f..1526dd7e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -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 = 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) } /** diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt index 730d8bd1..0798950b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt @@ -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(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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt index 4f4d1226..3e62541d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt @@ -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> { // 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() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/APIDataSource.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/APIDataSource.kt index a2747301..75789ae7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/APIDataSource.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/APIDataSource.kt @@ -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 } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt index 03f556c5..2a08df67 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt @@ -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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt index d591d630..98bb9206 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -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") } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt index 02ddb4a5..865dc29d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicServiceFactory.kt @@ -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) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt index 1ffb89b7..eb49df0d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt @@ -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 = + + var activeServerChangePublisher: PublishSubject = PublishSubject.create() - val mediaSessionTokenObservable: Observable = - mediaSessionTokenPublisher.observeOn(AndroidSchedulers.mainThread()) - .replay(1) - .autoConnect(0) + var activeServerChangeObservable: Observable = + activeServerChangePublisher.observeOn(AndroidSchedulers.mainThread()) val themeChangedEventPublisher: PublishSubject = PublishSubject.create() @@ -43,38 +39,11 @@ class RxBus { .replay(1) .autoConnect(0) - val playbackPositionPublisher: PublishSubject = - PublishSubject.create() - val playbackPositionObservable: Observable = - playbackPositionPublisher.observeOn(AndroidSchedulers.mainThread()) - .throttleFirst(1, TimeUnit.SECONDS) - .replay(1) - .autoConnect(0) - // Commands val dismissNowPlayingCommandPublisher: PublishSubject = PublishSubject.create() val dismissNowPlayingCommandObservable: Observable = dismissNowPlayingCommandPublisher.observeOn(AndroidSchedulers.mainThread()) - - val playFromMediaIdCommandPublisher: PublishSubject> = - PublishSubject.create() - val playFromMediaIdCommandObservable: Observable> = - playFromMediaIdCommandPublisher.observeOn(AndroidSchedulers.mainThread()) - - val playFromSearchCommandPublisher: PublishSubject> = - PublishSubject.create() - val playFromSearchCommandObservable: Observable> = - playFromSearchCommandPublisher.observeOn(AndroidSchedulers.mainThread()) - - val skipToQueueItemCommandPublisher: PublishSubject = - PublishSubject.create() - val skipToQueueItemCommandObservable: Observable = - skipToQueueItemCommandPublisher.observeOn(AndroidSchedulers.mainThread()) - - fun releaseMediaSessionToken() { - mediaSessionTokenPublisher = PublishSubject.create() - } } data class StateWithTrack(val state: PlayerState, val track: DownloadFile?, val index: Int = -1)