UNPKG

shaka-player

Version:
211 lines (187 loc) 6.55 kB
/*! @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'); /** * The region observer watches a region timeline and playhead, and fires events * (onEnter, onExit, and onSkip) as the playhead moves. * * @implements {shaka.media.IPlayheadObserver} * @final */ shaka.media.RegionObserver = class { /** * 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) { /** @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(); /** @private {shaka.media.RegionObserver.EventListener} */ this.onEnter_ = (region, seeking) => {}; /** @private {shaka.media.RegionObserver.EventListener} */ this.onExit_ = (region, seeking) => {}; /** @private {shaka.media.RegionObserver.EventListener} */ this.onSkip_ = (region, seeking) => {}; // 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.onEnter_(region, seeking), }, { weWere: BEFORE_THE_REGION, weAre: IN_THE_REGION, invoke: (region, seeking) => this.onEnter_(region, seeking), }, { weWere: AFTER_THE_REGION, weAre: IN_THE_REGION, invoke: (region, seeking) => this.onEnter_(region, seeking), }, { weWere: IN_THE_REGION, weAre: BEFORE_THE_REGION, invoke: (region, seeking) => this.onExit_(region, seeking), }, { weWere: IN_THE_REGION, weAre: AFTER_THE_REGION, invoke: (region, seeking) => this.onExit_(region, seeking), }, { weWere: BEFORE_THE_REGION, weAre: AFTER_THE_REGION, invoke: (region, seeking) => this.onSkip_(region, seeking), }, { weWere: AFTER_THE_REGION, weAre: BEFORE_THE_REGION, invoke: (region, seeking) => this.onSkip_(region, seeking), }, ]; } /** @override */ release() { this.timeline_ = null; // Clear our maps so that we are not holding onto any more information than // needed. this.oldPosition_.clear(); // Clear the callbacks so that we don't hold onto any references external // to this class. this.onEnter_ = (region, seeking) => {}; this.onExit_ = (region, seeking) => {}; this.onSkip_ = (region, seeking) => {}; } /** @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); } } } } /** * Set all the listeners. This overrides any previous calls to |setListeners|. * * @param {shaka.media.RegionObserver.EventListener} onEnter * The callback for when we move from outside a region to inside a region. * @param {shaka.media.RegionObserver.EventListener} onExit * The callback for when we move from inside a region to outside a region. * @param {shaka.media.RegionObserver.EventListener} onSkip * The callback for when we move from before to after a region or from * after to before a region. */ setListeners(onEnter, onExit, onSkip) { this.onEnter_ = onEnter; this.onExit_ = onExit; this.onSkip_ = onSkip; } /** * 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_;