Skip to content

Commit

Permalink
Merge pull request #1244 from canalplus/misc/reload-position-in-init
Browse files Browse the repository at this point in the history
Decide the position and autoplay status of a Reload in the Initializer
  • Loading branch information
peaBerberian committed Aug 31, 2023
2 parents 148d1f7 + f218733 commit 4f77d99
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 93 deletions.
32 changes: 27 additions & 5 deletions src/core/init/media_source_content_initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,19 +621,41 @@ export default class MediaSourceContentInitializer extends ContentInitializer {

addedSegment: (value) => self.trigger("addedSegment", value),

needsMediaSourceReload: (value) => onReloadOrder(value),
needsMediaSourceReload: (payload) => {
const lastObservation = streamObserver.getReference().getValue();
const currentPosition = lastObservation.position.pending ??
streamObserver.getCurrentTime();
const isPaused = lastObservation.paused.pending ??
streamObserver.getIsPaused();
let position = currentPosition + payload.timeOffset;
if (payload.minimumPosition !== undefined) {
position = Math.max(payload.minimumPosition, position);
}
if (payload.maximumPosition !== undefined) {
position = Math.min(payload.maximumPosition, position);
}
onReloadOrder({ position, autoPlay: !isPaused });
},

needsDecipherabilityFlush(value) {
needsDecipherabilityFlush() {
const keySystem = getKeySystemConfiguration(mediaElement);
if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) {
onReloadOrder(value);
const lastObservation = streamObserver.getReference().getValue();
const position = lastObservation.position.pending ??
streamObserver.getCurrentTime();
const isPaused = lastObservation.paused.pending ??
streamObserver.getIsPaused();
onReloadOrder({ position, autoPlay: !isPaused });
} else {
const lastObservation = streamObserver.getReference().getValue();
const position = lastObservation.position.pending ??
streamObserver.getCurrentTime();
// simple seek close to the current position
// to flush the buffers
if (value.position + 0.001 < value.duration) {
if (position + 0.001 < lastObservation.duration) {
playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
} else {
playbackObserver.setCurrentTime(value.position);
playbackObserver.setCurrentTime(position);
}
}
},
Expand Down
31 changes: 12 additions & 19 deletions src/core/stream/adaptation/adaptation_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,25 +182,18 @@ export default function AdaptationStream<T>(
// the next observation (which may reflect very different playback conditions)
// is actually received.
return nextTick(() => {
playbackObserver.listen((observation) => {
const { manual: newManual } = estimateRef.getValue();
if (!newManual) {
return;
}
const currentTime = playbackObserver.getCurrentTime();
const pos = currentTime + DELTA_POSITION_AFTER_RELOAD.bitrateSwitch;

// Bind to Period start and end
const position = Math.min(Math.max(period.start, pos),
period.end ?? Infinity);
const autoPlay = !(observation.paused.pending ??
playbackObserver.getIsPaused());
return callbacks.waitingMediaSourceReload({ bufferType: adaptation.type,
period,
position,
autoPlay });
}, { includeLastObservation: true,
clearSignal: repStreamTerminatingCanceller.signal });
if (repStreamTerminatingCanceller.isUsed()) {
return;
}
const { manual: newManual } = estimateRef.getValue();
if (!newManual) {
return;
}
const timeOffset = DELTA_POSITION_AFTER_RELOAD.bitrateSwitch;
return callbacks.waitingMediaSourceReload({ bufferType: adaptation.type,
period,
timeOffset,
stayInPeriod: true });
});
}

Expand Down
20 changes: 13 additions & 7 deletions src/core/stream/adaptation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,22 @@ export interface IWaitingMediaSourceReloadPayload {
/** Buffer type concerned. */
bufferType : IBufferType;
/**
* The position in seconds and the time at which the MediaSource should be
* reset once it has been reloaded.
* Relative position, compared to the current position, at which we should
* restart playback after reloading. For example `-2` will reload 2 seconds
* before the current position.
*/
position : number;
timeOffset : number;
/**
* If `true`, we want the HTMLMediaElement to play right after the reload is
* done.
* If `false`, we want to stay in a paused state at that point.
* If `true`, we will control that the position we reload at, after applying
* `timeOffset`, is still part of the Period `period`.
*
* If it isn't we will re-calculate that reloaded position to be:
* - either the Period's start if the calculated position is before the
* Period's start.
* - either the Period'end start if the calculated position is after the
* Period's end.
*/
autoPlay : boolean;
stayInPeriod : boolean;
}

/** Regular playback information needed by the AdaptationStream. */
Expand Down
60 changes: 23 additions & 37 deletions src/core/stream/orchestrator/stream_orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,11 @@ export default function StreamOrchestrator(
callbacks.lockedStream({ bufferType: payload.bufferType,
period: payload.period });
} else {
const { position, autoPlay } = payload;
callbacks.needsMediaSourceReload({ position, autoPlay });
callbacks.needsMediaSourceReload({
timeOffset: payload.timeOffset,
minimumPosition: payload.stayInPeriod ? payload.period.start : undefined,
maximumPosition: payload.stayInPeriod ? payload.period.end : undefined,
});
}
},
periodStreamReady(payload : IPeriodStreamReadyPayload) : void {
Expand Down Expand Up @@ -345,11 +348,9 @@ export default function StreamOrchestrator(
}
const observation = playbackObserver.getReference().getValue();
if (needsFlushingAfterClean(observation, undecipherableRanges)) {
const shouldAutoPlay = !(observation.paused.pending ??
playbackObserver.getIsPaused());
callbacks.needsDecipherabilityFlush({ position: observation.position.last,
autoPlay: shouldAutoPlay,
duration: observation.duration });

// Bind to Period start and end
callbacks.needsDecipherabilityFlush();
if (orchestratorCancelSignal.isCancelled()) {
return ;
}
Expand Down Expand Up @@ -578,7 +579,7 @@ export interface IStreamOrchestratorCallbacks
* worst cases completely removed and re-created through the "reload" mechanism,
* depending on the platform.
*/
needsDecipherabilityFlush(payload : INeedsDecipherabilityFlushPayload) : void;
needsDecipherabilityFlush() : void;
}

/** Payload for the `periodStreamCleared` callback. */
Expand All @@ -602,16 +603,23 @@ export interface IPeriodStreamClearedPayload {
/** Payload for the `needsMediaSourceReload` callback. */
export interface INeedsMediaSourceReloadPayload {
/**
* The position in seconds and the time at which the MediaSource should be
* reset once it has been reloaded.
* Relative position, compared to the current one, at which we should
* restart playback after reloading. For example `-2` will reload 2 seconds
* before the current position.
*/
timeOffset : number;
/**
* If defined and if the new position obtained after relying on
* `timeOffset` is before `minimumPosition`, then we will reload at
* `minimumPosition` instead.
*/
position : number;
minimumPosition : number | undefined;
/**
* If `true`, we want the HTMLMediaElement to play right after the reload is
* done.
* If `false`, we want to stay in a paused state at that point.
* If defined and if the new position obtained after relying on
* `timeOffset` is after `maximumPosition`, then we will reload at
* `maximumPosition` instead.
*/
autoPlay : boolean;
maximumPosition : number | undefined;
}

/** Payload for the `lockedStream` callback. */
Expand All @@ -622,28 +630,6 @@ export interface ILockedStreamPayload {
bufferType : IBufferType;
}

/** Payload for the `needsDecipherabilityFlush` callback. */
export interface INeedsDecipherabilityFlushPayload {
/**
* Indicated in the case where the MediaSource has to be reloaded,
* in which case the time of the HTMLMediaElement should be reset to that
* position, in seconds, once reloaded.
*/
position : number;
/**
* If `true`, we want the HTMLMediaElement to play right after the flush is
* done.
* If `false`, we want to stay in a paused state at that point.
*/
autoPlay : boolean;
/**
* The duration (maximum seekable position) of the content.
* This is indicated in the case where a seek has to be performed, to avoid
* seeking too far in the content.
*/
duration : number;
}

/**
* Returns `true` if low-level buffers have to be "flushed" after the given
* `cleanedRanges` time ranges have been removed from an audio or video
Expand Down
48 changes: 23 additions & 25 deletions src/core/stream/period/period_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export default function PeriodStream(
if (segmentBufferStatus.type === "initialized") {
log.info(`Stream: Clearing previous ${bufferType} SegmentBuffer`);
if (SegmentBuffersStore.isNative(bufferType)) {
return askForMediaSourceReload(0, streamCanceller.signal);
return askForMediaSourceReload(0, true, streamCanceller.signal);
} else {
const periodEnd = period.end ?? Infinity;
if (period.start > periodEnd) {
Expand Down Expand Up @@ -186,7 +186,9 @@ export default function PeriodStream(
if (SegmentBuffersStore.isNative(bufferType) &&
segmentBuffersStore.getStatus(bufferType).type === "disabled")
{
return askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal);
return askForMediaSourceReload(relativePosAfterSwitch,
true,
streamCanceller.signal);
}

log.info(`Stream: Updating ${bufferType} adaptation`,
Expand All @@ -211,7 +213,9 @@ export default function PeriodStream(
playbackInfos,
options);
if (strategy.type === "needs-reload") {
return askForMediaSourceReload(relativePosAfterSwitch, streamCanceller.signal);
return askForMediaSourceReload(relativePosAfterSwitch,
true,
streamCanceller.signal);
}

await segmentBuffersStore.waitForUsableBuffers(streamCanceller.signal);
Expand Down Expand Up @@ -304,22 +308,23 @@ export default function PeriodStream(
* Regularly ask to reload the MediaSource on each playback observation
* performed by the playback observer.
*
* If and only if the Period currently played corresponds to the concerned
* Period, applies an offset to the reloaded position corresponding to
* `deltaPos`.
* This can be useful for example when switching the audio/video tracks, where
* you might want to give back some context if that was the currently played
* track.
* @param {number} timeOffset - Relative position, compared to the current
* playhead, at which we should restart playback after reloading.
* For example `-2` will reload 2 seconds before the current position.
* @param {boolean} stayInPeriod - If `true`, we will control that the position
* we reload at, after applying `timeOffset`, is still part of the Period
* `period`.
*
* @param {number} deltaPos - If the concerned Period is playing at the time
* this function is called, we will add this value, in seconds, to the current
* position to indicate the position we should reload at.
* This value allows to give back context (by replaying some media data) after
* a switch.
* If it isn't we will re-calculate that reloaded position to be:
* - either the Period's start if the calculated position is before the
* Period's start.
* - either the Period'end start if the calculated position is after the
* Period's end.
* @param {Object} cancelSignal
*/
function askForMediaSourceReload(
deltaPos : number,
timeOffset : number,
stayInPeriod: boolean,
cancelSignal : CancellationSignal
) : void {
// We begin by scheduling a micro-task to reduce the possibility of race
Expand All @@ -329,18 +334,11 @@ export default function PeriodStream(
// It can happen when `askForMediaSourceReload` is called as a side-effect of
// the same event that triggers the playback observation to be emitted.
nextTick(() => {
playbackObserver.listen((observation) => {
const currentTime = playbackObserver.getCurrentTime();
const pos = currentTime + deltaPos;

// Bind to Period start and end
const position = Math.min(Math.max(period.start, pos),
period.end ?? Infinity);
const autoPlay = !(observation.paused.pending ?? playbackObserver.getIsPaused());
playbackObserver.listen(() => {
callbacks.waitingMediaSourceReload({ bufferType,
period,
position,
autoPlay });
timeOffset,
stayInPeriod });
}, { includeLastObservation: true, clearSignal: cancelSignal });
});
}
Expand Down

0 comments on commit 4f77d99

Please sign in to comment.