mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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)).
|
([#4788](https://github.com/google/ExoPlayer/issues/4788)).
|
||||||
* SubRip: Add support for alignment tags, and remove tags from the displayed
|
* SubRip: Add support for alignment tags, and remove tags from the displayed
|
||||||
captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
|
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 ###
|
### 2.9.0 ###
|
||||||
|
|
||||||
* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend
|
* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to
|
||||||
on ExoPlayer via its source code rather than an AAR may need to add
|
add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
|
||||||
`compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
|
|
||||||
gradle settings to ensure bytecode compatibility.
|
gradle settings to ensure bytecode compatibility.
|
||||||
* Set `compileSdkVersion` and `targetSdkVersion` to 28.
|
* Set `compileSdkVersion` and `targetSdkVersion` to 28.
|
||||||
* Support for automatic audio focus handling via
|
* Support for automatic audio focus handling via
|
||||||
|
|
@ -346,18 +351,18 @@
|
||||||
begins, and poll the audio timestamp less frequently once it starts
|
begins, and poll the audio timestamp less frequently once it starts
|
||||||
advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
|
advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
|
||||||
* Add an option to skip silent audio in `PlaybackParameters`
|
* 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
|
* Fix an issue where playback of TrueHD streams would get stuck after seeking
|
||||||
due to not finding a syncframe
|
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
|
* 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
|
* 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.
|
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
|
* Allow trimming more than one sample when applying an elst audio edit via
|
||||||
gapless playback info.
|
gapless playback info.
|
||||||
* Allow overriding skipping/scaling with custom `AudioProcessor`s
|
* 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:
|
* Caching:
|
||||||
* Add release method to the `Cache` interface, and prevent multiple instances
|
* Add release method to the `Cache` interface, and prevent multiple instances
|
||||||
of `SimpleCache` using the same folder at the same time.
|
of `SimpleCache` using the same folder at the same time.
|
||||||
|
|
|
||||||
|
|
@ -350,8 +350,7 @@ public class SampleChooserActivity extends Activity
|
||||||
? null
|
? null
|
||||||
: new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession);
|
: new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession);
|
||||||
if (playlistSamples != null) {
|
if (playlistSamples != null) {
|
||||||
UriSample[] playlistSamplesArray = playlistSamples.toArray(
|
UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]);
|
||||||
new UriSample[playlistSamples.size()]);
|
|
||||||
return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray);
|
return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray);
|
||||||
} else {
|
} else {
|
||||||
return new UriSample(
|
return new UriSample(
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||||
onDisabled();
|
onDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void reset() {
|
||||||
|
Assertions.checkState(state == STATE_DISABLED);
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
// RendererCapabilities implementation.
|
// RendererCapabilities implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -247,6 +253,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||||
// Do nothing.
|
// 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.
|
// Methods to be called by subclasses.
|
||||||
|
|
||||||
/** Returns the formats of the currently enabled stream. */
|
/** Returns the formats of the currently enabled stream. */
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
|
||||||
extensionRendererMode, renderersList);
|
extensionRendererMode, renderersList);
|
||||||
buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
|
buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
|
||||||
buildMiscellaneousRenderers(context, eventHandler, 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) {
|
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||||
// Replace internal unset start position with externally visible start position of zero.
|
// Replace internal unset start position with externally visible start position of zero.
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(
|
playbackInfo.resetToNewPosition(
|
||||||
playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs);
|
playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs);
|
||||||
}
|
}
|
||||||
if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare)
|
if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare)
|
||||||
|
|
|
||||||
|
|
@ -448,7 +448,11 @@ import java.util.Collections;
|
||||||
seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
|
seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
|
||||||
if (newPositionUs != playbackInfo.positionUs) {
|
if (newPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
|
playbackInfo.copyWithNewPosition(
|
||||||
|
periodId,
|
||||||
|
newPositionUs,
|
||||||
|
playbackInfo.contentPositionUs,
|
||||||
|
getTotalBufferedDurationUs());
|
||||||
if (sendDiscontinuity) {
|
if (sendDiscontinuity) {
|
||||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
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
|
// 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.
|
// renderers are flushed. Only report the discontinuity externally if the position changed.
|
||||||
if (periodPositionUs != playbackInfo.positionUs) {
|
if (periodPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
playbackInfo =
|
||||||
playbackInfo.contentPositionUs);
|
playbackInfo.copyWithNewPosition(
|
||||||
|
playbackInfo.periodId,
|
||||||
|
periodPositionUs,
|
||||||
|
playbackInfo.contentPositionUs,
|
||||||
|
getTotalBufferedDurationUs());
|
||||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -496,10 +504,8 @@ import java.util.Collections;
|
||||||
|
|
||||||
// Update the buffered position and total buffered duration.
|
// Update the buffered position and total buffered duration.
|
||||||
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
|
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
|
||||||
playbackInfo.bufferedPositionUs =
|
playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs();
|
||||||
loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true);
|
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
|
||||||
playbackInfo.totalBufferedDurationUs =
|
|
||||||
playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSomeWork() throws ExoPlaybackException, IOException {
|
private void doSomeWork() throws ExoPlaybackException, IOException {
|
||||||
|
|
@ -647,7 +653,9 @@ import java.util.Collections;
|
||||||
periodPositionUs = newPeriodPositionUs;
|
periodPositionUs = newPeriodPositionUs;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
|
playbackInfo =
|
||||||
|
playbackInfo.copyWithNewPosition(
|
||||||
|
periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||||
if (seekPositionAdjusted) {
|
if (seekPositionAdjusted) {
|
||||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
|
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
|
||||||
}
|
}
|
||||||
|
|
@ -981,6 +989,7 @@ import java.util.Collections;
|
||||||
mediaClock.onRendererDisabled(renderer);
|
mediaClock.onRendererDisabled(renderer);
|
||||||
ensureStopped(renderer);
|
ensureStopped(renderer);
|
||||||
renderer.disable();
|
renderer.disable();
|
||||||
|
renderer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||||
|
|
@ -993,7 +1002,7 @@ import java.util.Collections;
|
||||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||||
boolean selectionsChangedForReadPeriod = true;
|
boolean selectionsChangedForReadPeriod = true;
|
||||||
TrackSelectorResult newTrackSelectorResult = null;
|
TrackSelectorResult newTrackSelectorResult;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (periodHolder == null || !periodHolder.prepared) {
|
if (periodHolder == null || !periodHolder.prepared) {
|
||||||
// The reselection did not change any prepared periods.
|
// The reselection did not change any prepared periods.
|
||||||
|
|
@ -1022,8 +1031,12 @@ import java.util.Collections;
|
||||||
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags);
|
newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags);
|
||||||
if (playbackInfo.playbackState != Player.STATE_ENDED
|
if (playbackInfo.playbackState != Player.STATE_ENDED
|
||||||
&& periodPositionUs != playbackInfo.positionUs) {
|
&& periodPositionUs != playbackInfo.positionUs) {
|
||||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
playbackInfo =
|
||||||
playbackInfo.contentPositionUs);
|
playbackInfo.copyWithNewPosition(
|
||||||
|
playbackInfo.periodId,
|
||||||
|
periodPositionUs,
|
||||||
|
playbackInfo.contentPositionUs,
|
||||||
|
getTotalBufferedDurationUs());
|
||||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
}
|
}
|
||||||
|
|
@ -1097,12 +1110,10 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
|
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
|
||||||
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
||||||
long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal);
|
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||||
return bufferedPositionUs == C.TIME_END_OF_SOURCE
|
return bufferedToEnd
|
||||||
|| loadControl.shouldStartPlayback(
|
|| loadControl.shouldStartPlayback(
|
||||||
bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs),
|
getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
|
||||||
mediaClock.getPlaybackParameters().speed,
|
|
||||||
rebuffering);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTimelineReady() {
|
private boolean isTimelineReady() {
|
||||||
|
|
@ -1167,7 +1178,7 @@ import java.util.Collections;
|
||||||
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
|
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
|
||||||
} catch (IllegalSeekPositionException e) {
|
} catch (IllegalSeekPositionException e) {
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
|
playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
pendingInitialSeekPosition = null;
|
pendingInitialSeekPosition = null;
|
||||||
|
|
@ -1180,7 +1191,7 @@ import java.util.Collections;
|
||||||
long positionUs = periodPosition.second;
|
long positionUs = periodPosition.second;
|
||||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
|
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(
|
playbackInfo.resetToNewPosition(
|
||||||
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
|
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
|
||||||
}
|
}
|
||||||
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||||
|
|
@ -1194,7 +1205,7 @@ import java.util.Collections;
|
||||||
long startPositionUs = defaultPosition.second;
|
long startPositionUs = defaultPosition.second;
|
||||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(
|
playbackInfo.resetToNewPosition(
|
||||||
periodId,
|
periodId,
|
||||||
periodId.isAd() ? 0 : startPositionUs,
|
periodId.isAd() ? 0 : startPositionUs,
|
||||||
/* contentPositionUs= */ startPositionUs);
|
/* contentPositionUs= */ startPositionUs);
|
||||||
|
|
@ -1213,7 +1224,7 @@ import java.util.Collections;
|
||||||
long startPositionUs = defaultPosition.second;
|
long startPositionUs = defaultPosition.second;
|
||||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
playbackInfo.fromNewPosition(
|
playbackInfo.resetToNewPosition(
|
||||||
periodId,
|
periodId,
|
||||||
/* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs,
|
/* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs,
|
||||||
/* contentPositionUs= */ startPositionUs);
|
/* contentPositionUs= */ startPositionUs);
|
||||||
|
|
@ -1252,7 +1263,9 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
// Actually do the seek.
|
// Actually do the seek.
|
||||||
long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
||||||
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
|
playbackInfo =
|
||||||
|
playbackInfo.copyWithNewPosition(
|
||||||
|
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1264,7 +1277,9 @@ import java.util.Collections;
|
||||||
// The previously playing ad should no longer be played, so skip it.
|
// The previously playing ad should no longer be played, so skip it.
|
||||||
long seekPositionUs =
|
long seekPositionUs =
|
||||||
seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
||||||
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
|
playbackInfo =
|
||||||
|
playbackInfo.copyWithNewPosition(
|
||||||
|
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1420,8 +1435,12 @@ import java.util.Collections;
|
||||||
MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
|
MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
|
||||||
playingPeriodHolder = queue.advancePlayingPeriod();
|
playingPeriodHolder = queue.advancePlayingPeriod();
|
||||||
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
|
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
|
||||||
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
|
playbackInfo =
|
||||||
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
|
playbackInfo.copyWithNewPosition(
|
||||||
|
playingPeriodHolder.info.id,
|
||||||
|
playingPeriodHolder.info.startPositionUs,
|
||||||
|
playingPeriodHolder.info.contentPositionUs,
|
||||||
|
getTotalBufferedDurationUs());
|
||||||
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
|
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
advancedPlayingPeriod = true;
|
advancedPlayingPeriod = true;
|
||||||
|
|
@ -1574,7 +1593,7 @@ import java.util.Collections;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long bufferedDurationUs =
|
long bufferedDurationUs =
|
||||||
nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
|
getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs);
|
||||||
boolean continueLoading =
|
boolean continueLoading =
|
||||||
loadControl.shouldContinueLoading(
|
loadControl.shouldContinueLoading(
|
||||||
bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
||||||
|
|
@ -1671,6 +1690,11 @@ import java.util.Collections;
|
||||||
if (loadingMediaPeriodChanged) {
|
if (loadingMediaPeriodChanged) {
|
||||||
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
|
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
|
||||||
}
|
}
|
||||||
|
playbackInfo.bufferedPositionUs =
|
||||||
|
loadingMediaPeriodHolder == null
|
||||||
|
? playbackInfo.positionUs
|
||||||
|
: loadingMediaPeriodHolder.getBufferedPositionUs();
|
||||||
|
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
|
||||||
if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged)
|
if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged)
|
||||||
&& loadingMediaPeriodHolder != null
|
&& loadingMediaPeriodHolder != null
|
||||||
&& loadingMediaPeriodHolder.prepared) {
|
&& 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(
|
private void updateLoadControlTrackSelection(
|
||||||
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
|
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
|
||||||
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
|
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
|
* Returns the buffered position in microseconds. If the period is buffered to the end, then the
|
||||||
* {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which
|
* period duration is returned.
|
||||||
* case 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.
|
* @return The buffered position in microseconds.
|
||||||
*/
|
*/
|
||||||
public long getBufferedPositionUs(boolean convertEosToDuration) {
|
public long getBufferedPositionUs() {
|
||||||
if (!prepared) {
|
if (!prepared) {
|
||||||
return info.startPositionUs;
|
return info.startPositionUs;
|
||||||
}
|
}
|
||||||
long bufferedPositionUs =
|
long bufferedPositionUs =
|
||||||
hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
|
hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
|
||||||
return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
|
return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs;
|
||||||
? 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
|
// 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.
|
// timeline is updated, to avoid repeatedly checking the same timeline.
|
||||||
MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info;
|
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) {
|
if (mediaPeriodInfo.isLastInTimelinePeriod) {
|
||||||
int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid);
|
int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid);
|
||||||
int nextPeriodIndex =
|
int nextPeriodIndex =
|
||||||
|
|
@ -550,19 +555,15 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
|
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
|
||||||
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
|
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
|
||||||
// We're starting to buffer a new window. When playback transitions to this window we'll
|
// 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
|
// want it to be from its default start position, so project the default start position
|
||||||
// transitions is equal the duration of media that's currently buffered (assuming no
|
// forward by the duration of the buffer, and start buffering from this point.
|
||||||
// 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;
|
|
||||||
Pair<Object, Long> defaultPosition =
|
Pair<Object, Long> defaultPosition =
|
||||||
timeline.getPeriodPosition(
|
timeline.getPeriodPosition(
|
||||||
window,
|
window,
|
||||||
period,
|
period,
|
||||||
nextWindowIndex,
|
nextWindowIndex,
|
||||||
C.TIME_UNSET,
|
/* windowPositionUs= */ C.TIME_UNSET,
|
||||||
Math.max(0, defaultPositionProjectionUs));
|
/* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs));
|
||||||
if (defaultPosition == null) {
|
if (defaultPosition == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -603,11 +604,27 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
mediaPeriodInfo.contentPositionUs,
|
mediaPeriodInfo.contentPositionUs,
|
||||||
currentPeriodId.windowSequenceNumber);
|
currentPeriodId.windowSequenceNumber);
|
||||||
} else {
|
} 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(
|
return getMediaPeriodInfoForContent(
|
||||||
currentPeriodId.periodUid,
|
currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber);
|
||||||
mediaPeriodInfo.contentPositionUs,
|
|
||||||
currentPeriodId.windowSequenceNumber);
|
|
||||||
}
|
}
|
||||||
} else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) {
|
} else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) {
|
||||||
// Play the next ad group if it's available.
|
// Play the next ad group if it's available.
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,12 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
|
||||||
onDisabled();
|
onDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void reset() {
|
||||||
|
Assertions.checkState(state == STATE_DISABLED);
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -260,6 +266,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
|
||||||
// Do nothing.
|
// 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.
|
// Methods to be called by subclasses.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import android.support.annotation.CheckResult;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
|
@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
public final MediaPeriodId periodId;
|
public final MediaPeriodId periodId;
|
||||||
/**
|
/**
|
||||||
* The start position at which playback started in {@link #periodId} relative to the start of the
|
* 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;
|
public final long startPositionUs;
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
startPositionUs);
|
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(
|
public PlaybackInfo(
|
||||||
Timeline timeline,
|
Timeline timeline,
|
||||||
@Nullable Object manifest,
|
@Nullable Object manifest,
|
||||||
|
|
@ -132,7 +151,17 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
this.positionUs = positionUs;
|
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) {
|
MediaPeriodId periodId, long startPositionUs, long contentPositionUs) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
|
|
@ -150,6 +179,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
startPositionUs);
|
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) {
|
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
|
|
@ -167,6 +236,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
positionUs);
|
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) {
|
public PlaybackInfo copyWithPlaybackState(int playbackState) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
|
|
@ -184,6 +260,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
positionUs);
|
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) {
|
public PlaybackInfo copyWithIsLoading(boolean isLoading) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
|
|
@ -201,6 +284,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
positionUs);
|
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(
|
public PlaybackInfo copyWithTrackInfo(
|
||||||
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
|
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
|
|
@ -219,6 +310,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||||
positionUs);
|
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) {
|
public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,16 @@ public interface Renderer extends PlayerMessage.Target {
|
||||||
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
|
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
|
||||||
@interface State {}
|
@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;
|
int STATE_DISABLED = 0;
|
||||||
/**
|
/**
|
||||||
* The renderer is enabled but not started. A renderer in this state is not actively rendering
|
* The renderer is enabled but not started. A renderer in this state may render media at the
|
||||||
* media, but will typically hold resources that it requires for rendering (e.g. media decoders).
|
* 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;
|
int STATE_ENABLED = 1;
|
||||||
/**
|
/**
|
||||||
|
|
@ -279,4 +283,12 @@ public interface Renderer extends PlayerMessage.Target {
|
||||||
*/
|
*/
|
||||||
void disable();
|
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,
|
channelMappingAudioProcessor,
|
||||||
trimmingAudioProcessor);
|
trimmingAudioProcessor);
|
||||||
Collections.addAll(toIntPcmAudioProcessors, audioProcessorChain.getAudioProcessors());
|
Collections.addAll(toIntPcmAudioProcessors, audioProcessorChain.getAudioProcessors());
|
||||||
toIntPcmAvailableAudioProcessors =
|
toIntPcmAvailableAudioProcessors = toIntPcmAudioProcessors.toArray(new AudioProcessor[0]);
|
||||||
toIntPcmAudioProcessors.toArray(new AudioProcessor[toIntPcmAudioProcessors.size()]);
|
|
||||||
toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()};
|
toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()};
|
||||||
volume = 1.0f;
|
volume = 1.0f;
|
||||||
startMediaTimeState = START_NOT_SET;
|
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.
|
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||||
*/
|
*/
|
||||||
public DrmInitData(List<SchemeData> schemeDatas) {
|
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.
|
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||||
*/
|
*/
|
||||||
public DrmInitData(@Nullable String schemeType, List<SchemeData> schemeDatas) {
|
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 static final class OutputFrameHolder {
|
||||||
|
|
||||||
|
public final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
public long timeUs;
|
public long timeUs;
|
||||||
public ByteBuffer byteBuffer;
|
|
||||||
|
|
||||||
/** Constructs an instance, wrapping the given byte buffer. */
|
/** Constructs an instance, wrapping the given byte buffer. */
|
||||||
public OutputFrameHolder(ByteBuffer outputByteBuffer) {
|
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_DEFAULT = 0x88;
|
||||||
private static final int ID_FLAG_FORCED = 0x55AA;
|
private static final int ID_FLAG_FORCED = 0x55AA;
|
||||||
private static final int ID_DEFAULT_DURATION = 0x23E383;
|
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_ID = 0x86;
|
||||||
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
||||||
private static final int ID_CODEC_DELAY = 0x56AA;
|
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");
|
throw new ParserException("DocType " + value + " not supported");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ID_NAME:
|
||||||
|
currentTrack.name = value;
|
||||||
|
break;
|
||||||
case ID_CODEC_ID:
|
case ID_CODEC_ID:
|
||||||
currentTrack.codecId = value;
|
currentTrack.codecId = value;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1463,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
case ID_MAX_FALL:
|
case ID_MAX_FALL:
|
||||||
return TYPE_UNSIGNED_INT;
|
return TYPE_UNSIGNED_INT;
|
||||||
case ID_DOC_TYPE:
|
case ID_DOC_TYPE:
|
||||||
|
case ID_NAME:
|
||||||
case ID_CODEC_ID:
|
case ID_CODEC_ID:
|
||||||
case ID_LANGUAGE:
|
case ID_LANGUAGE:
|
||||||
return TYPE_STRING;
|
return TYPE_STRING;
|
||||||
|
|
@ -1609,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
private static final int DEFAULT_MAX_FALL = 200; // nits.
|
private static final int DEFAULT_MAX_FALL = 200; // nits.
|
||||||
|
|
||||||
// Common elements.
|
// Common elements.
|
||||||
|
public String name;
|
||||||
public String codecId;
|
public String codecId;
|
||||||
public int number;
|
public int number;
|
||||||
public int type;
|
public int type;
|
||||||
|
|
@ -1833,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor {
|
||||||
byte[] hdrStaticInfo = getHdrStaticInfo();
|
byte[] hdrStaticInfo = getHdrStaticInfo();
|
||||||
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
|
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
|
||||||
}
|
}
|
||||||
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
|
int rotationDegrees = Format.NO_VALUE;
|
||||||
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
|
// Some HTC devices signal rotation in track names.
|
||||||
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo,
|
if ("htc_video_rotA-000".equals(name)) {
|
||||||
drmInitData);
|
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)) {
|
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
||||||
type = C.TRACK_TYPE_TEXT;
|
type = C.TRACK_TYPE_TEXT;
|
||||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags,
|
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags,
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||||
}
|
}
|
||||||
this.firstVideoTrackIndex = firstVideoTrackIndex;
|
this.firstVideoTrackIndex = firstVideoTrackIndex;
|
||||||
this.durationUs = durationUs;
|
this.durationUs = durationUs;
|
||||||
this.tracks = tracks.toArray(new Mp4Track[tracks.size()]);
|
this.tracks = tracks.toArray(new Mp4Track[0]);
|
||||||
accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks);
|
accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks);
|
||||||
|
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
|
|
|
||||||
|
|
@ -546,9 +546,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
if (codec != null) {
|
flushOrReinitCodec();
|
||||||
flushCodec();
|
|
||||||
}
|
|
||||||
formatQueue.clear();
|
formatQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -560,6 +558,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
|
if (drmSession != null || pendingDrmSession != null) {
|
||||||
|
// TODO: Do something better with this case.
|
||||||
|
onReset();
|
||||||
|
} else {
|
||||||
|
flushOrReleaseCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
format = null;
|
format = null;
|
||||||
availableCodecInfos = null;
|
availableCodecInfos = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -687,10 +695,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
decoderCounters.ensureUpdated();
|
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;
|
codecHotswapDeadlineMs = C.TIME_UNSET;
|
||||||
resetInputBuffer();
|
resetInputBuffer();
|
||||||
resetOutputBuffer();
|
resetOutputBuffer();
|
||||||
|
codecReceivedBuffers = false;
|
||||||
waitingForFirstSyncFrame = true;
|
waitingForFirstSyncFrame = true;
|
||||||
waitingForKeys = false;
|
waitingForKeys = false;
|
||||||
shouldSkipOutputBuffer = false;
|
shouldSkipOutputBuffer = false;
|
||||||
|
|
@ -699,28 +733,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
shouldSkipAdaptationWorkaroundOutputBuffer = false;
|
shouldSkipAdaptationWorkaroundOutputBuffer = false;
|
||||||
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
|
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
|
||||||
releaseCodec();
|
releaseCodec();
|
||||||
maybeInitCodec();
|
return true;
|
||||||
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
|
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
|
||||||
// We're already waiting to re-initialize the codec. Since we're now flushing, there's no need
|
// We're already waiting to re-initialize the codec. Since we're now flushing, there's no need
|
||||||
// to wait any longer.
|
// to wait any longer.
|
||||||
if (codecReinitializationIsRelease) {
|
if (codecReinitializationIsRelease) {
|
||||||
releaseCodec();
|
releaseCodec();
|
||||||
maybeInitCodec();
|
return true;
|
||||||
} else {
|
} else {
|
||||||
codec.flush();
|
codec.flush();
|
||||||
codecReceivedBuffers = false;
|
|
||||||
codecReinitializationState = REINITIALIZATION_STATE_NONE;
|
codecReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can flush and re-use the existing decoder.
|
// We can flush and re-use the existing decoder.
|
||||||
codec.flush();
|
codec.flush();
|
||||||
codecReceivedBuffers = false;
|
|
||||||
}
|
}
|
||||||
if (codecReconfigured && format != null) {
|
if (codecReconfigured && format != null) {
|
||||||
// Any reconfiguration data that we send shortly before the flush may be discarded. We
|
// Any reconfiguration data that we send shortly before the flush may be discarded. We
|
||||||
// avoid this issue by sending reconfiguration data following every flush.
|
// avoid this issue by sending reconfiguration data following every flush.
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder)
|
private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder)
|
||||||
|
|
@ -1496,7 +1529,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
releaseCodec();
|
releaseCodec();
|
||||||
maybeInitCodec();
|
maybeInitCodec();
|
||||||
} else {
|
} else {
|
||||||
flushCodec();
|
flushOrReinitCodec();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outputStreamEnded = true;
|
outputStreamEnded = true;
|
||||||
|
|
|
||||||
|
|
@ -801,6 +801,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
|
|
||||||
public final MediaSource mediaSource;
|
public final MediaSource mediaSource;
|
||||||
public final Object uid;
|
public final Object uid;
|
||||||
|
public final List<DeferredMediaPeriod> activeMediaPeriods;
|
||||||
|
|
||||||
public DeferredTimeline timeline;
|
public DeferredTimeline timeline;
|
||||||
public int childIndex;
|
public int childIndex;
|
||||||
|
|
@ -809,7 +810,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
public boolean hasStartedPreparing;
|
public boolean hasStartedPreparing;
|
||||||
public boolean isPrepared;
|
public boolean isPrepared;
|
||||||
public boolean isRemoved;
|
public boolean isRemoved;
|
||||||
public List<DeferredMediaPeriod> activeMediaPeriods;
|
|
||||||
|
|
||||||
public MediaSourceHolder(MediaSource mediaSource) {
|
public MediaSourceHolder(MediaSource mediaSource) {
|
||||||
this.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.
|
// chunk even if the sample timestamps are slightly offset from the chunk start times.
|
||||||
seekInsideBuffer =
|
seekInsideBuffer =
|
||||||
primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0));
|
primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0));
|
||||||
decodeOnlyUntilPositionUs = Long.MIN_VALUE;
|
decodeOnlyUntilPositionUs = 0;
|
||||||
} else {
|
} else {
|
||||||
seekInsideBuffer =
|
seekInsideBuffer =
|
||||||
primarySampleQueue.advanceTo(
|
primarySampleQueue.advanceTo(
|
||||||
|
|
@ -583,7 +583,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
||||||
if (pendingReset) {
|
if (pendingReset) {
|
||||||
boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs;
|
boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs;
|
||||||
// Only enable setting of the decode only flag if we're not resetting to a chunk boundary.
|
// 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;
|
pendingResetPositionUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
mediaChunk.init(mediaChunkOutput);
|
mediaChunk.init(mediaChunkOutput);
|
||||||
|
|
|
||||||
|
|
@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator {
|
||||||
Math.max(availableAllocations.length * 2, availableCount + allocations.length));
|
Math.max(availableAllocations.length * 2, availableCount + allocations.length));
|
||||||
}
|
}
|
||||||
for (Allocation allocation : allocations) {
|
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;
|
availableAllocations[availableCount++] = allocation;
|
||||||
}
|
}
|
||||||
allocatedCount -= allocations.length;
|
allocatedCount -= allocations.length;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream.cache;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.NavigableSet;
|
import java.util.NavigableSet;
|
||||||
|
|
@ -195,8 +196,7 @@ public final class CachedRegionTracker implements Cache.Listener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NonNull Region another) {
|
public int compareTo(@NonNull Region another) {
|
||||||
return startOffset < another.startOffset ? -1
|
return Util.compareLong(startOffset, another.startOffset);
|
||||||
: startOffset == another.startOffset ? 0 : 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -520,9 +520,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
@Override
|
@Override
|
||||||
protected void flushCodec() throws ExoPlaybackException {
|
protected boolean flushOrReleaseCodec() {
|
||||||
super.flushCodec();
|
try {
|
||||||
buffersInCodecCount = 0;
|
return super.flushOrReleaseCodec();
|
||||||
|
} finally {
|
||||||
|
buffersInCodecCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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,
|
// 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.
|
// which releases all pending buffers buffers including the current output buffer.
|
||||||
updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
|
updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
|
||||||
flushCodec();
|
flushOrReinitCodec();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,12 +72,12 @@ public class CameraMotionRenderer extends BaseRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
reset();
|
resetListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
reset();
|
resetListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -124,7 +124,7 @@ public class CameraMotionRenderer extends BaseRenderer {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void resetListener() {
|
||||||
lastTimestampUs = 0;
|
lastTimestampUs = 0;
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onCameraMotionReset();
|
listener.onCameraMotionReset();
|
||||||
|
|
|
||||||
|
|
@ -2534,6 +2534,59 @@ public final class ExoPlayerTest {
|
||||||
assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10);
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ public class SimpleDecoderAudioRendererTest {
|
||||||
}
|
}
|
||||||
verify(mockAudioSink, times(1)).playToEndOfStream();
|
verify(mockAudioSink, times(1)).playToEndOfStream();
|
||||||
audioRenderer.disable();
|
audioRenderer.disable();
|
||||||
|
audioRenderer.reset();
|
||||||
verify(mockAudioSink, times(1)).release();
|
verify(mockAudioSink, times(1)).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
|
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
|
||||||
long periodDurationUs = representationHolder.periodDurationUs;
|
long periodDurationUs = representationHolder.periodDurationUs;
|
||||||
long clippedEndTimeUs =
|
long clippedEndTimeUs =
|
||||||
periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs
|
periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs
|
||||||
? periodDurationUs
|
? periodDurationUs
|
||||||
: C.TIME_UNSET;
|
: C.TIME_UNSET;
|
||||||
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
|
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
: (period.startMs + periodDurationMs);
|
: (period.startMs + periodDurationMs);
|
||||||
periods.add(period);
|
periods.add(period);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "MPD"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "MPD"));
|
||||||
|
|
||||||
|
|
@ -224,6 +226,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
segmentBase = parseSegmentList(xpp, null);
|
segmentBase = parseSegmentList(xpp, null);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBase = parseSegmentTemplate(xpp, null);
|
segmentBase = parseSegmentTemplate(xpp, null);
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "Period"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "Period"));
|
||||||
|
|
||||||
|
|
@ -412,22 +416,26 @@ public class DashManifestParser extends DefaultHandler
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
|
||||||
String robustnessLevel = xpp.getAttributeValue(null, "robustness_level");
|
String robustnessLevel = xpp.getAttributeValue(null, "robustness_level");
|
||||||
requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW");
|
requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW");
|
||||||
} else if (data == null) {
|
} else if (data == null
|
||||||
if (XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
|
&& XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh")
|
||||||
&& xpp.next() == XmlPullParser.TEXT) {
|
&& xpp.next() == XmlPullParser.TEXT) {
|
||||||
// The cenc:pssh element is defined in 23001-7:2015.
|
// The cenc:pssh element is defined in 23001-7:2015.
|
||||||
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
||||||
uuid = PsshAtomUtil.parseUuid(data);
|
uuid = PsshAtomUtil.parseUuid(data);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
Log.w(TAG, "Skipping malformed cenc:pssh data");
|
Log.w(TAG, "Skipping malformed cenc:pssh data");
|
||||||
data = null;
|
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
|
||||||
|
&& 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"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection"));
|
||||||
SchemeData schemeData =
|
SchemeData schemeData =
|
||||||
|
|
@ -465,7 +473,7 @@ public class DashManifestParser extends DefaultHandler
|
||||||
*/
|
*/
|
||||||
protected void parseAdaptationSetChild(XmlPullParser xpp)
|
protected void parseAdaptationSetChild(XmlPullParser xpp)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
// pass
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Representation parsing.
|
// Representation parsing.
|
||||||
|
|
@ -529,6 +537,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
|
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
|
||||||
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
|
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "Representation"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "Representation"));
|
||||||
|
|
||||||
|
|
@ -667,6 +677,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) {
|
||||||
initialization = parseInitialization(xpp);
|
initialization = parseInitialization(xpp);
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase"));
|
||||||
|
|
||||||
|
|
@ -704,6 +716,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
segments = new ArrayList<>();
|
segments = new ArrayList<>();
|
||||||
}
|
}
|
||||||
segments.add(parseSegmentUrl(xpp));
|
segments.add(parseSegmentUrl(xpp));
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList"));
|
||||||
|
|
||||||
|
|
@ -750,6 +764,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
initialization = parseInitialization(xpp);
|
initialization = parseInitialization(xpp);
|
||||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) {
|
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) {
|
||||||
timeline = parseSegmentTimeline(xpp);
|
timeline = parseSegmentTimeline(xpp);
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate"));
|
||||||
|
|
||||||
|
|
@ -797,6 +813,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
|
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
|
||||||
scratchOutputStream);
|
scratchOutputStream);
|
||||||
eventMessages.add(event);
|
eventMessages.add(event);
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "EventStream"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "EventStream"));
|
||||||
|
|
||||||
|
|
@ -935,6 +953,8 @@ public class DashManifestParser extends DefaultHandler
|
||||||
segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration));
|
segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration));
|
||||||
elapsedTime += duration;
|
elapsedTime += duration;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
maybeSkipTag(xpp);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline"));
|
} while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline"));
|
||||||
return segmentTimeline;
|
return segmentTimeline;
|
||||||
|
|
@ -1017,6 +1037,29 @@ public class DashManifestParser extends DefaultHandler
|
||||||
|
|
||||||
// Utility methods.
|
// 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}.
|
* 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) {
|
if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performClick() {
|
||||||
|
super.performClick();
|
||||||
return toggleControllerVisibility();
|
return toggleControllerVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<string name="exo_controls_repeat_one_description">एक रीपीट करा</string>
|
<string name="exo_controls_repeat_one_description">एक रीपीट करा</string>
|
||||||
<string name="exo_controls_repeat_all_description">सर्व रीपीट करा</string>
|
<string name="exo_controls_repeat_all_description">सर्व रीपीट करा</string>
|
||||||
<string name="exo_controls_shuffle_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_description">डाउनलोड करा</string>
|
||||||
<string name="exo_download_notification_channel_name">डाउनलोड</string>
|
<string name="exo_download_notification_channel_name">डाउनलोड</string>
|
||||||
<string name="exo_download_downloading">डाउनलोड होत आहे</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()}.
|
* 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.SetVideoSurface;
|
||||||
import com.google.android.exoplayer2.testutil.Action.Stop;
|
import com.google.android.exoplayer2.testutil.Action.Stop;
|
||||||
import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException;
|
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.WaitForPlaybackState;
|
||||||
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
|
import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity;
|
||||||
import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed;
|
import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed;
|
||||||
|
|
@ -414,6 +415,16 @@ public final class ActionSchedule {
|
||||||
return apply(new WaitForPlaybackState(tag, targetPlaybackState));
|
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.
|
* Schedules a {@link Runnable} to be executed.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,12 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void flushCodec() throws ExoPlaybackException {
|
protected boolean flushOrReleaseCodec() {
|
||||||
super.flushCodec();
|
try {
|
||||||
clearTimestamps();
|
return super.flushOrReleaseCodec();
|
||||||
|
} finally {
|
||||||
|
clearTimestamps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue