diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index a2949e6bf..b85c7ae93 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -228,4 +228,7 @@ -keep public class org.chromium.net.* { !private *; *; -} \ No newline at end of file +} + +## 保证该私有变量不被混淆 +-keepclassmembers class com.google.android.exoplayer2.upstream.cache.CacheDataSource$Factory{upstreamDataSourceFactory;} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/exoplayer/ExoPlayerHelper.kt b/app/src/main/java/io/legado/app/help/exoplayer/ExoPlayerHelper.kt index 8579edde3..af826d6b0 100644 --- a/app/src/main/java/io/legado/app/help/exoplayer/ExoPlayerHelper.kt +++ b/app/src/main/java/io/legado/app/help/exoplayer/ExoPlayerHelper.kt @@ -4,16 +4,19 @@ import android.net.Uri import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource +import com.google.android.exoplayer2.offline.DefaultDownloaderFactory +import com.google.android.exoplayer2.offline.DownloadRequest +import com.google.android.exoplayer2.offline.DownloaderFactory import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource +import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.FileDataSource -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.CacheDataSink -import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor -import com.google.android.exoplayer2.upstream.cache.SimpleCache +import com.google.android.exoplayer2.upstream.cache.* import io.legado.app.help.http.okHttpClient +import okhttp3.CacheControl import splitties.init.appCtx import java.io.File +import java.util.concurrent.TimeUnit object ExoPlayerHelper { @@ -29,13 +32,42 @@ object ExoPlayerHelper { return mediaSourceFactory.createMediaSource(mediaItem) } + /** + * 预下载 + * @param uri 音频资源uri + * @param defaultRequestProperties 请求头 + * @param progressCallBack 下载进度回调 + */ + fun preDownload( + uri: Uri, + defaultRequestProperties: Map, + progressCallBack: (contentLength: Long, bytesDownloaded: Long, percentDownloaded: Float) -> Unit = { _: Long, _: Long, _: Float -> } + ) { + val request = DownloadRequest.Builder(uri.toString(), uri).build() + cacheDataSourceFactory.setDefaultRequestProperties(defaultRequestProperties) + okHttpClient.dispatcher.executorService.submit { + downloaderFactory.createDownloader(request) + .download { contentLength, bytesDownloaded, percentDownloaded -> + progressCallBack(contentLength, bytesDownloaded, percentDownloaded) + + } + + } + + } + + + private val downloaderFactory: DownloaderFactory by lazy { + DefaultDownloaderFactory(cacheDataSourceFactory, okHttpClient.dispatcher.executorService) + } + /** * 支持缓存的DataSource.Factory */ private val cacheDataSourceFactory by lazy { //使用自定义的CacheDataSource以支持设置UA - return@lazy OkhttpCacheDataSource.Factory() + return@lazy CacheDataSource.Factory() .setCache(cache) .setUpstreamDataSourceFactory(okhttpDataFactory) .setCacheReadDataSourceFactory(FileDataSource.Factory()) @@ -51,6 +83,7 @@ object ExoPlayerHelper { */ private val okhttpDataFactory by lazy { OkHttpDataSource.Factory(okHttpClient) + .setCacheControl(CacheControl.Builder().maxAge(1, TimeUnit.DAYS).build()) } /** @@ -68,4 +101,20 @@ object ExoPlayerHelper { ) } + /** + * 通过kotlin扩展函数+反射实现CacheDataSource.Factory设置默认请求头 + * 需要添加混淆规则 -keepclassmembers class com.google.android.exoplayer2.upstream.cache.CacheDataSource$Factory{upstreamDataSourceFactory;} + * @param headers + * @return + */ + private fun CacheDataSource.Factory.setDefaultRequestProperties(headers: Map = mapOf()): CacheDataSource.Factory { + val declaredField = this.javaClass.getDeclaredField("upstreamDataSourceFactory") + declaredField.isAccessible = true + val df = declaredField[this] as DataSource.Factory + if (df is OkHttpDataSource.Factory) { + df.setDefaultRequestProperties(headers) + } + return this + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/exoplayer/OkhttpCacheDataSource.java b/app/src/main/java/io/legado/app/help/exoplayer/OkhttpCacheDataSource.java deleted file mode 100644 index 479bb3b70..000000000 --- a/app/src/main/java/io/legado/app/help/exoplayer/OkhttpCacheDataSource.java +++ /dev/null @@ -1,940 +0,0 @@ -package io.legado.app.help.exoplayer; - -/* - * Copyright (C) 2016 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. - */ - -import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.Util.castNonNull; -import static java.lang.Math.min; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.LOCAL_VARIABLE; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE_USE; - -import android.net.Uri; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource; -import com.google.android.exoplayer2.upstream.DataSink; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSourceException; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DummyDataSource; -import com.google.android.exoplayer2.upstream.FileDataSource; -import com.google.android.exoplayer2.upstream.PriorityDataSource; -import com.google.android.exoplayer2.upstream.TeeDataSource; -import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.upstream.cache.Cache; -import com.google.android.exoplayer2.upstream.cache.Cache.CacheException; -import com.google.android.exoplayer2.upstream.cache.CacheDataSink; -import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory; -import com.google.android.exoplayer2.upstream.cache.CacheSpan; -import com.google.android.exoplayer2.upstream.cache.ContentMetadata; -import com.google.android.exoplayer2.upstream.cache.ContentMetadataMutations; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.PriorityTaskManager; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Collections; -import java.util.List; -import java.util.Map; - - -/** - * A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache - * when possible. When data is not cached it is requested from an upstream {@link DataSource} and - * written into the cache. - */ -@SuppressWarnings("unused") -public final class OkhttpCacheDataSource implements DataSource { - - /** - * {@link DataSource.Factory} for {@link OkhttpCacheDataSource} instances. - */ - @SuppressWarnings("unused") - public static final class Factory implements DataSource.Factory { - - private Cache cache; - private DataSource.Factory cacheReadDataSourceFactory; - @Nullable - private DataSink.Factory cacheWriteDataSinkFactory; - private CacheKeyFactory cacheKeyFactory; - private boolean cacheIsReadOnly; - @Nullable - private OkHttpDataSource.Factory upstreamDataSourceFactory; - @Nullable - private PriorityTaskManager upstreamPriorityTaskManager; - private int upstreamPriority; - private @OkhttpCacheDataSource.Flags int flags; - @Nullable - private OkhttpCacheDataSource.EventListener eventListener; - - public Factory() { - cacheReadDataSourceFactory = new FileDataSource.Factory(); - cacheKeyFactory = CacheKeyFactory.DEFAULT; - } - - /** - * Sets the cache that will be used. - * - *

Must be called before the factory is used. - * - * @param cache The cache that will be used. - * @return This factory. - */ - public Factory setCache(Cache cache) { - this.cache = cache; - return this; - } - - /** - * Returns the cache that will be used, or {@code null} if {@link #setCache} has yet to be - * called. - */ - @Nullable - public Cache getCache() { - return cache; - } - - /** - * Sets the {@link DataSource.Factory} for {@link DataSource DataSources} for reading from the - * cache. - * - *

The default is a {@link FileDataSource.Factory} in its default configuration. - * - * @param cacheReadDataSourceFactory The {@link DataSource.Factory} for reading from the cache. - * @return This factory. - */ - public Factory setCacheReadDataSourceFactory(DataSource.Factory cacheReadDataSourceFactory) { - this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; - return this; - } - - /** - * Sets the {@link DataSink.Factory} for generating {@link DataSink DataSinks} for writing data - * to the cache. Passing {@code null} causes the cache to be read-only. - * - *

The default is a {@link CacheDataSink.Factory} in its default configuration. - * - * @param cacheWriteDataSinkFactory The {@link DataSink.Factory} for generating {@link DataSink - * DataSinks} for writing data to the cache, or {@code null} to disable writing. - * @return This factory. - */ - public Factory setCacheWriteDataSinkFactory( - @Nullable DataSink.Factory cacheWriteDataSinkFactory) { - this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; - this.cacheIsReadOnly = cacheWriteDataSinkFactory == null; - return this; - } - - /** - * Sets the {@link CacheKeyFactory}. - * - *

The default is {@link CacheKeyFactory#DEFAULT}. - * - * @param cacheKeyFactory The {@link CacheKeyFactory}. - * @return This factory. - */ - public Factory setCacheKeyFactory(CacheKeyFactory cacheKeyFactory) { - this.cacheKeyFactory = cacheKeyFactory; - return this; - } - - /** - * Returns the {@link CacheKeyFactory} that will be used. - */ - public CacheKeyFactory getCacheKeyFactory() { - return cacheKeyFactory; - } - - /** - * Sets the {@link DataSource.Factory} for upstream {@link DataSource DataSources}, which are - * used to read data in the case of a cache miss. - * - *

The default is {@code null}, and so this method must be called before the factory is used - * in order for data to be read from upstream in the case of a cache miss. - * - * @param upstreamDataSourceFactory The upstream {@link DataSource} for reading data not in the - * cache, or {@code null} to cause failure in the case of a cache miss. - * @return This factory. - */ - public Factory setUpstreamDataSourceFactory( - @Nullable OkHttpDataSource.Factory upstreamDataSourceFactory) { - this.upstreamDataSourceFactory = upstreamDataSourceFactory; - return this; - } - - public Factory setUserAgent(String userAgent) { - if (this.upstreamDataSourceFactory != null) { - this.upstreamDataSourceFactory.setUserAgent(userAgent); - } - return this; - } - - public Factory setDefaultRequestProperties(Map defaultRequestProperties) { - if (this.upstreamDataSourceFactory != null) { - this.upstreamDataSourceFactory.setDefaultRequestProperties(defaultRequestProperties); - } - return this; - } - - /** - * Sets an optional {@link PriorityTaskManager} to use when requesting data from upstream. - * - *

If set, reads from the upstream {@link DataSource} will only be allowed to proceed if - * there are no higher priority tasks registered to the {@link PriorityTaskManager}. If there - * exists a higher priority task then {@link PriorityTaskManager.PriorityTooLowException} will - * be thrown instead. - * - *

Note that requests to {@link OkhttpCacheDataSource} instances are intended to be used as parts - * of (possibly larger) tasks that are registered with the {@link PriorityTaskManager}, and - * hence {@link OkhttpCacheDataSource} does not register a task by itself. This must be done - * by the surrounding code that uses the {@link OkhttpCacheDataSource} instances. - * - *

The default is {@code null}. - * - * @param upstreamPriorityTaskManager The upstream {@link PriorityTaskManager}. - * @return This factory. - */ - public Factory setUpstreamPriorityTaskManager( - @Nullable PriorityTaskManager upstreamPriorityTaskManager) { - this.upstreamPriorityTaskManager = upstreamPriorityTaskManager; - return this; - } - - /** - * Returns the {@link PriorityTaskManager} that will bs used when requesting data from upstream, - * or {@code null} if there is none. - */ - @Nullable - public PriorityTaskManager getUpstreamPriorityTaskManager() { - return upstreamPriorityTaskManager; - } - - /** - * Sets the priority to use when requesting data from upstream. The priority is only used if a - * {@link PriorityTaskManager} is set by calling {@link #setUpstreamPriorityTaskManager}. - * - *

The default is {@link C#PRIORITY_PLAYBACK}. - * - * @param upstreamPriority The priority to use when requesting data from upstream. - * @return This factory. - */ - public Factory setUpstreamPriority(int upstreamPriority) { - this.upstreamPriority = upstreamPriority; - return this; - } - - /** - * Sets the {@link OkhttpCacheDataSource.Flags}. - * - *

The default is {@code 0}. - * - * @param flags The {@link OkhttpCacheDataSource.Flags}. - * @return This factory. - */ - public Factory setFlags(@OkhttpCacheDataSource.Flags int flags) { - this.flags = flags; - return this; - } - - /** - * Sets the {link EventListener} to which events are delivered. - * - *

The default is {@code null}. - * - * @param eventListener The {@link EventListener}. - * @return This factory. - */ - public Factory setEventListener(@Nullable EventListener eventListener) { - this.eventListener = eventListener; - return this; - } - - @NonNull - @Override - public OkhttpCacheDataSource createDataSource() { - return createDataSourceInternal( - upstreamDataSourceFactory != null ? upstreamDataSourceFactory.createDataSource() : null, - flags, - upstreamPriority); - } - - /** - * Returns an instance suitable for downloading content. The created instance is equivalent to - * one that would be created by {@link #createDataSource()}, except: - * - *

- * - * @return An instance suitable for downloading content. - */ - public OkhttpCacheDataSource createDataSourceForDownloading() { - return createDataSourceInternal( - upstreamDataSourceFactory != null ? upstreamDataSourceFactory.createDataSource() : null, - flags | FLAG_BLOCK_ON_CACHE, - C.PRIORITY_DOWNLOAD); - } - - /** - * Returns an instance suitable for reading cached content as part of removing a download. The - * created instance is equivalent to one that would be created by {@link #createDataSource()}, - * except: - * - * - * - * @return An instance suitable for reading cached content as part of removing a download. - */ - public OkhttpCacheDataSource createDataSourceForRemovingDownload() { - return createDataSourceInternal( - /* upstreamDataSource= */ null, flags | FLAG_BLOCK_ON_CACHE, C.PRIORITY_DOWNLOAD); - } - - private OkhttpCacheDataSource createDataSourceInternal( - @Nullable DataSource upstreamDataSource, @Flags int flags, int upstreamPriority) { - Cache cache = checkNotNull(this.cache); - @Nullable DataSink cacheWriteDataSink; - if (cacheIsReadOnly || upstreamDataSource == null) { - cacheWriteDataSink = null; - } else if (cacheWriteDataSinkFactory != null) { - cacheWriteDataSink = cacheWriteDataSinkFactory.createDataSink(); - } else { - cacheWriteDataSink = new CacheDataSink.Factory().setCache(cache).createDataSink(); - } - return new OkhttpCacheDataSource( - cache, - upstreamDataSource, - cacheReadDataSourceFactory.createDataSource(), - cacheWriteDataSink, - cacheKeyFactory, - flags, - upstreamPriorityTaskManager, - upstreamPriority, - eventListener); - } - } - - /** - * Listener of {@link OkhttpCacheDataSource} events. - */ - public interface EventListener { - - /** - * Called when bytes have been read from the cache. - * - * @param cacheSizeBytes Current cache size in bytes. - * @param cachedBytesRead Total bytes read from the cache since this method was last called. - */ - void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead); - - /** - * Called when the current request ignores cache. - * - * @param reason Reason cache is bypassed. - */ - void onCacheIgnored(@CacheIgnoredReason int reason); - } - - /** - * Flags controlling the OkhttpCacheDataSource's behavior. Possible flag values are {@link - * #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link - * #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. - */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef( - flag = true, - value = { - FLAG_BLOCK_ON_CACHE, - FLAG_IGNORE_CACHE_ON_ERROR, - FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS - }) - public @interface Flags { - } - - /** - * A flag indicating whether we will block reads if the cache key is locked. If unset then data is - * read from upstream if the cache key is locked, regardless of whether the data is cached. - */ - public static final int FLAG_BLOCK_ON_CACHE = 1; - - /** - * A flag indicating whether the cache is bypassed following any cache related error. If set then - * cache related exceptions may be thrown for one cycle of open, read and close calls. Subsequent - * cycles of these calls will then bypass the cache. - */ - public static final int FLAG_IGNORE_CACHE_ON_ERROR = 1 << 1; // 2 - - /** - * A flag indicating that the cache should be bypassed for requests whose lengths are unset. This - * flag is provided for legacy reasons only. - */ - public static final int FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS = 1 << 2; // 4 - - /** - * Reasons the cache may be ignored. One of {@link #CACHE_IGNORED_REASON_ERROR} or {@link - * #CACHE_IGNORED_REASON_UNSET_LENGTH}. - */ - // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility - // with Kotlin usages from before TYPE_USE was added. - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) - @IntDef({CACHE_IGNORED_REASON_ERROR, CACHE_IGNORED_REASON_UNSET_LENGTH}) - public @interface CacheIgnoredReason { - } - - /** - * Cache not ignored. - */ - private static final int CACHE_NOT_IGNORED = -1; - - /** - * Cache ignored due to a cache related error. - */ - public static final int CACHE_IGNORED_REASON_ERROR = 0; - - /** - * Cache ignored due to a request with an unset length. - */ - public static final int CACHE_IGNORED_REASON_UNSET_LENGTH = 1; - - /** - * Minimum number of bytes to read before checking cache for availability. - */ - private static final long MIN_READ_BEFORE_CHECKING_CACHE = 100 * 1024; - - private final Cache cache; - private final DataSource cacheReadDataSource; - @Nullable - private final DataSource cacheWriteDataSource; - private final DataSource upstreamDataSource; - private final CacheKeyFactory cacheKeyFactory; - @Nullable - private final EventListener eventListener; - - private final boolean blockOnCache; - private final boolean ignoreCacheOnError; - private final boolean ignoreCacheForUnsetLengthRequests; - - @Nullable - private Uri actualUri; - @Nullable - private DataSpec requestDataSpec; - @Nullable - private DataSpec currentDataSpec; - @Nullable - private DataSource currentDataSource; - private long currentDataSourceBytesRead; - private long readPosition; - private long bytesRemaining; - @Nullable - private CacheSpan currentHoleSpan; - private boolean seenCacheError; - private boolean currentRequestIgnoresCache; - private long totalCachedBytesRead; - private long checkCachePosition; - - /** - * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. - * - * @param cache The cache. - * @param upstreamDataSource A {@link DataSource} for reading data not in the cache. If null, - * reading will fail if a cache miss occurs. - */ - public OkhttpCacheDataSource(Cache cache, @Nullable DataSource upstreamDataSource) { - this(cache, upstreamDataSource, /* flags= */ 0); - } - - /** - * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. - * - * @param cache The cache. - * @param upstreamDataSource A {@link DataSource} for reading data not in the cache. If null, - * reading will fail if a cache miss occurs. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. - */ - public OkhttpCacheDataSource(Cache cache, @Nullable DataSource upstreamDataSource, @Flags int flags) { - this( - cache, - upstreamDataSource, - new FileDataSource(), - new CacheDataSink(cache, CacheDataSink.DEFAULT_FRAGMENT_SIZE), - flags, - /* eventListener= */ null); - } - - /** - * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. One use of this constructor is to allow data to be transformed - * before it is written to disk. - * - * @param cache The cache. - * @param upstreamDataSource A {@link DataSource} for reading data not in the cache. If null, - * reading will fail if a cache miss occurs. - * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. - * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is - * accessed read-only. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. - * @param eventListener An optional {@link EventListener} to receive events. - */ - public OkhttpCacheDataSource( - Cache cache, - @Nullable DataSource upstreamDataSource, - DataSource cacheReadDataSource, - @Nullable DataSink cacheWriteDataSink, - @Flags int flags, - @Nullable EventListener eventListener) { - this( - cache, - upstreamDataSource, - cacheReadDataSource, - cacheWriteDataSink, - flags, - eventListener, - /* cacheKeyFactory= */ null); - } - - /** - * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. One use of this constructor is to allow data to be transformed - * before it is written to disk. - * - * @param cache The cache. - * @param upstreamDataSource A {@link DataSource} for reading data not in the cache. If null, - * reading will fail if a cache miss occurs. - * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. - * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is - * accessed read-only. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. - * @param eventListener An optional {@link EventListener} to receive events. - * @param cacheKeyFactory An optional factory for cache keys. - */ - public OkhttpCacheDataSource( - Cache cache, - @Nullable DataSource upstreamDataSource, - DataSource cacheReadDataSource, - @Nullable DataSink cacheWriteDataSink, - @Flags int flags, - @Nullable EventListener eventListener, - @Nullable CacheKeyFactory cacheKeyFactory) { - this( - cache, - upstreamDataSource, - cacheReadDataSource, - cacheWriteDataSink, - cacheKeyFactory, - flags, - /* upstreamPriorityTaskManager= */ null, - /* upstreamPriority= */ C.PRIORITY_PLAYBACK, - eventListener); - } - - private OkhttpCacheDataSource( - Cache cache, - @Nullable DataSource upstreamDataSource, - DataSource cacheReadDataSource, - @Nullable DataSink cacheWriteDataSink, - @Nullable CacheKeyFactory cacheKeyFactory, - @Flags int flags, - @Nullable PriorityTaskManager upstreamPriorityTaskManager, - int upstreamPriority, - @Nullable EventListener eventListener) { - this.cache = cache; - this.cacheReadDataSource = cacheReadDataSource; - this.cacheKeyFactory = cacheKeyFactory != null ? cacheKeyFactory : CacheKeyFactory.DEFAULT; - this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; - this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; - this.ignoreCacheForUnsetLengthRequests = - (flags & FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS) != 0; - if (upstreamDataSource != null) { - if (upstreamPriorityTaskManager != null) { - upstreamDataSource = - new PriorityDataSource( - upstreamDataSource, upstreamPriorityTaskManager, upstreamPriority); - } - this.upstreamDataSource = upstreamDataSource; - this.cacheWriteDataSource = - cacheWriteDataSink != null - ? new TeeDataSource(upstreamDataSource, cacheWriteDataSink) - : null; - } else { - this.upstreamDataSource = DummyDataSource.INSTANCE; - this.cacheWriteDataSource = null; - } - this.eventListener = eventListener; - } - - /** - * Returns the {@link Cache} used by this instance. - */ - public Cache getCache() { - return cache; - } - - /** - * Returns the {@link CacheKeyFactory} used by this instance. - */ - public CacheKeyFactory getCacheKeyFactory() { - return cacheKeyFactory; - } - - @Override - public void addTransferListener(@NonNull TransferListener transferListener) { - checkNotNull(transferListener); - cacheReadDataSource.addTransferListener(transferListener); - upstreamDataSource.addTransferListener(transferListener); - } - - @Override - public long open(@NonNull DataSpec dataSpec) throws IOException { - try { - String key = cacheKeyFactory.buildCacheKey(dataSpec); - DataSpec requestDataSpec = dataSpec.buildUpon().setKey(key).build(); - this.requestDataSpec = requestDataSpec; - actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ requestDataSpec.uri); - readPosition = dataSpec.position; - - int reason = shouldIgnoreCacheForRequest(dataSpec); - currentRequestIgnoresCache = reason != CACHE_NOT_IGNORED; - if (currentRequestIgnoresCache) { - notifyCacheIgnored(reason); - } - - if (currentRequestIgnoresCache) { - bytesRemaining = C.LENGTH_UNSET; - } else { - bytesRemaining = ContentMetadata.getContentLength(cache.getContentMetadata(key)); - if (bytesRemaining != C.LENGTH_UNSET) { - bytesRemaining -= dataSpec.position; - if (bytesRemaining < 0) { - throw new DataSourceException( - PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); - } - } - } - if (dataSpec.length != C.LENGTH_UNSET) { - bytesRemaining = - bytesRemaining == C.LENGTH_UNSET - ? dataSpec.length - : min(bytesRemaining, dataSpec.length); - } - if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { - openNextSource(requestDataSpec, false); - } - return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining; - } catch (Throwable e) { - handleBeforeThrow(e); - throw e; - } - } - - @Override - public int read(byte[] buffer, int offset, int length) throws IOException { - if (length == 0) { - return 0; - } - if (bytesRemaining == 0) { - return C.RESULT_END_OF_INPUT; - } - DataSpec requestDataSpec = checkNotNull(this.requestDataSpec); - DataSpec currentDataSpec = checkNotNull(this.currentDataSpec); - try { - if (readPosition >= checkCachePosition) { - openNextSource(requestDataSpec, true); - } - int bytesRead = checkNotNull(currentDataSource).read(buffer, offset, length); - if (bytesRead != C.RESULT_END_OF_INPUT) { - if (isReadingFromCache()) { - totalCachedBytesRead += bytesRead; - } - readPosition += bytesRead; - currentDataSourceBytesRead += bytesRead; - if (bytesRemaining != C.LENGTH_UNSET) { - bytesRemaining -= bytesRead; - } - } else if (isReadingFromUpstream() - && (currentDataSpec.length == C.LENGTH_UNSET - || currentDataSourceBytesRead < currentDataSpec.length)) { - // We've encountered RESULT_END_OF_INPUT from the upstream DataSource at a position not - // imposed by the current DataSpec. This must mean that we've reached the end of the - // resource. - setNoBytesRemainingAndMaybeStoreLength(castNonNull(requestDataSpec.key)); - } else if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { - closeCurrentSource(); - openNextSource(requestDataSpec, false); - return read(buffer, offset, length); - } - return bytesRead; - } catch (Throwable e) { - handleBeforeThrow(e); - throw e; - } - } - - @Override - @Nullable - public Uri getUri() { - return actualUri; - } - - @NonNull - @Override - public Map> getResponseHeaders() { - // TODO: Implement. - return isReadingFromUpstream() - ? upstreamDataSource.getResponseHeaders() - : Collections.emptyMap(); - } - - @Override - public void close() throws IOException { - requestDataSpec = null; - actualUri = null; - readPosition = 0; - notifyBytesRead(); - try { - closeCurrentSource(); - } catch (Throwable e) { - handleBeforeThrow(e); - throw e; - } - } - - /** - * Opens the next source. If the cache contains data spanning the current read position then - * {@link #cacheReadDataSource} is opened to read from it. Else {@link #upstreamDataSource} is - * opened to read from the upstream source and write into the cache. - * - *

There must not be a currently open source when this method is called, except in the case - * that {@code checkCache} is true. If {@code checkCache} is true then there must be a currently - * open source, and it must be {@link #upstreamDataSource}. It will be closed and a new source - * opened if it's possible to switch to reading from or writing to the cache. If a switch isn't - * possible then the current source is left unchanged. - * - * @param requestDataSpec The original {@link DataSpec} to build upon for the next source. - * @param checkCache If true tries to switch to reading from or writing to cache instead of - * reading from {@link #upstreamDataSource}, which is the currently open source. - */ - private void openNextSource(DataSpec requestDataSpec, boolean checkCache) throws IOException { - @Nullable CacheSpan nextSpan; - String key = castNonNull(requestDataSpec.key); - if (currentRequestIgnoresCache) { - nextSpan = null; - } else if (blockOnCache) { - try { - nextSpan = cache.startReadWrite(key, readPosition, bytesRemaining); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new InterruptedIOException(); - } - } else { - nextSpan = cache.startReadWriteNonBlocking(key, readPosition, bytesRemaining); - } - - DataSpec nextDataSpec; - DataSource nextDataSource; - if (nextSpan == null) { - // The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read - // from upstream. - nextDataSource = upstreamDataSource; - nextDataSpec = - requestDataSpec.buildUpon().setPosition(readPosition).setLength(bytesRemaining).build(); - } else if (nextSpan.isCached) { - // Data is cached in a span file starting at nextSpan.position. - Uri fileUri = Uri.fromFile(castNonNull(nextSpan.file)); - long filePositionOffset = nextSpan.position; - long positionInFile = readPosition - filePositionOffset; - long length = nextSpan.length - positionInFile; - if (bytesRemaining != C.LENGTH_UNSET) { - length = min(length, bytesRemaining); - } - nextDataSpec = - requestDataSpec - .buildUpon() - .setUri(fileUri) - .setUriPositionOffset(filePositionOffset) - .setPosition(positionInFile) - .setLength(length) - .build(); - nextDataSource = cacheReadDataSource; - } else { - // Data is not cached, and data is not locked, read from upstream with cache backing. - long length; - if (nextSpan.isOpenEnded()) { - length = bytesRemaining; - } else { - length = nextSpan.length; - if (bytesRemaining != C.LENGTH_UNSET) { - length = min(length, bytesRemaining); - } - } - nextDataSpec = - requestDataSpec.buildUpon().setPosition(readPosition).setLength(length).build(); - if (cacheWriteDataSource != null) { - nextDataSource = cacheWriteDataSource; - } else { - nextDataSource = upstreamDataSource; - cache.releaseHoleSpan(nextSpan); - nextSpan = null; - } - } - - checkCachePosition = - !currentRequestIgnoresCache && nextDataSource == upstreamDataSource - ? readPosition + MIN_READ_BEFORE_CHECKING_CACHE - : Long.MAX_VALUE; - if (checkCache) { - Assertions.checkState(isBypassingCache()); - if (nextDataSource == upstreamDataSource) { - // Continue reading from upstream. - return; - } - // We're switching to reading from or writing to the cache. - try { - closeCurrentSource(); - } catch (Throwable e) { - if (castNonNull(nextSpan).isHoleSpan()) { - // Release the hole span before throwing, else we'll hold it forever. - cache.releaseHoleSpan(nextSpan); - } - throw e; - } - } - - if (nextSpan != null && nextSpan.isHoleSpan()) { - currentHoleSpan = nextSpan; - } - currentDataSource = nextDataSource; - currentDataSpec = nextDataSpec; - currentDataSourceBytesRead = 0; - long resolvedLength = nextDataSource.open(nextDataSpec); - - // Update bytesRemaining, actualUri and (if writing to cache) the cache metadata. - ContentMetadataMutations mutations = new ContentMetadataMutations(); - if (nextDataSpec.length == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { - bytesRemaining = resolvedLength; - ContentMetadataMutations.setContentLength(mutations, readPosition + bytesRemaining); - } - if (isReadingFromUpstream()) { - actualUri = nextDataSource.getUri(); - boolean isRedirected = !requestDataSpec.uri.equals(actualUri); - ContentMetadataMutations.setRedirectedUri(mutations, isRedirected ? actualUri : null); - } - if (isWritingToCache()) { - cache.applyContentMetadataMutations(key, mutations); - } - } - - private void setNoBytesRemainingAndMaybeStoreLength(String key) throws IOException { - bytesRemaining = 0; - if (isWritingToCache()) { - ContentMetadataMutations mutations = new ContentMetadataMutations(); - ContentMetadataMutations.setContentLength(mutations, readPosition); - cache.applyContentMetadataMutations(key, mutations); - } - } - - private static Uri getRedirectedUriOrDefault(Cache cache, String key, Uri defaultUri) { - @Nullable Uri redirectedUri = ContentMetadata.getRedirectedUri(cache.getContentMetadata(key)); - return redirectedUri != null ? redirectedUri : defaultUri; - } - - private boolean isReadingFromUpstream() { - return !isReadingFromCache(); - } - - private boolean isBypassingCache() { - return currentDataSource == upstreamDataSource; - } - - private boolean isReadingFromCache() { - return currentDataSource == cacheReadDataSource; - } - - private boolean isWritingToCache() { - return currentDataSource == cacheWriteDataSource; - } - - private void closeCurrentSource() throws IOException { - if (currentDataSource == null) { - return; - } - try { - currentDataSource.close(); - } finally { - currentDataSpec = null; - currentDataSource = null; - if (currentHoleSpan != null) { - cache.releaseHoleSpan(currentHoleSpan); - currentHoleSpan = null; - } - } - } - - private void handleBeforeThrow(Throwable exception) { - if (isReadingFromCache() || exception instanceof CacheException) { - seenCacheError = true; - } - } - - private int shouldIgnoreCacheForRequest(DataSpec dataSpec) { - if (ignoreCacheOnError && seenCacheError) { - return CACHE_IGNORED_REASON_ERROR; - } else if (ignoreCacheForUnsetLengthRequests && dataSpec.length == C.LENGTH_UNSET) { - return CACHE_IGNORED_REASON_UNSET_LENGTH; - } else { - return CACHE_NOT_IGNORED; - } - } - - private void notifyCacheIgnored(@CacheIgnoredReason int reason) { - if (eventListener != null) { - eventListener.onCacheIgnored(reason); - } - } - - private void notifyBytesRead() { - if (eventListener != null && totalCachedBytesRead > 0) { - eventListener.onCachedBytesRead(cache.getCacheSpace(), totalCachedBytesRead); - totalCachedBytesRead = 0; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt index 93905843f..751051b0e 100644 --- a/app/src/main/java/io/legado/app/service/AudioPlayService.kt +++ b/app/src/main/java/io/legado/app/service/AudioPlayService.kt @@ -139,6 +139,7 @@ class AudioPlayService : BaseService(), headerMapF = AudioPlay.headers(true), ) val uri = Uri.parse(analyzeUrl.url) + ExoPlayerHelper.preDownload(uri, analyzeUrl.headerMap) val mediaSource = ExoPlayerHelper .createMediaSource(uri, analyzeUrl.headerMap) exoPlayer.setMediaSource(mediaSource)