mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
Merge branch 'dev-v2' into program_information
This commit is contained in:
commit
a7ace58712
32 changed files with 538 additions and 127 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ public class SimpleDecoderAudioRendererTest {
|
|||
}
|
||||
verify(mockAudioSink, times(1)).playToEndOfStream();
|
||||
audioRenderer.disable();
|
||||
audioRenderer.reset();
|
||||
verify(mockAudioSink, times(1)).release();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()}.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue