ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/PlaybackService.kt

153 lines
5.8 KiB
Kotlin

/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.C.CONTENT_TYPE_MUSIC
import androidx.media3.common.C.USAGE_MEDIA
import androidx.media3.common.MediaItem
import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
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..
*/
private class CustomMediaItemFiller : MediaSession.MediaItemFiller {
override fun fillInLocalConfiguration(
session: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItem: MediaItem
): MediaItem {
// Again, set the Uri, so that it will get a LocalConfiguration
return mediaItem.buildUpon()
.setUri(mediaItem.mediaMetadata.mediaUri)
.build()
}
}
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()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
private fun initializeSessionAndPlayer() {
/*
* TODO:
* * Could be refined to use WAKE_MODE_LOCAL when offline....
*/
setMediaNotificationProvider(MediaNotificationProvider(UApp.applicationContext()))
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(apiDataSource)
// Create a renderer with HW rendering support
val renderer = DefaultRenderersFactory(this)
if (Settings.useHwOffload) renderer.setEnableAudioOffload(true)
// Create the player
player = ExoPlayer.Builder(this)
.setAudioAttributes(getAudioAttributes(), true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.setHandleAudioBecomingNoisy(true)
.setMediaSourceFactory(DefaultMediaSourceFactory(cacheDataSourceFactory))
.setRenderersFactory(renderer)
.build()
// Enable audio offload
if (Settings.useHwOffload)
player.experimentalSetOffloadSchedulingEnabled(true)
// Create browser interface
librarySessionCallback = AutoMediaBrowserCallback(player)
// This will need to use the AutoCalls
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setMediaItemFiller(CustomMediaItemFiller())
.setSessionActivity(getPendingIntentForContent())
.build()
}
@SuppressLint("UnspecifiedImmutableFlag")
private fun getPendingIntentForContent(): PendingIntent {
val intent = Intent(this, NavigationActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val flags = PendingIntent.FLAG_UPDATE_CURRENT
intent.putExtra(Constants.INTENT_SHOW_PLAYER, true)
return PendingIntent.getActivity(this, 0, intent, flags)
}
private fun getAudioAttributes(): AudioAttributes {
return AudioAttributes.Builder()
.setUsage(USAGE_MEDIA)
.setContentType(CONTENT_TYPE_MUSIC)
.build()
}
}