mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
b7f04cb36b
EventStreams in DASH generate TimelineRegionInfo objects, which are then stored in the RegionTimeline and RegionObserver classes. But DashParser would add all regions to RegionTimeline, even if they would be quickly removed again, and RegionObserver would cache some regions from the timeline without ever removing them. This fixes the issue from both of those directions. DashParser will now ignore regions that are outside the DVR window (and therefore would soon be removed from RegionTimeline), and RegionObserver listens to an event on RegionTimeline to clean up its own storage when regions fall outside the DVR window during playback. Closes #3949 (memory leak in DASH live streams with inband EventStream)
217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.media.RegionObserver');
|
|
|
|
goog.require('shaka.media.IPlayheadObserver');
|
|
goog.require('shaka.media.RegionTimeline');
|
|
goog.require('shaka.util.EventManager');
|
|
goog.require('shaka.util.FakeEvent');
|
|
goog.require('shaka.util.FakeEventTarget');
|
|
|
|
|
|
/**
|
|
* The region observer watches a region timeline and playhead, and fires events
|
|
* ('enter', 'exit', 'skip') as the playhead moves.
|
|
*
|
|
* @implements {shaka.media.IPlayheadObserver}
|
|
* @final
|
|
*/
|
|
shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
|
|
/**
|
|
* Create a region observer for the given timeline. The observer does not
|
|
* own the timeline, only uses it. This means that the observer should NOT
|
|
* destroy the timeline.
|
|
*
|
|
* @param {!shaka.media.RegionTimeline} timeline
|
|
*/
|
|
constructor(timeline) {
|
|
super();
|
|
|
|
/** @private {shaka.media.RegionTimeline} */
|
|
this.timeline_ = timeline;
|
|
|
|
/**
|
|
* A mapping between a region and where we previously were relative to it.
|
|
* When the value here differs from what we calculate, it means we moved and
|
|
* should fire an event.
|
|
*
|
|
* @private {!Map.<shaka.extern.TimelineRegionInfo,
|
|
* shaka.media.RegionObserver.RelativePosition_>}
|
|
*/
|
|
this.oldPosition_ = new Map();
|
|
|
|
// To make the rules easier to read, alias all the relative positions.
|
|
const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
|
|
const BEFORE_THE_REGION = RelativePosition.BEFORE_THE_REGION;
|
|
const IN_THE_REGION = RelativePosition.IN_THE_REGION;
|
|
const AFTER_THE_REGION = RelativePosition.AFTER_THE_REGION;
|
|
|
|
/**
|
|
* A read-only collection of rules for what to do when we change position
|
|
* relative to a region.
|
|
*
|
|
* @private {!Iterable.<shaka.media.RegionObserver.Rule_>}
|
|
*/
|
|
this.rules_ = [
|
|
{
|
|
weWere: null,
|
|
weAre: IN_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
|
|
},
|
|
{
|
|
weWere: BEFORE_THE_REGION,
|
|
weAre: IN_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
|
|
},
|
|
{
|
|
weWere: AFTER_THE_REGION,
|
|
weAre: IN_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
|
|
},
|
|
{
|
|
weWere: IN_THE_REGION,
|
|
weAre: BEFORE_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
|
|
},
|
|
{
|
|
weWere: IN_THE_REGION,
|
|
weAre: AFTER_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
|
|
},
|
|
{
|
|
weWere: BEFORE_THE_REGION,
|
|
weAre: AFTER_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
|
|
},
|
|
{
|
|
weWere: AFTER_THE_REGION,
|
|
weAre: BEFORE_THE_REGION,
|
|
invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
|
|
},
|
|
];
|
|
|
|
/** @private {shaka.util.EventManager} */
|
|
this.eventManager_ = new shaka.util.EventManager();
|
|
|
|
this.eventManager_.listen(this.timeline_, 'regionremove', (event) => {
|
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
const region = event['region'];
|
|
this.oldPosition_.delete(region);
|
|
});
|
|
}
|
|
|
|
/** @override */
|
|
release() {
|
|
this.timeline_ = null;
|
|
|
|
// Clear our maps so that we are not holding onto any more information than
|
|
// needed.
|
|
this.oldPosition_.clear();
|
|
|
|
this.eventManager_.release();
|
|
this.eventManager_ = null;
|
|
|
|
super.release();
|
|
}
|
|
|
|
/** @override */
|
|
poll(positionInSeconds, wasSeeking) {
|
|
const RegionObserver = shaka.media.RegionObserver;
|
|
|
|
for (const region of this.timeline_.regions()) {
|
|
const previousPosition = this.oldPosition_.get(region);
|
|
const currentPosition = RegionObserver.determinePositionRelativeTo_(
|
|
region, positionInSeconds);
|
|
|
|
// We will only use |previousPosition| and |currentPosition|, so we can
|
|
// update our state now.
|
|
this.oldPosition_.set(region, currentPosition);
|
|
|
|
for (const rule of this.rules_) {
|
|
if (rule.weWere == previousPosition && rule.weAre == currentPosition) {
|
|
rule.invoke(region, wasSeeking);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch events of the given type. All event types in this class have the
|
|
* same parameters: region and seeking.
|
|
*
|
|
* @param {string} eventType
|
|
* @param {shaka.extern.TimelineRegionInfo} region
|
|
* @param {boolean} seeking
|
|
* @private
|
|
*/
|
|
onEvent_(eventType, region, seeking) {
|
|
const event = new shaka.util.FakeEvent(eventType, new Map([
|
|
['region', region],
|
|
['seeking', seeking],
|
|
]));
|
|
this.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Get the relative position of the playhead to |region| when the playhead is
|
|
* at |seconds|. We treat the region's start and end times as inclusive
|
|
* bounds.
|
|
*
|
|
* @param {shaka.extern.TimelineRegionInfo} region
|
|
* @param {number} seconds
|
|
* @return {shaka.media.RegionObserver.RelativePosition_}
|
|
* @private
|
|
*/
|
|
static determinePositionRelativeTo_(region, seconds) {
|
|
const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
|
|
|
|
if (seconds < region.startTime) {
|
|
return RelativePosition.BEFORE_THE_REGION;
|
|
}
|
|
|
|
if (seconds > region.endTime) {
|
|
return RelativePosition.AFTER_THE_REGION;
|
|
}
|
|
|
|
return RelativePosition.IN_THE_REGION;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An enum of relative positions between the playhead and a region. Each is
|
|
* phrased so that it works in "The playhead is X" where "X" is any value in
|
|
* the enum.
|
|
*
|
|
* @enum {number}
|
|
* @private
|
|
*/
|
|
shaka.media.RegionObserver.RelativePosition_ = {
|
|
BEFORE_THE_REGION: 1,
|
|
IN_THE_REGION: 2,
|
|
AFTER_THE_REGION: 3,
|
|
};
|
|
|
|
/**
|
|
* All region observer events (onEnter, onExit, and onSkip) will be passed the
|
|
* region that the playhead is interacting with and whether or not the playhead
|
|
* moving is part of a seek event.
|
|
*
|
|
* @typedef {function(shaka.extern.TimelineRegionInfo, boolean)}
|
|
*/
|
|
shaka.media.RegionObserver.EventListener;
|
|
|
|
/**
|
|
* @typedef {{
|
|
* weWere: ?shaka.media.RegionObserver.RelativePosition_,
|
|
* weAre: ?shaka.media.RegionObserver.RelativePosition_,
|
|
* invoke: shaka.media.RegionObserver.EventListener
|
|
* }}
|
|
*
|
|
* @private
|
|
*/
|
|
shaka.media.RegionObserver.Rule_;
|