Try to cache streaming contents

This commit is contained in:
Midori Kochiya 2024-03-11 17:21:28 +08:00
parent 3e1c3133ca
commit 3d3d0fa856
7 changed files with 152 additions and 1 deletions

View file

@ -8,10 +8,13 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.ResolvingDataSource;
import androidx.media3.datasource.cache.Cache;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
import androidx.media3.datasource.cache.NoOpCacheEvictor;
import androidx.media3.datasource.cache.SimpleCache;
import androidx.media3.exoplayer.DefaultRenderersFactory;
@ -42,6 +45,7 @@ public final class DownloadUtil {
private static DatabaseProvider databaseProvider;
private static File downloadDirectory;
private static Cache downloadCache;
private static SimpleCache streamingCache;
private static DownloadManager downloadManager;
private static DownloaderManager downloaderManager;
private static DownloadNotificationHelper downloadNotificationHelper;
@ -75,7 +79,27 @@ public final class DownloadUtil {
if (dataSourceFactory == null) {
context = context.getApplicationContext();
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
if (Preferences.getStreamingCacheSize() > 0) {
// Cache enabled
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 {
// Cache disabled
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
}
}
return dataSourceFactory;
@ -108,6 +132,18 @@ public final class DownloadUtil {
return downloadCache;
}
private static synchronized SimpleCache getStreamingCache(Context context) {
if (streamingCache == null) {
File streamingCacheDirectory = new File(context.getCacheDir(), "streamingCache");
streamingCache = new SimpleCache(
streamingCacheDirectory,
new LeastRecentlyUsedCacheEvictor(Preferences.getStreamingCacheSize() * 1024 * 1024),
getDatabaseProvider(context)
);
}
return streamingCache;
}
private static synchronized void ensureDownloadManagerInitialized(Context context) {
if (downloadManager == null) {
downloadManager = new DownloadManager(
@ -187,6 +223,10 @@ public final class DownloadUtil {
return files;
}
public static synchronized long getStreamingCacheSize(Context context) {
return getStreamingCache(context).getCacheSpace();
}
public static Notification buildGroupSummaryNotification(Context context, String channelId, String groupId, int icon, String title) {
return new NotificationCompat.Builder(context, channelId)
.setContentTitle(title)

View file

@ -22,6 +22,7 @@ object Preferences {
private const val PLAYBACK_SPEED = "playback_speed"
private const val SKIP_SILENCE = "skip_silence"
private const val IMAGE_CACHE_SIZE = "image_cache_size"
private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
private const val IMAGE_SIZE = "image_size"
private const val MAX_BITRATE_WIFI = "max_bitrate_wifi"
private const val MAX_BITRATE_MOBILE = "max_bitrate_mobile"
@ -187,6 +188,14 @@ object Preferences {
return App.getInstance().preferences.getString(IMAGE_SIZE, "-1")!!.toInt()
}
/**
* Size of streaming cache in MiB.
*/
@JvmStatic
fun getStreamingCacheSize(): Long {
return App.getInstance().preferences.getString(STREAMING_CACHE_SIZE, "256")!!.toLong()
}
@JvmStatic
fun getMaxBitrateWifi(): String {
return App.getInstance().preferences.getString(MAX_BITRATE_WIFI, "0")!!

View file

@ -0,0 +1,61 @@
package com.cappielloantonio.tempo.util
import android.net.Uri
import android.util.Log
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DataSpec
import androidx.media3.datasource.TransferListener
import androidx.media3.datasource.cache.CacheDataSource
import androidx.media3.datasource.cache.ContentMetadata
@UnstableApi
class StreamingCacheDataSource private constructor(
private val cacheDataSource: CacheDataSource,
): DataSource {
private val TAG = "StreamingCacheDataSource"
private var currentDataSpec: DataSpec? = null
class Factory(private val cacheDatasourceFactory: CacheDataSource.Factory): DataSource.Factory {
override fun createDataSource(): DataSource {
return StreamingCacheDataSource(cacheDatasourceFactory.createDataSource())
}
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
return cacheDataSource.read(buffer, offset, length)
}
override fun addTransferListener(transferListener: TransferListener) {
return cacheDataSource.addTransferListener(transferListener)
}
override fun open(dataSpec: DataSpec): Long {
val ret = cacheDataSource.open(dataSpec)
currentDataSpec = dataSpec
Log.d(TAG, "Opened $currentDataSpec")
return ret
}
override fun getUri(): Uri? {
return cacheDataSource.uri
}
override fun close() {
cacheDataSource.close()
Log.d(TAG, "Closed $currentDataSpec")
val dataSpec = currentDataSpec
if (dataSpec != null) {
val cacheKey = cacheDataSource.cacheKeyFactory.buildCacheKey(dataSpec)
val contentLength = ContentMetadata.getContentLength(cacheDataSource.cache.getContentMetadata(cacheKey));
if (contentLength == C.LENGTH_UNSET.toLong()) {
Log.d(TAG, "Removing partial cache for $cacheKey")
cacheDataSource.cache.removeResource(cacheKey)
} else {
Log.d(TAG, "Key $cacheKey has been fully cached")
}
}
}
}