下面列出了android.media.MediaCrypto#com.google.android.exoplayer2.util.Assertions 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
@Override
public void queueInput(ByteBuffer inputBuffer) {
Assertions.checkState(sonic != null);
if (inputBuffer.hasRemaining()) {
ShortBuffer shortBuffer = inputBuffer.asShortBuffer();
int inputSize = inputBuffer.remaining();
inputBytes += inputSize;
sonic.queueInput(shortBuffer);
inputBuffer.position(inputBuffer.position() + inputSize);
}
int outputSize = sonic.getFramesAvailable() * channelCount * 2;
if (outputSize > 0) {
if (buffer.capacity() < outputSize) {
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
shortBuffer = buffer.asShortBuffer();
} else {
buffer.clear();
shortBuffer.clear();
}
sonic.getOutput(shortBuffer);
outputBytes += outputSize;
buffer.limit(outputSize);
outputBuffer = buffer;
}
}
/**
* @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*}
* constants defined in {@link C}.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted media. May be null if support for encrypted
* media is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
*/
public MediaCodecRenderer(int trackType, MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(trackType);
Assertions.checkState(Util.SDK_INT >= 16);
this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector);
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
formatHolder = new FormatHolder();
decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo();
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
}
private int getDefaultBufferSize() {
if (isInputPcm) {
int minBufferSize =
AudioTrack.getMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding);
Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize =
(int) durationUsToFrames(MIN_BUFFER_DURATION_US) * outputPcmFrameSize;
int maxAppBufferSize =
(int)
Math.max(
minBufferSize, durationUsToFrames(MAX_BUFFER_DURATION_US) * outputPcmFrameSize);
return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize);
} else {
int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding);
if (outputEncoding == C.ENCODING_AC3) {
rate *= AC3_BUFFER_MULTIPLICATION_FACTOR;
}
return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND);
}
}
/**
* @param group The {@link TrackGroup}. Must not be null.
* @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
* null or empty. May be in any order.
*/
public BaseTrackSelection(TrackGroup group, int... tracks) {
Assertions.checkState(tracks.length > 0);
this.group = Assertions.checkNotNull(group);
this.length = tracks.length;
// Set the formats, sorted in order of decreasing bandwidth.
formats = new Format[length];
for (int i = 0; i < tracks.length; i++) {
formats[i] = group.getFormat(tracks[i]);
}
Arrays.sort(formats, new DecreasingBandwidthComparator());
// Set the format indices in the same order.
this.tracks = new int[length];
for (int i = 0; i < length; i++) {
this.tracks[i] = group.indexOf(formats[i]);
}
blacklistUntilTimes = new long[length];
}
/**
* @param isAtomic Whether the concatenating media source will be treated as atomic, i.e., treated
* as a single item for repeating and shuffling.
* @param useLazyPreparation Whether playlist items are prepared lazily. If false, all manifest
* loads and other initial preparation steps happen immediately. If true, these initial
* preparations are triggered only when the player starts buffering the media.
* @param shuffleOrder The {@link ShuffleOrder} to use when shuffling the child media sources.
* @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same {@link
* MediaSource} instance to be present more than once in the array.
*/
@SuppressWarnings("initialization")
public ConcatenatingMediaSource(
boolean isAtomic,
boolean useLazyPreparation,
ShuffleOrder shuffleOrder,
MediaSource... mediaSources) {
for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource);
}
this.shuffleOrder = shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder;
this.mediaSourceByMediaPeriod = new IdentityHashMap<>();
this.mediaSourceByUid = new HashMap<>();
this.mediaSourcesPublic = new ArrayList<>();
this.mediaSourceHolders = new ArrayList<>();
this.nextTimelineUpdateOnCompletionActions = new HashSet<>();
this.pendingOnCompletionActions = new HashSet<>();
this.isAtomic = isAtomic;
this.useLazyPreparation = useLazyPreparation;
window = new Timeline.Window();
period = new Timeline.Period();
addMediaSources(Arrays.asList(mediaSources));
}
private void notifyListener() {
listenerNotificationScheduled = false;
List<Runnable> actionsOnCompletion =
pendingOnCompletionActions.isEmpty()
? Collections.emptyList()
: new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear();
refreshSourceInfo(
new ConcatenatedTimeline(
mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic),
/* manifest= */ null);
if (!actionsOnCompletion.isEmpty()) {
Assertions.checkNotNull(player)
.createMessage(this)
.setType(MSG_ON_COMPLETION)
.setPayload(actionsOnCompletion)
.send();
}
}
/**
* @param userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is
* interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use the
* default value.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted as
* an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value.
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled.
* @param defaultRequestProperties The default request properties to be sent to the server as HTTP
* headers or {@code null} if not required.
*/
public DefaultHttpDataSource(
String userAgent,
@Nullable Predicate<String> contentTypePredicate,
int connectTimeoutMillis,
int readTimeoutMillis,
boolean allowCrossProtocolRedirects,
@Nullable RequestProperties defaultRequestProperties) {
super(/* isNetwork= */ true);
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.requestProperties = new RequestProperties();
this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis;
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
this.defaultRequestProperties = defaultRequestProperties;
}
private void maybeUpdateLatency(long systemTimeUs) {
if (isOutputPcm
&& getLatencyMethod != null
&& systemTimeUs - lastLatencySampleTimeUs >= MIN_LATENCY_SAMPLE_INTERVAL_US) {
try {
// Compute the audio track latency, excluding the latency due to the buffer (leaving
// latency due to the mixer and audio hardware driver).
latencyUs =
castNonNull((Integer) getLatencyMethod.invoke(Assertions.checkNotNull(audioTrack)))
* 1000L
- bufferSizeUs;
// Sanity check that the latency is non-negative.
latencyUs = Math.max(latencyUs, 0);
// Sanity check that the latency isn't too large.
if (latencyUs > MAX_LATENCY_US) {
listener.onInvalidLatency(latencyUs);
latencyUs = 0;
}
} catch (Exception e) {
// The method existed, but doesn't work. Don't try again.
getLatencyMethod = null;
}
lastLatencySampleTimeUs = systemTimeUs;
}
}
@Override
public Format getOutputFormat() {
Assertions.checkNotNull(decoder);
int channelCount = decoder.getChannelCount();
int sampleRate = decoder.getSampleRate();
@C.PcmEncoding int encoding = decoder.getEncoding();
return Format.createAudioSampleFormat(
/* id= */ null,
MimeTypes.AUDIO_RAW,
/* codecs= */ null,
Format.NO_VALUE,
Format.NO_VALUE,
channelCount,
sampleRate,
encoding,
Collections.emptyList(),
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null);
}
/**
* @param isEncrypted See {@link #isEncrypted}.
* @param schemeType See {@link #schemeType}.
* @param perSampleIvSize See {@link #perSampleIvSize}.
* @param keyId See {@link TrackOutput.CryptoData#encryptionKey}.
* @param defaultEncryptedBlocks See {@link TrackOutput.CryptoData#encryptedBlocks}.
* @param defaultClearBlocks See {@link TrackOutput.CryptoData#clearBlocks}.
* @param defaultInitializationVector See {@link #defaultInitializationVector}.
*/
public TrackEncryptionBox(
boolean isEncrypted,
@Nullable String schemeType,
int perSampleIvSize,
byte[] keyId,
int defaultEncryptedBlocks,
int defaultClearBlocks,
@Nullable byte[] defaultInitializationVector) {
Assertions.checkArgument(perSampleIvSize == 0 ^ defaultInitializationVector == null);
this.isEncrypted = isEncrypted;
this.schemeType = schemeType;
this.perSampleIvSize = perSampleIvSize;
this.defaultInitializationVector = defaultInitializationVector;
cryptoData = new TrackOutput.CryptoData(schemeToCryptoMode(schemeType), keyId,
defaultEncryptedBlocks, defaultClearBlocks);
}
/**
* Converts (windowIndex, windowPositionUs) to the corresponding (periodUid, periodPositionUs).
*
* @param window A {@link Window} that may be overwritten.
* @param period A {@link Period} that may be overwritten.
* @param windowIndex The window index.
* @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default
* start position.
* @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
* duration into the future by which the window's position should be projected.
* @return The corresponding (periodUid, periodPositionUs), or null if {@code #windowPositionUs}
* is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
* position could not be projected by {@code defaultPositionProjectionUs}.
*/
@Nullable
public final Pair<Object, Long> getPeriodPosition(
Window window,
Period period,
int windowIndex,
long windowPositionUs,
long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, getWindowCount());
getWindow(windowIndex, window, defaultPositionProjectionUs);
if (windowPositionUs == C.TIME_UNSET) {
windowPositionUs = window.getDefaultPositionUs();
if (windowPositionUs == C.TIME_UNSET) {
return null;
}
}
int periodIndex = window.firstPeriodIndex;
long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs;
long periodDurationUs = getPeriod(periodIndex, period, /* setIds= */ true).getDurationUs();
while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
&& periodIndex < window.lastPeriodIndex) {
periodPositionUs -= periodDurationUs;
periodDurationUs = getPeriod(++periodIndex, period, /* setIds= */ true).getDurationUs();
}
return Pair.create(Assertions.checkNotNull(period.uid), periodPositionUs);
}
/**
* Registers the receiver, meaning it will notify the listener when audio capability changes
* occur. The current audio capabilities will be returned. It is important to call
* {@link #unregister} when the receiver is no longer required.
*
* @return The current audio capabilities for the device.
*/
@SuppressWarnings("InlinedApi")
public AudioCapabilities register() {
if (registered) {
return Assertions.checkNotNull(audioCapabilities);
}
registered = true;
if (externalSurroundSoundSettingObserver != null) {
externalSurroundSoundSettingObserver.register();
}
Intent stickyIntent = null;
if (receiver != null) {
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG);
stickyIntent =
context.registerReceiver(
receiver, intentFilter, /* broadcastPermission= */ null, handler);
}
audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent);
return audioCapabilities;
}
/**
* Sets audio attributes that should be used to manage audio focus.
*
* @param audioAttributes The audio attributes or {@code null} if audio focus should not be
* managed automatically.
* @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}.
* @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}.
* @return A {@link PlayerCommand} to execute on the player.
*/
@PlayerCommand
public int setAudioAttributes(
@Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) {
if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
this.audioAttributes = audioAttributes;
focusGain = convertAudioAttributesToFocusGain(audioAttributes);
Assertions.checkArgument(
focusGain == C.AUDIOFOCUS_GAIN || focusGain == C.AUDIOFOCUS_NONE,
"Automatic handling of audio focus is only available for USAGE_MEDIA and USAGE_GAME.");
if (playWhenReady
&& (playerState == Player.STATE_BUFFERING || playerState == Player.STATE_READY)) {
return requestAudioFocus();
}
}
return playerState == Player.STATE_IDLE
? handleIdle(playWhenReady)
: handlePrepare(playWhenReady);
}
/**
* @param isEncrypted See {@link #isEncrypted}.
* @param schemeType See {@link #schemeType}.
* @param perSampleIvSize See {@link #perSampleIvSize}.
* @param keyId See {@link TrackOutput.CryptoData#encryptionKey}.
* @param defaultEncryptedBlocks See {@link TrackOutput.CryptoData#encryptedBlocks}.
* @param defaultClearBlocks See {@link TrackOutput.CryptoData#clearBlocks}.
* @param defaultInitializationVector See {@link #defaultInitializationVector}.
*/
public TrackEncryptionBox(
boolean isEncrypted,
@Nullable String schemeType,
int perSampleIvSize,
byte[] keyId,
int defaultEncryptedBlocks,
int defaultClearBlocks,
@Nullable byte[] defaultInitializationVector) {
Assertions.checkArgument(perSampleIvSize == 0 ^ defaultInitializationVector == null);
this.isEncrypted = isEncrypted;
this.schemeType = schemeType;
this.perSampleIvSize = perSampleIvSize;
this.defaultInitializationVector = defaultInitializationVector;
cryptoData = new TrackOutput.CryptoData(schemeToCryptoMode(schemeType), keyId,
defaultEncryptedBlocks, defaultClearBlocks);
}
@Override
public void prepareSourceInternal(
final ExoPlayer player,
boolean isTopLevelSource,
@Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
Assertions.checkArgument(isTopLevelSource);
final ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener;
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
mainHandler.post(new Runnable() {
@Override
public void run() {
adsLoader.attachPlayer(player, componentListener, adUiViewGroup);
}
});
}
private void syncStoppedDownload(@Nullable Task activeTask) {
if (activeTask != null) {
// We have a task, which must be a download task. Cancel it.
Assertions.checkState(!activeTask.isRemove);
activeTask.cancel(/* released= */ false);
}
}
@Override
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name);
codecHandlesHdr10PlusOutOfBandMetadata =
Assertions.checkNotNull(getCodecInfo()).isHdr10PlusOutOfBandMetadataSupported();
}
@Override
public int read(@NonNull byte[] buffer, int offset, int length) throws IOException {
Assertions.checkState(!closed);
checkOpened();
int bytesRead = dataSource.read(buffer, offset, length);
if (bytesRead == C.RESULT_END_OF_INPUT) {
return -1;
} else {
totalBytesRead += bytesRead;
return bytesRead;
}
}
private void syncTasks() {
int accumulatingDownloadTaskCount = 0;
for (int i = 0; i < downloads.size(); i++) {
Download download = downloads.get(i);
Task activeTask = activeTasks.get(download.request.id);
switch (download.state) {
case STATE_STOPPED:
syncStoppedDownload(activeTask);
break;
case STATE_QUEUED:
activeTask = syncQueuedDownload(activeTask, download);
break;
case STATE_DOWNLOADING:
Assertions.checkNotNull(activeTask);
syncDownloadingDownload(activeTask, download, accumulatingDownloadTaskCount);
break;
case STATE_REMOVING:
case STATE_RESTARTING:
syncRemovingDownload(activeTask, download);
break;
case STATE_COMPLETED:
case STATE_FAILED:
default:
throw new IllegalStateException();
}
if (activeTask != null && !activeTask.isRemove) {
accumulatingDownloadTaskCount++;
}
}
}
/**
* Initializes the helper for starting a download.
*
* @param callback A callback to be notified when preparation completes or fails.
* @throws IllegalStateException If the download helper has already been prepared.
*/
public void prepare(Callback callback) {
Assertions.checkState(this.callback == null);
this.callback = callback;
if (mediaSource != null) {
mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
} else {
callbackHandler.post(() -> callback.onPrepared(this));
}
}
@Override
public final void disable() {
Assertions.checkState(state == STATE_ENABLED);
state = STATE_DISABLED;
stream = null;
streamFormats = null;
streamIsFinal = false;
onDisabled();
}
/** Stops watching for changes. */
public void stop() {
context.unregisterReceiver(Assertions.checkNotNull(receiver));
receiver = null;
if (networkCallback != null) {
unregisterNetworkCallback();
}
}
/**
* Checks two languages for consistency, returning the consistent language, or throwing an {@link
* IllegalStateException} if the languages are inconsistent.
*
* <p>Two languages are consistent if they are equal, or if one is null.
*
* @param firstLanguage The first language.
* @param secondLanguage The second language.
* @return The consistent language.
*/
@Nullable
private static String checkLanguageConsistency(
@Nullable String firstLanguage, @Nullable String secondLanguage) {
if (firstLanguage == null) {
return secondLanguage;
} else if (secondLanguage == null) {
return firstLanguage;
} else {
Assertions.checkState(firstLanguage.equals(secondLanguage));
return firstLanguage;
}
}
@Override
public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException {
Assertions.checkState(dequeuedInputBuffer == null);
if (availableInputBuffers.isEmpty()) {
return null;
}
dequeuedInputBuffer = availableInputBuffers.pollFirst();
return dequeuedInputBuffer;
}
@Override
public KeyRequest getKeyRequest(
byte[] scope,
@Nullable List<DrmInitData.SchemeData> schemeDatas,
int keyType,
@Nullable HashMap<String, String> optionalParameters)
throws NotProvisionedException {
SchemeData schemeData = null;
byte[] initData = null;
String mimeType = null;
if (schemeDatas != null) {
schemeData = getSchemeData(uuid, schemeDatas);
initData = adjustRequestInitData(uuid, Assertions.checkNotNull(schemeData.data));
mimeType = adjustRequestMimeType(uuid, schemeData.mimeType);
}
MediaDrm.KeyRequest request =
mediaDrm.getKeyRequest(scope, initData, mimeType, keyType, optionalParameters);
byte[] requestData = adjustRequestData(uuid, request.getData());
String licenseServerUrl = request.getDefaultUrl();
if (MOCK_LA_URL_VALUE.equals(licenseServerUrl)) {
licenseServerUrl = "";
}
if (TextUtils.isEmpty(licenseServerUrl)
&& schemeData != null
&& !TextUtils.isEmpty(schemeData.licenseServerUrl)) {
licenseServerUrl = schemeData.licenseServerUrl;
}
return new KeyRequest(requestData, licenseServerUrl);
}
@Override
public void seek(long position, long timeUs) {
Assertions.checkState(mode != MODE_HLS);
int timestampAdjustersCount = timestampAdjusters.size();
for (int i = 0; i < timestampAdjustersCount; i++) {
TimestampAdjuster timestampAdjuster = timestampAdjusters.get(i);
boolean hasNotEncounteredFirstTimestamp =
timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET;
if (hasNotEncounteredFirstTimestamp
|| (timestampAdjuster.getTimestampOffsetUs() != 0
&& timestampAdjuster.getFirstSampleTimestampUs() != timeUs)) {
// - If a track in the TS stream has not encountered any sample, it's going to treat the
// first sample encountered as timestamp 0, which is incorrect. So we have to set the first
// sample timestamp for that track manually.
// - If the timestamp adjuster has its timestamp set manually before, and now we seek to a
// different position, we need to set the first sample timestamp manually again.
timestampAdjuster.reset();
timestampAdjuster.setFirstSampleTimestampUs(timeUs);
}
}
if (timeUs != 0 && tsBinarySearchSeeker != null) {
tsBinarySearchSeeker.setSeekTargetUs(timeUs);
}
tsPacketBuffer.reset();
continuityCounters.clear();
for (int i = 0; i < tsPayloadReaders.size(); i++) {
tsPayloadReaders.valueAt(i).seek();
}
bytesSinceLastSync = 0;
}
private Download putDownloadWithState(Download download, @Download.State int state) {
// Downloads in terminal states shouldn't be in the downloads list. This method cannot be used
// to set STATE_STOPPED either, because it doesn't have a stopReason argument.
Assertions.checkState(
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED);
return putDownload(copyDownloadWithState(download, state));
}
/**
* Skips to the data in the given WAV input stream. After calling, the input stream's position
* will point to the start of sample data in the WAV, and the data bounds of the provided {@link
* WavHeader} will have been set.
*
* <p>If an exception is thrown, the input position will be left pointing to a chunk header and
* the bounds of the provided {@link WavHeader} will not have been set.
*
* @param input Input stream to skip to the data chunk in. Its peek position must be pointing to a
* valid chunk header.
* @param wavHeader WAV header to populate with data bounds.
* @throws ParserException If an error occurs parsing chunks.
* @throws IOException If reading from the input fails.
* @throws InterruptedException If interrupted while reading from input.
*/
public static void skipToData(ExtractorInput input, WavHeader wavHeader)
throws IOException, InterruptedException {
Assertions.checkNotNull(input);
Assertions.checkNotNull(wavHeader);
// Make sure the peek position is set to the read position before we peek the first header.
input.resetPeekPosition();
ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES);
// Skip all chunks until we hit the data header.
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
while (chunkHeader.id != WavUtil.DATA_FOURCC) {
if (chunkHeader.id != WavUtil.RIFF_FOURCC && chunkHeader.id != WavUtil.FMT_FOURCC) {
Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id);
}
long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size;
// Override size of RIFF chunk, since it describes its size as the entire file.
if (chunkHeader.id == WavUtil.RIFF_FOURCC) {
bytesToSkip = ChunkHeader.SIZE_IN_BYTES + 4;
}
if (bytesToSkip > Integer.MAX_VALUE) {
throw new ParserException("Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id);
}
input.skipFully((int) bytesToSkip);
chunkHeader = ChunkHeader.peek(input, scratch);
}
// Skip past the "data" header.
input.skipFully(ChunkHeader.SIZE_IN_BYTES);
int dataStartPosition = (int) input.getPosition();
long dataEndPosition = dataStartPosition + chunkHeader.size;
long inputLength = input.getLength();
if (inputLength != C.LENGTH_UNSET && dataEndPosition > inputLength) {
Log.w(TAG, "Data exceeds input length: " + dataEndPosition + ", " + inputLength);
dataEndPosition = inputLength;
}
wavHeader.setDataBounds(dataStartPosition, dataEndPosition);
}
/**
* Initializes the helper for starting a download.
*
* @param callback A callback to be notified when preparation completes or fails.
* @throws IllegalStateException If the download helper has already been prepared.
*/
public void prepare(Callback callback) {
Assertions.checkState(this.callback == null);
this.callback = callback;
if (mediaSource != null) {
mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
} else {
callbackHandler.post(() -> callback.onPrepared(this));
}
}
@Override
public int read(@NonNull byte[] buffer, int offset, int length) throws IOException {
Assertions.checkState(!closed);
checkOpened();
int bytesRead = dataSource.read(buffer, offset, length);
if (bytesRead == C.RESULT_END_OF_INPUT) {
return -1;
} else {
totalBytesRead += bytesRead;
return bytesRead;
}
}