mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-28 22:03:01 +02:00
Refactors PlaybackService
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.playback
|
||||||
|
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||||
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLS LiveStreams cannot use cache.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
class CustomMediaSourceFactory : MediaSource.Factory {
|
||||||
|
private var cachingFactory: MediaSource.Factory =
|
||||||
|
DefaultMediaSourceFactory(Amethyst.instance.videoCache.get(HttpClientManager.getHttpClient()))
|
||||||
|
private var nonCachingFactory: MediaSource.Factory =
|
||||||
|
DefaultMediaSourceFactory(OkHttpDataSource.Factory(HttpClientManager.getHttpClient()))
|
||||||
|
|
||||||
|
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
|
||||||
|
cachingFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
||||||
|
nonCachingFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
|
||||||
|
cachingFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
|
||||||
|
nonCachingFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedTypes(): IntArray = nonCachingFactory.supportedTypes
|
||||||
|
|
||||||
|
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
||||||
|
if (mediaItem.mediaId.contains(".m3u8", true)) {
|
||||||
|
return nonCachingFactory.createMediaSource(mediaItem)
|
||||||
|
}
|
||||||
|
return cachingFactory.createMediaSource(mediaItem)
|
||||||
|
}
|
||||||
|
}
|
@@ -68,14 +68,13 @@ class MultiPlayerPlaybackManager(
|
|||||||
private fun getCallbackIntent(
|
private fun getCallbackIntent(
|
||||||
callbackUri: String,
|
callbackUri: String,
|
||||||
applicationContext: Context,
|
applicationContext: Context,
|
||||||
): PendingIntent {
|
): PendingIntent =
|
||||||
return PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
0,
|
0,
|
||||||
Intent(Intent.ACTION_VIEW, callbackUri.toUri(), applicationContext, MainActivity::class.java),
|
Intent(Intent.ACTION_VIEW, callbackUri.toUri(), applicationContext, MainActivity::class.java),
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
fun getMediaSession(
|
fun getMediaSession(
|
||||||
@@ -86,8 +85,6 @@ class MultiPlayerPlaybackManager(
|
|||||||
applicationContext: Context,
|
applicationContext: Context,
|
||||||
): MediaSession {
|
): MediaSession {
|
||||||
val existingSession = playingMap.get(id) ?: cache.get(id)
|
val existingSession = playingMap.get(id) ?: cache.get(id)
|
||||||
// avoids saving positions for live streams otherwise caching goes crazy
|
|
||||||
val mustCachePositions = !uri.contains(".m3u8", true)
|
|
||||||
if (existingSession != null) return existingSession
|
if (existingSession != null) return existingSession
|
||||||
|
|
||||||
val player =
|
val player =
|
||||||
@@ -111,6 +108,9 @@ class MultiPlayerPlaybackManager(
|
|||||||
|
|
||||||
player.addListener(
|
player.addListener(
|
||||||
object : Player.Listener {
|
object : Player.Listener {
|
||||||
|
// avoids saving positions for live streams otherwise caching goes crazy
|
||||||
|
val mustCachePositions = !uri.contains(".m3u8", true)
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
player.setWakeMode(C.WAKE_MODE_NETWORK)
|
player.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
@@ -180,7 +180,5 @@ class MultiPlayerPlaybackManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun playingContent(): Collection<MediaSession> {
|
fun playingContent(): Collection<MediaSession> = playingMap.values
|
||||||
return playingMap.values
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,70 +23,23 @@ package com.vitorpamplona.amethyst.service.playback
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider
|
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
|
||||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.MediaSessionService
|
import androidx.media3.session.MediaSessionService
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
|
||||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||||
|
|
||||||
/**
|
|
||||||
* HLS LiveStreams cannot use cache.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
class CustomMediaSourceFactory() : MediaSource.Factory {
|
|
||||||
private var cachingFactory: MediaSource.Factory =
|
|
||||||
DefaultMediaSourceFactory(Amethyst.instance.videoCache.get(HttpClientManager.getHttpClient()))
|
|
||||||
private var nonCachingFactory: MediaSource.Factory =
|
|
||||||
DefaultMediaSourceFactory(OkHttpDataSource.Factory(HttpClientManager.getHttpClient()))
|
|
||||||
|
|
||||||
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
|
|
||||||
cachingFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
|
||||||
nonCachingFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
|
|
||||||
cachingFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
|
|
||||||
nonCachingFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportedTypes(): IntArray {
|
|
||||||
return nonCachingFactory.supportedTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
|
||||||
if (mediaItem.mediaId.contains(".m3u8", true)) {
|
|
||||||
return nonCachingFactory.createMediaSource(mediaItem)
|
|
||||||
}
|
|
||||||
return cachingFactory.createMediaSource(mediaItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlaybackService : MediaSessionService() {
|
class PlaybackService : MediaSessionService() {
|
||||||
private var videoViewedPositionCache = VideoViewedPositionCache()
|
private var videoViewedPositionCache = VideoViewedPositionCache()
|
||||||
|
|
||||||
private var managerAllInOne: MultiPlayerPlaybackManager? = null
|
private var managerAllInOne: MultiPlayerPlaybackManager? = null
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun newAllInOneDataSource(): MediaSource.Factory {
|
|
||||||
// This might be needed for live kit.
|
|
||||||
// return WssOrHttpFactory(HttpClientManager.getHttpClient())
|
|
||||||
return CustomMediaSourceFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lazyDS(): MultiPlayerPlaybackManager {
|
fun lazyDS(): MultiPlayerPlaybackManager {
|
||||||
managerAllInOne?.let {
|
managerAllInOne?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
val newInstance = MultiPlayerPlaybackManager(newAllInOneDataSource(), videoViewedPositionCache)
|
val newInstance = MultiPlayerPlaybackManager(CustomMediaSourceFactory(), videoViewedPositionCache)
|
||||||
managerAllInOne = newInstance
|
managerAllInOne = newInstance
|
||||||
return newInstance
|
return newInstance
|
||||||
}
|
}
|
||||||
@@ -102,10 +55,11 @@ class PlaybackService : MediaSessionService() {
|
|||||||
HttpClientManager.proxyChangeListeners.add(this@PlaybackService::onProxyUpdated)
|
HttpClientManager.proxyChangeListeners.add(this@PlaybackService::onProxyUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private fun onProxyUpdated() {
|
private fun onProxyUpdated() {
|
||||||
val toDestroyAllInOne = managerAllInOne
|
val toDestroyAllInOne = managerAllInOne
|
||||||
|
|
||||||
managerAllInOne = MultiPlayerPlaybackManager(newAllInOneDataSource(), videoViewedPositionCache)
|
managerAllInOne = MultiPlayerPlaybackManager(CustomMediaSourceFactory(), videoViewedPositionCache)
|
||||||
|
|
||||||
toDestroyAllInOne?.releaseAppPlayers()
|
toDestroyAllInOne?.releaseAppPlayers()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user