eyevinn-channel-engine 3.1.5 → 3.1.8
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/engine/session.js +202 -50
- package/engine/session_live.js +72 -27
- package/engine/session_state.js +9 -2
- package/engine/stream_switcher.js +7 -6
- package/package.json +1 -1
- package/spec/engine/init_switching_spec.js +118 -153
- package/spec/engine/stream_switcher_spec.js +91 -68
package/engine/session.js
CHANGED
|
@@ -43,7 +43,20 @@ class Session {
|
|
|
43
43
|
this.playheadDiffThreshold = DEFAULT_PLAYHEAD_DIFF_THRESHOLD;
|
|
44
44
|
this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
|
|
45
45
|
this.diffCompensation = null;
|
|
46
|
+
this.prevVodMediaSeq = {
|
|
47
|
+
video: null,
|
|
48
|
+
audio: null
|
|
49
|
+
}
|
|
46
50
|
this.waitingForNextVod = false;
|
|
51
|
+
this.leaderIsSettingNextVod = false;
|
|
52
|
+
this.isSwitchingBackToV2L = false;
|
|
53
|
+
this.switchDataForSession = {
|
|
54
|
+
mediaSeq: null,
|
|
55
|
+
discSeq: null,
|
|
56
|
+
mediaSeqOffset: null,
|
|
57
|
+
transitionSegments: null,
|
|
58
|
+
reloadBehind: null,
|
|
59
|
+
}
|
|
47
60
|
|
|
48
61
|
if (config) {
|
|
49
62
|
if (config.sessionId) {
|
|
@@ -137,7 +150,14 @@ class Session {
|
|
|
137
150
|
state = await this._playheadState.getState();
|
|
138
151
|
|
|
139
152
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
140
|
-
if (isLeader &&
|
|
153
|
+
if (isLeader &&
|
|
154
|
+
[
|
|
155
|
+
SessionState.VOD_NEXT_INIT,
|
|
156
|
+
SessionState.VOD_NEXT_INITIATING,
|
|
157
|
+
SessionState.VOD_RELOAD_INIT,
|
|
158
|
+
SessionState.VOD_RELOAD_INITIATING
|
|
159
|
+
].indexOf(sessionState.state) !== -1) {
|
|
160
|
+
|
|
141
161
|
const firstDuration = await this._getFirstDuration(manifest);
|
|
142
162
|
const tickInterval = firstDuration < 2 ? 2 : firstDuration;
|
|
143
163
|
debug(`[${this._sessionId}]: I am the leader and updated tick interval to ${tickInterval} sec`);
|
|
@@ -150,13 +170,13 @@ class Session {
|
|
|
150
170
|
} else {
|
|
151
171
|
const reqTickInterval = playheadState.tickInterval;
|
|
152
172
|
const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
|
|
153
|
-
let tickInterval =
|
|
173
|
+
let tickInterval = reqTickInterval - timeSpentInIncrement;
|
|
154
174
|
const delta = await this._getCurrentDeltaTime();
|
|
155
175
|
if (delta != 0) {
|
|
156
176
|
debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
|
|
157
177
|
tickInterval += delta;
|
|
158
178
|
}
|
|
159
|
-
const position = await this._getCurrentPlayheadPosition() * 1000;
|
|
179
|
+
const position = (await this._getCurrentPlayheadPosition()) * 1000;
|
|
160
180
|
const timePosition = Date.now() - playheadState.playheadRef;
|
|
161
181
|
const diff = position - timePosition;
|
|
162
182
|
debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
|
|
@@ -212,7 +232,7 @@ class Session {
|
|
|
212
232
|
if (this.disabledPlayhead) {
|
|
213
233
|
return {
|
|
214
234
|
sessionId: this._sessionId,
|
|
215
|
-
}
|
|
235
|
+
};
|
|
216
236
|
} else {
|
|
217
237
|
const playheadState = await this._playheadState.getValues(["tickMs", "mediaSeq", "vodMediaSeqVideo"]);
|
|
218
238
|
const state = await this._playheadState.getState();
|
|
@@ -241,11 +261,16 @@ class Session {
|
|
|
241
261
|
await this._playheadStateStore.reset(this._sessionId);
|
|
242
262
|
}
|
|
243
263
|
|
|
264
|
+
async getSessionState() {
|
|
265
|
+
const state = await this._sessionState.get("state");
|
|
266
|
+
return state;
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
async getTruncatedVodSegments(vodUri, duration) {
|
|
245
270
|
try {
|
|
246
271
|
const hlsVod = await this._truncateSlate(null, duration, vodUri);
|
|
247
272
|
let vodSegments = hlsVod.getMediaSegments();
|
|
248
|
-
Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
|
|
273
|
+
Object.keys(vodSegments).forEach((bw) => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
|
|
249
274
|
return vodSegments;
|
|
250
275
|
} catch (exc) {
|
|
251
276
|
debug(`[${this._sessionId}]: Failed to generate truncated VOD!`);
|
|
@@ -255,18 +280,25 @@ class Session {
|
|
|
255
280
|
|
|
256
281
|
async setCurrentMediaSequenceSegments(segments, mSeqOffset, reloadBehind) {
|
|
257
282
|
if (!this._sessionState) {
|
|
258
|
-
throw new Error(
|
|
283
|
+
throw new Error("Session not ready");
|
|
259
284
|
}
|
|
285
|
+
this.isSwitchingBackToV2L = true;
|
|
286
|
+
|
|
287
|
+
this.switchDataForSession.reloadBehind = reloadBehind;
|
|
288
|
+
this.switchDataForSession.transitionSegments = segments;
|
|
289
|
+
this.switchDataForSession.mediaSeqOffset = mSeqOffset;
|
|
260
290
|
|
|
261
291
|
let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
262
292
|
if (!isLeader) {
|
|
263
293
|
debug(`[${this._sessionId}]: FOLLOWER: Invalidate cache to ensure having the correct VOD!`);
|
|
264
294
|
await this._sessionState.clearCurrentVodCache();
|
|
295
|
+
|
|
265
296
|
let vodReloaded = await this._sessionState.get("vodReloaded");
|
|
266
297
|
let attempts = 9;
|
|
267
298
|
while (!isLeader && !vodReloaded && attempts > 0) {
|
|
268
299
|
debug(`[${this._sessionId}]: FOLLOWER: I arrived before LEADER. Waiting (1000ms) for LEADER to reload currentVod in store! (tries left=${attempts})`);
|
|
269
300
|
await timer(1000);
|
|
301
|
+
await this._sessionStateStore.clearLeaderCache();
|
|
270
302
|
isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
271
303
|
vodReloaded = await this._sessionState.get("vodReloaded");
|
|
272
304
|
attempts--;
|
|
@@ -280,38 +312,23 @@ class Session {
|
|
|
280
312
|
await this._sessionState.clearCurrentVodCache();
|
|
281
313
|
return;
|
|
282
314
|
}
|
|
283
|
-
|
|
315
|
+
debug(`[${this._sessionId}]: NEW LEADER: Setting state=VOD_RELOAD_INIT`);
|
|
316
|
+
this.isSwitchingBackToV2L = true;
|
|
317
|
+
await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT)
|
|
284
318
|
|
|
285
|
-
|
|
286
|
-
let
|
|
287
|
-
let
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
debug(`[${this._sessionId}]: LEADER: Wait for session to initialize next VOD. (tries left=${ATTEMPTS})`);
|
|
319
|
+
} else {
|
|
320
|
+
let vodReloaded = await this._sessionState.get("vodReloaded");
|
|
321
|
+
let attempts = 9;
|
|
322
|
+
while (!vodReloaded && attempts > 0) {
|
|
323
|
+
debug(`[${this._sessionId}]: LEADER: Waiting (1000ms) to buy some time reloading vod and adding it to store! (tries left=${attempts})`);
|
|
291
324
|
await timer(1000);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
if (ATTEMPTS === 0) {
|
|
296
|
-
throw new Error("Failed to reload currentVod! Need a new LEADER!");
|
|
325
|
+
vodReloaded = await this._sessionState.get("vodReloaded");
|
|
326
|
+
attempts--;
|
|
297
327
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
let currentVod = await this._sessionState.getCurrentVod();
|
|
302
|
-
let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
|
|
303
|
-
if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
304
|
-
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
328
|
+
if (attempts === 0) {
|
|
329
|
+
debug(`[${this._sessionId}]: LEADER: WARNING! Vod was never Reloaded!`);
|
|
330
|
+
return;
|
|
305
331
|
}
|
|
306
|
-
// TODO: Support reloading with audioSegments as well
|
|
307
|
-
await currentVod.reload(currentMseq, segments, null, reloadBehind);
|
|
308
|
-
await this._sessionState.setCurrentVod(currentVod, { ttl: (currentVod.getDuration() * 1000) });
|
|
309
|
-
await this._sessionState.set("vodReloaded", 1);
|
|
310
|
-
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
311
|
-
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
312
|
-
await this._playheadState.set("vodMediaSeqVideo", 0);
|
|
313
|
-
await this._playheadState.set("vodMediaSeqAudio", 0);
|
|
314
|
-
await this._playheadState.set("playheadRef", Date.now());
|
|
315
332
|
}
|
|
316
333
|
}
|
|
317
334
|
|
|
@@ -323,6 +340,18 @@ class Session {
|
|
|
323
340
|
if (isLeader) {
|
|
324
341
|
await this._sessionState.set("vodReloaded", 0);
|
|
325
342
|
}
|
|
343
|
+
|
|
344
|
+
// Only read data from store if state is VOD_PLAYING
|
|
345
|
+
let state = await this.getSessionState();
|
|
346
|
+
let tries = 12;
|
|
347
|
+
while (state !== SessionState.VOD_PLAYING && tries > 0) {
|
|
348
|
+
const waitTimeMs = 500;
|
|
349
|
+
debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
|
|
350
|
+
await timer(waitTimeMs);
|
|
351
|
+
tries--;
|
|
352
|
+
state = await this.getSessionState();
|
|
353
|
+
}
|
|
354
|
+
|
|
326
355
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
327
356
|
if (playheadState.vodMediaSeqVideo === 0) {
|
|
328
357
|
if (!isLeader) {
|
|
@@ -334,7 +363,7 @@ class Session {
|
|
|
334
363
|
if (currentVod) {
|
|
335
364
|
try {
|
|
336
365
|
const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
|
|
337
|
-
debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}`);
|
|
366
|
+
debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}_${currentVod.getLiveMediaSequencesCount()}`);
|
|
338
367
|
return mediaSegments;
|
|
339
368
|
} catch (err) {
|
|
340
369
|
logerror(this._sessionId, err);
|
|
@@ -348,21 +377,31 @@ class Session {
|
|
|
348
377
|
|
|
349
378
|
async setCurrentMediaAndDiscSequenceCount(_mediaSeq, _discSeq) {
|
|
350
379
|
if (!this._sessionState) {
|
|
351
|
-
throw new Error(
|
|
352
|
-
}
|
|
353
|
-
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
354
|
-
if (isLeader) {
|
|
355
|
-
await this._sessionState.set("mediaSeq", _mediaSeq);
|
|
356
|
-
await this._playheadState.set("mediaSeq", _mediaSeq);
|
|
357
|
-
await this._sessionState.set("discSeq", _discSeq);
|
|
358
|
-
debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${_mediaSeq}]:[${_discSeq}]`);
|
|
380
|
+
throw new Error("Session not ready");
|
|
359
381
|
}
|
|
382
|
+
|
|
383
|
+
this.isSwitchingBackToV2L = true;
|
|
384
|
+
|
|
385
|
+
this.switchDataForSession.mediaSeq = _mediaSeq;
|
|
386
|
+
this.switchDataForSession.discSeq = _discSeq;
|
|
360
387
|
}
|
|
361
388
|
|
|
362
389
|
async getCurrentMediaAndDiscSequenceCount() {
|
|
363
390
|
if (!this._sessionState) {
|
|
364
391
|
throw new Error('Session not ready');
|
|
365
392
|
}
|
|
393
|
+
|
|
394
|
+
// Only read data from store if state is VOD_PLAYING
|
|
395
|
+
let state = await this.getSessionState();
|
|
396
|
+
let tries = 12;
|
|
397
|
+
while (state !== SessionState.VOD_PLAYING && tries > 0) {
|
|
398
|
+
const waitTimeMs = 500;
|
|
399
|
+
debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
|
|
400
|
+
await timer(waitTimeMs);
|
|
401
|
+
tries--;
|
|
402
|
+
state = await this.getSessionState();
|
|
403
|
+
}
|
|
404
|
+
|
|
366
405
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
367
406
|
const discSeqOffset = await this._sessionState.get("discSeq");
|
|
368
407
|
if (playheadState.vodMediaSeqVideo === 0) {
|
|
@@ -395,33 +434,49 @@ class Session {
|
|
|
395
434
|
if (!this._sessionState) {
|
|
396
435
|
throw new Error('Session not ready');
|
|
397
436
|
}
|
|
437
|
+
// Be sure that the leader is not in the middle of setting new vod data in store.
|
|
438
|
+
// Followers will never run this part...
|
|
439
|
+
let tries = 12;
|
|
440
|
+
while (tries > 0 && this.leaderIsSettingNextVod) {
|
|
441
|
+
debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
|
|
442
|
+
await timer(500);
|
|
443
|
+
tries--;
|
|
444
|
+
}
|
|
445
|
+
|
|
398
446
|
const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
|
|
399
447
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
400
448
|
|
|
449
|
+
if (this.prevVodMediaSeq.video !== null) {
|
|
450
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
451
|
+
}
|
|
452
|
+
|
|
401
453
|
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
402
454
|
debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
|
|
403
455
|
const m3u8 = await this._playheadState.getLastM3u8();
|
|
404
456
|
if (m3u8) {
|
|
457
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
405
458
|
return m3u8;
|
|
406
459
|
} else {
|
|
407
460
|
debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
|
|
408
461
|
}
|
|
409
462
|
}
|
|
410
463
|
|
|
411
|
-
if (playheadState.vodMediaSeqVideo < 2) {
|
|
464
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
465
|
+
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
412
466
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
413
467
|
if (!isLeader) {
|
|
414
468
|
debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
415
469
|
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
416
470
|
}
|
|
417
471
|
}
|
|
418
|
-
|
|
472
|
+
|
|
419
473
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
420
474
|
if (currentVod) {
|
|
421
475
|
try {
|
|
422
476
|
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
423
477
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
424
478
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
479
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
425
480
|
return m3u8;
|
|
426
481
|
} catch (err) {
|
|
427
482
|
logerror(this._sessionId, err);
|
|
@@ -463,6 +518,7 @@ class Session {
|
|
|
463
518
|
async incrementAsync() {
|
|
464
519
|
await this._tickAsync();
|
|
465
520
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
521
|
+
|
|
466
522
|
let sessionState = await this._sessionState.getValues(
|
|
467
523
|
["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
468
524
|
let playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
@@ -475,15 +531,17 @@ class Session {
|
|
|
475
531
|
sessionState.discSeq === null) {
|
|
476
532
|
debug(`[${this._sessionId}]: Session is not ready yet`);
|
|
477
533
|
debug(sessionState);
|
|
534
|
+
await this._sessionState.clearCurrentVodCache();
|
|
478
535
|
if (isLeader) {
|
|
479
536
|
debug(`[${this._sessionId}]: I am the leader, trying to initiate the session`);
|
|
480
537
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_INIT);
|
|
481
538
|
}
|
|
482
539
|
return null;
|
|
483
540
|
}
|
|
484
|
-
if (sessionState.state === SessionState.VOD_NEXT_INITIATING) {
|
|
541
|
+
if (sessionState.state === SessionState.VOD_NEXT_INITIATING || sessionState.state === SessionState.VOD_RELOAD_INITIATING) {
|
|
485
542
|
if (isLeader) {
|
|
486
|
-
|
|
543
|
+
const leaderAction = sessionState.state === SessionState.VOD_NEXT_INITIATING ? "initiated" : "reloaded";
|
|
544
|
+
debug(`[${this._sessionId}]: I am the leader and have just ${leaderAction} next VOD, let's move to VOD_PLAYING`);
|
|
487
545
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
|
|
488
546
|
} else {
|
|
489
547
|
debug(`[${this._sessionId}]: Return the last generated m3u8 to give the leader some time`);
|
|
@@ -498,13 +556,18 @@ class Session {
|
|
|
498
556
|
sessionState.vodMediaSeqVideo = await this._sessionState.increment("vodMediaSeqVideo");
|
|
499
557
|
sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio");
|
|
500
558
|
}
|
|
501
|
-
|
|
559
|
+
|
|
502
560
|
if (sessionState.vodMediaSeqVideo >= currentVod.getLiveMediaSequencesCount() - 1) {
|
|
503
561
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", currentVod.getLiveMediaSequencesCount() - 1);
|
|
504
562
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
|
|
505
563
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
|
|
506
564
|
}
|
|
507
565
|
|
|
566
|
+
if (this.isSwitchingBackToV2L) {
|
|
567
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT);
|
|
568
|
+
this.isSwitchingBackToV2L = false;
|
|
569
|
+
}
|
|
570
|
+
|
|
508
571
|
playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
|
|
509
572
|
playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
|
|
510
573
|
playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
|
|
@@ -782,6 +845,10 @@ class Session {
|
|
|
782
845
|
case SessionState.VOD_INIT:
|
|
783
846
|
case SessionState.VOD_INIT_BY_ID:
|
|
784
847
|
try {
|
|
848
|
+
// Needed if store was reset
|
|
849
|
+
await this._sessionStateStore.clearLeaderCache();
|
|
850
|
+
isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
851
|
+
|
|
785
852
|
let nextVodPromise;
|
|
786
853
|
if (sessionState.state === SessionState.VOD_INIT) {
|
|
787
854
|
debug(`[${this._sessionId}]: state=VOD_INIT`);
|
|
@@ -871,6 +938,17 @@ class Session {
|
|
|
871
938
|
currentVod = await this._sessionState.getCurrentVod();
|
|
872
939
|
this.waitingForNextVod = false;
|
|
873
940
|
}
|
|
941
|
+
} else {
|
|
942
|
+
// Handle edge case where store has been reset, but leader has not cleared cache.
|
|
943
|
+
if (this.prevVodMediaSeq.video !== null) {
|
|
944
|
+
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
945
|
+
}
|
|
946
|
+
if (this.prevVodMediaSeq.video < sessionState.vodMediaSeqVideo) {
|
|
947
|
+
debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
948
|
+
await this._sessionState.clearCurrentVodCache();
|
|
949
|
+
currentVod = await this._sessionState.getCurrentVod();
|
|
950
|
+
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
951
|
+
}
|
|
874
952
|
}
|
|
875
953
|
debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
876
954
|
return;
|
|
@@ -943,16 +1021,19 @@ class Session {
|
|
|
943
1021
|
await ChaosMonkey.loadVod(loadPromise);
|
|
944
1022
|
cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
|
|
945
1023
|
{ event: 'loadVod', channel: this._sessionId, loadTimeMs: Date.now() - loadStart });
|
|
1024
|
+
this.leaderIsSettingNextVod = true;
|
|
946
1025
|
debug(`[${this._sessionId}]: next VOD loaded (${newVod.getDeltaTimes()})`);
|
|
947
1026
|
debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
|
|
948
1027
|
currentVod = newVod;
|
|
949
1028
|
debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
|
|
950
|
-
await this._sessionState.remove("nextVod");
|
|
951
|
-
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
952
1029
|
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
953
1030
|
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
954
1031
|
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
|
|
955
1032
|
sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
1033
|
+
debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
|
|
1034
|
+
await this._sessionState.remove("nextVod");
|
|
1035
|
+
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1036
|
+
this.leaderIsSettingNextVod = false;
|
|
956
1037
|
await this._playheadState.set("playheadRef", Date.now());
|
|
957
1038
|
this.produceEvent({
|
|
958
1039
|
type: 'NOW_PLAYING',
|
|
@@ -984,6 +1065,77 @@ class Session {
|
|
|
984
1065
|
}
|
|
985
1066
|
}
|
|
986
1067
|
break;
|
|
1068
|
+
case SessionState.VOD_RELOAD_INIT:
|
|
1069
|
+
try {
|
|
1070
|
+
debug(`[${this._sessionId}]: state=VOD_RELOAD_INIT`);
|
|
1071
|
+
if (isLeader) {
|
|
1072
|
+
|
|
1073
|
+
const startTS = Date.now();
|
|
1074
|
+
|
|
1075
|
+
// 1) To tell Follower that, Leader is working on it!
|
|
1076
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1077
|
+
|
|
1078
|
+
// 2) Set new 'offset' sequences, to carry on the continuity from session-live
|
|
1079
|
+
const mSeq = this.switchDataForSession.mediaSeq;
|
|
1080
|
+
const dSeq = this.switchDataForSession.discSeq;
|
|
1081
|
+
const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
|
|
1082
|
+
const reloadBehind = this.switchDataForSession.reloadBehind;
|
|
1083
|
+
const segments = this.switchDataForSession.transitionSegments;
|
|
1084
|
+
|
|
1085
|
+
if ([mSeq,dSeq,mSeqOffset,reloadBehind,segments].includes(null)) {
|
|
1086
|
+
debug(`[${this._sessionId}]: LEADER: Cannot Reload VOD, missing switch-back data`);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
await this._sessionState.set("mediaSeq", mSeq);
|
|
1091
|
+
await this._playheadState.set("mediaSeq", mSeq);
|
|
1092
|
+
await this._sessionState.set("discSeq", dSeq);
|
|
1093
|
+
debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
|
|
1094
|
+
|
|
1095
|
+
// 3) Set new media segments/currentVod, to carry on the continuity from session-live
|
|
1096
|
+
debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
|
|
1097
|
+
const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
|
|
1098
|
+
let currentVod = await this._sessionState.getCurrentVod();
|
|
1099
|
+
let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
|
|
1100
|
+
if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
1101
|
+
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// TODO: Support reloading with audioSegments as well
|
|
1105
|
+
|
|
1106
|
+
await currentVod.reload(currentMseq, segments, null, reloadBehind);
|
|
1107
|
+
await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1108
|
+
await this._sessionState.set("vodReloaded", 1);
|
|
1109
|
+
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1110
|
+
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1111
|
+
await this._playheadState.set("vodMediaSeqVideo", 0);
|
|
1112
|
+
await this._playheadState.set("vodMediaSeqAudio", 0);
|
|
1113
|
+
await this._playheadState.set("playheadRef", Date.now());
|
|
1114
|
+
debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
|
|
1115
|
+
// 4) emit cloudwatch event object
|
|
1116
|
+
cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
|
|
1117
|
+
|
|
1118
|
+
return;
|
|
1119
|
+
} else {
|
|
1120
|
+
debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_RELOAD_INITIATING`);
|
|
1121
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1122
|
+
}
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
debug("Failed to init reload vod");
|
|
1125
|
+
throw err;
|
|
1126
|
+
}
|
|
1127
|
+
break;
|
|
1128
|
+
case SessionState.VOD_RELOAD_INITIATING:
|
|
1129
|
+
debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
1130
|
+
if (!isLeader) {
|
|
1131
|
+
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
|
|
1132
|
+
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
1133
|
+
debug(`[${this._sessionId}]: First mediasequence in VOD and I am not the leader so invalidate current VOD cache and fetch the new one from the leader`);
|
|
1134
|
+
await this._sessionState.clearCurrentVodCache();
|
|
1135
|
+
}
|
|
1136
|
+
this.waitingForNextVod = true;
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
987
1139
|
default:
|
|
988
1140
|
throw new Error("Invalid state: " + sessionState.state);
|
|
989
1141
|
}
|