@playkit-js/rapt 0.4.10-canary.0-878cdb7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/run_canary_full_flow.yaml +28 -0
- package/.github/workflows/run_prod.yaml +24 -0
- package/.github/workflows/run_tests.yaml +24 -0
- package/.github/workflows/semantic-pr-title.yml +15 -0
- package/CHANGELOG.md +5 -0
- package/README.md +223 -0
- package/docs/API.md +222 -0
- package/index-API.html +307 -0
- package/index.template.html +266 -0
- package/libs/engineWithNodeId.min.js +31 -0
- package/libs/kaltura-typescript-client-7.0.1-v20200720-164020.tgz +0 -0
- package/package.json +75 -0
- package/scripts/dist-tgz.js +25 -0
- package/scripts/setversion.js +22 -0
- package/scripts/tag.sh +6 -0
- package/src/KalturaInteractiveVideo.ts +311 -0
- package/src/Kip.scss +270 -0
- package/src/Kip.ts +57 -0
- package/src/KipClient.ts +250 -0
- package/src/PlayersBufferManager.ts +549 -0
- package/src/PlayersDomManager.ts +128 -0
- package/src/PlayersFactory.ts +236 -0
- package/src/PlayersManager.ts +866 -0
- package/src/helpers/CreateElement.ts +14 -0
- package/src/helpers/Dispatcher.ts +52 -0
- package/src/helpers/KipEvents.ts +6 -0
- package/src/helpers/convertApiResponce.ts +209 -0
- package/src/helpers/detectIe.ts +23 -0
- package/src/helpers/logger.ts +9 -0
- package/src/ui/PlaybackPreset.ts +152 -0
- package/tsconfig.json +13 -0
- package/webpack.config.js +61 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
import { Dispatcher } from "./helpers/Dispatcher";
|
|
2
|
+
import { KipEvent } from "./helpers/KipEvents";
|
|
3
|
+
import { KalturaPlayer, PlayersFactory } from "./PlayersFactory";
|
|
4
|
+
import { PlayersBufferManager } from "./PlayersBufferManager";
|
|
5
|
+
import { PlayersDomManager } from "./PlayersDomManager";
|
|
6
|
+
import { log } from "./helpers/logger";
|
|
7
|
+
|
|
8
|
+
declare var Rapt: any;
|
|
9
|
+
|
|
10
|
+
export const KipFullscreen = {
|
|
11
|
+
FULL_SCREEN_CLICKED: "fullScreenClicked",
|
|
12
|
+
ENTER_FULL_SCREEN: "enterFullScreen",
|
|
13
|
+
EXIT_FULL_SCREEN: "exitFullScreen",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface RaptNode {
|
|
17
|
+
onEnded?: [];
|
|
18
|
+
id: string;
|
|
19
|
+
entryId: string;
|
|
20
|
+
name: string;
|
|
21
|
+
customData?: any;
|
|
22
|
+
prefetchNodeIds?: string[];
|
|
23
|
+
startFrom?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export enum Persistency {
|
|
27
|
+
mute = "mute",
|
|
28
|
+
textStyle = "textStyle",
|
|
29
|
+
captions = "captions",
|
|
30
|
+
volume = "volume",
|
|
31
|
+
audioTrack = "audioTrack",
|
|
32
|
+
rate = "rate",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This class manages players, and places and interact with the Rapt engine layer
|
|
37
|
+
* This class creates and manages BufferManager
|
|
38
|
+
*/
|
|
39
|
+
export class PlayersManager extends Dispatcher {
|
|
40
|
+
private playersBufferManager: PlayersBufferManager;
|
|
41
|
+
private playersFactory: PlayersFactory;
|
|
42
|
+
private activePlayer: KalturaPlayer = null;
|
|
43
|
+
private clickedHotspotId: string = undefined;
|
|
44
|
+
private activeNode: RaptNode = null;
|
|
45
|
+
static playerTickInterval: number = 250;
|
|
46
|
+
public raptEngine: any;
|
|
47
|
+
private analyticsModel: any = undefined;
|
|
48
|
+
private isAvailable: boolean;
|
|
49
|
+
private firstPlay: boolean = true;
|
|
50
|
+
private playerWidth: number = NaN;
|
|
51
|
+
private playerHeight: number = NaN;
|
|
52
|
+
private resizeInterval: number = NaN;
|
|
53
|
+
private endFlag: boolean = false;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
private config: any,
|
|
57
|
+
private playerLibrary: any,
|
|
58
|
+
readonly raptProjectId: string,
|
|
59
|
+
private raptData: any,
|
|
60
|
+
private domManager: PlayersDomManager,
|
|
61
|
+
private impressionAnalyticEventSent: boolean = false,
|
|
62
|
+
private playRequestedAnalyticEventSent: boolean = false,
|
|
63
|
+
private playAnalyticEventSent: boolean = false
|
|
64
|
+
) {
|
|
65
|
+
super();
|
|
66
|
+
this.isAvailable =
|
|
67
|
+
this.initPlayersFactory() && this.initPlayersBufferManager();
|
|
68
|
+
|
|
69
|
+
if (this.isAvailable) {
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
this.isAvailable = this.initRaptEngine();
|
|
72
|
+
// responsiveness resize support
|
|
73
|
+
this.resizeInterval = window.setInterval(
|
|
74
|
+
this.handleWindowResized.bind(this),
|
|
75
|
+
250
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private initPlayersBufferManager(): boolean {
|
|
82
|
+
// create the PlayersBufferManager
|
|
83
|
+
this.playersBufferManager = new PlayersBufferManager(
|
|
84
|
+
this.raptData,
|
|
85
|
+
this.playersFactory
|
|
86
|
+
);
|
|
87
|
+
if (
|
|
88
|
+
(this.config.rapt &&
|
|
89
|
+
this.config.rapt.hasOwnProperty("bufferNextNodes") &&
|
|
90
|
+
this.config.rapt.bufferNextNodes === false) ||
|
|
91
|
+
this.config.rapt.syncVideos
|
|
92
|
+
) {
|
|
93
|
+
this.playersBufferManager.disable();
|
|
94
|
+
} else {
|
|
95
|
+
const bufferingEvents = [
|
|
96
|
+
"buffer:prebuffer",
|
|
97
|
+
"buffer:bufferend",
|
|
98
|
+
"buffer:bufferstart",
|
|
99
|
+
"buffer:allbuffered",
|
|
100
|
+
];
|
|
101
|
+
for (let bufferEvent of bufferingEvents) {
|
|
102
|
+
this.playersBufferManager.addListener(bufferEvent, (event) => {
|
|
103
|
+
this.dispatch(event);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public getActiveKalturaPlayer(): any {
|
|
111
|
+
if (this.activePlayer) {
|
|
112
|
+
return this.activePlayer.player;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public getActiveNode(): RaptNode {
|
|
118
|
+
return this.activeNode;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private initPlayersFactory(): boolean {
|
|
122
|
+
/**
|
|
123
|
+
* This function interrupts the player kava beacons, alter some attributes (in case of need) and prevents some
|
|
124
|
+
* of the events.
|
|
125
|
+
* @param model
|
|
126
|
+
*/
|
|
127
|
+
const analyticsInterruptFunc = (model: any): boolean => {
|
|
128
|
+
if (!this.analyticsModel) {
|
|
129
|
+
this.analyticsModel = model; // store model data for future use of Rapt sending data
|
|
130
|
+
}
|
|
131
|
+
model.rootEntryId = this.raptProjectId;
|
|
132
|
+
model.nodeId = this.activeNode.id;
|
|
133
|
+
model.entryId = this.activeNode.entryId;
|
|
134
|
+
switch (model.eventType) {
|
|
135
|
+
case 14:
|
|
136
|
+
model.eventType = 99; // on event 14 (video-end) convert to event 99. ON RAPT CONTEXT ONLY!
|
|
137
|
+
break;
|
|
138
|
+
case 11:
|
|
139
|
+
case 12:
|
|
140
|
+
case 13:
|
|
141
|
+
return false; // don't send quartiles events
|
|
142
|
+
case 1:
|
|
143
|
+
// send this once
|
|
144
|
+
if (this.impressionAnalyticEventSent) {
|
|
145
|
+
return false;
|
|
146
|
+
} else {
|
|
147
|
+
this.impressionAnalyticEventSent = true;
|
|
148
|
+
model.entryId = this.raptProjectId;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 2:
|
|
152
|
+
// send this once
|
|
153
|
+
if (this.playRequestedAnalyticEventSent) {
|
|
154
|
+
return false;
|
|
155
|
+
} else {
|
|
156
|
+
this.playRequestedAnalyticEventSent = true;
|
|
157
|
+
model.entryId = this.raptProjectId;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 3:
|
|
161
|
+
// send this once
|
|
162
|
+
if (this.playAnalyticEventSent) {
|
|
163
|
+
return false;
|
|
164
|
+
} else {
|
|
165
|
+
this.playAnalyticEventSent = true;
|
|
166
|
+
model.entryId = this.raptProjectId;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// create a PlayersFactory instance
|
|
174
|
+
this.playersFactory = new PlayersFactory(
|
|
175
|
+
this.domManager,
|
|
176
|
+
this.raptProjectId,
|
|
177
|
+
this.playerLibrary,
|
|
178
|
+
analyticsInterruptFunc,
|
|
179
|
+
this.config
|
|
180
|
+
);
|
|
181
|
+
// listen to fullscreen events from the players
|
|
182
|
+
this.playersFactory.addListener(KipFullscreen.FULL_SCREEN_CLICKED, () => {
|
|
183
|
+
this.toggleFullscreenState();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private initRaptEngine(): boolean {
|
|
190
|
+
const { id: raptEngineId, domElement } = this.domManager.createDomElement(
|
|
191
|
+
"div",
|
|
192
|
+
"rapt-engine",
|
|
193
|
+
"kiv-rapt-engine"
|
|
194
|
+
);
|
|
195
|
+
// create the rapt-engine layer. We must use this.element because of rapt delegate names
|
|
196
|
+
this.element = domElement;
|
|
197
|
+
this.element.setAttribute("tabindex", "-1");
|
|
198
|
+
const { nodes, settings } = this.raptData;
|
|
199
|
+
const startNodeId = settings.startNodeId;
|
|
200
|
+
// retrieve the 1st node
|
|
201
|
+
const firstNode: RaptNode = nodes.find(function (element: any) {
|
|
202
|
+
return element.id === startNodeId;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!firstNode) {
|
|
206
|
+
this.dispatch({ type: KipEvent.FIRST_PLAY_ERROR });
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// apply google-analytics track id if exist
|
|
211
|
+
const options: any = {};
|
|
212
|
+
if (this.config.gaTrackId) {
|
|
213
|
+
options.$ga = this.config.gaTrackId;
|
|
214
|
+
}
|
|
215
|
+
this.raptEngine = new Rapt.Engine(this, options);
|
|
216
|
+
this.raptEngine.load(this.raptData);
|
|
217
|
+
|
|
218
|
+
this.resizeEngine();
|
|
219
|
+
|
|
220
|
+
setInterval(() => this.syncRaptStatus(), PlayersManager.playerTickInterval);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private handleWindowResized() {
|
|
225
|
+
// onResize is a window event - make sure we run this only when we change the player div
|
|
226
|
+
const currentWidth = this.domManager.getContainer().offsetWidth;
|
|
227
|
+
const currentHeight = this.domManager.getContainer().offsetHeight;
|
|
228
|
+
if (
|
|
229
|
+
currentWidth !== this.playerWidth ||
|
|
230
|
+
currentHeight !== this.playerHeight
|
|
231
|
+
) {
|
|
232
|
+
this.playerWidth = currentWidth;
|
|
233
|
+
this.playerHeight = currentHeight;
|
|
234
|
+
this.resizeEngine();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private syncRaptStatus() {
|
|
239
|
+
if (!this.activePlayer) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const currentPlayingVideoElement: any = this.activePlayer.player.getVideoElement();
|
|
243
|
+
if (currentPlayingVideoElement) {
|
|
244
|
+
this.raptEngine.update({
|
|
245
|
+
currentTime: currentPlayingVideoElement.currentTime,
|
|
246
|
+
duration: currentPlayingVideoElement.duration,
|
|
247
|
+
ended: currentPlayingVideoElement.ended,
|
|
248
|
+
videoWidth: currentPlayingVideoElement.videoWidth,
|
|
249
|
+
videoHeight: currentPlayingVideoElement.videoHeight,
|
|
250
|
+
paused: currentPlayingVideoElement.paused,
|
|
251
|
+
readyState: currentPlayingVideoElement.readyState,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private toggleFullscreenState() {
|
|
257
|
+
const doc: any = document;
|
|
258
|
+
if (doc.fullscreenElement || doc.webkitFullscreenElement) {
|
|
259
|
+
if (document.exitFullscreen) {
|
|
260
|
+
doc.exitFullscreen();
|
|
261
|
+
} else if (doc.webkitExitFullscreen) {
|
|
262
|
+
doc.webkitExitFullscreen();
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
this.domManager.requestFullscreen();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// re-render the rapt engine according to current dimension of the main container
|
|
270
|
+
private resizeEngine() {
|
|
271
|
+
const raptContainer = this.domManager.getContainer();
|
|
272
|
+
this.raptEngine.resize({
|
|
273
|
+
width: raptContainer.offsetWidth,
|
|
274
|
+
height: raptContainer.offsetHeight,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public destroy() {
|
|
279
|
+
this.removeListeners();
|
|
280
|
+
clearInterval(this.resizeInterval);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
////////////////////////////////////////////
|
|
284
|
+
// store the player and the node
|
|
285
|
+
private updateActiveItems(
|
|
286
|
+
nextPlayer: KalturaPlayer,
|
|
287
|
+
nextNode: RaptNode
|
|
288
|
+
): void {
|
|
289
|
+
const hasActivePlayer = !!this.activePlayer;
|
|
290
|
+
const isSwitchingPlayer = this.activePlayer !== nextPlayer;
|
|
291
|
+
const isSwitchingRaptNode = this.activeNode !== nextNode;
|
|
292
|
+
log("log", "pm_updateActiveItems", "executed", {
|
|
293
|
+
hasActivePlayer,
|
|
294
|
+
isSwitchingPlayer,
|
|
295
|
+
isSwitchingRaptNode,
|
|
296
|
+
nodeId: nextNode ? nextNode.id : null,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (hasActivePlayer) {
|
|
300
|
+
this.activePlayer.player.pause();
|
|
301
|
+
if (isSwitchingPlayer) {
|
|
302
|
+
// remove listeners
|
|
303
|
+
this.removeListeners();
|
|
304
|
+
if (!this.playersBufferManager.isAvailable()) {
|
|
305
|
+
// TODO 1 must destroy active player
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.activePlayer = nextPlayer;
|
|
310
|
+
this.activeNode = nextNode;
|
|
311
|
+
if (isSwitchingRaptNode) {
|
|
312
|
+
this.playersBufferManager.switchPlayer(this.activeNode);
|
|
313
|
+
}
|
|
314
|
+
if (isSwitchingPlayer) {
|
|
315
|
+
this.domManager.changeActivePlayer(this.activePlayer);
|
|
316
|
+
// register listeners
|
|
317
|
+
this.addListenersToPlayer();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* This will send the first nodePlay event only if it has analytics model and after the 1st play
|
|
323
|
+
* If it doesn't have a model it will try again later.
|
|
324
|
+
*/
|
|
325
|
+
private trySendingFirstEvent(entryId: string) {
|
|
326
|
+
setTimeout(() => {
|
|
327
|
+
if (this.analyticsModel && this.playAnalyticEventSent) {
|
|
328
|
+
this.sendAnalytics(44, { entryId: entryId });
|
|
329
|
+
} else {
|
|
330
|
+
this.trySendingFirstEvent(entryId);
|
|
331
|
+
}
|
|
332
|
+
}, 250);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
////////////////////////////////////////////
|
|
336
|
+
|
|
337
|
+
private projectStarted() {
|
|
338
|
+
// project had started - unhide the rapt layer now and hide the large-play-button layer
|
|
339
|
+
this.domManager.showRaptLayer();
|
|
340
|
+
this.domManager.addClass("kiv-initiated");
|
|
341
|
+
this.removeListener("project:start", () => this.projectStarted);
|
|
342
|
+
// make sure we are setting the current player to autoplay. For both non-buffered path and in case we reuse this player as a buffered player
|
|
343
|
+
try {
|
|
344
|
+
this.activePlayer.player.configure({ playback: { autoplay: true } });
|
|
345
|
+
} catch (e) {
|
|
346
|
+
log("log", "pm_projectStarted", "Could not apply autoplay to player", e);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// called by Rapt on first-node, user click, defaultPath and external API "jump"
|
|
351
|
+
private switchPlayer(media: any): void {
|
|
352
|
+
let currentPlayerPosition = 0;
|
|
353
|
+
// check if there is a need to sync videos, that there is a player and that we are not
|
|
354
|
+
// switching at the end of the video (might cause an endless loop)
|
|
355
|
+
if (
|
|
356
|
+
this.config.rapt.syncVideos &&
|
|
357
|
+
this.activePlayer &&
|
|
358
|
+
this.activePlayer.player &&
|
|
359
|
+
this.activePlayer.player.currentTime &&
|
|
360
|
+
this.activePlayer.player.duration - this.activePlayer.player.currentTime >
|
|
361
|
+
5
|
|
362
|
+
) {
|
|
363
|
+
currentPlayerPosition = this.activePlayer.player.currentTime;
|
|
364
|
+
}
|
|
365
|
+
this.domManager.getContainer().classList.add("rapt-switching");
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
this.domManager.getContainer().classList.remove("rapt-switching");
|
|
368
|
+
}, 269);
|
|
369
|
+
const newEntryId = media.sources[0].src;
|
|
370
|
+
const nextRaptNode: RaptNode = media.node;
|
|
371
|
+
const raptLayer: HTMLElement = document.querySelector(" .kiv-rapt-engine");
|
|
372
|
+
|
|
373
|
+
// send analytics of nodePlay - event44
|
|
374
|
+
if (this.analyticsModel) {
|
|
375
|
+
this.sendAnalytics(44, { entryId: newEntryId, nodeId: nextRaptNode.id });
|
|
376
|
+
} else {
|
|
377
|
+
// At this point we do not have the 1st player nor analytics model. This will make sure to send the 44 event when
|
|
378
|
+
// we have a model.
|
|
379
|
+
this.trySendingFirstEvent(newEntryId);
|
|
380
|
+
}
|
|
381
|
+
let params: any = {};
|
|
382
|
+
if (this.activeNode) {
|
|
383
|
+
params = {
|
|
384
|
+
entryId: this.activeNode.entryId,
|
|
385
|
+
fromNodeId: this.activeNode.id,
|
|
386
|
+
toNodeId: nextRaptNode.id,
|
|
387
|
+
};
|
|
388
|
+
if (this.clickedHotspotId) {
|
|
389
|
+
params.hotspotId = this.clickedHotspotId;
|
|
390
|
+
// clear saved hotspotId
|
|
391
|
+
this.clickedHotspotId = undefined;
|
|
392
|
+
}
|
|
393
|
+
if (this.activeNode.id) {
|
|
394
|
+
params.nodeId = this.activeNode.id;
|
|
395
|
+
}
|
|
396
|
+
// node play = event 48!
|
|
397
|
+
this.sendAnalytics(48, params);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
log("log", "pm_switchPlayer", "executed", {
|
|
401
|
+
entryId: newEntryId,
|
|
402
|
+
nodeId: nextRaptNode.id,
|
|
403
|
+
});
|
|
404
|
+
if (this.activePlayer && this.activeNode === nextRaptNode) {
|
|
405
|
+
log(
|
|
406
|
+
"log",
|
|
407
|
+
"pm_switchPlayer",
|
|
408
|
+
"switch to same node, seek to the beginning",
|
|
409
|
+
{ entryId: newEntryId }
|
|
410
|
+
);
|
|
411
|
+
// node is "switched" to itself
|
|
412
|
+
const hotspotId = params.hotspotId;
|
|
413
|
+
const clickedHotspot = this.raptData.hotspots.find(
|
|
414
|
+
(item) => item.id === hotspotId
|
|
415
|
+
);
|
|
416
|
+
let startFrom = 0;
|
|
417
|
+
|
|
418
|
+
if (clickedHotspot) {
|
|
419
|
+
// this is a hotspot click - find if the hotspot project:jump has startFrom
|
|
420
|
+
let hotspotsJumpData = clickedHotspot.onClick.find(
|
|
421
|
+
(item) => item.type === "project:jump"
|
|
422
|
+
).payload;
|
|
423
|
+
if (hotspotsJumpData && hotspotsJumpData.startFrom) {
|
|
424
|
+
startFrom = hotspotsJumpData.startFrom;
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
// this is a defaultPath - find if the defaultPath project:jump has startFrom
|
|
428
|
+
if (this.activeNode.onEnded) {
|
|
429
|
+
const onEnded: any = this.activeNode.onEnded.find(
|
|
430
|
+
(item: any) => item.type === "project:jump"
|
|
431
|
+
);
|
|
432
|
+
if (onEnded && onEnded.payload && onEnded.payload.startFrom) {
|
|
433
|
+
startFrom = onEnded.payload.startFrom;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
this.activePlayer.player.currentTime = startFrom;
|
|
438
|
+
this.activePlayer.player.play();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// autoplay false detection
|
|
443
|
+
let shouldPlayNow = true;
|
|
444
|
+
if (
|
|
445
|
+
this.firstPlay &&
|
|
446
|
+
this.config.playback &&
|
|
447
|
+
this.config.playback.autoplay === false
|
|
448
|
+
) {
|
|
449
|
+
shouldPlayNow = false;
|
|
450
|
+
log(
|
|
451
|
+
"log",
|
|
452
|
+
"pm_switchPlayer",
|
|
453
|
+
"setting autoplay to false on first node with playersBufferManager",
|
|
454
|
+
{
|
|
455
|
+
entryId: newEntryId,
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (!shouldPlayNow && this.firstPlay) {
|
|
460
|
+
// autoplay = false. Hide the rapt layer until the project is started
|
|
461
|
+
this.domManager.hideRaptLayer();
|
|
462
|
+
this.addListener("project:start", () => this.projectStarted());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (this.playersBufferManager.isAvailable()) {
|
|
466
|
+
log(
|
|
467
|
+
"log",
|
|
468
|
+
"pm_switchPlayer",
|
|
469
|
+
"use buffer manager to get player for entry",
|
|
470
|
+
{ entryId: newEntryId }
|
|
471
|
+
);
|
|
472
|
+
const seekTo = this.checkSeekTo(nextRaptNode, this.activeNode, params.hotspotId);
|
|
473
|
+
const bufferedPlayer = this.playersBufferManager.getPlayer(
|
|
474
|
+
newEntryId,
|
|
475
|
+
shouldPlayNow,
|
|
476
|
+
this.firstPlay && !shouldPlayNow,
|
|
477
|
+
seekTo
|
|
478
|
+
);
|
|
479
|
+
this.firstPlay = false;
|
|
480
|
+
this.updateActiveItems(bufferedPlayer, nextRaptNode);
|
|
481
|
+
} else {
|
|
482
|
+
log(
|
|
483
|
+
"log",
|
|
484
|
+
"pm_switchPlayer",
|
|
485
|
+
"buffer manager not available, switch media on current player"
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if (!this.activePlayer) {
|
|
489
|
+
log("log", "pm_switchPlayer", "no player found, create main player", {
|
|
490
|
+
entryId: newEntryId,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const newPlayer = this.playersFactory.createPlayer(
|
|
494
|
+
newEntryId,
|
|
495
|
+
shouldPlayNow,
|
|
496
|
+
null,
|
|
497
|
+
this.firstPlay && !shouldPlayNow
|
|
498
|
+
);
|
|
499
|
+
this.firstPlay = false;
|
|
500
|
+
this.updateActiveItems(newPlayer, nextRaptNode);
|
|
501
|
+
} else {
|
|
502
|
+
log("log", "pm_switchPlayer", "switch media on main player", {
|
|
503
|
+
entryId: newEntryId,
|
|
504
|
+
});
|
|
505
|
+
// Even if autoplay was set to false, any next video must be played automaticaly
|
|
506
|
+
if (this.firstPlay && this.config.playback.autoplay === false) {
|
|
507
|
+
this.activePlayer.player.configure({ playback: { autoplay: true } });
|
|
508
|
+
// this is a >= 2nd entry - from here on we do not need the poster for nxt entries
|
|
509
|
+
this.activePlayer.player.configure({ sources: { poster: "" } });
|
|
510
|
+
log(
|
|
511
|
+
"log",
|
|
512
|
+
"pm_switchPlayer",
|
|
513
|
+
"setting autoplay to true (after first load) and poster to off"
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
this.firstPlay = false;
|
|
517
|
+
// grab seekto before updateActiveItems
|
|
518
|
+
const seekTo = this.checkSeekTo(nextRaptNode, this.activeNode, params.hotspotId);
|
|
519
|
+
this.updateActiveItems(this.activePlayer, nextRaptNode);
|
|
520
|
+
if (this.config.rapt.syncVideos && currentPlayerPosition) {
|
|
521
|
+
this.activePlayer.player.configure({
|
|
522
|
+
playback: { startTime: currentPlayerPosition },
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// handle seekTo logic
|
|
526
|
+
if (seekTo) {
|
|
527
|
+
this.activePlayer.player.configure({
|
|
528
|
+
playback: { startTime: seekTo },
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
this.activePlayer.player.configure({ playback: { startTime: 0 } });
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
this.activePlayer.player.loadMedia({
|
|
535
|
+
entryId: newEntryId,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// reparenting rapt layer on mobile.
|
|
540
|
+
if (this.playersFactory.playerLibrary.core.Env.device.model) {
|
|
541
|
+
this.domManager.reparentRaptLayer(raptLayer);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
public execute(command: any) {
|
|
546
|
+
if (!this.raptEngine) {
|
|
547
|
+
log(
|
|
548
|
+
"error",
|
|
549
|
+
"pm_execute",
|
|
550
|
+
"Error: Rapt Media commands received before initialization is complete"
|
|
551
|
+
);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
this.raptEngine.execute(command);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private handleTextTrackChanged = (event: any) => {
|
|
558
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
559
|
+
Persistency.captions,
|
|
560
|
+
event.payload.selectedTextTrack._language,
|
|
561
|
+
this.activePlayer.player
|
|
562
|
+
);
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
private handleRateChanged = (event: any) => {
|
|
566
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
567
|
+
Persistency.rate,
|
|
568
|
+
this.activePlayer.player.playbackRate,
|
|
569
|
+
this.activePlayer.player
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
private handleTrackStyleChanged = (event: any) => {
|
|
574
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
575
|
+
Persistency.textStyle,
|
|
576
|
+
this.activePlayer.player.textStyle,
|
|
577
|
+
this.activePlayer.player
|
|
578
|
+
);
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
private handleAudiotrackChanged = (event: any) => {
|
|
582
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
583
|
+
Persistency.audioTrack,
|
|
584
|
+
event.payload.selectedAudioTrack._language,
|
|
585
|
+
this.activePlayer.player
|
|
586
|
+
);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
private handleVolumeChanged = (event: any) => {
|
|
590
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
591
|
+
Persistency.volume,
|
|
592
|
+
this.activePlayer.player.volume,
|
|
593
|
+
this.activePlayer.player
|
|
594
|
+
);
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
private handleMuteChanged = (event: any) => {
|
|
598
|
+
const isMuted = event.payload.mute;
|
|
599
|
+
this.playersBufferManager.syncPlayersStatus(
|
|
600
|
+
Persistency.mute,
|
|
601
|
+
isMuted,
|
|
602
|
+
this.activePlayer.player
|
|
603
|
+
);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
removeListeners() {
|
|
607
|
+
if (this.activePlayer && this.activePlayer.player) {
|
|
608
|
+
const player: any = this.activePlayer.player;
|
|
609
|
+
player.removeEventListener(
|
|
610
|
+
player.Event.Core.TEXT_TRACK_CHANGED,
|
|
611
|
+
this.handleTextTrackChanged
|
|
612
|
+
);
|
|
613
|
+
player.removeEventListener(
|
|
614
|
+
player.Event.Core.AUDIO_TRACK_CHANGED,
|
|
615
|
+
this.handleAudiotrackChanged
|
|
616
|
+
);
|
|
617
|
+
player.removeEventListener(
|
|
618
|
+
player.Event.Core.TEXT_STYLE_CHANGED,
|
|
619
|
+
this.handleTrackStyleChanged
|
|
620
|
+
);
|
|
621
|
+
player.removeEventListener(
|
|
622
|
+
player.Event.Core.RATE_CHANGE,
|
|
623
|
+
this.handleRateChanged
|
|
624
|
+
);
|
|
625
|
+
player.removeEventListener(
|
|
626
|
+
player.Event.Core.VOLUME_CHANGE,
|
|
627
|
+
this.handleVolumeChanged
|
|
628
|
+
);
|
|
629
|
+
player.removeEventListener(
|
|
630
|
+
player.Event.Core.MUTE_CHANGE,
|
|
631
|
+
this.handleMuteChanged
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// this function checks if the current node that points to the next node is expected to have a seekto
|
|
637
|
+
checkSeekTo(nextNode: RaptNode, currentNode: RaptNode, clickedHotspotId: string) {
|
|
638
|
+
try {
|
|
639
|
+
if (!currentNode) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const onEnded = currentNode.onEnded;
|
|
643
|
+
if (onEnded && this.endFlag) {
|
|
644
|
+
const defaultPathWithSeekTo: any = onEnded.find((item: any) => {
|
|
645
|
+
if (
|
|
646
|
+
item.type === "project:jump" &&
|
|
647
|
+
item.payload &&
|
|
648
|
+
item.payload.startFrom
|
|
649
|
+
) {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
});
|
|
654
|
+
if (
|
|
655
|
+
defaultPathWithSeekTo &&
|
|
656
|
+
nextNode.id === defaultPathWithSeekTo.payload.destination
|
|
657
|
+
) {
|
|
658
|
+
return defaultPathWithSeekTo.payload.startFrom;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// we got here - this is not a defaultPath - we need to find the relevant hotspot and see if it has startFrom
|
|
662
|
+
if (!clickedHotspotId) {
|
|
663
|
+
log(
|
|
664
|
+
"error",
|
|
665
|
+
"pm_checkSeekTo",
|
|
666
|
+
`Error: hotspot with id: ${clickedHotspotId} is undefined. returning startFrom=0`
|
|
667
|
+
);
|
|
668
|
+
return 0;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const clickedHotspot = this.raptData.hotspots.find(
|
|
672
|
+
(hs) => hs.id === clickedHotspotId
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
if (clickedHotspot && clickedHotspot.onClick) {
|
|
676
|
+
let hotspotsJumpData = clickedHotspot.onClick.find(
|
|
677
|
+
(item) => item.type === "project:jump"
|
|
678
|
+
).payload;
|
|
679
|
+
if (hotspotsJumpData &&
|
|
680
|
+
hotspotsJumpData.destination &&
|
|
681
|
+
hotspotsJumpData.destination === nextNode.id &&
|
|
682
|
+
typeof hotspotsJumpData.startFrom === 'number') {
|
|
683
|
+
return hotspotsJumpData.startFrom;
|
|
684
|
+
} else {
|
|
685
|
+
log(
|
|
686
|
+
"error",
|
|
687
|
+
"pm_checkSeekTo",
|
|
688
|
+
`Error: hotspotsJumpData of id ${clickedHotspotId} did not satisfy one or more of the conditions. returning startFrom=0`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
} else {
|
|
692
|
+
log(
|
|
693
|
+
"error",
|
|
694
|
+
"pm_checkSeekTo",
|
|
695
|
+
`Error: Could not find data of hotspot with id: ${clickedHotspotId}. returning startFrom=0`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return 0;
|
|
699
|
+
} catch (e) {
|
|
700
|
+
return 0;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
addListenersToPlayer() {
|
|
705
|
+
if (this.activePlayer && this.activePlayer.player) {
|
|
706
|
+
const player: any = this.activePlayer.player;
|
|
707
|
+
player.addEventListener(player.Event.Core.PLAY, () => {
|
|
708
|
+
const tracks = player.getTracks();
|
|
709
|
+
const audioTracks = tracks.find(
|
|
710
|
+
(track) => track._kind !== "subtitles" && !track._bandwidth
|
|
711
|
+
);
|
|
712
|
+
const captionsTracks = tracks.find(
|
|
713
|
+
(track) => track._kind === "subtitles"
|
|
714
|
+
);
|
|
715
|
+
if (
|
|
716
|
+
captionsTracks ||
|
|
717
|
+
(audioTracks &&
|
|
718
|
+
audioTracks._language &&
|
|
719
|
+
audioTracks._language != undefined)
|
|
720
|
+
) {
|
|
721
|
+
// this video has captions or audio
|
|
722
|
+
const view = player.getView().parentElement.parentElement
|
|
723
|
+
.parentElement;
|
|
724
|
+
view.classList.add("has-extra-tracks");
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
player.addEventListener(
|
|
728
|
+
player.Event.Core.TEXT_TRACK_CHANGED,
|
|
729
|
+
this.handleTextTrackChanged
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
player.addEventListener(
|
|
733
|
+
player.Event.Core.AUDIO_TRACK_CHANGED,
|
|
734
|
+
this.handleAudiotrackChanged
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
player.addEventListener(
|
|
738
|
+
player.Event.Core.TEXT_STYLE_CHANGED,
|
|
739
|
+
this.handleTrackStyleChanged
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
player.addEventListener(
|
|
743
|
+
player.Event.Core.RATE_CHANGE,
|
|
744
|
+
this.handleRateChanged
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
player.addEventListener(
|
|
748
|
+
player.Event.Core.VOLUME_CHANGE,
|
|
749
|
+
this.handleVolumeChanged
|
|
750
|
+
);
|
|
751
|
+
player.addEventListener(
|
|
752
|
+
player.Event.Core.MUTE_CHANGE,
|
|
753
|
+
this.handleMuteChanged
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Rapt interface - don't change signature //
|
|
759
|
+
private element: HTMLElement; // must be called 'element' because rapt delegate implementation
|
|
760
|
+
|
|
761
|
+
public pause() {
|
|
762
|
+
if (this.activePlayer && this.activePlayer.player) {
|
|
763
|
+
this.activePlayer.player.pause();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
public play() {
|
|
768
|
+
if (this.activePlayer && this.activePlayer.player) {
|
|
769
|
+
this.activePlayer.player.play();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
public seek(n) {
|
|
774
|
+
if (this.activePlayer && this.activePlayer.player) {
|
|
775
|
+
this.activePlayer.player.currentTime = n;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
load(media: any) {
|
|
780
|
+
this.switchPlayer(media);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Rapt interface - don't change signature //
|
|
784
|
+
event(event: any) {
|
|
785
|
+
if (event.type === "node:ended") {
|
|
786
|
+
this.endFlag = true;
|
|
787
|
+
}
|
|
788
|
+
if (event.type === "node:enter") {
|
|
789
|
+
this.endFlag = false;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (event.type === "hotspot:click") {
|
|
793
|
+
// save the hotspot id so we can send it to analytics
|
|
794
|
+
this.clickedHotspotId = event.payload.hotspot.id;
|
|
795
|
+
}
|
|
796
|
+
if (event.type === "browser:open") {
|
|
797
|
+
// track hotspot click
|
|
798
|
+
const additionalData = {
|
|
799
|
+
entryId: this.activeNode.entryId,
|
|
800
|
+
nodeId: this.activeNode.id,
|
|
801
|
+
target: event.payload.href,
|
|
802
|
+
hotspotId: event.context.payload.hotspot.id,
|
|
803
|
+
};
|
|
804
|
+
this.sendAnalytics(47, additionalData);
|
|
805
|
+
}
|
|
806
|
+
if (event.type === "project:ready") {
|
|
807
|
+
this.raptEngine.metadata.account = this.config.partnetId;
|
|
808
|
+
}
|
|
809
|
+
this.dispatch(event);
|
|
810
|
+
|
|
811
|
+
// handle seeks
|
|
812
|
+
if (event.type === "hotspot:click") {
|
|
813
|
+
if (this.raptData.hotspots && this.raptData.hotspots.length) {
|
|
814
|
+
const hs = this.raptData.hotspots.find(
|
|
815
|
+
(item) => item.id === event.payload.hotspot.id
|
|
816
|
+
);
|
|
817
|
+
if (hs.clickSeek) {
|
|
818
|
+
this.seek(hs.clickSeek);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* The function sends an anlytics beacon.
|
|
826
|
+
* @param eventNumber
|
|
827
|
+
* @param attributes - an object of attributes to send on top of the event (will override existing attr
|
|
828
|
+
* @param fieldsToRemove - array of strings that are not required on the event
|
|
829
|
+
*/
|
|
830
|
+
sendAnalytics(
|
|
831
|
+
eventNumber: number,
|
|
832
|
+
attributes?: object,
|
|
833
|
+
fieldsToRemove?: string[]
|
|
834
|
+
) {
|
|
835
|
+
if (!this.analyticsModel) {
|
|
836
|
+
log(
|
|
837
|
+
"error",
|
|
838
|
+
"pm_sendAnalytics",
|
|
839
|
+
"Missing basic event model - cannot send analytics"
|
|
840
|
+
);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
let tmpModel = Object.assign({}, this.analyticsModel);
|
|
844
|
+
tmpModel.eventType = eventNumber;
|
|
845
|
+
|
|
846
|
+
if (attributes) {
|
|
847
|
+
Object.keys(attributes).forEach((item) => {
|
|
848
|
+
tmpModel[item] = attributes[item];
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (fieldsToRemove) {
|
|
853
|
+
fieldsToRemove.forEach((field) => {
|
|
854
|
+
delete tmpModel[field];
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
if (
|
|
858
|
+
this.activePlayer &&
|
|
859
|
+
this.activePlayer.player &&
|
|
860
|
+
this.activePlayer.player.currentTime
|
|
861
|
+
) {
|
|
862
|
+
tmpModel.position = this.activePlayer.player.currentTime;
|
|
863
|
+
}
|
|
864
|
+
this.activePlayer.player.plugins.kava.sendAnalytics(tmpModel);
|
|
865
|
+
}
|
|
866
|
+
}
|