Files
shaka-player/lib/queue/queue_manager.js
T
Joey Parrish ab16b73614 fix: Improve handling of in-progress content (#9414)
The HLS parser used isLive_() in some places, which was equal to
presentationType_ != VOD, and in other places, checked presentationType_
directly.

In fact, isLive_()'s old formulation was checking for all dynamic
content (live & event types), so it should be called isDynamic_(). But
some usages of the old isLive_() actually only cared about live, and
should exclude event types.

This cleans up usage and clarifies each condition to either strictly
live (excluding VOD & event types) or all dynamic content (live & event,
excluding VOD). This is done both inside and outside the HLS parser.

This also makes one behavioral change to HLS event type streams. These
are now shown as "in progress" (dynamic, finite duration) instead of
"live" (dynamic, infinite duration).

---------

Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
2025-12-04 09:54:02 +01:00

345 lines
9.5 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.queue.QueueManager');
goog.require('goog.asserts');
goog.require('shaka.Player');
goog.require('shaka.config.RepeatMode');
goog.require('shaka.util.Error');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.FakeEvent');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.IDestroyable');
goog.requireType('shaka.media.PreloadManager');
/**
* @implements {shaka.extern.IQueueManager}
* @implements {shaka.util.IDestroyable}
* @export
*/
shaka.queue.QueueManager = class extends shaka.util.FakeEventTarget {
/**
* @param {shaka.Player} player
*/
constructor(player) {
super();
/** @private {?shaka.Player} */
this.player_ = player;
/** @private {?shaka.extern.QueueConfiguration} */
this.config_ = null;
/** @private {!Array<shaka.extern.QueueItem>} */
this.items_ = [];
/** @private {number} */
this.currentItemIndex_ = -1;
/**
* @private {?{
* item: shaka.extern.QueueItem,
* preloadManager: ?shaka.media.PreloadManager,
* }}
*/
this.preloadNext_ = null;
/**
* @private {?{
* item: shaka.extern.QueueItem,
* preloadManager: ?shaka.media.PreloadManager,
* }}
*/
this.preloadPrev_ = null;
/** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
}
/**
* @override
* @export
*/
async destroy() {
await this.removeAllItems();
this.player_ = null;
if (this.eventManager_) {
this.eventManager_.release();
this.eventManager_ = null;
}
// FakeEventTarget implements IReleasable
super.release();
}
/**
* @override
* @export
*/
configure(config) {
this.config_ = config;
}
/**
* @override
* @export
*/
getConfiguration() {
return this.config_;
}
/**
* @override
* @export
*/
setCustomPlayer(player) {
this.player_ = player;
}
/**
* @override
* @export
*/
getCurrentItem() {
if (this.items_.length && this.currentItemIndex_ >= 0 &&
this.currentItemIndex_ < this.items_.length) {
return this.items_[this.currentItemIndex_];
}
return null;
}
/**
* @override
* @export
*/
getCurrentItemIndex() {
return this.currentItemIndex_;
}
/**
* @override
* @export
*/
getItems() {
return this.items_.slice();
}
/**
* @override
* @export
*/
insertItems(items) {
this.items_.push(...items);
this.dispatchEvent(new shaka.util.FakeEvent(
shaka.util.FakeEvent.EventName.ItemsInserted));
}
/**
* @override
* @export
*/
async removeAllItems() {
this.eventManager_.removeAll();
if (this.items_.length && this.currentItemIndex_ >= 0) {
await this.player_.unload();
}
const promises = [];
if (this.preloadPrev_) {
if (this.preloadPrev_.preloadManager &&
!this.preloadPrev_.preloadManager.isDestroyed()) {
promises.push(this.preloadPrev_.preloadManager.destroy());
}
this.preloadPrev_ = null;
}
if (this.preloadNext_) {
if (this.preloadNext_.preloadManager &&
!this.preloadNext_.preloadManager.isDestroyed()) {
promises.push(this.preloadNext_.preloadManager.destroy());
}
this.preloadNext_ = null;
}
for (const item of this.items_) {
if (item.preloadManager && !item.preloadManager.isDestroyed()) {
promises.push(item.preloadManager.destroy());
}
}
if (promises.length) {
await Promise.all(promises);
}
this.items_ = [];
this.currentItemIndex_ = -1;
this.dispatchEvent(new shaka.util.FakeEvent(
shaka.util.FakeEvent.EventName.ItemsRemoved));
}
/**
* @override
* @export
*/
async playItem(itemIndex) {
goog.asserts.assert(this.player_, 'We should have player');
this.eventManager_.removeAll();
if (!this.items_.length || itemIndex >= this.items_.length) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.PLAYER,
shaka.util.Error.Code.QUEUE_INDEX_OUT_OF_BOUNDS);
}
const currentItem = this.getCurrentItem();
const item = this.items_[itemIndex];
if (this.currentItemIndex_ != itemIndex) {
this.currentItemIndex_ = itemIndex;
this.dispatchEvent(new shaka.util.FakeEvent(
shaka.util.FakeEvent.EventName.CurrentItemChanged));
}
const mediaElement = this.player_.getMediaElement();
const preloadNextUrlWindow =
this.config_ ? this.config_.preloadNextUrlWindow : 0;
if (preloadNextUrlWindow > 0) {
let preloadInProcess = false;
this.eventManager_.listen(mediaElement, 'timeupdate', async () => {
if (this.preloadNext_ || this.items_.length <= 1 || preloadInProcess ||
this.player_.isDynamic() || !mediaElement.duration) {
return;
}
const timeToEnd =
this.player_.seekRange().end - mediaElement.currentTime;
if (!isNaN(timeToEnd)) {
if (timeToEnd <= preloadNextUrlWindow) {
const repeatMode = this.config_ && this.config_.repeatMode;
let nextItem = null;
if ((this.currentItemIndex_ + 1) < this.items_.length) {
nextItem = this.items_[this.currentItemIndex_ + 1];
} else if (repeatMode == shaka.config.RepeatMode.ALL) {
nextItem = this.items_[0];
}
if (nextItem && (!nextItem.preloadManager ||
nextItem.preloadManager.isDestroyed())) {
preloadInProcess = true;
let preloadManager = null;
try {
preloadManager = await this.player_.preload(
nextItem.manifestUri,
nextItem.startTime,
nextItem.mimeType,
nextItem.config);
} catch (error) {
preloadManager = null;
// Ignore errors.
}
this.preloadNext_ = {
item: nextItem,
preloadManager,
};
preloadInProcess = false;
}
}
}
});
}
this.eventManager_.listen(this.player_, 'complete', () => {
const repeatMode = this.config_ && this.config_.repeatMode;
let playAgain = false;
if (repeatMode == shaka.config.RepeatMode.SINGLE) {
playAgain = true;
} else {
const nextItemIndex = this.currentItemIndex_ + 1;
if (nextItemIndex < this.items_.length) {
this.playItem(nextItemIndex);
} else if (repeatMode == shaka.config.RepeatMode.ALL) {
if (this.items_.length == 1) {
playAgain = true;
} else {
this.playItem(0);
}
}
}
if (playAgain) {
if (mediaElement.paused) {
mediaElement.currentTime = this.player_.seekRange().start;
mediaElement.play();
} else {
this.eventManager_.listen(mediaElement, 'paused', () => {
mediaElement.currentTime = this.player_.seekRange().start;
mediaElement.play();
});
}
}
});
let assetUriOrPreloader = item.manifestUri;
if (item.preloadManager && !item.preloadManager.isDestroyed()) {
assetUriOrPreloader = item.preloadManager;
}
let usingPrev = false;
if (this.preloadNext_ && this.preloadNext_.item == item &&
this.preloadNext_.preloadManager) {
assetUriOrPreloader = this.preloadNext_.preloadManager;
} else if (this.preloadPrev_ && this.preloadPrev_.item == item &&
this.preloadPrev_.preloadManager) {
assetUriOrPreloader = this.preloadPrev_.preloadManager;
usingPrev = true;
}
if (this.preloadPrev_) {
if (!usingPrev && this.preloadPrev_.preloadManager &&
!this.preloadPrev_.preloadManager.isDestroyed()) {
await this.preloadPrev_.preloadManager.destroy();
}
this.preloadPrev_ = null;
}
if (this.config_ && this.config_.preloadPrevItem && currentItem &&
this.player_.getLoadMode() === shaka.Player.LoadMode.MEDIA_SOURCE) {
let preloadManager = null;
try {
preloadManager = await this.player_.unloadAndSavePreload();
} catch (error) {
preloadManager = null;
// Ignore errors.
}
this.preloadPrev_ = {
item: currentItem,
preloadManager,
};
}
if (item.config) {
this.player_.resetConfiguration();
this.player_.configure(item.config);
}
await this.player_.load(assetUriOrPreloader, item.startTime, item.mimeType);
this.preloadNext_ = null;
if (item.extraText) {
for (const extraText of item.extraText) {
if (extraText.mime) {
this.player_.addTextTrackAsync(extraText.uri, extraText.language,
extraText.kind, extraText.mime, extraText.codecs);
} else {
this.player_.addTextTrackAsync(extraText.uri, extraText.language,
extraText.kind);
}
}
}
if (item.extraThumbnail) {
for (const extraThumbnail of item.extraThumbnail) {
this.player_.addThumbnailsTrack(extraThumbnail);
}
}
if (item.extraChapter) {
for (const extraChapter of item.extraChapter) {
this.player_.addChaptersTrack(
extraChapter.uri, extraChapter.language, extraChapter.mime);
}
}
}
};
shaka.Player.setQueueManagerFactory((player) => {
return new shaka.queue.QueueManager(player);
});