Merge branch 'dev-v2' into program_information

This commit is contained in:
Lieblich, Jonathan 2018-10-15 15:35:49 -06:00
commit a7ace58712
32 changed files with 538 additions and 127 deletions

View file

@ -19,12 +19,17 @@
([#4788](https://github.com/google/ExoPlayer/issues/4788)).
* SubRip: Add support for alignment tags, and remove tags from the displayed
captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
* Fix issue where buffered position is not updated correctly when transitioning
between periods
([#4899](https://github.com/google/ExoPlayer/issues/4899)).
* IMA extension:
* For preroll to live stream transitions, project forward the loading position
to avoid being behind the live window.
### 2.9.0 ###
* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend
on ExoPlayer via its source code rather than an AAR may need to add
`compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to
add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
gradle settings to ensure bytecode compatibility.
* Set `compileSdkVersion` and `targetSdkVersion` to 28.
* Support for automatic audio focus handling via
@ -346,18 +351,18 @@
begins, and poll the audio timestamp less frequently once it starts
advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
* Add an option to skip silent audio in `PlaybackParameters`
((#2635)[https://github.com/google/ExoPlayer/issues/2635]).
([#2635](https://github.com/google/ExoPlayer/issues/2635)).
* Fix an issue where playback of TrueHD streams would get stuck after seeking
due to not finding a syncframe
((#3845)[https://github.com/google/ExoPlayer/issues/3845]).
([#3845](https://github.com/google/ExoPlayer/issues/3845)).
* Fix an issue with eac3-joc playback where a codec would fail to configure
((#4165)[https://github.com/google/ExoPlayer/issues/4165]).
([#4165](https://github.com/google/ExoPlayer/issues/4165)).
* Handle non-empty end-of-stream buffers, to fix gapless playback of streams
with encoder padding when the decoder returns a non-empty final buffer.
* Allow trimming more than one sample when applying an elst audio edit via
gapless playback info.
* Allow overriding skipping/scaling with custom `AudioProcessor`s
((#3142)[https://github.com/google/ExoPlayer/issues/3142]).
([#3142](https://github.com/google/ExoPlayer/issues/3142)).
* Caching:
* Add release method to the `Cache` interface, and prevent multiple instances
of `SimpleCache` using the same folder at the same time.

View file

@ -350,8 +350,7 @@ public class SampleChooserActivity extends Activity
? null
: new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession);
if (playlistSamples != null) {
UriSample[] playlistSamplesArray = playlistSamples.toArray(
new UriSample[playlistSamples.size()]);
UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]);
return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray);
} else {
return new UriSample(

View file

@ -153,6 +153,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
onDisabled();
}
@Override
public final void reset() {
Assertions.checkState(state == STATE_DISABLED);
onReset();
}
// RendererCapabilities implementation.
@Override
@ -247,6 +253,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
// Do nothing.
}
/**
* Called when the renderer is reset.
*
* <p>The default implementation is a no-op.
*/
protected void onReset() {
// Do nothing.
}
// Methods to be called by subclasses.
/** Returns the formats of the currently enabled stream. */

View file

@ -187,7 +187,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
extensionRendererMode, renderersList);
buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList);
return renderersList.toArray(new Renderer[renderersList.size()]);
return renderersList.toArray(new Renderer[0]);
}
/**

View file

@ -619,7 +619,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
// Replace internal unset start position with externally visible start position of zero.
playbackInfo =
playbackInfo.fromNewPosition(
playbackInfo.resetToNewPosition(
playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs);
}
if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare)

View file

@ -448,7 +448,11 @@ import java.util.Collections;
seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
if (newPositionUs != playbackInfo.positionUs) {
playbackInfo =
playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
playbackInfo.copyWithNewPosition(
periodId,
newPositionUs,
playbackInfo.contentPositionUs,
getTotalBufferedDurationUs());
if (sendDiscontinuity) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
@ -483,8 +487,12 @@ import java.util.Collections;
// A MediaPeriod may report a discontinuity at the current playback position to ensure the
// renderers are flushed. Only report the discontinuity externally if the position changed.
if (periodPositionUs != playbackInfo.positionUs) {
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
playbackInfo.contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
playbackInfo.periodId,
periodPositionUs,
playbackInfo.contentPositionUs,
getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
} else {
@ -496,10 +504,8 @@ import java.util.Collections;
// Update the buffered position and total buffered duration.
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
playbackInfo.bufferedPositionUs =
loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true);
playbackInfo.totalBufferedDurationUs =
playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs);
playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs();
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
}
private void doSomeWork() throws ExoPlaybackException, IOException {
@ -647,7 +653,9 @@ import java.util.Collections;
periodPositionUs = newPeriodPositionUs;
}
} finally {
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs());
if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
}
@ -981,6 +989,7 @@ import java.util.Collections;
mediaClock.onRendererDisabled(renderer);
ensureStopped(renderer);
renderer.disable();
renderer.reset();
}
private void reselectTracksInternal() throws ExoPlaybackException {
@ -993,7 +1002,7 @@ import java.util.Collections;
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
boolean selectionsChangedForReadPeriod = true;
TrackSelectorResult newTrackSelectorResult = null;
TrackSelectorResult newTrackSelectorResult;
while (true) {
if (periodHolder == null || !periodHolder.prepared) {
// The reselection did not change any prepared periods.
@ -1022,8 +1031,12 @@ import java.util.Collections;
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags);
if (playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs) {
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
playbackInfo.contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
playbackInfo.periodId,
periodPositionUs,
playbackInfo.contentPositionUs,
getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
resetRendererPosition(periodPositionUs);
}
@ -1097,12 +1110,10 @@ import java.util.Collections;
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal);
return bufferedPositionUs == C.TIME_END_OF_SOURCE
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
|| loadControl.shouldStartPlayback(
bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs),
mediaClock.getPlaybackParameters().speed,
rebuffering);
getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
}
private boolean isTimelineReady() {
@ -1167,7 +1178,7 @@ import java.util.Collections;
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
} catch (IllegalSeekPositionException e) {
playbackInfo =
playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
throw e;
}
pendingInitialSeekPosition = null;
@ -1180,7 +1191,7 @@ import java.util.Collections;
long positionUs = periodPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
playbackInfo =
playbackInfo.fromNewPosition(
playbackInfo.resetToNewPosition(
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
}
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
@ -1194,7 +1205,7 @@ import java.util.Collections;
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo =
playbackInfo.fromNewPosition(
playbackInfo.resetToNewPosition(
periodId,
periodId.isAd() ? 0 : startPositionUs,
/* contentPositionUs= */ startPositionUs);
@ -1213,7 +1224,7 @@ import java.util.Collections;
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo =
playbackInfo.fromNewPosition(
playbackInfo.resetToNewPosition(
periodId,
/* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs,
/* contentPositionUs= */ startPositionUs);
@ -1252,7 +1263,9 @@ import java.util.Collections;
}
// Actually do the seek.
long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
return;
}
@ -1264,7 +1277,9 @@ import java.util.Collections;
// The previously playing ad should no longer be played, so skip it.
long seekPositionUs =
seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
return;
}
}
@ -1420,8 +1435,12 @@ import java.util.Collections;
MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
playingPeriodHolder = queue.advancePlayingPeriod();
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
playbackInfo =
playbackInfo.copyWithNewPosition(
playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs,
playingPeriodHolder.info.contentPositionUs,
getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
updatePlaybackPositions();
advancedPlayingPeriod = true;
@ -1574,7 +1593,7 @@ import java.util.Collections;
return;
}
long bufferedDurationUs =
nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs);
boolean continueLoading =
loadControl.shouldContinueLoading(
bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
@ -1671,6 +1690,11 @@ import java.util.Collections;
if (loadingMediaPeriodChanged) {
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
}
playbackInfo.bufferedPositionUs =
loadingMediaPeriodHolder == null
? playbackInfo.positionUs
: loadingMediaPeriodHolder.getBufferedPositionUs();
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged)
&& loadingMediaPeriodHolder != null
&& loadingMediaPeriodHolder.prepared) {
@ -1680,6 +1704,17 @@ import java.util.Collections;
}
}
private long getTotalBufferedDurationUs() {
return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs);
}
private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
return loadingPeriodHolder == null
? 0
: bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
}
private void updateLoadControlTrackSelection(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);

View file

@ -133,23 +133,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
/**
* Returns the buffered position in microseconds. If the period is buffered to the end then
* {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which
* case the period duration is returned.
* Returns the buffered position in microseconds. If the period is buffered to the end, then the
* period duration is returned.
*
* @param convertEosToDuration Whether to return the period duration rather than
* {@link C#TIME_END_OF_SOURCE} if the period is fully buffered.
* @return The buffered position in microseconds.
*/
public long getBufferedPositionUs(boolean convertEosToDuration) {
public long getBufferedPositionUs() {
if (!prepared) {
return info.startPositionUs;
}
long bufferedPositionUs =
hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
? info.durationUs
: bufferedPositionUs;
return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs;
}
/**

View file

@ -533,6 +533,11 @@ import com.google.android.exoplayer2.util.Assertions;
// until the timeline is updated. Store whether the next timeline period is ready when the
// timeline is updated, to avoid repeatedly checking the same timeline.
MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info;
// The expected delay until playback transitions to the new period is equal the duration of
// media that's currently buffered (assuming no interruptions). This is used to project forward
// the start position for transitions to new windows.
long bufferedDurationUs =
mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs;
if (mediaPeriodInfo.isLastInTimelinePeriod) {
int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid);
int nextPeriodIndex =
@ -550,19 +555,15 @@ import com.google.android.exoplayer2.util.Assertions;
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
// We're starting to buffer a new window. When playback transitions to this window we'll
// want it to be from its default start position. The expected delay until playback
// transitions is equal the duration of media that's currently buffered (assuming no
// interruptions). Hence we project the default start position forward by the duration of
// the buffer, and start buffering from this point.
long defaultPositionProjectionUs =
mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs;
// want it to be from its default start position, so project the default start position
// forward by the duration of the buffer, and start buffering from this point.
Pair<Object, Long> defaultPosition =
timeline.getPeriodPosition(
window,
period,
nextWindowIndex,
C.TIME_UNSET,
Math.max(0, defaultPositionProjectionUs));
/* windowPositionUs= */ C.TIME_UNSET,
/* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs));
if (defaultPosition == null) {
return null;
}
@ -603,11 +604,27 @@ import com.google.android.exoplayer2.util.Assertions;
mediaPeriodInfo.contentPositionUs,
currentPeriodId.windowSequenceNumber);
} else {
// Play content from the ad group position.
// Play content from the ad group position. As a special case, if we're transitioning from a
// preroll ad group to content and there are no other ad groups, project the start position
// forward as if this were a transition to a new window. No attempt is made to handle
// midrolls in live streams, as it's unclear what content position should play after an ad
// (server-side dynamic ad insertion is more appropriate for this use case).
long startPositionUs = mediaPeriodInfo.contentPositionUs;
if (period.getAdGroupCount() == 1 && period.getAdGroupTimeUs(0) == 0) {
Pair<Object, Long> defaultPosition =
timeline.getPeriodPosition(
window,
period,
period.windowIndex,
/* windowPositionUs= */ C.TIME_UNSET,
/* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs));
if (defaultPosition == null) {
return null;
}
startPositionUs = defaultPosition.second;
}
return getMediaPeriodInfoForContent(
currentPeriodId.periodUid,
mediaPeriodInfo.contentPositionUs,
currentPeriodId.windowSequenceNumber);
currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber);
}
} else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) {
// Play the next ad group if it's available.

View file

@ -158,6 +158,12 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
onDisabled();
}
@Override
public final void reset() {
Assertions.checkState(state == STATE_DISABLED);
onReset();
}
@Override
public boolean isReady() {
return true;
@ -260,6 +266,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
// Do nothing.
}
/**
* Called when the renderer is reset.
*
* <p>The default implementation is a no-op.
*/
protected void onReset() {
// Do nothing.
}
// Methods to be called by subclasses.
/**

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
import android.support.annotation.CheckResult;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public final MediaPeriodId periodId;
/**
* The start position at which playback started in {@link #periodId} relative to the start of the
* associated period in the {@link #timeline}, in microseconds.
* associated period in the {@link #timeline}, in microseconds. Note that this value changes for
* each position discontinuity.
*/
public final long startPositionUs;
/**
@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs);
}
/**
* Create playback info.
*
* @param timeline See {@link #timeline}.
* @param manifest See {@link #manifest}.
* @param periodId See {@link #periodId}.
* @param startPositionUs See {@link #startPositionUs}.
* @param contentPositionUs See {@link #contentPositionUs}.
* @param playbackState See {@link #playbackState}.
* @param isLoading See {@link #isLoading}.
* @param trackGroups See {@link #trackGroups}.
* @param trackSelectorResult See {@link #trackSelectorResult}.
* @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}.
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
* @param positionUs See {@link #positionUs}.
*/
public PlaybackInfo(
Timeline timeline,
@Nullable Object manifest,
@ -132,7 +151,17 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
this.positionUs = positionUs;
}
public PlaybackInfo fromNewPosition(
/**
* Copies playback info and resets playing and loading position.
*
* @param periodId New playing and loading {@link MediaPeriodId}.
* @param startPositionUs New start position. See {@link #startPositionUs}.
* @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored
* if {@code periodId.isAd()} is true.
* @return Copied playback info with reset position.
*/
@CheckResult
public PlaybackInfo resetToNewPosition(
MediaPeriodId periodId, long startPositionUs, long contentPositionUs) {
return new PlaybackInfo(
timeline,
@ -150,6 +179,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs);
}
/**
* Copied playback info with new playing position.
*
* @param periodId New playing media period. See {@link #periodId}.
* @param positionUs New position. See {@link #positionUs}.
* @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored
* if {@code periodId.isAd()} is true.
* @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}.
* @return Copied playback info with new playing position.
*/
@CheckResult
public PlaybackInfo copyWithNewPosition(
MediaPeriodId periodId,
long positionUs,
long contentPositionUs,
long totalBufferedDurationUs) {
return new PlaybackInfo(
timeline,
manifest,
periodId,
positionUs,
periodId.isAd() ? contentPositionUs : C.TIME_UNSET,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
/**
* Copies playback info with new timeline and manifest.
*
* @param timeline New timeline. See {@link #timeline}.
* @param manifest New manifest. See {@link #manifest}.
* @return Copied playback info with new timeline and manifest.
*/
@CheckResult
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
return new PlaybackInfo(
timeline,
@ -167,6 +236,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new playback state.
*
* @param playbackState New playback state. See {@link #playbackState}.
* @return Copied playback info with new playback state.
*/
@CheckResult
public PlaybackInfo copyWithPlaybackState(int playbackState) {
return new PlaybackInfo(
timeline,
@ -184,6 +260,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new loading state.
*
* @param isLoading New loading state. See {@link #isLoading}.
* @return Copied playback info with new loading state.
*/
@CheckResult
public PlaybackInfo copyWithIsLoading(boolean isLoading) {
return new PlaybackInfo(
timeline,
@ -201,6 +284,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new track information.
*
* @param trackGroups New track groups. See {@link #trackGroups}.
* @param trackSelectorResult New track selector result. See {@link #trackSelectorResult}.
* @return Copied playback info with new track information.
*/
@CheckResult
public PlaybackInfo copyWithTrackInfo(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
return new PlaybackInfo(
@ -219,6 +310,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new loading media period.
*
* @param loadingMediaPeriodId New loading media period id. See {@link #loadingMediaPeriodId}.
* @return Copied playback info with new loading media period.
*/
@CheckResult
public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) {
return new PlaybackInfo(
timeline,

View file

@ -44,12 +44,16 @@ public interface Renderer extends PlayerMessage.Target {
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
@interface State {}
/**
* The renderer is disabled.
* The renderer is disabled. A renderer in this state may hold resources that it requires for
* rendering (e.g. media decoders), for use if it's subsequently enabled. {@link #reset()} can be
* called to force the renderer to release these resources.
*/
int STATE_DISABLED = 0;
/**
* The renderer is enabled but not started. A renderer in this state is not actively rendering
* media, but will typically hold resources that it requires for rendering (e.g. media decoders).
* The renderer is enabled but not started. A renderer in this state may render media at the
* current position (e.g. an initial video frame), but the position will not advance. A renderer
* in this state will typically hold resources that it requires for rendering (e.g. media
* decoders).
*/
int STATE_ENABLED = 1;
/**
@ -279,4 +283,12 @@ public interface Renderer extends PlayerMessage.Target {
*/
void disable();
/**
* Forces the renderer to give up any resources (e.g. media decoders) that it may be holding. If
* the renderer is not holding any resources, the call is a no-op.
*
* <p>This method may be called when the renderer is in the following states: {@link
* #STATE_DISABLED}.
*/
void reset();
}

View file

@ -351,8 +351,7 @@ public final class DefaultAudioSink implements AudioSink {
channelMappingAudioProcessor,
trimmingAudioProcessor);
Collections.addAll(toIntPcmAudioProcessors, audioProcessorChain.getAudioProcessors());
toIntPcmAvailableAudioProcessors =
toIntPcmAudioProcessors.toArray(new AudioProcessor[toIntPcmAudioProcessors.size()]);
toIntPcmAvailableAudioProcessors = toIntPcmAudioProcessors.toArray(new AudioProcessor[0]);
toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()};
volume = 1.0f;
startMediaTimeState = START_NOT_SET;

View file

@ -97,7 +97,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(List<SchemeData> schemeDatas) {
this(null, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
this(null, false, schemeDatas.toArray(new SchemeData[0]));
}
/**
@ -105,7 +105,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(@Nullable String schemeType, List<SchemeData> schemeDatas) {
this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
}
/**

View file

@ -81,8 +81,9 @@ public abstract class BinarySearchSeeker {
*/
public static final class OutputFrameHolder {
public final ByteBuffer byteBuffer;
public long timeUs;
public ByteBuffer byteBuffer;
/** Constructs an instance, wrapping the given byte buffer. */
public OutputFrameHolder(ByteBuffer outputByteBuffer) {

View file

@ -157,6 +157,7 @@ public final class MatroskaExtractor implements Extractor {
private static final int ID_FLAG_DEFAULT = 0x88;
private static final int ID_FLAG_FORCED = 0x55AA;
private static final int ID_DEFAULT_DURATION = 0x23E383;
private static final int ID_NAME = 0x536E;
private static final int ID_CODEC_ID = 0x86;
private static final int ID_CODEC_PRIVATE = 0x63A2;
private static final int ID_CODEC_DELAY = 0x56AA;
@ -815,6 +816,9 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("DocType " + value + " not supported");
}
break;
case ID_NAME:
currentTrack.name = value;
break;
case ID_CODEC_ID:
currentTrack.codecId = value;
break;
@ -1463,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_MAX_FALL:
return TYPE_UNSIGNED_INT;
case ID_DOC_TYPE:
case ID_NAME:
case ID_CODEC_ID:
case ID_LANGUAGE:
return TYPE_STRING;
@ -1609,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor {
private static final int DEFAULT_MAX_FALL = 200; // nits.
// Common elements.
public String name;
public String codecId;
public int number;
public int type;
@ -1833,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor {
byte[] hdrStaticInfo = getHdrStaticInfo();
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
}
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo,
drmInitData);
int rotationDegrees = Format.NO_VALUE;
// Some HTC devices signal rotation in track names.
if ("htc_video_rotA-000".equals(name)) {
rotationDegrees = 0;
} else if ("htc_video_rotA-090".equals(name)) {
rotationDegrees = 90;
} else if ("htc_video_rotA-180".equals(name)) {
rotationDegrees = 180;
} else if ("htc_video_rotA-270".equals(name)) {
rotationDegrees = 270;
}
format =
Format.createVideoSampleFormat(
Integer.toString(trackId),
mimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
maxInputSize,
width,
height,
/* frameRate= */ Format.NO_VALUE,
initializationData,
rotationDegrees,
pixelWidthHeightRatio,
projectionData,
stereoMode,
colorInfo,
drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags,

View file

@ -423,7 +423,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}
this.firstVideoTrackIndex = firstVideoTrackIndex;
this.durationUs = durationUs;
this.tracks = tracks.toArray(new Mp4Track[tracks.size()]);
this.tracks = tracks.toArray(new Mp4Track[0]);
accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks);
extractorOutput.endTracks();

View file

@ -546,9 +546,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
inputStreamEnded = false;
outputStreamEnded = false;
if (codec != null) {
flushCodec();
}
flushOrReinitCodec();
formatQueue.clear();
}
@ -560,6 +558,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override
protected void onDisabled() {
if (drmSession != null || pendingDrmSession != null) {
// TODO: Do something better with this case.
onReset();
} else {
flushOrReleaseCodec();
}
}
@Override
protected void onReset() {
format = null;
availableCodecInfos = null;
try {
@ -687,10 +695,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decoderCounters.ensureUpdated();
}
protected void flushCodec() throws ExoPlaybackException {
/**
* Flushes the codec. If flushing is not possible, the codec will be released and re-instantiated.
* This method is a no-op if the codec is {@code null}.
*
* <p>The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link
* #maybeInitCodec()} if the codec needs to be re-instantiated.
*
* @throws ExoPlaybackException If an error occurs re-instantiating the codec.
*/
protected final void flushOrReinitCodec() throws ExoPlaybackException {
if (flushOrReleaseCodec()) {
maybeInitCodec();
}
}
/**
* Flushes the codec. If flushing is not possible, the codec will be released. This method is a
* no-op if the codec is {@code null}.
*
* @return Whether the codec was released.
*/
protected boolean flushOrReleaseCodec() {
if (codec == null) {
// Nothing to do.
return false;
}
codecHotswapDeadlineMs = C.TIME_UNSET;
resetInputBuffer();
resetOutputBuffer();
codecReceivedBuffers = false;
waitingForFirstSyncFrame = true;
waitingForKeys = false;
shouldSkipOutputBuffer = false;
@ -699,28 +733,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
shouldSkipAdaptationWorkaroundOutputBuffer = false;
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
releaseCodec();
maybeInitCodec();
return true;
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
// We're already waiting to re-initialize the codec. Since we're now flushing, there's no need
// to wait any longer.
if (codecReinitializationIsRelease) {
releaseCodec();
maybeInitCodec();
return true;
} else {
codec.flush();
codecReceivedBuffers = false;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
}
} else {
// We can flush and re-use the existing decoder.
codec.flush();
codecReceivedBuffers = false;
}
if (codecReconfigured && format != null) {
// Any reconfiguration data that we send shortly before the flush may be discarded. We
// avoid this issue by sending reconfiguration data following every flush.
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
}
return false;
}
private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder)
@ -1496,7 +1529,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
releaseCodec();
maybeInitCodec();
} else {
flushCodec();
flushOrReinitCodec();
}
} else {
outputStreamEnded = true;

View file

@ -801,6 +801,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public final MediaSource mediaSource;
public final Object uid;
public final List<DeferredMediaPeriod> activeMediaPeriods;
public DeferredTimeline timeline;
public int childIndex;
@ -809,7 +810,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public boolean hasStartedPreparing;
public boolean isPrepared;
public boolean isRemoved;
public List<DeferredMediaPeriod> activeMediaPeriods;
public MediaSourceHolder(MediaSource mediaSource) {
this.mediaSource = mediaSource;

View file

@ -308,7 +308,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
// chunk even if the sample timestamps are slightly offset from the chunk start times.
seekInsideBuffer =
primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0));
decodeOnlyUntilPositionUs = Long.MIN_VALUE;
decodeOnlyUntilPositionUs = 0;
} else {
seekInsideBuffer =
primarySampleQueue.advanceTo(
@ -583,7 +583,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (pendingReset) {
boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs;
// Only enable setting of the decode only flag if we're not resetting to a chunk boundary.
decodeOnlyUntilPositionUs = resetToMediaChunk ? Long.MIN_VALUE : pendingResetPositionUs;
decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs;
pendingResetPositionUs = C.TIME_UNSET;
}
mediaChunk.init(mediaChunkOutput);

View file

@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator {
Math.max(availableAllocations.length * 2, availableCount + allocations.length));
}
for (Allocation allocation : allocations) {
// Weak sanity check that the allocation probably originated from this pool.
if (allocation.data != initialAllocationBlock
&& allocation.data.length != individualAllocationSize) {
throw new IllegalArgumentException(
"Unexpected allocation: "
+ System.identityHashCode(allocation.data)
+ ", "
+ System.identityHashCode(initialAllocationBlock)
+ ", "
+ allocation.data.length
+ ", "
+ individualAllocationSize);
}
availableAllocations[availableCount++] = allocation;
}
allocatedCount -= allocations.length;

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream.cache;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NavigableSet;
@ -195,8 +196,7 @@ public final class CachedRegionTracker implements Cache.Listener {
@Override
public int compareTo(@NonNull Region another) {
return startOffset < another.startOffset ? -1
: startOffset == another.startOffset ? 0 : 1;
return Util.compareLong(startOffset, another.startOffset);
}
}

View file

@ -520,9 +520,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@CallSuper
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
buffersInCodecCount = 0;
protected boolean flushOrReleaseCodec() {
try {
return super.flushOrReleaseCodec();
} finally {
buffersInCodecCount = 0;
}
}
@Override
@ -859,7 +862,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// We dropped some buffers to catch up, so update the decoder counters and flush the codec,
// which releases all pending buffers buffers including the current output buffer.
updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
flushCodec();
flushOrReinitCodec();
return true;
}

View file

@ -72,12 +72,12 @@ public class CameraMotionRenderer extends BaseRenderer {
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
reset();
resetListener();
}
@Override
protected void onDisabled() {
reset();
resetListener();
}
@Override
@ -124,7 +124,7 @@ public class CameraMotionRenderer extends BaseRenderer {
return result;
}
private void reset() {
private void resetListener() {
lastTimestampUs = 0;
if (listener != null) {
listener.onCameraMotionReset();

View file

@ -2534,6 +2534,59 @@ public final class ExoPlayerTest {
assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10);
}
@Test
public void periodTransitionReportsCorrectBufferedPosition() throws Exception {
int periodCount = 3;
long periodDurationUs = 5 * C.MICROS_PER_SECOND;
long windowDurationUs = periodCount * periodDurationUs;
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
periodCount,
/* id= */ new Object(),
/* isSeekable= */ true,
/* isDynamic= */ false,
windowDurationUs));
AtomicReference<Player> playerReference = new AtomicReference<>();
AtomicLong bufferedPositionAtFirstDiscontinuityMs = new AtomicLong(C.TIME_UNSET);
EventListener eventListener =
new EventListener() {
@Override
public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
if (bufferedPositionAtFirstDiscontinuityMs.get() == C.TIME_UNSET) {
bufferedPositionAtFirstDiscontinuityMs.set(
playerReference.get().getBufferedPosition());
}
}
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder("periodTransitionReportsCorrectBufferedPosition")
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
playerReference.set(player);
player.addListener(eventListener);
}
})
.pause()
// Wait until all periods are fully buffered.
.waitForIsLoading(/* targetIsLoading= */ true)
.waitForIsLoading(/* targetIsLoading= */ false)
.play()
.build();
new Builder()
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build(context)
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(bufferedPositionAtFirstDiscontinuityMs.get()).isEqualTo(C.usToMs(windowDurationUs));
}
// Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {

View file

@ -103,6 +103,7 @@ public class SimpleDecoderAudioRendererTest {
}
verify(mockAudioSink, times(1)).playToEndOfStream();
audioRenderer.disable();
audioRenderer.reset();
verify(mockAudioSink, times(1)).release();
}

View file

@ -544,7 +544,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
long periodDurationUs = representationHolder.periodDurationUs;
long clippedEndTimeUs =
periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs
periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs
? periodDurationUs
: C.TIME_UNSET;
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),

View file

@ -158,6 +158,8 @@ public class DashManifestParser extends DefaultHandler
: (period.startMs + periodDurationMs);
periods.add(period);
}
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "MPD"));
@ -224,6 +226,8 @@ public class DashManifestParser extends DefaultHandler
segmentBase = parseSegmentList(xpp, null);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, null);
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "Period"));
@ -412,22 +416,26 @@ public class DashManifestParser extends DefaultHandler
} else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
String robustnessLevel = xpp.getAttributeValue(null, "robustness_level");
requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW");
} else if (data == null) {
if (XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
&& xpp.next() == XmlPullParser.TEXT) {
// The cenc:pssh element is defined in 23001-7:2015.
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
uuid = PsshAtomUtil.parseUuid(data);
if (uuid == null) {
Log.w(TAG, "Skipping malformed cenc:pssh data");
data = null;
}
} else if (C.PLAYREADY_UUID.equals(uuid) && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
&& xpp.next() == XmlPullParser.TEXT) {
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
Base64.decode(xpp.getText(), Base64.DEFAULT));
} else if (data == null
&& XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
&& xpp.next() == XmlPullParser.TEXT) {
// The cenc:pssh element is defined in 23001-7:2015.
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
uuid = PsshAtomUtil.parseUuid(data);
if (uuid == null) {
Log.w(TAG, "Skipping malformed cenc:pssh data");
data = null;
}
} else if (data == null
&& C.PLAYREADY_UUID.equals(uuid)
&& XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
&& xpp.next() == XmlPullParser.TEXT) {
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
data =
PsshAtomUtil.buildPsshAtom(
C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT));
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection"));
SchemeData schemeData =
@ -465,7 +473,7 @@ public class DashManifestParser extends DefaultHandler
*/
protected void parseAdaptationSetChild(XmlPullParser xpp)
throws XmlPullParserException, IOException {
// pass
maybeSkipTag(xpp);
}
// Representation parsing.
@ -529,6 +537,8 @@ public class DashManifestParser extends DefaultHandler
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "Representation"));
@ -667,6 +677,8 @@ public class DashManifestParser extends DefaultHandler
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) {
initialization = parseInitialization(xpp);
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase"));
@ -704,6 +716,8 @@ public class DashManifestParser extends DefaultHandler
segments = new ArrayList<>();
}
segments.add(parseSegmentUrl(xpp));
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList"));
@ -750,6 +764,8 @@ public class DashManifestParser extends DefaultHandler
initialization = parseInitialization(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) {
timeline = parseSegmentTimeline(xpp);
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate"));
@ -797,6 +813,8 @@ public class DashManifestParser extends DefaultHandler
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
scratchOutputStream);
eventMessages.add(event);
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "EventStream"));
@ -935,6 +953,8 @@ public class DashManifestParser extends DefaultHandler
segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration));
elapsedTime += duration;
}
} else {
maybeSkipTag(xpp);
}
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline"));
return segmentTimeline;
@ -1017,6 +1037,29 @@ public class DashManifestParser extends DefaultHandler
// Utility methods.
/**
* If the provided {@link XmlPullParser} is currently positioned at the start of a tag, skips
* forward to the end of that tag.
*
* @param xpp The {@link XmlPullParser}.
* @throws XmlPullParserException If an error occurs parsing the stream.
* @throws IOException If an error occurs reading the stream.
*/
public static void maybeSkipTag(XmlPullParser xpp) throws IOException, XmlPullParserException {
if (!XmlPullParserUtil.isStartTag(xpp)) {
return;
}
int depth = 1;
while (depth != 0) {
xpp.next();
if (XmlPullParserUtil.isStartTag(xpp)) {
depth++;
} else if (XmlPullParserUtil.isEndTag(xpp)) {
depth--;
}
}
}
/**
* Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}.
*/

View file

@ -1032,6 +1032,12 @@ public class PlayerView extends FrameLayout {
if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false;
}
return performClick();
}
@Override
public boolean performClick() {
super.performClick();
return toggleControllerVisibility();
}

View file

@ -11,7 +11,7 @@
<string name="exo_controls_repeat_one_description">एक रीपीट करा</string>
<string name="exo_controls_repeat_all_description">सर्व रीपीट करा</string>
<string name="exo_controls_shuffle_description">शफल करा</string>
<string name="exo_controls_fullscreen_description">पूर्ण स्क्रीन मोड</string>
<string name="exo_controls_fullscreen_description">फुल स्क्रीन मोड</string>
<string name="exo_download_description">डाउनलोड करा</string>
<string name="exo_download_notification_channel_name">डाउनलोड</string>
<string name="exo_download_downloading">डाउनलोड होत आहे</string>

View file

@ -686,6 +686,56 @@ public abstract class Action {
}
}
/**
* Waits for a specified loading state, returning either immediately or after a call to {@link
* Player.EventListener#onLoadingChanged(boolean)}.
*/
public static final class WaitForIsLoading extends Action {
private final boolean targetIsLoading;
/**
* @param tag A tag to use for logging.
* @param targetIsLoading The loading state to wait for.
*/
public WaitForIsLoading(String tag, boolean targetIsLoading) {
super(tag, "WaitForIsLoading");
this.targetIsLoading = targetIsLoading;
}
@Override
protected void doActionAndScheduleNextImpl(
final SimpleExoPlayer player,
final DefaultTrackSelector trackSelector,
final Surface surface,
final HandlerWrapper handler,
final ActionNode nextAction) {
if (nextAction == null) {
return;
}
if (targetIsLoading == player.isLoading()) {
nextAction.schedule(player, trackSelector, surface, handler);
} else {
player.addListener(
new Player.EventListener() {
@Override
public void onLoadingChanged(boolean isLoading) {
if (targetIsLoading == isLoading) {
player.removeListener(this);
nextAction.schedule(player, trackSelector, surface, handler);
}
}
});
}
}
@Override
protected void doActionImpl(
SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) {
// Not triggered.
}
}
/**
* Waits for {@link Player.EventListener#onSeekProcessed()}.
*/

View file

@ -41,6 +41,7 @@ import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
import com.google.android.exoplayer2.testutil.Action.SetVideoSurface;
import com.google.android.exoplayer2.testutil.Action.Stop;
import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException;
import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading;
import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState;
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed;
@ -414,6 +415,16 @@ public final class ActionSchedule {
return apply(new WaitForPlaybackState(tag, targetPlaybackState));
}
/**
* Schedules a delay until {@code player.isLoading()} changes to the specified value.
*
* @param targetIsLoading The target value of {@code player.isLoading()}.
* @return The builder, for convenience.
*/
public Builder waitForIsLoading(boolean targetIsLoading) {
return apply(new WaitForIsLoading(tag, targetIsLoading));
}
/**
* Schedules a {@link Runnable} to be executed.
*

View file

@ -105,9 +105,12 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
}
@Override
protected void flushCodec() throws ExoPlaybackException {
super.flushCodec();
clearTimestamps();
protected boolean flushOrReleaseCodec() {
try {
return super.flushOrReleaseCodec();
} finally {
clearTimestamps();
}
}
@Override