-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
region_observer.js
203 lines (180 loc) · 5.86 KB
/
region_observer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*! @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.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),
},
];
}
/** @override */
release() {
this.timeline_ = null;
// Clear our maps so that we are not holding onto any more information than
// needed.
this.oldPosition_.clear();
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_;