@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,549 @@
|
|
|
1
|
+
import {Dispatcher} from "./helpers/Dispatcher";
|
|
2
|
+
import {KalturaPlayer, PlayersFactory} from "./PlayersFactory";
|
|
3
|
+
import {Persistency, RaptNode} from "./PlayersManager";
|
|
4
|
+
import {log} from "./helpers/logger";
|
|
5
|
+
import {getInternetExplorerVersion, isWindows7} from "./helpers/detectIe";
|
|
6
|
+
|
|
7
|
+
interface BufferItem {
|
|
8
|
+
player: KalturaPlayer;
|
|
9
|
+
isReady: boolean;
|
|
10
|
+
isRunning: boolean;
|
|
11
|
+
bufferingTimeoutToken: number | null;
|
|
12
|
+
entryId: string;
|
|
13
|
+
startTime?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class PlayersBufferManager extends Dispatcher {
|
|
17
|
+
private shortEntryThreshold: number = 6;
|
|
18
|
+
private bufferThreshold: number = 2.5;
|
|
19
|
+
private isIe11Win7 = getInternetExplorerVersion() === 11 && isWindows7();
|
|
20
|
+
readonly secondsToBuffer: number;
|
|
21
|
+
private bufferList: BufferItem[] = [];
|
|
22
|
+
private persistenceObj: {
|
|
23
|
+
muted?: boolean;
|
|
24
|
+
rate?: number;
|
|
25
|
+
captions?: string;
|
|
26
|
+
volume?: number;
|
|
27
|
+
audio?: string;
|
|
28
|
+
} = {};
|
|
29
|
+
private _isAvailable: boolean;
|
|
30
|
+
|
|
31
|
+
constructor(private raptData: any, private playersFactory: PlayersFactory) {
|
|
32
|
+
super();
|
|
33
|
+
this.secondsToBuffer = playersFactory.secondsToBuffer;
|
|
34
|
+
this.initializeAvailablity();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private initializeAvailablity(): void {
|
|
38
|
+
// prevent caching on Safari and IE11-Win7 and if config set to no-cache
|
|
39
|
+
const browser = this.playersFactory.playerLibrary.core.Env.browser.name;
|
|
40
|
+
// TODO 9 will be used later to exclude other OS & browsers
|
|
41
|
+
// const browserVersion = this.playersFactory.playerLibrary.core.Env.major;
|
|
42
|
+
// const os = this.playersFactory.playerLibrary.core.Env.os.name;
|
|
43
|
+
const model =
|
|
44
|
+
this.playersFactory.playerLibrary.core.Env.device &&
|
|
45
|
+
this.playersFactory.playerLibrary.core.Env.device.model; // desktops will be undefined
|
|
46
|
+
this._isAvailable = true;
|
|
47
|
+
// On Safari desktop use pre-buffer, on ipad and iphone - don't because autoplay (defaultPath) needs user-gesture
|
|
48
|
+
if (model === "iPad" || model === "iPhone" || model === "Nexus 9") {
|
|
49
|
+
this._isAvailable = false;
|
|
50
|
+
log("log", "pbm_initializeAvailablity", "disabling prefetch", model);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Look if there is a relevant player that was created already
|
|
56
|
+
* @param playerId
|
|
57
|
+
*/
|
|
58
|
+
public getPlayer(
|
|
59
|
+
entryId: string,
|
|
60
|
+
playImmediate: boolean,
|
|
61
|
+
showPoster?: boolean,
|
|
62
|
+
seekTo?: number
|
|
63
|
+
): KalturaPlayer {
|
|
64
|
+
log("log", "pbm_getPlayer", "executed", {entryId, playImmediate});
|
|
65
|
+
if (!this._isAvailable) {
|
|
66
|
+
throw new Error("BufferManager is not available");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let result: KalturaPlayer;
|
|
70
|
+
let bufferedItem = this.bufferList.find(item => item.entryId === entryId);
|
|
71
|
+
|
|
72
|
+
if (bufferedItem) {
|
|
73
|
+
if (bufferedItem.player) {
|
|
74
|
+
log("log", "pbm_getPlayer", "found player in buffer list", {entryId});
|
|
75
|
+
result = bufferedItem.player;
|
|
76
|
+
|
|
77
|
+
log("log", "pbm_getPlayer", "seek player to the beginning or a specific time", {entryId});
|
|
78
|
+
result.player.currentTime = seekTo ? seekTo : 0;
|
|
79
|
+
// TODO 3 [eitan] for persistancy - assign only synced persistancy
|
|
80
|
+
if (playImmediate && !result.player.isPlaying) {
|
|
81
|
+
log("log", "pbm_getPlayer", "execute play command", {entryId});
|
|
82
|
+
result.player.play();
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
log(
|
|
86
|
+
"log",
|
|
87
|
+
"pbm_getPlayer",
|
|
88
|
+
"player buffered in pending mode, create player",
|
|
89
|
+
{entryId, trackBuffer: playImmediate}
|
|
90
|
+
);
|
|
91
|
+
bufferedItem.player = this.createPlayer(entryId, playImmediate, false, seekTo);
|
|
92
|
+
if (playImmediate) {
|
|
93
|
+
this.trackBufferOfItem(bufferedItem);
|
|
94
|
+
}
|
|
95
|
+
result = bufferedItem.player;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
log(
|
|
99
|
+
"log",
|
|
100
|
+
"pbm_getPlayer",
|
|
101
|
+
"not found in buffer list, create player for entry",
|
|
102
|
+
{entryId}
|
|
103
|
+
);
|
|
104
|
+
result = this.createPlayer(entryId, playImmediate, showPoster, seekTo);
|
|
105
|
+
const newItem = {
|
|
106
|
+
entryId: entryId,
|
|
107
|
+
player: result,
|
|
108
|
+
isRunning: playImmediate,
|
|
109
|
+
bufferingTimeoutToken: NaN,
|
|
110
|
+
isReady: false
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this.bufferList.push(newItem);
|
|
114
|
+
|
|
115
|
+
if (playImmediate) {
|
|
116
|
+
this.trackBufferOfItem(newItem);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public isAvailable(): boolean {
|
|
124
|
+
return this._isAvailable;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public disable(): void {
|
|
128
|
+
this._isAvailable = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private createPlayer(
|
|
132
|
+
entryId: string,
|
|
133
|
+
playImmediate: boolean,
|
|
134
|
+
showPoster?: boolean,
|
|
135
|
+
startTime?: number
|
|
136
|
+
): KalturaPlayer {
|
|
137
|
+
log("log", "pbm_createPlayer", "create player for entry", {
|
|
138
|
+
entryId,
|
|
139
|
+
playImmediate,
|
|
140
|
+
showPoster,
|
|
141
|
+
startTime
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// TODO 3 [eitan] for persistancy - apply async info
|
|
145
|
+
const kalturaPlayer = this.playersFactory.createPlayer(
|
|
146
|
+
entryId,
|
|
147
|
+
playImmediate,
|
|
148
|
+
this.persistenceObj,
|
|
149
|
+
showPoster,
|
|
150
|
+
startTime
|
|
151
|
+
);
|
|
152
|
+
return kalturaPlayer;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Anytime a new entry is requested to play (1st node, or on any nodeChange, by user or by defaultPath
|
|
157
|
+
* The function loads/play the nodes entry-player and handles buffering of next optional entries players
|
|
158
|
+
* @param nextNode
|
|
159
|
+
*/
|
|
160
|
+
public switchPlayer(nextNode?: RaptNode) {
|
|
161
|
+
if (!this._isAvailable) {
|
|
162
|
+
log(
|
|
163
|
+
"warn",
|
|
164
|
+
"pbm_switchPlayer",
|
|
165
|
+
"buffer manager is disabled, ignore switch player request"
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (nextNode) {
|
|
170
|
+
const nodesToBuffer = [nextNode, ...this.getNextNodes(nextNode)];
|
|
171
|
+
this.dispatch({type: "buffer:prebuffer", payload: nodesToBuffer});
|
|
172
|
+
const prevItemsMap = this.bufferList.reduce((acc, item) => {
|
|
173
|
+
acc[item.entryId] = item;
|
|
174
|
+
return acc;
|
|
175
|
+
}, {});
|
|
176
|
+
|
|
177
|
+
const prevItemsCount = this.bufferList.length;
|
|
178
|
+
|
|
179
|
+
this.bufferList = [];
|
|
180
|
+
|
|
181
|
+
log("log", "pbm_switchPlayer", "rebuliding buffer list", {
|
|
182
|
+
prevCount: prevItemsCount,
|
|
183
|
+
newCount: nodesToBuffer.length
|
|
184
|
+
});
|
|
185
|
+
nodesToBuffer.forEach(node => {
|
|
186
|
+
const existinItem = prevItemsMap[node.entryId];
|
|
187
|
+
if (existinItem) {
|
|
188
|
+
log(
|
|
189
|
+
"log",
|
|
190
|
+
"pbm_switchPlayer",
|
|
191
|
+
"entry id was in previous buffer list, copy to current list",
|
|
192
|
+
{entryId: node.entryId}
|
|
193
|
+
);
|
|
194
|
+
this.bufferList.push(existinItem);
|
|
195
|
+
delete prevItemsMap[node.entryId];
|
|
196
|
+
} else {
|
|
197
|
+
log("log", "pbm_switchPlayer", "add entry to buffer queue", {
|
|
198
|
+
entryId: node.entryId
|
|
199
|
+
});
|
|
200
|
+
this.bufferList.push({
|
|
201
|
+
entryId: node.entryId,
|
|
202
|
+
player: null,
|
|
203
|
+
isRunning: false,
|
|
204
|
+
bufferingTimeoutToken: null,
|
|
205
|
+
isReady: false,
|
|
206
|
+
startTime: node.startFrom
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.destroyBufferedItems(Object.values(prevItemsMap));
|
|
211
|
+
} else {
|
|
212
|
+
this.destroyBufferedItems(this.bufferList);
|
|
213
|
+
}
|
|
214
|
+
// remove duplicity items //todo 4 - prefer items that are better ready (has players, has token or isReady / isRunning
|
|
215
|
+
this.bufferList = this.bufferList.reduce((acc, item) => {
|
|
216
|
+
if (
|
|
217
|
+
!acc.some(
|
|
218
|
+
(bufferItem: BufferItem) => bufferItem.entryId === item.entryId
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
acc.push(item);
|
|
222
|
+
}
|
|
223
|
+
return acc;
|
|
224
|
+
}, []);
|
|
225
|
+
this.handleBufferList();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private handleBufferList(): void {
|
|
229
|
+
log("log", "pbm_handleBufferList", "executed");
|
|
230
|
+
const allReady =
|
|
231
|
+
this.bufferList.length === 0 ||
|
|
232
|
+
this.bufferList.every(item => item.isReady);
|
|
233
|
+
if (allReady) {
|
|
234
|
+
log("log", "pbm_handleBufferList", "all items are buffered", {
|
|
235
|
+
count: this.bufferList.length
|
|
236
|
+
});
|
|
237
|
+
this.dispatch({type: "buffer:allbuffered"});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const isSomeoneBuffering = this.bufferList.some(item => item.isRunning);
|
|
242
|
+
// check if need to buffer an item (has items, no one is running and someone is not ready)
|
|
243
|
+
if (!isSomeoneBuffering) {
|
|
244
|
+
// find the 1st item on the list that is not buffering (list is ordered)
|
|
245
|
+
const unbufferedItem = this.bufferList.find(item => !item.isReady);
|
|
246
|
+
log("log", "pbm_handleBufferList", "start track item", {
|
|
247
|
+
entryId: unbufferedItem.entryId
|
|
248
|
+
});
|
|
249
|
+
this.executeItemBuffering(unbufferedItem);
|
|
250
|
+
} else {
|
|
251
|
+
log(
|
|
252
|
+
"log",
|
|
253
|
+
"pbm_handleBufferList",
|
|
254
|
+
"an item is already in buffer mode, nothing to do"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private trackBufferOfItem(item: BufferItem) {
|
|
260
|
+
item.isRunning = true;
|
|
261
|
+
item.bufferingTimeoutToken = window.setTimeout(
|
|
262
|
+
this.executeItemBuffering.bind(this, item),
|
|
263
|
+
200
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private executeItemBuffering(item: BufferItem): void {
|
|
268
|
+
if (item.bufferingTimeoutToken >= 100) {
|
|
269
|
+
log(
|
|
270
|
+
"log",
|
|
271
|
+
"pbm_executeItemBuffering",
|
|
272
|
+
"failed to prebuffer - release player",
|
|
273
|
+
{entryId: item.entryId}
|
|
274
|
+
);
|
|
275
|
+
item.isRunning = false;
|
|
276
|
+
item.isReady = true;
|
|
277
|
+
item.bufferingTimeoutToken = null;
|
|
278
|
+
this.dispatch({type: "buffer:bufferend", payload: item.entryId});
|
|
279
|
+
this.handleBufferList();
|
|
280
|
+
// stop! dont continue to loop on this entry. Let it go...
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
item.bufferingTimeoutToken = null;
|
|
284
|
+
|
|
285
|
+
if (!item.isRunning) {
|
|
286
|
+
log("log", "pbm_executeItemBuffering", "start buffering entry", {
|
|
287
|
+
entryId: item.entryId
|
|
288
|
+
});
|
|
289
|
+
this.dispatch({type: "buffer:bufferstart", payload: item.entryId});
|
|
290
|
+
item.player = this.createPlayer(item.entryId, false, false, item.startTime);
|
|
291
|
+
this.trackBufferOfItem(item);
|
|
292
|
+
} else {
|
|
293
|
+
// has player ! find if we have duration
|
|
294
|
+
const player = item.player.player;
|
|
295
|
+
if (
|
|
296
|
+
player.duration &&
|
|
297
|
+
player.duration !== NaN &&
|
|
298
|
+
player.duration < this.shortEntryThreshold
|
|
299
|
+
) {
|
|
300
|
+
log(
|
|
301
|
+
"log",
|
|
302
|
+
"pbm_executeItemBuffering",
|
|
303
|
+
"buffered not needed, mark entry as ready",
|
|
304
|
+
{entryId: item.entryId}
|
|
305
|
+
);
|
|
306
|
+
item.isReady = true;
|
|
307
|
+
item.isRunning = false;
|
|
308
|
+
this.handleBufferList();
|
|
309
|
+
} else if (
|
|
310
|
+
item.isRunning &&
|
|
311
|
+
player.buffered &&
|
|
312
|
+
player.buffered.length &&
|
|
313
|
+
player.buffered.end
|
|
314
|
+
) {
|
|
315
|
+
// if we reached here we have something in the buffer. Let's check now if that's enough
|
|
316
|
+
// IE11 on Win7 sometimes has a very low buffer - it gets a special case
|
|
317
|
+
if (player.buffered.end(0) > this.secondsToBuffer - this.bufferThreshold ||
|
|
318
|
+
(this.isIe11Win7 && player.buffered.end(0) > 0.8)) {
|
|
319
|
+
log(
|
|
320
|
+
"log",
|
|
321
|
+
"pbm_executeItemBuffering",
|
|
322
|
+
"buffer completed, mark entry as ready",
|
|
323
|
+
{entryId: item.entryId}
|
|
324
|
+
);
|
|
325
|
+
item.isRunning = false;
|
|
326
|
+
item.isReady = true;
|
|
327
|
+
this.dispatch({type: "buffer:bufferend", payload: item.entryId});
|
|
328
|
+
this.handleBufferList();
|
|
329
|
+
} else {
|
|
330
|
+
this.trackBufferOfItem(item);
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
this.trackBufferOfItem(item);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// if has not running -> set running, create player and set a timeout on that item which will execute executeItemBuffering(item)
|
|
338
|
+
// if item is too short => set is ready and reexecute handlebufferlist
|
|
339
|
+
// if running -> check if ready mark as is ready and not running, and execute handleBufferList, otherwise, reset timeout
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Clear all current players (but the players that are relevant for next node)
|
|
344
|
+
* @param nextNode
|
|
345
|
+
*/
|
|
346
|
+
private destroyBufferedItems(items: BufferItem[]) {
|
|
347
|
+
log("log", "pbm_destroyBufferedItems", "executed", {
|
|
348
|
+
entries: items.map(item => item.entryId)
|
|
349
|
+
});
|
|
350
|
+
items.forEach(item => {
|
|
351
|
+
if (item.player) {
|
|
352
|
+
log(
|
|
353
|
+
"log",
|
|
354
|
+
"pbm_destroyBufferedItems",
|
|
355
|
+
"remove entry from buffer queue",
|
|
356
|
+
{entryId: item.entryId}
|
|
357
|
+
);
|
|
358
|
+
// TODO - maybe find a better way to unregister this. Consult Eran when we have more time
|
|
359
|
+
item.player.player.removeEventListener(
|
|
360
|
+
item.player.player.Event.Core.FIRST_PLAY,
|
|
361
|
+
e => this.playersFactory.onFirstPlay(e)
|
|
362
|
+
);
|
|
363
|
+
try {
|
|
364
|
+
item.player.destroy();
|
|
365
|
+
} catch (e) {
|
|
366
|
+
log(
|
|
367
|
+
"log",
|
|
368
|
+
"pbm_destroyBufferedItems",
|
|
369
|
+
"Failed to destroy player ",
|
|
370
|
+
{entryId: item.entryId , error : e}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (item.bufferingTimeoutToken) {
|
|
376
|
+
log(
|
|
377
|
+
"log",
|
|
378
|
+
"pbm_destroyBufferedItems",
|
|
379
|
+
"cancel running buffer for entry",
|
|
380
|
+
{entryId: item.entryId}
|
|
381
|
+
);
|
|
382
|
+
clearTimeout(item.bufferingTimeoutToken);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public syncPlayersStatus(
|
|
388
|
+
attribute: Persistency,
|
|
389
|
+
value: number | string | boolean,
|
|
390
|
+
currentPlayer: any
|
|
391
|
+
) {
|
|
392
|
+
log("log", "pbm_applyToPlayers", "executed", {
|
|
393
|
+
attribute,
|
|
394
|
+
value,
|
|
395
|
+
currentPlayer
|
|
396
|
+
});
|
|
397
|
+
// store to local
|
|
398
|
+
this.persistenceObj[attribute] = value;
|
|
399
|
+
|
|
400
|
+
const availablePlayers = this.bufferList
|
|
401
|
+
.filter(item => item.player)
|
|
402
|
+
.map(item => item.player);
|
|
403
|
+
|
|
404
|
+
availablePlayers.forEach(kalturaPlayer => {
|
|
405
|
+
const player: any = kalturaPlayer.player;
|
|
406
|
+
|
|
407
|
+
// no point applying the change to the player that triggered the change - it causes infinite loops
|
|
408
|
+
if (currentPlayer === player) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
switch (attribute) {
|
|
413
|
+
case Persistency.captions:
|
|
414
|
+
// get current player text-tracks
|
|
415
|
+
const textTracks = player.getTracks(
|
|
416
|
+
this.playersFactory.playerLibrary.core.TrackType.TEXT
|
|
417
|
+
);
|
|
418
|
+
// find the track that has the language that the user selected
|
|
419
|
+
const textTrack = textTracks.find(
|
|
420
|
+
track => track.language === this.persistenceObj.captions
|
|
421
|
+
);
|
|
422
|
+
if (textTrack && textTrack._language !== "off") {
|
|
423
|
+
player.selectTrack(textTrack);
|
|
424
|
+
} else {
|
|
425
|
+
// if we did not find a track or if user selected "off" - turn off the captions
|
|
426
|
+
player.hideTextTrack();
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case Persistency.audioTrack:
|
|
431
|
+
// iterate all buffered players
|
|
432
|
+
const audioTracks = player.getTracks(
|
|
433
|
+
this.playersFactory.playerLibrary.core.TrackType.AUDIO
|
|
434
|
+
);
|
|
435
|
+
const audioTrack = audioTracks.find(
|
|
436
|
+
track => track.language === this.persistenceObj.audio
|
|
437
|
+
);
|
|
438
|
+
if (audioTrack) {
|
|
439
|
+
player.selectTrack(audioTrack);
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
|
|
443
|
+
case Persistency.rate:
|
|
444
|
+
player.playbackRate = this.persistenceObj.rate;
|
|
445
|
+
break;
|
|
446
|
+
case Persistency.volume:
|
|
447
|
+
player.volume = this.persistenceObj.volume;
|
|
448
|
+
break;
|
|
449
|
+
case Persistency.mute:
|
|
450
|
+
player.muted = value;
|
|
451
|
+
break;
|
|
452
|
+
case Persistency.textStyle:
|
|
453
|
+
player.textStyle = value;
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get a list of optional nodes for the given node
|
|
461
|
+
* @param node
|
|
462
|
+
*/
|
|
463
|
+
private getNextNodes(node: RaptNode): RaptNode[] {
|
|
464
|
+
let nodes: RaptNode[] = node.prefetchNodeIds.length
|
|
465
|
+
? node.prefetchNodeIds.map((nodeId: string) =>
|
|
466
|
+
this.getNodeByRaptId(nodeId)
|
|
467
|
+
)
|
|
468
|
+
: [];
|
|
469
|
+
// at this point we have a list of next nodes without default-path and without order according to appearance time
|
|
470
|
+
nodes = this.sortByApearenceTime(nodes, node);
|
|
471
|
+
return nodes;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Return a rapt node
|
|
476
|
+
* @param id
|
|
477
|
+
*/
|
|
478
|
+
private getNodeByRaptId(id: string): RaptNode {
|
|
479
|
+
const nodes: RaptNode[] = this.raptData.nodes;
|
|
480
|
+
return nodes.find((item: RaptNode) => item.id === id);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Sort a given nodes-array by the appearance-order of the hotspots in that node
|
|
485
|
+
* @param arr of Nodes
|
|
486
|
+
* @param givenNode
|
|
487
|
+
*/
|
|
488
|
+
private sortByApearenceTime(
|
|
489
|
+
arr: RaptNode[],
|
|
490
|
+
givenNode: RaptNode
|
|
491
|
+
): RaptNode[] {
|
|
492
|
+
// get relevant hotspots (that has the givenNode as 'nodeId' ) and sort them by their showAt time
|
|
493
|
+
const hotspots: any[] = this.raptData.hotspots
|
|
494
|
+
.filter((hotSpot: any) => {
|
|
495
|
+
return (
|
|
496
|
+
hotSpot.nodeId === givenNode.id &&
|
|
497
|
+
hotSpot.onClick &&
|
|
498
|
+
hotSpot.onClick.find((itm: any) => itm.type === "project:jump") // filter out only-URL clicks
|
|
499
|
+
);
|
|
500
|
+
})
|
|
501
|
+
.sort((a: any, b: any) => a.showAt > b.showAt); // sort by appearance time
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
// find if there is any hotspot that has a startFrom attribute
|
|
505
|
+
const hotspotsWithStartFrom = hotspots.filter((hs: any) => {
|
|
506
|
+
return hs.onClick && hs.onClick.find((itm: any) => typeof itm.payload.startFrom === 'number');
|
|
507
|
+
});
|
|
508
|
+
const arrayToCache: RaptNode[] = [];
|
|
509
|
+
for (const hotSpot of hotspots) {
|
|
510
|
+
arrayToCache.push(
|
|
511
|
+
arr.find((itm: RaptNode) => {
|
|
512
|
+
// extract the onClick element with type 'project:jump'
|
|
513
|
+
const clickItem: any = hotSpot.onClick.find(
|
|
514
|
+
(itm: any) => itm.type === "project:jump"
|
|
515
|
+
);
|
|
516
|
+
return clickItem.payload.destination === itm.id;
|
|
517
|
+
})
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
// if there was a default-path on the current node - make sure it is returned as well
|
|
521
|
+
const defaultPathData: any = givenNode.onEnded.find(
|
|
522
|
+
(itm: any) => itm.type === "project:jump"
|
|
523
|
+
);
|
|
524
|
+
if (defaultPathData) {
|
|
525
|
+
const defaultPathNodeId: string = defaultPathData.payload.destination;
|
|
526
|
+
const defaultPathNode: RaptNode = this.raptData.nodes.find(
|
|
527
|
+
n => n.id === defaultPathNodeId
|
|
528
|
+
);
|
|
529
|
+
// add startFrom default path
|
|
530
|
+
if (defaultPathData.payload.startFrom) {
|
|
531
|
+
defaultPathNode.startFrom = defaultPathData.payload.startFrom
|
|
532
|
+
}
|
|
533
|
+
arrayToCache.push(defaultPathNode);
|
|
534
|
+
}
|
|
535
|
+
// add startTime if available
|
|
536
|
+
if (hotspotsWithStartFrom.length > 0) {
|
|
537
|
+
// fill startFrom attributes if they match a hotspot
|
|
538
|
+
for (const hsWithStartTime of hotspotsWithStartFrom) {
|
|
539
|
+
// find if we have a relevant start hotspot
|
|
540
|
+
const jumpingToData = hsWithStartTime.onClick.find((itm: any) => typeof itm.payload.startFrom === 'number').payload;
|
|
541
|
+
const matchingItem = arrayToCache.find((raptNode: RaptNode) => raptNode.id === jumpingToData.destination);
|
|
542
|
+
if (matchingItem) {
|
|
543
|
+
matchingItem.startFrom = jumpingToData.startFrom;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return arrayToCache;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createElement } from "./helpers/CreateElement";
|
|
2
|
+
import { KalturaPlayer } from "./PlayersFactory";
|
|
3
|
+
import { log } from "./helpers/logger";
|
|
4
|
+
|
|
5
|
+
export class PlayersDomManager {
|
|
6
|
+
private static raptPlayerCounter = 1;
|
|
7
|
+
private static kalturaPlayerCounter = 1;
|
|
8
|
+
|
|
9
|
+
private namespace: string;
|
|
10
|
+
private raptContainer: HTMLElement;
|
|
11
|
+
|
|
12
|
+
public getContainer() {
|
|
13
|
+
return this.raptContainer;
|
|
14
|
+
}
|
|
15
|
+
// add a class to the main container
|
|
16
|
+
public addClass(className) {
|
|
17
|
+
if (this.getContainer()) {
|
|
18
|
+
this.getContainer().classList.add(className);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// remove a class from the main container
|
|
22
|
+
public removeClass(className) {
|
|
23
|
+
if (this.getContainer()) {
|
|
24
|
+
this.getContainer().classList.remove(className);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reparent the rapt layer into the player that is marked as .current-playing
|
|
30
|
+
*/
|
|
31
|
+
public reparentRaptLayer(raptLayer:HTMLElement) {
|
|
32
|
+
const mainContainerId = this.getContainer().id;
|
|
33
|
+
const newParent = document.querySelector(
|
|
34
|
+
"#" + mainContainerId + " .current-playing .playkit-container"
|
|
35
|
+
);
|
|
36
|
+
if (newParent) {
|
|
37
|
+
newParent.appendChild(raptLayer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private createElementName(suffix: string) {
|
|
42
|
+
return `${this.namespace}-${suffix}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public createDomElement(
|
|
46
|
+
type: string,
|
|
47
|
+
idSuffix: string,
|
|
48
|
+
initialClasses?: string
|
|
49
|
+
): { id: string; domElement: HTMLElement } {
|
|
50
|
+
const newElement = document.createElement(type);
|
|
51
|
+
const newElementId = this.createElementName(idSuffix);
|
|
52
|
+
newElement.setAttribute("id", newElementId);
|
|
53
|
+
|
|
54
|
+
if (initialClasses) {
|
|
55
|
+
newElement.setAttribute("class", initialClasses);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.raptContainer.appendChild(newElement);
|
|
59
|
+
return { id: newElementId, domElement: newElement };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor(parentId: string) {
|
|
63
|
+
this.createRaptPlayerElement(parentId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public requestFullscreen(): void {
|
|
67
|
+
if (this.raptContainer && this.raptContainer.requestFullscreen) {
|
|
68
|
+
this.raptContainer.requestFullscreen();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public changeActivePlayer(player: KalturaPlayer): void {
|
|
73
|
+
log("log", "dm_changeActivePlayer", "executed", { id: player.id });
|
|
74
|
+
const allMarkedPlayers = this.raptContainer.querySelectorAll(
|
|
75
|
+
".current-playing"
|
|
76
|
+
);
|
|
77
|
+
// IE cannot iterate normally with forEach
|
|
78
|
+
[].forEach.call(allMarkedPlayers, function(div) {
|
|
79
|
+
// do whatever
|
|
80
|
+
div.classList.remove("current-playing");
|
|
81
|
+
});
|
|
82
|
+
if (player) {
|
|
83
|
+
player.container.classList.add("current-playing");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public showRaptLayer() {
|
|
88
|
+
const mainContainerId = this.getContainer().id;
|
|
89
|
+
const raptLayer: HTMLElement = document.querySelector(
|
|
90
|
+
"#" + mainContainerId + " .kiv-rapt-engine"
|
|
91
|
+
);
|
|
92
|
+
raptLayer.classList.remove("kiv-hidden");
|
|
93
|
+
raptLayer.focus();
|
|
94
|
+
}
|
|
95
|
+
public hideRaptLayer() {
|
|
96
|
+
const mainContainerId = this.getContainer().id;
|
|
97
|
+
const raptLayer = document.querySelector(
|
|
98
|
+
"#" + mainContainerId + " .kiv-rapt-engine"
|
|
99
|
+
);
|
|
100
|
+
raptLayer.classList.add("kiv-hidden");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public createKalturaPlayerContainer(): {
|
|
104
|
+
id: string;
|
|
105
|
+
container: HTMLElement;
|
|
106
|
+
} {
|
|
107
|
+
const containerId = `kaltura-container__${
|
|
108
|
+
PlayersDomManager.kalturaPlayerCounter
|
|
109
|
+
}`;
|
|
110
|
+
PlayersDomManager.kalturaPlayerCounter++;
|
|
111
|
+
|
|
112
|
+
let playerClass = "kiv-player kiv-cache-player";
|
|
113
|
+
const kalturaContainer = createElement("div", containerId, playerClass);
|
|
114
|
+
this.raptContainer.appendChild(kalturaContainer);
|
|
115
|
+
|
|
116
|
+
return { id: containerId, container: kalturaContainer };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private createRaptPlayerElement(parentId: string) {
|
|
120
|
+
this.namespace = "kiv-container__" + PlayersDomManager.raptPlayerCounter;
|
|
121
|
+
|
|
122
|
+
// create a top-level container
|
|
123
|
+
this.raptContainer = createElement("div", this.namespace, "kiv-container");
|
|
124
|
+
|
|
125
|
+
PlayersDomManager.raptPlayerCounter++;
|
|
126
|
+
document.getElementById(parentId).appendChild(this.raptContainer);
|
|
127
|
+
}
|
|
128
|
+
}
|