Emit onPositionDiscontinuity event when silence is skipped
Issue: androidx/media#765 PiperOrigin-RevId: 584024654
This commit is contained in:
parent
5eb6a889a7
commit
89bedf0fb5
@ -22,6 +22,8 @@
|
|||||||
`ImageRenderer.ImageOutput`.
|
`ImageRenderer.ImageOutput`.
|
||||||
* `DefaultRenderersFactory` now provides an `ImageRenderer` to the player
|
* `DefaultRenderersFactory` now provides an `ImageRenderer` to the player
|
||||||
by default with null `ImageOutput` and `ImageDecoder.Factory.DEFAULT`.
|
by default with null `ImageOutput` and `ImageDecoder.Factory.DEFAULT`.
|
||||||
|
* Emit `Player.Listener.onPositionDiscontinuity` event when silence is
|
||||||
|
skipped ([#765](https://github.com/androidx/media/issues/765)).
|
||||||
* Transformer:
|
* Transformer:
|
||||||
* Add support for flattening H.265/HEVC SEF slow motion videos.
|
* Add support for flattening H.265/HEVC SEF slow motion videos.
|
||||||
* Increase transmuxing speed, especially for 'remove video' edits.
|
* Increase transmuxing speed, especially for 'remove video' edits.
|
||||||
|
3
api.txt
3
api.txt
@ -826,6 +826,7 @@ package androidx.media3.common {
|
|||||||
field public static final int DISCONTINUITY_REASON_REMOVE = 4; // 0x4
|
field public static final int DISCONTINUITY_REASON_REMOVE = 4; // 0x4
|
||||||
field public static final int DISCONTINUITY_REASON_SEEK = 1; // 0x1
|
field public static final int DISCONTINUITY_REASON_SEEK = 1; // 0x1
|
||||||
field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2
|
field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2
|
||||||
|
field public static final int DISCONTINUITY_REASON_SILENCE_SKIP = 6; // 0x6
|
||||||
field public static final int DISCONTINUITY_REASON_SKIP = 3; // 0x3
|
field public static final int DISCONTINUITY_REASON_SKIP = 3; // 0x3
|
||||||
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
|
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
|
||||||
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
|
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
|
||||||
@ -894,7 +895,7 @@ package androidx.media3.common {
|
|||||||
field public static final androidx.media3.common.Player.Commands EMPTY;
|
field public static final androidx.media3.common.Player.Commands EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason {
|
@IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL, androidx.media3.common.Player.DISCONTINUITY_REASON_SILENCE_SKIP}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.Player.EVENT_TIMELINE_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION, androidx.media3.common.Player.EVENT_TRACKS_CHANGED, androidx.media3.common.Player.EVENT_IS_LOADING_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_STATE_CHANGED, androidx.media3.common.Player.EVENT_PLAY_WHEN_READY_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED, androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED, androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_PLAYER_ERROR, androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY, androidx.media3.common.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AVAILABLE_COMMANDS_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED, androidx.media3.common.Player.EVENT_PLAYLIST_METADATA_CHANGED, androidx.media3.common.Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, androidx.media3.common.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_SESSION_ID, androidx.media3.common.Player.EVENT_VOLUME_CHANGED, androidx.media3.common.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_SURFACE_SIZE_CHANGED, androidx.media3.common.Player.EVENT_VIDEO_SIZE_CHANGED, androidx.media3.common.Player.EVENT_RENDERED_FIRST_FRAME, androidx.media3.common.Player.EVENT_CUES, androidx.media3.common.Player.EVENT_METADATA, androidx.media3.common.Player.EVENT_DEVICE_INFO_CHANGED, androidx.media3.common.Player.EVENT_DEVICE_VOLUME_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Event {
|
@IntDef({androidx.media3.common.Player.EVENT_TIMELINE_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION, androidx.media3.common.Player.EVENT_TRACKS_CHANGED, androidx.media3.common.Player.EVENT_IS_LOADING_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_STATE_CHANGED, androidx.media3.common.Player.EVENT_PLAY_WHEN_READY_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED, androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED, androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_PLAYER_ERROR, androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY, androidx.media3.common.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AVAILABLE_COMMANDS_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED, androidx.media3.common.Player.EVENT_PLAYLIST_METADATA_CHANGED, androidx.media3.common.Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, androidx.media3.common.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_SESSION_ID, androidx.media3.common.Player.EVENT_VOLUME_CHANGED, androidx.media3.common.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_SURFACE_SIZE_CHANGED, androidx.media3.common.Player.EVENT_VIDEO_SIZE_CHANGED, androidx.media3.common.Player.EVENT_RENDERED_FIRST_FRAME, androidx.media3.common.Player.EVENT_CUES, androidx.media3.common.Player.EVENT_METADATA, androidx.media3.common.Player.EVENT_DEVICE_INFO_CHANGED, androidx.media3.common.Player.EVENT_DEVICE_VOLUME_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Event {
|
||||||
|
@ -1395,7 +1395,8 @@ public interface Player {
|
|||||||
DISCONTINUITY_REASON_SEEK_ADJUSTMENT,
|
DISCONTINUITY_REASON_SEEK_ADJUSTMENT,
|
||||||
DISCONTINUITY_REASON_SKIP,
|
DISCONTINUITY_REASON_SKIP,
|
||||||
DISCONTINUITY_REASON_REMOVE,
|
DISCONTINUITY_REASON_REMOVE,
|
||||||
DISCONTINUITY_REASON_INTERNAL
|
DISCONTINUITY_REASON_INTERNAL,
|
||||||
|
DISCONTINUITY_REASON_SILENCE_SKIP
|
||||||
})
|
})
|
||||||
@interface DiscontinuityReason {}
|
@interface DiscontinuityReason {}
|
||||||
|
|
||||||
@ -1427,6 +1428,9 @@ public interface Player {
|
|||||||
/** Discontinuity introduced internally (e.g. by the source). */
|
/** Discontinuity introduced internally (e.g. by the source). */
|
||||||
int DISCONTINUITY_REASON_INTERNAL = 5;
|
int DISCONTINUITY_REASON_INTERNAL = 5;
|
||||||
|
|
||||||
|
/** Discontinuity introduced by a skipped silence. */
|
||||||
|
int DISCONTINUITY_REASON_SILENCE_SKIP = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED} or {@link
|
* Reasons for timeline changes. One of {@link #TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED} or {@link
|
||||||
* #TIMELINE_CHANGE_REASON_SOURCE_UPDATE}.
|
* #TIMELINE_CHANGE_REASON_SOURCE_UPDATE}.
|
||||||
|
@ -134,6 +134,13 @@ import androidx.media3.common.util.Clock;
|
|||||||
: Assertions.checkNotNull(rendererClock).getPositionUs();
|
: Assertions.checkNotNull(rendererClock).getPositionUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSkippedSilenceSinceLastCall() {
|
||||||
|
return isUsingStandaloneClock
|
||||||
|
? standaloneClock.hasSkippedSilenceSinceLastCall()
|
||||||
|
: Assertions.checkNotNull(rendererClock).hasSkippedSilenceSinceLastCall();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
if (rendererClock != null) {
|
if (rendererClock != null) {
|
||||||
|
@ -993,8 +993,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
/* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod());
|
/* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod());
|
||||||
long periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
|
long periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
|
||||||
maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs);
|
maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs);
|
||||||
|
if (mediaClock.hasSkippedSilenceSinceLastCall()) {
|
||||||
|
playbackInfo =
|
||||||
|
handlePositionDiscontinuity(
|
||||||
|
playbackInfo.periodId,
|
||||||
|
/* positionUs= */ periodPositionUs,
|
||||||
|
playbackInfo.requestedContentPositionUs,
|
||||||
|
/* discontinuityStartPositionUs= */ periodPositionUs,
|
||||||
|
/* reportDiscontinuity= */ true,
|
||||||
|
Player.DISCONTINUITY_REASON_SILENCE_SKIP);
|
||||||
|
} else {
|
||||||
playbackInfo.updatePositionUs(periodPositionUs);
|
playbackInfo.updatePositionUs(periodPositionUs);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the buffered position and total buffered duration.
|
// Update the buffered position and total buffered duration.
|
||||||
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
|
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
|
||||||
|
@ -25,6 +25,11 @@ public interface MediaClock {
|
|||||||
/** Returns the current media position in microseconds. */
|
/** Returns the current media position in microseconds. */
|
||||||
long getPositionUs();
|
long getPositionUs();
|
||||||
|
|
||||||
|
/** Returns whether there is a skipped silence since the last call to this method. */
|
||||||
|
default boolean hasSkippedSilenceSinceLastCall() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to set the playback parameters. The media clock may override the speed if changing the
|
* Attempts to set the playback parameters. The media clock may override the speed if changing the
|
||||||
* playback parameters is not supported.
|
* playback parameters is not supported.
|
||||||
|
@ -152,6 +152,9 @@ public interface AudioSink {
|
|||||||
* @param audioTrackConfig The {@link AudioTrackConfig} of the released {@link AudioTrack}.
|
* @param audioTrackConfig The {@link AudioTrackConfig} of the released {@link AudioTrack}.
|
||||||
*/
|
*/
|
||||||
default void onAudioTrackReleased(AudioTrackConfig audioTrackConfig) {}
|
default void onAudioTrackReleased(AudioTrackConfig audioTrackConfig) {}
|
||||||
|
|
||||||
|
/** Called when a period of silence has been skipped. */
|
||||||
|
default void onSilenceSkipped() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration parameters used for an {@link AudioTrack}. */
|
/** Configuration parameters used for an {@link AudioTrack}. */
|
||||||
|
@ -165,6 +165,7 @@ public abstract class DecoderAudioRenderer<
|
|||||||
private long outputStreamOffsetUs;
|
private long outputStreamOffsetUs;
|
||||||
private final long[] pendingOutputStreamOffsetsUs;
|
private final long[] pendingOutputStreamOffsetsUs;
|
||||||
private int pendingOutputStreamOffsetCount;
|
private int pendingOutputStreamOffsetCount;
|
||||||
|
private boolean hasPendingReportedSkippedSilence;
|
||||||
|
|
||||||
public DecoderAudioRenderer() {
|
public DecoderAudioRenderer() {
|
||||||
this(/* eventHandler= */ null, /* eventListener= */ null);
|
this(/* eventHandler= */ null, /* eventListener= */ null);
|
||||||
@ -577,6 +578,13 @@ public abstract class DecoderAudioRenderer<
|
|||||||
return currentPositionUs;
|
return currentPositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSkippedSilenceSinceLastCall() {
|
||||||
|
boolean hasPendingReportedSkippedSilence = this.hasPendingReportedSkippedSilence;
|
||||||
|
this.hasPendingReportedSkippedSilence = false;
|
||||||
|
return hasPendingReportedSkippedSilence;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
audioSink.setPlaybackParameters(playbackParameters);
|
audioSink.setPlaybackParameters(playbackParameters);
|
||||||
@ -606,6 +614,7 @@ public abstract class DecoderAudioRenderer<
|
|||||||
audioSink.flush();
|
audioSink.flush();
|
||||||
|
|
||||||
currentPositionUs = positionUs;
|
currentPositionUs = positionUs;
|
||||||
|
hasPendingReportedSkippedSilence = false;
|
||||||
allowPositionDiscontinuity = true;
|
allowPositionDiscontinuity = true;
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
@ -630,6 +639,7 @@ public abstract class DecoderAudioRenderer<
|
|||||||
inputFormat = null;
|
inputFormat = null;
|
||||||
audioTrackNeedsConfigure = true;
|
audioTrackNeedsConfigure = true;
|
||||||
setOutputStreamOffsetUs(C.TIME_UNSET);
|
setOutputStreamOffsetUs(C.TIME_UNSET);
|
||||||
|
hasPendingReportedSkippedSilence = false;
|
||||||
try {
|
try {
|
||||||
setSourceDrmSession(null);
|
setSourceDrmSession(null);
|
||||||
releaseDecoder();
|
releaseDecoder();
|
||||||
@ -829,6 +839,11 @@ public abstract class DecoderAudioRenderer<
|
|||||||
DecoderAudioRenderer.this.onPositionDiscontinuity();
|
DecoderAudioRenderer.this.onPositionDiscontinuity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSilenceSkipped() {
|
||||||
|
hasPendingReportedSkippedSilence = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
@ -96,6 +96,16 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
*/
|
*/
|
||||||
private static final int AUDIO_TRACK_SMALLER_BUFFER_RETRY_SIZE = 1_000_000;
|
private static final int AUDIO_TRACK_SMALLER_BUFFER_RETRY_SIZE = 1_000_000;
|
||||||
|
|
||||||
|
/** The minimum duration of the skipped silence to be reported as discontinuity. */
|
||||||
|
private static final int MINIMUM_REPORT_SKIPPED_SILENCE_DURATION_US = 1_000_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay of reporting the skipped silence, during which the default audio sink checks if there
|
||||||
|
* is any further skipped silence that is close to the delayed silence. If any, the further
|
||||||
|
* skipped silence will be concatenated to the delayed one.
|
||||||
|
*/
|
||||||
|
private static final int REPORT_SKIPPED_SILENCE_DELAY_MS = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when the audio track has provided a spurious timestamp, if {@link
|
* Thrown when the audio track has provided a spurious timestamp, if {@link
|
||||||
* #failOnSpuriousAudioTimestamp} is set.
|
* #failOnSpuriousAudioTimestamp} is set.
|
||||||
@ -542,6 +552,9 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
private boolean offloadDisabledUntilNextConfiguration;
|
private boolean offloadDisabledUntilNextConfiguration;
|
||||||
private boolean isWaitingForOffloadEndOfStreamHandled;
|
private boolean isWaitingForOffloadEndOfStreamHandled;
|
||||||
@Nullable private Looper playbackLooper;
|
@Nullable private Looper playbackLooper;
|
||||||
|
private long skippedOutputFrameCountAtLastPosition;
|
||||||
|
private long accumulatedSkippedSilenceDurationUs;
|
||||||
|
private @MonotonicNonNull Handler reportSkippedSilenceHandler;
|
||||||
|
|
||||||
@RequiresNonNull("#1.audioProcessorChain")
|
@RequiresNonNull("#1.audioProcessorChain")
|
||||||
private DefaultAudioSink(Builder builder) {
|
private DefaultAudioSink(Builder builder) {
|
||||||
@ -1443,6 +1456,11 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
writeExceptionPendingExceptionHolder.clear();
|
writeExceptionPendingExceptionHolder.clear();
|
||||||
initializationExceptionPendingExceptionHolder.clear();
|
initializationExceptionPendingExceptionHolder.clear();
|
||||||
|
skippedOutputFrameCountAtLastPosition = 0;
|
||||||
|
accumulatedSkippedSilenceDurationUs = 0;
|
||||||
|
if (reportSkippedSilenceHandler != null) {
|
||||||
|
checkNotNull(reportSkippedSilenceHandler).removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1645,8 +1663,28 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long applySkipping(long positionUs) {
|
private long applySkipping(long positionUs) {
|
||||||
return positionUs
|
long skippedOutputFrameCountAtCurrentPosition =
|
||||||
+ configuration.framesToDurationUs(audioProcessorChain.getSkippedOutputFrameCount());
|
audioProcessorChain.getSkippedOutputFrameCount();
|
||||||
|
long adjustedPositionUs =
|
||||||
|
positionUs + configuration.framesToDurationUs(skippedOutputFrameCountAtCurrentPosition);
|
||||||
|
if (skippedOutputFrameCountAtCurrentPosition > skippedOutputFrameCountAtLastPosition) {
|
||||||
|
long silenceDurationUs =
|
||||||
|
configuration.framesToDurationUs(
|
||||||
|
skippedOutputFrameCountAtCurrentPosition - skippedOutputFrameCountAtLastPosition);
|
||||||
|
skippedOutputFrameCountAtLastPosition = skippedOutputFrameCountAtCurrentPosition;
|
||||||
|
handleSkippedSilence(silenceDurationUs);
|
||||||
|
}
|
||||||
|
return adjustedPositionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSkippedSilence(long silenceDurationUs) {
|
||||||
|
accumulatedSkippedSilenceDurationUs += silenceDurationUs;
|
||||||
|
if (reportSkippedSilenceHandler == null) {
|
||||||
|
reportSkippedSilenceHandler = new Handler(Looper.myLooper());
|
||||||
|
}
|
||||||
|
reportSkippedSilenceHandler.removeCallbacksAndMessages(null);
|
||||||
|
reportSkippedSilenceHandler.postDelayed(
|
||||||
|
this::maybeReportSkippedSilence, /* delayMillis= */ REPORT_SKIPPED_SILENCE_DELAY_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAudioTrackInitialized() {
|
private boolean isAudioTrackInitialized() {
|
||||||
@ -2225,6 +2263,16 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeReportSkippedSilence() {
|
||||||
|
if (accumulatedSkippedSilenceDurationUs >= MINIMUM_REPORT_SKIPPED_SILENCE_DURATION_US) {
|
||||||
|
// If the existing silence is already long enough, report the silence
|
||||||
|
listener.onSilenceSkipped();
|
||||||
|
}
|
||||||
|
// Reset the accumulated silence anyway as the later silences are far from the current one
|
||||||
|
// and should be treated separately.
|
||||||
|
accumulatedSkippedSilenceDurationUs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
private static final class AudioDeviceInfoApi23 {
|
private static final class AudioDeviceInfoApi23 {
|
||||||
|
|
||||||
|
@ -118,6 +118,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
private boolean audioSinkNeedsReset;
|
private boolean audioSinkNeedsReset;
|
||||||
|
|
||||||
@Nullable private WakeupListener wakeupListener;
|
@Nullable private WakeupListener wakeupListener;
|
||||||
|
private boolean hasPendingReportedSkippedSilence;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param context A context.
|
* @param context A context.
|
||||||
@ -613,6 +614,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
audioSink.flush();
|
audioSink.flush();
|
||||||
|
|
||||||
currentPositionUs = positionUs;
|
currentPositionUs = positionUs;
|
||||||
|
hasPendingReportedSkippedSilence = false;
|
||||||
allowPositionDiscontinuity = true;
|
allowPositionDiscontinuity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,6 +648,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onReset() {
|
protected void onReset() {
|
||||||
|
hasPendingReportedSkippedSilence = false;
|
||||||
try {
|
try {
|
||||||
super.onReset();
|
super.onReset();
|
||||||
} finally {
|
} finally {
|
||||||
@ -679,6 +682,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
return currentPositionUs;
|
return currentPositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSkippedSilenceSinceLastCall() {
|
||||||
|
boolean hasPendingReportedSkippedSilence = this.hasPendingReportedSkippedSilence;
|
||||||
|
this.hasPendingReportedSkippedSilence = false;
|
||||||
|
return hasPendingReportedSkippedSilence;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
audioSink.setPlaybackParameters(playbackParameters);
|
audioSink.setPlaybackParameters(playbackParameters);
|
||||||
@ -969,6 +979,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
|
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSilenceSkipped() {
|
||||||
|
hasPendingReportedSkippedSilence = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
@ -707,6 +707,8 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
return "SKIP";
|
return "SKIP";
|
||||||
case Player.DISCONTINUITY_REASON_INTERNAL:
|
case Player.DISCONTINUITY_REASON_INTERNAL:
|
||||||
return "INTERNAL";
|
return "INTERNAL";
|
||||||
|
case Player.DISCONTINUITY_REASON_SILENCE_SKIP:
|
||||||
|
return "SILENCE_SKIP";
|
||||||
default:
|
default:
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
@ -14054,6 +14054,80 @@ public final class ExoPlayerTest {
|
|||||||
.isSameInstanceAs(mediaItem2);
|
.isSameInstanceAs(mediaItem2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void silenceSkipped_playerEmitOnPositionDiscontinuity() throws Exception {
|
||||||
|
Timeline timeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* isLive= */ false,
|
||||||
|
/* isPlaceholder= */ false,
|
||||||
|
/* durationUs= */ 10 * C.MICROS_PER_SECOND,
|
||||||
|
/* defaultPositionUs= */ 0,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||||
|
AdPlaybackState.NONE));
|
||||||
|
FakeMediaClockRenderer audioRenderer =
|
||||||
|
new FakeMediaClockRenderer(C.TRACK_TYPE_AUDIO) {
|
||||||
|
private long offsetUs;
|
||||||
|
private long positionUs;
|
||||||
|
private boolean hasPendingReportedSkippedSilence;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStreamChanged(
|
||||||
|
Format[] formats, long startPositionUs, long offsetUs, MediaPeriodId mediaPeriodId) {
|
||||||
|
this.offsetUs = offsetUs;
|
||||||
|
this.positionUs = offsetUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPositionUs() {
|
||||||
|
// Continuously increase position to let playback progress, and simulate the silence
|
||||||
|
// skip until it reaches some points of time.
|
||||||
|
if (positionUs - offsetUs == 10_000) {
|
||||||
|
hasPendingReportedSkippedSilence = true;
|
||||||
|
positionUs += 30_000;
|
||||||
|
} else {
|
||||||
|
positionUs += 10_000;
|
||||||
|
}
|
||||||
|
return positionUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSkippedSilenceSinceLastCall() {
|
||||||
|
boolean hasPendingReportedSkippedSilence = this.hasPendingReportedSkippedSilence;
|
||||||
|
if (hasPendingReportedSkippedSilence) {
|
||||||
|
this.hasPendingReportedSkippedSilence = false;
|
||||||
|
}
|
||||||
|
return hasPendingReportedSkippedSilence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
|
return PlaybackParameters.DEFAULT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(audioRenderer).build();
|
||||||
|
Player.Listener mockPlayerListener = mock(Player.Listener.class);
|
||||||
|
player.addListener(mockPlayerListener);
|
||||||
|
|
||||||
|
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
||||||
|
verify(mockPlayerListener)
|
||||||
|
.onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SILENCE_SKIP));
|
||||||
|
assertThat(audioRenderer.isEnded).isTrue();
|
||||||
|
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void addWatchAsSystemFeature() {
|
private void addWatchAsSystemFeature() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user