mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
fix: Resolve playback issues with live radio MPEG & HLS streams
This commit is contained in:
parent
c62d2ace4d
commit
e43a2b6fe5
3 changed files with 103 additions and 31 deletions
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
@ -78,35 +79,36 @@ public final class DownloadUtil {
|
||||||
return httpDataSourceFactory;
|
return httpDataSourceFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
|
public static synchronized DataSource.Factory getUpstreamDataSourceFactory(Context context) {
|
||||||
if (dataSourceFactory == null) {
|
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||||
context = context.getApplicationContext();
|
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||||
|
|
||||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
|
||||||
|
|
||||||
if (Preferences.getStreamingCacheSize() > 0) {
|
|
||||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
|
||||||
.setCache(getStreamingCache(context))
|
|
||||||
.setUpstreamDataSourceFactory(upstreamFactory);
|
|
||||||
|
|
||||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
|
||||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
|
||||||
dataSpec -> {
|
|
||||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
|
||||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
|
||||||
} else {
|
|
||||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataSourceFactory;
|
return dataSourceFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized DataSource.Factory getCacheDataSourceFactory(Context context) {
|
||||||
|
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||||
|
.setCache(getStreamingCache(context))
|
||||||
|
.setUpstreamDataSourceFactory(getUpstreamDataSourceFactory(context));
|
||||||
|
|
||||||
|
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||||
|
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||||
|
dataSpec -> {
|
||||||
|
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||||
|
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||||
|
return dataSourceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldBypassCache(Uri uri) {
|
||||||
|
if (uri == null) return true;
|
||||||
|
String url = uri.toString();
|
||||||
|
String mainServer = Preferences.getServer();
|
||||||
|
return mainServer != null && !url.startsWith(mainServer);
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(Context context) {
|
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(Context context) {
|
||||||
if (downloadNotificationHelper == null) {
|
if (downloadNotificationHelper == null) {
|
||||||
downloadNotificationHelper = new DownloadNotificationHelper(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
|
downloadNotificationHelper = new DownloadNotificationHelper(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.MimeTypes
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider
|
||||||
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||||
|
import androidx.media3.extractor.DefaultExtractorsFactory
|
||||||
|
import androidx.media3.extractor.ExtractorsFactory
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
class DynamicMediaSourceFactory(
|
||||||
|
private val context: Context
|
||||||
|
) : MediaSource.Factory {
|
||||||
|
|
||||||
|
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
||||||
|
val uri: Uri = mediaItem.localConfiguration?.uri ?: mediaItem.requestMetadata.mediaUri
|
||||||
|
?: throw IllegalArgumentException("MediaItem must contain a valid URI")
|
||||||
|
|
||||||
|
val streamingCacheSize = Preferences.getStreamingCacheSize()
|
||||||
|
val bypassCache = DownloadUtil.shouldBypassCache(uri)
|
||||||
|
|
||||||
|
val useUpstream = when {
|
||||||
|
streamingCacheSize.toInt() == 0 -> true
|
||||||
|
streamingCacheSize > 0 && bypassCache -> true
|
||||||
|
streamingCacheSize > 0 && !bypassCache -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
val dataSourceFactory: DataSource.Factory = if (useUpstream) {
|
||||||
|
DownloadUtil.getUpstreamDataSourceFactory(context)
|
||||||
|
} else {
|
||||||
|
DownloadUtil.getCacheDataSourceFactory(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
mediaItem.localConfiguration?.mimeType == MimeTypes.APPLICATION_M3U8 ||
|
||||||
|
uri.toString().endsWith(".m3u8") -> {
|
||||||
|
HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory()
|
||||||
|
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||||
|
.createMediaSource(mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedTypes(): IntArray {
|
||||||
|
return intArrayOf(
|
||||||
|
C.CONTENT_TYPE_HLS,
|
||||||
|
C.CONTENT_TYPE_OTHER
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,6 @@ import androidx.media3.common.*
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
||||||
import androidx.media3.exoplayer.source.TrackGroupArray
|
import androidx.media3.exoplayer.source.TrackGroupArray
|
||||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray
|
import androidx.media3.exoplayer.trackselection.TrackSelectionArray
|
||||||
import androidx.media3.session.*
|
import androidx.media3.session.*
|
||||||
|
|
@ -19,6 +18,7 @@ import com.cappielloantonio.tempo.R
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
import com.cappielloantonio.tempo.util.Constants
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
|
import com.cappielloantonio.tempo.service.DynamicMediaSourceFactory
|
||||||
import com.cappielloantonio.tempo.util.Preferences
|
import com.cappielloantonio.tempo.util.Preferences
|
||||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
|
@ -186,7 +186,7 @@ class MediaService : MediaLibraryService() {
|
||||||
private fun initializePlayer() {
|
private fun initializePlayer() {
|
||||||
player = ExoPlayer.Builder(this)
|
player = ExoPlayer.Builder(this)
|
||||||
.setRenderersFactory(getRenderersFactory())
|
.setRenderersFactory(getRenderersFactory())
|
||||||
.setMediaSourceFactory(getMediaSourceFactory())
|
.setMediaSourceFactory(DynamicMediaSourceFactory(this))
|
||||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||||
.setHandleAudioBecomingNoisy(true)
|
.setHandleAudioBecomingNoisy(true)
|
||||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
|
|
@ -346,7 +346,4 @@ class MediaService : MediaLibraryService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||||
|
|
||||||
private fun getMediaSourceFactory() =
|
|
||||||
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue