/** * @param upstream The upstream {@link DataSource}. * @param priorityTaskManager The priority manager to which the task is registered. * @param priority The priority of the task. */ public PriorityDataSource(DataSource upstream, PriorityTaskManager priorityTaskManager, int priority) { this.upstream = Assertions.checkNotNull(upstream); this.priorityTaskManager = Assertions.checkNotNull(priorityTaskManager); this.priority = priority; }
/** * @param upstreamFactory A {@link DataSource.Factory} to be used to create an upstream {@link * DataSource} for {@link PriorityDataSource}. * @param priorityTaskManager The priority manager to which PriorityDataSource task is registered. * @param priority The priority of PriorityDataSource task. */ public PriorityDataSourceFactory(Factory upstreamFactory, PriorityTaskManager priorityTaskManager, int priority) { this.upstreamFactory = upstreamFactory; this.priorityTaskManager = priorityTaskManager; this.priority = priority; }
/** * Reads and discards all data specified by the {@code dataSpec}. * * @param dataSpec Defines the data to be read. * @param dataSource The {@link DataSource} to read the data from. * @param buffer The buffer to be used while downloading. * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with * caching. * @param priority The priority of this task. * @return Number of read bytes, or 0 if no data is available because the end of the opened range * has been reached. */ private static long readAndDiscard(DataSpec dataSpec, DataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority) throws IOException, InterruptedException { while (true) { if (priorityTaskManager != null) { // Wait for any other thread with higher priority to finish its job. priorityTaskManager.proceed(priority); } try { dataSource.open(dataSpec); long totalRead = 0; while (true) { if (Thread.interrupted()) { throw new InterruptedException(); } int read = dataSource.read(buffer, 0, buffer.length); if (read == C.RESULT_END_OF_INPUT) { return totalRead; } totalRead += read; } } catch (PriorityTaskManager.PriorityTooLowException exception) { // catch and try again } finally { Util.closeQuietly(dataSource); } } }
/** * @param cache Cache instance to be used to store downloaded data. * @param upstreamDataSourceFactory A {@link Factory} for downloading data. * @param cacheReadDataSourceFactory A {@link Factory} for reading data from the cache. * If null, null is passed to {@link Downloader} constructor. * @param cacheWriteDataSinkFactory A {@link DataSink.Factory} for writing data to the cache. If * null, null is passed to {@link Downloader} constructor. * @param priorityTaskManager If one is given then the download priority is set lower than * loading. If null, null is passed to {@link Downloader} constructor. */ public DownloaderConstructorHelper(Cache cache, Factory upstreamDataSourceFactory, @Nullable Factory cacheReadDataSourceFactory, @Nullable DataSink.Factory cacheWriteDataSinkFactory, @Nullable PriorityTaskManager priorityTaskManager) { Assertions.checkNotNull(upstreamDataSourceFactory); this.cache = cache; this.upstreamDataSourceFactory = upstreamDataSourceFactory; this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; this.priorityTaskManager = priorityTaskManager; }
/** * Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code * dataSource} or {@code buffer} is null performs a dry run. * * @param dataSpec Defines the data to be cached. * @param cache A {@link Cache} to store the data. * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. If null a dry run * is performed. * @param buffer The buffer to be used while caching. If null a dry run is performed. * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with * caching. * @param priority The priority of this task. Used with {@code priorityTaskManager}. * @param counters The counters to be set during caching. If not null its values reset to * zero before using. If null a new {@link CachingCounters} is created and used. * @return The used {@link CachingCounters} instance. * @throws IOException If not dry run and an error occurs reading from the source. * @throws InterruptedException If not dry run and the thread was interrupted. */ private static CachingCounters internalCache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, CachingCounters counters) throws IOException, InterruptedException { long start = dataSpec.position; long left = dataSpec.length; String key = getKey(dataSpec); if (left == C.LENGTH_UNSET) { left = cache.getContentLength(key); if (left == C.LENGTH_UNSET) { left = Long.MAX_VALUE; } } if (counters == null) { counters = new CachingCounters(); } else { counters.alreadyCachedBytes = 0; counters.downloadedBytes = 0; } while (left > 0) { long blockLength = cache.getCachedBytes(key, start, left); // Skip already cached data if (blockLength > 0) { counters.alreadyCachedBytes += blockLength; } else { // There is a hole in the cache which is at least "-blockLength" long. blockLength = -blockLength; if (dataSource != null && buffer != null) { DataSpec subDataSpec = new DataSpec(dataSpec.uri, start, blockLength == Long.MAX_VALUE ? C.LENGTH_UNSET : blockLength, key); long read = readAndDiscard(subDataSpec, dataSource, buffer, priorityTaskManager, priority); counters.downloadedBytes += read; if (read < blockLength) { // Reached end of data. break; } } else if (blockLength == Long.MAX_VALUE) { counters.downloadedBytes = C.LENGTH_UNSET; break; } else { counters.downloadedBytes += blockLength; } } start += blockLength; if (left != Long.MAX_VALUE) { left -= blockLength; } } return counters; }
/** * Constructs a new instance. * * @param allocator The {@link DefaultAllocator} used by the loader. * @param minBufferMs The minimum duration of media that the player will attempt to ensure is * buffered at all times, in milliseconds. * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in * milliseconds. * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or * resume following a user action such as a seek, in milliseconds. * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by * buffer depletion rather than a user action. * @param targetBufferBytes The target buffer size in bytes. If set to {@link C#LENGTH_UNSET}, the * target buffer size will be calculated using {@link #calculateTargetBufferSize(Renderer[], * TrackSelectionArray)}. * @param prioritizeTimeOverSizeThresholds Whether the load control prioritizes buffer time * constraints over buffer size constraints. * @param priorityTaskManager If not null, registers itself as a task with priority {@link * C#PRIORITY_PLAYBACK} during loading periods, and unregisters itself during draining */ public DefaultLoadControl( DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, PriorityTaskManager priorityTaskManager) { this.allocator = allocator; minBufferUs = minBufferMs * 1000L; maxBufferUs = maxBufferMs * 1000L; targetBufferBytesOverwrite = targetBufferBytes; bufferForPlaybackUs = bufferForPlaybackMs * 1000L; bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; this.priorityTaskManager = priorityTaskManager; }
/** Returns a {@link PriorityTaskManager} instance.*/ public PriorityTaskManager getPriorityTaskManager() { // Return a dummy PriorityTaskManager if none is provided. Create a new PriorityTaskManager // each time so clients don't affect each other over the dummy PriorityTaskManager instance. return priorityTaskManager != null ? priorityTaskManager : new PriorityTaskManager(); }
/** * Reads and discards all data specified by the {@code dataSpec}. * * @param dataSpec Defines the data to be read. {@code absoluteStreamPosition} and {@code length} * fields are overwritten by the following parameters. * @param absoluteStreamPosition The absolute position of the data to be read. * @param length Length of the data to be read, or {@link C#LENGTH_UNSET} if it is unknown. * @param dataSource The {@link DataSource} to read the data from. * @param buffer The buffer to be used while downloading. * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with * caching. * @param priority The priority of this task. * @param counters Counters to be set during reading. * @return Number of read bytes, or 0 if no data is available because the end of the opened range * has been reached. */ private static long readAndDiscard(DataSpec dataSpec, long absoluteStreamPosition, long length, DataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, CachingCounters counters) throws IOException, InterruptedException { while (true) { if (priorityTaskManager != null) { // Wait for any other thread with higher priority to finish its job. priorityTaskManager.proceed(priority); } try { if (Thread.interrupted()) { throw new InterruptedException(); } // Create a new dataSpec setting length to C.LENGTH_UNSET to prevent getting an error in // case the given length exceeds the end of input. dataSpec = new DataSpec(dataSpec.uri, dataSpec.postBody, absoluteStreamPosition, dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition, C.LENGTH_UNSET, dataSpec.key, dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); long resolvedLength = dataSource.open(dataSpec); if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength; } long totalRead = 0; while (totalRead != length) { if (Thread.interrupted()) { throw new InterruptedException(); } int read = dataSource.read(buffer, 0, length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead) : buffer.length); if (read == C.RESULT_END_OF_INPUT) { if (counters.contentLength == C.LENGTH_UNSET) { counters.contentLength = dataSpec.absoluteStreamPosition + totalRead; } break; } totalRead += read; counters.newlyCachedBytes += read; } return totalRead; } catch (PriorityTaskManager.PriorityTooLowException exception) { // catch and try again } finally { Util.closeQuietly(dataSource); } } }
/** * Instantiates a new Buffering load control. * * @param allocator the allocator * @param minBufferMs the min buffer ms * @param maxBufferMs the max buffer ms * @param bufferForPlaybackMs the buffer for playback ms * @param bufferForPlaybackAfterRebufferMs the buffer for playback after rebuffer ms * @param priorityTaskManager the priority task manager */ public BufferingLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, PriorityTaskManager priorityTaskManager) { this.allocator = allocator; minBufferUs = minBufferMs * 2000L; maxBufferUs = maxBufferMs * 2000L; bufferForPlaybackUs = bufferForPlaybackMs * 2000L; bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 2000L; this.priorityTaskManager = priorityTaskManager; }
/** * Constructs a new instance. * * @param allocator The {@link DefaultAllocator} used by the loader. * @param minBufferMs The minimum duration of media that the player will attempt to ensure is * buffered at all times, in milliseconds. * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in * milliseconds. * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or * resume following a user action such as a seek, in milliseconds. * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by * buffer depletion rather than a user action. * @param priorityTaskManager If not null, registers itself as a task with priority * {@link C#PRIORITY_PLAYBACK} during loading periods, and unregisters itself during draining * periods. */ public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, PriorityTaskManager priorityTaskManager) { this.allocator = allocator; minBufferUs = minBufferMs * 1000L; maxBufferUs = maxBufferMs * 1000L; bufferForPlaybackUs = bufferForPlaybackMs * 1000L; bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; this.priorityTaskManager = priorityTaskManager; }