use of androidx.media3.common.Player.State in project media by androidx.
the class ExoPlayerTest method onPositionDiscontinuity_recursiveStateChange_mediaItemMaskingCorrect.
@Test
public void onPositionDiscontinuity_recursiveStateChange_mediaItemMaskingCorrect() throws Exception {
ExoPlayer player = new TestExoPlayerBuilder(context).build();
Player.Listener listener = mock(Player.Listener.class);
MediaItem[] currentMediaItems = new MediaItem[2];
int[] mediaItemCount = new int[2];
player.addListener(new Listener() {
@Override
public void onPositionDiscontinuity(PositionInfo oldPosition, PositionInfo newPosition, int reason) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
mediaItemCount[0] = player.getMediaItemCount();
currentMediaItems[0] = player.getCurrentMediaItem();
// This is called before the second listener is called.
player.removeMediaItem(/* index= */
1);
}
}
});
player.addListener(new Listener() {
@Override
public void onPositionDiscontinuity(PositionInfo oldPosition, PositionInfo newPosition, int reason) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
mediaItemCount[1] = player.getMediaItemCount();
currentMediaItems[1] = player.getCurrentMediaItem();
}
}
});
player.addListener(listener);
player.setMediaSources(ImmutableList.of(createFakeMediaSource(/* id= */
"id-0"), createFakeMediaSource(/* id= */
"id-1")));
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor = ArgumentCaptor.forClass(PositionInfo.class);
InOrder inOrder = inOrder(listener);
inOrder.verify(listener).onPositionDiscontinuity(any(), newPositionArgumentCaptor.capture(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION));
inOrder.verify(listener).onPositionDiscontinuity(any(), newPositionArgumentCaptor.capture(), eq(Player.DISCONTINUITY_REASON_REMOVE));
// The state at auto-transition event time.
assertThat(mediaItemCount[0]).isEqualTo(2);
assertThat(currentMediaItems[0].localConfiguration.tag).isEqualTo("id-1");
// The masked state after id-1 has been removed.
assertThat(mediaItemCount[1]).isEqualTo(1);
assertThat(currentMediaItems[1].localConfiguration.tag).isEqualTo("id-0");
// PositionInfo reports the media item at event time.
assertThat(newPositionArgumentCaptor.getAllValues().get(0).mediaItem.localConfiguration.tag).isEqualTo("id-1");
assertThat(newPositionArgumentCaptor.getAllValues().get(1).mediaItem.localConfiguration.tag).isEqualTo("id-0");
player.release();
}
use of androidx.media3.common.Player.State in project media by androidx.
the class ExoPlayerTest method recursivePlayerChangesReportConsistentValuesForAllListeners.
@Test
public void recursivePlayerChangesReportConsistentValuesForAllListeners() throws Exception {
// We add two listeners to the player. The first stops the player as soon as it's ready and both
// record the state change events they receive.
final AtomicReference<Player> playerReference = new AtomicReference<>();
final List<Integer> playerListener1States = new ArrayList<>();
final List<Integer> playerListener2States = new ArrayList<>();
final Player.Listener playerListener1 = new Player.Listener() {
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
playerListener1States.add(playbackState);
if (playbackState == Player.STATE_READY) {
playerReference.get().stop(/* reset= */
true);
}
}
};
final Player.Listener playerListener2 = new Player.Listener() {
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
playerListener2States.add(playbackState);
}
};
ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG).executeRunnable(new PlayerRunnable() {
@Override
public void run(ExoPlayer player) {
playerReference.set(player);
player.addListener(playerListener1);
player.addListener(playerListener2);
}
}).build();
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start().blockUntilEnded(TIMEOUT_MS);
assertThat(playerListener1States).containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE).inOrder();
assertThat(playerListener2States).containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE).inOrder();
}
use of androidx.media3.common.Player.State in project media by androidx.
the class HlsChunkSource method getChunkPublicationState.
/**
* Returns the publication state of the given chunk.
*
* @param mediaChunk The media chunk for which to evaluate the publication state.
* @return Whether the media chunk is {@link #CHUNK_PUBLICATION_STATE_PRELOAD a preload chunk},
* has been {@link #CHUNK_PUBLICATION_STATE_REMOVED removed} or is definitely {@link
* #CHUNK_PUBLICATION_STATE_PUBLISHED published}.
*/
@ChunkPublicationState
public int getChunkPublicationState(HlsMediaChunk mediaChunk) {
if (mediaChunk.partIndex == C.INDEX_UNSET) {
// Chunks based on full segments can't be removed and are always published.
return CHUNK_PUBLICATION_STATE_PUBLISHED;
}
Uri playlistUrl = playlistUrls[trackGroup.indexOf(mediaChunk.trackFormat)];
HlsMediaPlaylist mediaPlaylist = checkNotNull(playlistTracker.getPlaylistSnapshot(playlistUrl, /* isForPlayback= */
false));
int segmentIndexInPlaylist = (int) (mediaChunk.chunkIndex - mediaPlaylist.mediaSequence);
if (segmentIndexInPlaylist < 0) {
// The parent segment of the previous chunk is not in the current playlist anymore.
return CHUNK_PUBLICATION_STATE_PUBLISHED;
}
List<HlsMediaPlaylist.Part> partsInCurrentPlaylist = segmentIndexInPlaylist < mediaPlaylist.segments.size() ? mediaPlaylist.segments.get(segmentIndexInPlaylist).parts : mediaPlaylist.trailingParts;
if (mediaChunk.partIndex >= partsInCurrentPlaylist.size()) {
// sequence in the new playlist.
return CHUNK_PUBLICATION_STATE_REMOVED;
}
HlsMediaPlaylist.Part newPart = partsInCurrentPlaylist.get(mediaChunk.partIndex);
if (newPart.isPreload) {
// The playlist did not change and the part in the new playlist is still a preload hint.
return CHUNK_PUBLICATION_STATE_PRELOAD;
}
Uri newUri = Uri.parse(UriUtil.resolve(mediaPlaylist.baseUri, newPart.url));
return Util.areEqual(newUri, mediaChunk.dataSpec.uri) ? CHUNK_PUBLICATION_STATE_PUBLISHED : CHUNK_PUBLICATION_STATE_REMOVED;
}
use of androidx.media3.common.Player.State in project media by androidx.
the class AdTagLoader method addListenerWithAdView.
/**
* Starts passing events from this instance (including any pending ad playback state) and
* registers obstructions.
*/
public void addListenerWithAdView(EventListener eventListener, AdViewProvider adViewProvider) {
boolean isStarted = !eventListeners.isEmpty();
eventListeners.add(eventListener);
if (isStarted) {
if (!AdPlaybackState.NONE.equals(adPlaybackState)) {
// Pass the existing ad playback state to the new listener.
eventListener.onAdPlaybackState(adPlaybackState);
}
return;
}
lastVolumePercent = 0;
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
maybeNotifyPendingAdLoadError();
if (!AdPlaybackState.NONE.equals(adPlaybackState)) {
// Pass the ad playback state to the player, and resume ads if necessary.
eventListener.onAdPlaybackState(adPlaybackState);
} else if (adsManager != null) {
adPlaybackState = new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
updateAdPlaybackState();
}
for (AdOverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
adDisplayContainer.registerFriendlyObstruction(imaFactory.createFriendlyObstruction(overlayInfo.view, ImaUtil.getFriendlyObstructionPurpose(overlayInfo.purpose), overlayInfo.reasonDetail));
}
}
use of androidx.media3.common.Player.State in project media by androidx.
the class FragmentedMp4Extractor method readSample.
/**
* Attempts to read the next sample in the current mdat atom. The read sample may be output or
* skipped.
*
* <p>If there are no more samples in the current mdat atom then the parser state is transitioned
* to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
*
* <p>It is possible for a sample to be partially read in the case that an exception is thrown. In
* this case the method can be called again to read the remainder of the sample.
*
* @param input The {@link ExtractorInput} from which to read data.
* @return Whether a sample was read. The read sample may have been output or skipped. False
* indicates that there are no samples left to read in the current mdat.
* @throws IOException If an error occurs reading from the input.
*/
private boolean readSample(ExtractorInput input) throws IOException {
@Nullable TrackBundle trackBundle = currentTrackBundle;
if (trackBundle == null) {
trackBundle = getNextTrackBundle(trackBundles);
if (trackBundle == null) {
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
// read the header of the next atom.
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
if (bytesToSkip < 0) {
throw ParserException.createForMalformedContainer("Offset to end of mdat was negative.", /* cause= */
null);
}
input.skipFully(bytesToSkip);
enterReadingAtomHeaderState();
return false;
}
long nextDataPosition = trackBundle.getCurrentSampleOffset();
// We skip bytes preceding the next sample to read.
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
if (bytesToSkip < 0) {
// Assume the sample data must be contiguous in the mdat with no preceding data.
Log.w(TAG, "Ignoring negative offset to sample data.");
bytesToSkip = 0;
}
input.skipFully(bytesToSkip);
currentTrackBundle = trackBundle;
}
if (parserState == STATE_READING_SAMPLE_START) {
sampleSize = trackBundle.getCurrentSampleSize();
if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) {
input.skipFully(sampleSize);
trackBundle.skipSampleEncryptionData();
if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
if (trackBundle.moovSampleTable.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
// AC4 samples need to be prefixed with a clear sample header.
sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
} else {
sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */
0);
}
sampleSize += sampleBytesWritten;
parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0;
}
Track track = trackBundle.moovSampleTable.track;
TrackOutput output = trackBundle.output;
long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs();
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
if (track.nalUnitLengthFieldLength != 0) {
// Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
// they're only 1 or 2 bytes long.
byte[] nalPrefixData = nalPrefix.getData();
nalPrefixData[0] = 0;
nalPrefixData[1] = 0;
nalPrefixData[2] = 0;
int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
// start codes as we encounter them.
while (sampleBytesWritten < sampleSize) {
if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one, and its type.
input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
nalPrefix.setPosition(0);
int nalLengthInt = nalPrefix.readInt();
if (nalLengthInt < 1) {
throw ParserException.createForMalformedContainer("Invalid NAL length", /* cause= */
null);
}
sampleCurrentNalBytesRemaining = nalLengthInt - 1;
// Write a start code for the current NAL unit.
nalStartCode.setPosition(0);
output.sampleData(nalStartCode, 4);
// Write the NAL unit type byte.
output.sampleData(nalPrefix, 1);
processSeiNalUnitPayload = ceaTrackOutputs.length > 0 && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
sampleBytesWritten += 5;
sampleSize += nalUnitLengthFieldLengthDiff;
} else {
int writtenBytes;
if (processSeiNalUnitPayload) {
// Read and write the payload of the SEI NAL unit.
nalBuffer.reset(sampleCurrentNalBytesRemaining);
input.readFully(nalBuffer.getData(), 0, sampleCurrentNalBytesRemaining);
output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
writtenBytes = sampleCurrentNalBytesRemaining;
// Unescape and process the SEI NAL unit.
int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.getData(), nalBuffer.limit());
// If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
nalBuffer.setLimit(unescapedLength);
CeaUtil.consume(sampleTimeUs, nalBuffer, ceaTrackOutputs);
} else {
// Write the payload of the NAL unit.
writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
}
sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes;
}
}
} else {
while (sampleBytesWritten < sampleSize) {
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes;
}
}
@C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags();
// Encryption data.
@Nullable TrackOutput.CryptoData cryptoData = null;
@Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted();
if (encryptionBox != null) {
cryptoData = encryptionBox.cryptoData;
}
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData);
// After we have the sampleTimeUs, we can commit all the pending metadata samples
outputPendingMetadataSamples(sampleTimeUs);
if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
Aggregations