diff --git a/src/core/init/media_source_content_initializer.ts b/src/core/init/media_source_content_initializer.ts index 9361d68015..fd21928fa9 100644 --- a/src/core/init/media_source_content_initializer.ts +++ b/src/core/init/media_source_content_initializer.ts @@ -569,8 +569,6 @@ export default class MediaSourceContentInitializer extends ContentInitializer { return this.trigger("periodStreamCleared", evt.value); case "representationChange": return this.trigger("representationChange", evt.value); - case "complete-stream": - return this.trigger("completeStream", evt.value); case "bitrateEstimationChange": return this.trigger("bitrateEstimationChange", evt.value); case "added-segment": diff --git a/src/core/init/types.ts b/src/core/init/types.ts index 665fb4eb9d..bd29f41f0e 100644 --- a/src/core/init/types.ts +++ b/src/core/init/types.ts @@ -177,11 +177,6 @@ export interface IContentInitializerEvents { */ period : Period; }; - /** - * The last (chronologically) `Period` for a given type has pushed all - * the segments it needs until the end. - */ - completeStream: { type: IBufferType }; /** Emitted when a new `Adaptation` is being considered. */ adaptationChange: { /** The type of buffer for which the Representation is changing. */ diff --git a/src/core/stream/events_generators.ts b/src/core/stream/events_generators.ts index bd1856f6fb..d170a03695 100644 --- a/src/core/stream/events_generators.ts +++ b/src/core/stream/events_generators.ts @@ -29,7 +29,6 @@ import { IActivePeriodChangedEvent, IAdaptationChangeEvent, IBitrateEstimationChangeEvent, - ICompletedStreamEvent, IEncryptionDataEncounteredEvent, IEndOfStreamEvent, ILockedStreamEvent, @@ -88,11 +87,6 @@ const EVENTS = { value: { type, bitrate } }; }, - streamComplete(bufferType: IBufferType) : ICompletedStreamEvent { - return { type: "complete-stream", - value: { type: bufferType } }; - }, - endOfStream() : IEndOfStreamEvent { return { type: "end-of-stream", value: undefined }; diff --git a/src/core/stream/orchestrator/are_streams_complete.ts b/src/core/stream/orchestrator/are_streams_complete.ts index 49ae11138c..77bb925801 100644 --- a/src/core/stream/orchestrator/are_streams_complete.ts +++ b/src/core/stream/orchestrator/are_streams_complete.ts @@ -22,7 +22,9 @@ import { Observable, startWith, } from "rxjs"; -import { IStreamOrchestratorEvent } from "../types"; +import Manifest from "../../../manifest"; +import filterMap from "../../../utils/filter_map"; +import { IStreamOrchestratorEvent, IStreamStatusEvent } from "../types"; /** * Returns an Observable which emits ``true`` when all PeriodStreams given are @@ -38,10 +40,12 @@ import { IStreamOrchestratorEvent } from "../types"; * segments needed for this Stream have been downloaded. * * When the Observable returned here emits, every Stream are finished. + * @param {Object} manifest * @param {...Observable} streams * @returns {Observable} */ export default function areStreamsComplete( + manifest : Manifest, ...streams : Array> ) : Observable { /** @@ -53,11 +57,15 @@ export default function areStreamsComplete( const isCompleteArray : Array> = streams .map((stream) => { return stream.pipe( - filter((evt) => { - return evt.type === "complete-stream" || - (evt.type === "stream-status" && !evt.value.hasFinishedLoading); - }), - map((evt) => evt.type === "complete-stream"), + filter((evt) : evt is IStreamStatusEvent => evt.type === "stream-status"), + filterMap((evt) => { + if (evt.value.hasFinishedLoading || evt.value.isEmptyStream) { + return manifest.getPeriodAfter(evt.value.period) === null ? + true : + null; // not the last Period: ignore event + } + return false; + }, null), startWith(false), distinctUntilChanged() ); diff --git a/src/core/stream/orchestrator/stream_orchestrator.ts b/src/core/stream/orchestrator/stream_orchestrator.ts index 6b3f3c67d9..a16ebece28 100644 --- a/src/core/stream/orchestrator/stream_orchestrator.ts +++ b/src/core/stream/orchestrator/stream_orchestrator.ts @@ -187,7 +187,7 @@ export default function StreamOrchestrator( // Emits an "end-of-stream" event once every PeriodStream are complete. // Emits a 'resume-stream" when it's not - const endOfStream$ = combineLatest([areStreamsComplete(...streamsArray), + const endOfStream$ = combineLatest([areStreamsComplete(manifest, ...streamsArray), isLastPeriodKnown$]) .pipe(map(([areComplete, isLastPeriodKnown]) => areComplete && isLastPeriodKnown), distinctUntilChanged(), @@ -506,8 +506,7 @@ export default function StreamOrchestrator( if (evt.value.hasFinishedLoading) { const nextPeriod = manifest.getPeriodAfter(basePeriod); if (nextPeriod === null) { - return observableConcat(observableOf(evt), - observableOf(EVENTS.streamComplete(bufferType))); + return observableOf(evt); } // current Stream is full, create the next one if not diff --git a/src/core/stream/period/create_empty_adaptation_stream.ts b/src/core/stream/period/create_empty_adaptation_stream.ts index 5b874de3b5..d744cf4754 100644 --- a/src/core/stream/period/create_empty_adaptation_stream.ts +++ b/src/core/stream/period/create_empty_adaptation_stream.ts @@ -62,6 +62,7 @@ export default function createEmptyAdaptationStream( bufferType, position, imminentDiscontinuity: null, + isEmptyStream: true, hasFinishedLoading, neededSegments: [], shouldRefreshManifest: false } }); diff --git a/src/core/stream/representation/representation_stream.ts b/src/core/stream/representation/representation_stream.ts index 998e63569e..fb26692594 100644 --- a/src/core/stream/representation/representation_stream.ts +++ b/src/core/stream/representation/representation_stream.ts @@ -260,6 +260,7 @@ export default function RepresentationStream({ position: observation.position.last, bufferType, imminentDiscontinuity: status.imminentDiscontinuity, + isEmptyStream: false, hasFinishedLoading: status.hasFinishedLoading, neededSegments: status.neededSegments } }); let bufferRemoval = EMPTY; diff --git a/src/core/stream/types.ts b/src/core/stream/types.ts index 70f7f171e4..63386ef61d 100644 --- a/src/core/stream/types.ts +++ b/src/core/stream/types.ts @@ -97,6 +97,11 @@ export interface IStreamStatusEvent { * end of the Period. */ hasFinishedLoading : boolean; + /** + * If `true`, this stream is a placeholder stream which will never load any + * segment. + */ + isEmptyStream : boolean; /** * Segments that will be scheduled for download to fill the buffer until * the buffer goal (first element of that list might already be ). @@ -317,13 +322,6 @@ export interface IEndOfStreamEvent { type: "end-of-stream"; export interface IResumeStreamEvent { type: "resume-stream"; value: undefined; } -/** - * The last (chronologically) `PeriodStream` for a given type has pushed all - * the segments it needs until the end. - */ -export interface ICompletedStreamEvent { type: "complete-stream"; - value : { type: IBufferType }; } - /** * A situation needs the MediaSource to be reloaded. * @@ -499,7 +497,6 @@ export type IPeriodStreamEvent = IPeriodStreamReadyEvent | /** Event coming from function(s) managing multiple PeriodStreams. */ export type IMultiplePeriodStreamsEvent = IPeriodStreamClearedEvent | - ICompletedStreamEvent | // From a PeriodStream @@ -531,7 +528,6 @@ export type IStreamOrchestratorEvent = IActivePeriodChangedEvent | IResumeStreamEvent | IPeriodStreamClearedEvent | - ICompletedStreamEvent | // From a PeriodStream diff --git a/tests/integration/scenarios/end_number.js b/tests/integration/scenarios/end_number.js index b0a2fb5f1e..13ca19c803 100644 --- a/tests/integration/scenarios/end_number.js +++ b/tests/integration/scenarios/end_number.js @@ -11,7 +11,7 @@ import { } from "../../contents/DASH_static_SegmentTimeline"; import RxPlayer from "../../../src"; import sleep from "../../utils/sleep.js"; -import { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; +import waitForState, { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; let player; @@ -71,13 +71,11 @@ describe("end number", function () { await sleep(500); expect(xhrMock.getLockedXHR().length).to.equal(2); xhrMock.flush(); - player.seekTo(19.7); + player.seekTo(19); await sleep(50); expect(xhrMock.getLockedXHR().length).to.equal(2); xhrMock.flush(); - await sleep(5000); - expect(xhrMock.getLockedXHR().length).to.equal(0); - expect(player.getPlayerState()).to.eql("ENDED"); + await waitForState(player, "ENDED", ["BUFFERING", "RELOADING", "PLAYING"]); expect(player.getPosition()).to.be.closeTo(20, 1); });