eyevinn-channel-engine 3.1.7 → 3.2.0
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/server.js +7 -4
- package/engine/session.js +206 -90
- package/engine/session_live.js +86 -26
- package/engine/session_state.js +9 -2
- package/engine/stream_switcher.js +13 -6
- package/engine/util.js +30 -0
- 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/server.js
CHANGED
|
@@ -12,7 +12,7 @@ const { SessionStateStore } = require('./session_state.js');
|
|
|
12
12
|
const { SessionLiveStateStore } = require('./session_live_state.js');
|
|
13
13
|
const { PlayheadStateStore } = require('./playhead_state.js');
|
|
14
14
|
|
|
15
|
-
const { filterQueryParser, toHHMMSS } = require('./util.js');
|
|
15
|
+
const { filterQueryParser, toHHMMSS, WaitTimeGenerator } = require('./util.js');
|
|
16
16
|
const { version } = require('../package.json');
|
|
17
17
|
|
|
18
18
|
const timer = ms => new Promise(res => setTimeout(res, ms));
|
|
@@ -166,14 +166,17 @@ class ChannelEngine {
|
|
|
166
166
|
}, 3000);
|
|
167
167
|
|
|
168
168
|
const StreamSwitchLoop = async (timeIntervalMs) => {
|
|
169
|
+
const minIntervalMs = 50;
|
|
170
|
+
const WTG = new WaitTimeGenerator(timeIntervalMs, minIntervalMs);
|
|
169
171
|
while(true) {
|
|
170
172
|
try {
|
|
171
173
|
const ts_1 = Date.now();
|
|
172
174
|
await this.updateStreamSwitchAsync()
|
|
173
175
|
const ts_2 = Date.now();
|
|
174
|
-
|
|
175
|
-
await
|
|
176
|
-
|
|
176
|
+
const interval = (timeIntervalMs - (ts_2 - ts_1)) < 0 ? minIntervalMs : (timeIntervalMs - (ts_2 - ts_1));
|
|
177
|
+
const tickInterval = await WTG.getWaitTime(interval);
|
|
178
|
+
await timer(tickInterval)
|
|
179
|
+
debug(`StreamSwitchLoop waited for all channels. Next tick in: ${tickInterval}ms`)
|
|
177
180
|
} catch (err) {
|
|
178
181
|
console.error(err)
|
|
179
182
|
debug(`StreamSwitchLoop iteration failed. Trying Again in 1000ms!`);
|
package/engine/session.js
CHANGED
|
@@ -43,9 +43,21 @@ 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;
|
|
47
51
|
this.leaderIsSettingNextVod = false;
|
|
48
|
-
this.
|
|
52
|
+
this.isSwitchingBackToV2L = false;
|
|
53
|
+
this.switchDataForSession = {
|
|
54
|
+
mediaSeq: null,
|
|
55
|
+
discSeq: null,
|
|
56
|
+
mediaSeqOffset: null,
|
|
57
|
+
transitionSegments: null,
|
|
58
|
+
reloadBehind: null,
|
|
59
|
+
}
|
|
60
|
+
this.alreadyClearedVodCache = null;
|
|
49
61
|
|
|
50
62
|
if (config) {
|
|
51
63
|
if (config.sessionId) {
|
|
@@ -139,7 +151,14 @@ class Session {
|
|
|
139
151
|
state = await this._playheadState.getState();
|
|
140
152
|
|
|
141
153
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
142
|
-
if (isLeader &&
|
|
154
|
+
if (isLeader &&
|
|
155
|
+
[
|
|
156
|
+
SessionState.VOD_NEXT_INIT,
|
|
157
|
+
SessionState.VOD_NEXT_INITIATING,
|
|
158
|
+
SessionState.VOD_RELOAD_INIT,
|
|
159
|
+
SessionState.VOD_RELOAD_INITIATING
|
|
160
|
+
].indexOf(sessionState.state) !== -1) {
|
|
161
|
+
|
|
143
162
|
const firstDuration = await this._getFirstDuration(manifest);
|
|
144
163
|
const tickInterval = firstDuration < 2 ? 2 : firstDuration;
|
|
145
164
|
debug(`[${this._sessionId}]: I am the leader and updated tick interval to ${tickInterval} sec`);
|
|
@@ -152,13 +171,13 @@ class Session {
|
|
|
152
171
|
} else {
|
|
153
172
|
const reqTickInterval = playheadState.tickInterval;
|
|
154
173
|
const timeSpentInIncrement = (tsIncrementEnd - tsIncrementBegin) / 1000;
|
|
155
|
-
let tickInterval =
|
|
174
|
+
let tickInterval = reqTickInterval - timeSpentInIncrement;
|
|
156
175
|
const delta = await this._getCurrentDeltaTime();
|
|
157
176
|
if (delta != 0) {
|
|
158
177
|
debug(`[${this._sessionId}]: Delta time is != 0 need will adjust ${delta}sec to tick interval`);
|
|
159
178
|
tickInterval += delta;
|
|
160
179
|
}
|
|
161
|
-
const position = await this._getCurrentPlayheadPosition() * 1000;
|
|
180
|
+
const position = (await this._getCurrentPlayheadPosition()) * 1000;
|
|
162
181
|
const timePosition = Date.now() - playheadState.playheadRef;
|
|
163
182
|
const diff = position - timePosition;
|
|
164
183
|
debug(`[${this._sessionId}]: ${timePosition}:${position}:${diff > 0 ? '+' : ''}${diff}ms`);
|
|
@@ -214,7 +233,7 @@ class Session {
|
|
|
214
233
|
if (this.disabledPlayhead) {
|
|
215
234
|
return {
|
|
216
235
|
sessionId: this._sessionId,
|
|
217
|
-
}
|
|
236
|
+
};
|
|
218
237
|
} else {
|
|
219
238
|
const playheadState = await this._playheadState.getValues(["tickMs", "mediaSeq", "vodMediaSeqVideo"]);
|
|
220
239
|
const state = await this._playheadState.getState();
|
|
@@ -243,11 +262,16 @@ class Session {
|
|
|
243
262
|
await this._playheadStateStore.reset(this._sessionId);
|
|
244
263
|
}
|
|
245
264
|
|
|
265
|
+
async getSessionState() {
|
|
266
|
+
const state = await this._sessionState.get("state");
|
|
267
|
+
return state;
|
|
268
|
+
}
|
|
269
|
+
|
|
246
270
|
async getTruncatedVodSegments(vodUri, duration) {
|
|
247
271
|
try {
|
|
248
272
|
const hlsVod = await this._truncateSlate(null, duration, vodUri);
|
|
249
273
|
let vodSegments = hlsVod.getMediaSegments();
|
|
250
|
-
Object.keys(vodSegments).forEach(bw => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
|
|
274
|
+
Object.keys(vodSegments).forEach((bw) => vodSegments[bw].unshift({ discontinuity: true, cue: { in: true } }));
|
|
251
275
|
return vodSegments;
|
|
252
276
|
} catch (exc) {
|
|
253
277
|
debug(`[${this._sessionId}]: Failed to generate truncated VOD!`);
|
|
@@ -257,20 +281,25 @@ class Session {
|
|
|
257
281
|
|
|
258
282
|
async setCurrentMediaSequenceSegments(segments, mSeqOffset, reloadBehind) {
|
|
259
283
|
if (!this._sessionState) {
|
|
260
|
-
throw new Error(
|
|
284
|
+
throw new Error("Session not ready");
|
|
261
285
|
}
|
|
286
|
+
this.isSwitchingBackToV2L = true;
|
|
262
287
|
|
|
263
|
-
this.
|
|
288
|
+
this.switchDataForSession.reloadBehind = reloadBehind;
|
|
289
|
+
this.switchDataForSession.transitionSegments = segments;
|
|
290
|
+
this.switchDataForSession.mediaSeqOffset = mSeqOffset;
|
|
264
291
|
|
|
265
292
|
let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
266
293
|
if (!isLeader) {
|
|
267
294
|
debug(`[${this._sessionId}]: FOLLOWER: Invalidate cache to ensure having the correct VOD!`);
|
|
268
295
|
await this._sessionState.clearCurrentVodCache();
|
|
296
|
+
|
|
269
297
|
let vodReloaded = await this._sessionState.get("vodReloaded");
|
|
270
298
|
let attempts = 9;
|
|
271
299
|
while (!isLeader && !vodReloaded && attempts > 0) {
|
|
272
300
|
debug(`[${this._sessionId}]: FOLLOWER: I arrived before LEADER. Waiting (1000ms) for LEADER to reload currentVod in store! (tries left=${attempts})`);
|
|
273
301
|
await timer(1000);
|
|
302
|
+
await this._sessionStateStore.clearLeaderCache();
|
|
274
303
|
isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
275
304
|
vodReloaded = await this._sessionState.get("vodReloaded");
|
|
276
305
|
attempts--;
|
|
@@ -282,43 +311,26 @@ class Session {
|
|
|
282
311
|
if (!isLeader || vodReloaded) {
|
|
283
312
|
debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
|
|
284
313
|
await this._sessionState.clearCurrentVodCache();
|
|
285
|
-
this.isSwitchingBackFromLive = false;
|
|
286
314
|
return;
|
|
287
315
|
}
|
|
288
|
-
|
|
316
|
+
debug(`[${this._sessionId}]: NEW LEADER: Setting state=VOD_RELOAD_INIT`);
|
|
317
|
+
this.isSwitchingBackToV2L = true;
|
|
318
|
+
await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT)
|
|
289
319
|
|
|
290
|
-
|
|
291
|
-
let
|
|
292
|
-
let
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
debug(`[${this._sessionId}]: LEADER: Wait for session to initialize next VOD. (tries left=${ATTEMPTS})`);
|
|
320
|
+
} else {
|
|
321
|
+
let vodReloaded = await this._sessionState.get("vodReloaded");
|
|
322
|
+
let attempts = 9;
|
|
323
|
+
while (!vodReloaded && attempts > 0) {
|
|
324
|
+
debug(`[${this._sessionId}]: LEADER: Waiting (1000ms) to buy some time reloading vod and adding it to store! (tries left=${attempts})`);
|
|
296
325
|
await timer(1000);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
if (ATTEMPTS === 0) {
|
|
301
|
-
throw new Error("Failed to reload currentVod! Need a new LEADER!");
|
|
326
|
+
vodReloaded = await this._sessionState.get("vodReloaded");
|
|
327
|
+
attempts--;
|
|
302
328
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
let currentVod = await this._sessionState.getCurrentVod();
|
|
307
|
-
let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
|
|
308
|
-
if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
309
|
-
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
329
|
+
if (attempts === 0) {
|
|
330
|
+
debug(`[${this._sessionId}]: LEADER: WARNING! Vod was never Reloaded!`);
|
|
331
|
+
return;
|
|
310
332
|
}
|
|
311
|
-
// TODO: Support reloading with audioSegments as well
|
|
312
|
-
await currentVod.reload(currentMseq, segments, null, reloadBehind);
|
|
313
|
-
await this._sessionState.setCurrentVod(currentVod, { ttl: (currentVod.getDuration() * 1000) });
|
|
314
|
-
await this._sessionState.set("vodReloaded", 1);
|
|
315
|
-
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
316
|
-
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
317
|
-
await this._playheadState.set("vodMediaSeqVideo", 0);
|
|
318
|
-
await this._playheadState.set("vodMediaSeqAudio", 0);
|
|
319
|
-
await this._playheadState.set("playheadRef", Date.now());
|
|
320
333
|
}
|
|
321
|
-
this.isSwitchingBackFromLive = false;
|
|
322
334
|
}
|
|
323
335
|
|
|
324
336
|
async getCurrentMediaSequenceSegments() {
|
|
@@ -329,18 +341,27 @@ class Session {
|
|
|
329
341
|
if (isLeader) {
|
|
330
342
|
await this._sessionState.set("vodReloaded", 0);
|
|
331
343
|
}
|
|
332
|
-
|
|
333
|
-
if
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
344
|
+
|
|
345
|
+
// Only read data from store if state is VOD_PLAYING
|
|
346
|
+
let state = await this.getSessionState();
|
|
347
|
+
let tries = 12;
|
|
348
|
+
while (state !== SessionState.VOD_PLAYING && tries > 0) {
|
|
349
|
+
const waitTimeMs = 500;
|
|
350
|
+
debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
|
|
351
|
+
await timer(waitTimeMs);
|
|
352
|
+
tries--;
|
|
353
|
+
state = await this.getSessionState();
|
|
338
354
|
}
|
|
355
|
+
|
|
356
|
+
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
357
|
+
|
|
358
|
+
// NOTE: Assume that VOD cache was already cleared in 'getCurrentMediaAndDiscSequenceCount()'
|
|
359
|
+
// and that we now have access to the correct vod cache
|
|
339
360
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
340
361
|
if (currentVod) {
|
|
341
362
|
try {
|
|
342
363
|
const mediaSegments = currentVod.getLiveMediaSequenceSegments(playheadState.vodMediaSeqVideo);
|
|
343
|
-
debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}`);
|
|
364
|
+
debug(`[${this._sessionId}]: Requesting all segments from Media Sequence: ${playheadState.vodMediaSeqVideo}_${currentVod.getLiveMediaSequencesCount()}`);
|
|
344
365
|
return mediaSegments;
|
|
345
366
|
} catch (err) {
|
|
346
367
|
logerror(this._sessionId, err);
|
|
@@ -354,31 +375,38 @@ class Session {
|
|
|
354
375
|
|
|
355
376
|
async setCurrentMediaAndDiscSequenceCount(_mediaSeq, _discSeq) {
|
|
356
377
|
if (!this._sessionState) {
|
|
357
|
-
throw new Error(
|
|
378
|
+
throw new Error("Session not ready");
|
|
358
379
|
}
|
|
359
|
-
|
|
360
|
-
this.isSwitchingBackFromLive = true;
|
|
361
380
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
await this._sessionState.set("discSeq", _discSeq);
|
|
367
|
-
debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${_mediaSeq}]:[${_discSeq}]`);
|
|
368
|
-
}
|
|
369
|
-
this.isSwitchingBackFromLive = false;
|
|
381
|
+
this.isSwitchingBackToV2L = true;
|
|
382
|
+
|
|
383
|
+
this.switchDataForSession.mediaSeq = _mediaSeq;
|
|
384
|
+
this.switchDataForSession.discSeq = _discSeq;
|
|
370
385
|
}
|
|
371
386
|
|
|
372
387
|
async getCurrentMediaAndDiscSequenceCount() {
|
|
373
388
|
if (!this._sessionState) {
|
|
374
389
|
throw new Error('Session not ready');
|
|
375
390
|
}
|
|
391
|
+
|
|
392
|
+
// Only read data from store if state is VOD_PLAYING
|
|
393
|
+
let state = await this.getSessionState();
|
|
394
|
+
let tries = 12;
|
|
395
|
+
while (state !== SessionState.VOD_PLAYING && tries > 0) {
|
|
396
|
+
const waitTimeMs = 500;
|
|
397
|
+
debug(`[${this._sessionId}]: state=${state} - Waiting ${waitTimeMs}ms_${tries} until Leader has finished loading next vod.`);
|
|
398
|
+
await timer(waitTimeMs);
|
|
399
|
+
tries--;
|
|
400
|
+
state = await this.getSessionState();
|
|
401
|
+
}
|
|
402
|
+
|
|
376
403
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
377
404
|
const discSeqOffset = await this._sessionState.get("discSeq");
|
|
405
|
+
// Clear Vod Cache here when Switching to Live just to be safe...
|
|
378
406
|
if (playheadState.vodMediaSeqVideo === 0) {
|
|
379
407
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
380
408
|
if (!isLeader) {
|
|
381
|
-
debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
409
|
+
debug(`[${this._sessionId}]: Not a leader, about to switch, and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
382
410
|
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
383
411
|
}
|
|
384
412
|
}
|
|
@@ -407,8 +435,8 @@ class Session {
|
|
|
407
435
|
}
|
|
408
436
|
// Be sure that the leader is not in the middle of setting new vod data in store.
|
|
409
437
|
// Followers will never run this part...
|
|
410
|
-
let tries =
|
|
411
|
-
while(tries > 0 && this.leaderIsSettingNextVod) {
|
|
438
|
+
let tries = 12;
|
|
439
|
+
while (tries > 0 && this.leaderIsSettingNextVod) {
|
|
412
440
|
debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
|
|
413
441
|
await timer(500);
|
|
414
442
|
tries--;
|
|
@@ -417,22 +445,33 @@ class Session {
|
|
|
417
445
|
const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
|
|
418
446
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
419
447
|
|
|
448
|
+
if (this.prevVodMediaSeq.video === null) {
|
|
449
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
450
|
+
}
|
|
451
|
+
|
|
420
452
|
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
421
453
|
debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
|
|
422
454
|
const m3u8 = await this._playheadState.getLastM3u8();
|
|
423
455
|
if (m3u8) {
|
|
456
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
424
457
|
return m3u8;
|
|
425
458
|
} else {
|
|
426
459
|
debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
|
|
427
460
|
}
|
|
428
461
|
}
|
|
429
462
|
|
|
430
|
-
if
|
|
463
|
+
// Force reading up from store, but only once if the condition is right
|
|
464
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
465
|
+
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
431
466
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
432
|
-
if (!isLeader) {
|
|
467
|
+
if (!isLeader && !this.alreadyClearedVodCache) {
|
|
433
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.`);
|
|
434
|
-
await this._sessionState.clearCurrentVodCache();
|
|
469
|
+
await this._sessionState.clearCurrentVodCache();
|
|
470
|
+
this.alreadyClearedVodCache = true;
|
|
435
471
|
}
|
|
472
|
+
} else {
|
|
473
|
+
// Reset the value
|
|
474
|
+
this.alreadyClearedVodCache = false;
|
|
436
475
|
}
|
|
437
476
|
|
|
438
477
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
@@ -441,6 +480,7 @@ class Session {
|
|
|
441
480
|
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
442
481
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
443
482
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
483
|
+
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
444
484
|
return m3u8;
|
|
445
485
|
} catch (err) {
|
|
446
486
|
logerror(this._sessionId, err);
|
|
@@ -482,13 +522,6 @@ class Session {
|
|
|
482
522
|
async incrementAsync() {
|
|
483
523
|
await this._tickAsync();
|
|
484
524
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
485
|
-
// Avoid reading old values if new ones are on their way...
|
|
486
|
-
let tries = 10;
|
|
487
|
-
while (tries > 0 && this.isSwitchingBackFromLive) {
|
|
488
|
-
debug(`[${this._sessionId}]: Leader is currently setting new data in store. Waiting 500ms_${tries}`);
|
|
489
|
-
await timer(500);
|
|
490
|
-
tries--;
|
|
491
|
-
}
|
|
492
525
|
|
|
493
526
|
let sessionState = await this._sessionState.getValues(
|
|
494
527
|
["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
|
|
@@ -502,15 +535,17 @@ class Session {
|
|
|
502
535
|
sessionState.discSeq === null) {
|
|
503
536
|
debug(`[${this._sessionId}]: Session is not ready yet`);
|
|
504
537
|
debug(sessionState);
|
|
538
|
+
await this._sessionState.clearCurrentVodCache();
|
|
505
539
|
if (isLeader) {
|
|
506
540
|
debug(`[${this._sessionId}]: I am the leader, trying to initiate the session`);
|
|
507
541
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_INIT);
|
|
508
542
|
}
|
|
509
543
|
return null;
|
|
510
544
|
}
|
|
511
|
-
if (sessionState.state === SessionState.VOD_NEXT_INITIATING) {
|
|
545
|
+
if (sessionState.state === SessionState.VOD_NEXT_INITIATING || sessionState.state === SessionState.VOD_RELOAD_INITIATING) {
|
|
512
546
|
if (isLeader) {
|
|
513
|
-
|
|
547
|
+
const leaderAction = sessionState.state === SessionState.VOD_NEXT_INITIATING ? "initiated" : "reloaded";
|
|
548
|
+
debug(`[${this._sessionId}]: I am the leader and have just ${leaderAction} next VOD, let's move to VOD_PLAYING`);
|
|
514
549
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
|
|
515
550
|
} else {
|
|
516
551
|
debug(`[${this._sessionId}]: Return the last generated m3u8 to give the leader some time`);
|
|
@@ -532,6 +567,11 @@ class Session {
|
|
|
532
567
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
|
|
533
568
|
}
|
|
534
569
|
|
|
570
|
+
if (this.isSwitchingBackToV2L) {
|
|
571
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INIT);
|
|
572
|
+
this.isSwitchingBackToV2L = false;
|
|
573
|
+
}
|
|
574
|
+
|
|
535
575
|
playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
|
|
536
576
|
playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
|
|
537
577
|
playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
|
|
@@ -539,9 +579,17 @@ class Session {
|
|
|
539
579
|
let m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, 180000, playheadState.vodMediaSeqVideo, sessionState.discSeq);
|
|
540
580
|
await this._playheadState.setLastM3u8(m3u8);
|
|
541
581
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
582
|
+
// As a FOLLOWER, we might need to read up from shared store...
|
|
583
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video) {
|
|
584
|
+
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
585
|
+
if (!isLeader && !this.alreadyClearedVodCache) {
|
|
586
|
+
debug(`[${this._sessionId}]: Not a leader and have just set 'playheadState.vodMediaSeqVideo' to 0|1. Invalidate cache to ensure having the correct VOD.`);
|
|
587
|
+
await this._sessionState.clearCurrentVodCache();
|
|
588
|
+
this.alreadyClearedVodCache = true;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
// Reset the value
|
|
592
|
+
this.alreadyClearedVodCache = false;
|
|
545
593
|
}
|
|
546
594
|
|
|
547
595
|
return m3u8;
|
|
@@ -798,17 +846,14 @@ class Session {
|
|
|
798
846
|
sessionState.state = SessionState.VOD_INIT;
|
|
799
847
|
}
|
|
800
848
|
|
|
801
|
-
if (!isLeader && this.waitingForNextVod) {
|
|
802
|
-
// By now Leader should have added the next Vod in store
|
|
803
|
-
debug(`[${this._sessionId}]: Not leader! New VOD loaded during last tick. Invalidate current VOD cache`);
|
|
804
|
-
await this._sessionState.clearCurrentVodCache();
|
|
805
|
-
this.waitingForNextVod = false;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
849
|
switch (sessionState.state) {
|
|
809
850
|
case SessionState.VOD_INIT:
|
|
810
851
|
case SessionState.VOD_INIT_BY_ID:
|
|
811
852
|
try {
|
|
853
|
+
// Needed if store was reset
|
|
854
|
+
await this._sessionStateStore.clearLeaderCache();
|
|
855
|
+
isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
856
|
+
|
|
812
857
|
let nextVodPromise;
|
|
813
858
|
if (sessionState.state === SessionState.VOD_INIT) {
|
|
814
859
|
debug(`[${this._sessionId}]: state=VOD_INIT`);
|
|
@@ -891,12 +936,17 @@ class Session {
|
|
|
891
936
|
}
|
|
892
937
|
}
|
|
893
938
|
case SessionState.VOD_PLAYING:
|
|
894
|
-
if (
|
|
895
|
-
|
|
896
|
-
|
|
939
|
+
if (isLeader) {
|
|
940
|
+
// Handle edge case where store has been reset, but leader has not cleared cache.
|
|
941
|
+
if (this.prevVodMediaSeq.video === null) {
|
|
942
|
+
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
943
|
+
}
|
|
944
|
+
// Clear Cache if prev count is HIGHER than current...
|
|
945
|
+
if (sessionState.vodMediaSeqVideo < this.prevVodMediaSeq.video ) {
|
|
946
|
+
debug(`[${this._sessionId}]: current[${sessionState.vodMediaSeqVideo}], prev[${this.prevVodMediaSeq.video}], total[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
897
947
|
await this._sessionState.clearCurrentVodCache();
|
|
898
948
|
currentVod = await this._sessionState.getCurrentVod();
|
|
899
|
-
this.
|
|
949
|
+
this.prevVodMediaSeq.video = sessionState.vodMediaSeqVideo;
|
|
900
950
|
}
|
|
901
951
|
}
|
|
902
952
|
debug(`[${this._sessionId}]: state=VOD_PLAYING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
@@ -905,11 +955,6 @@ class Session {
|
|
|
905
955
|
debug(`[${this._sessionId}]: state=VOD_NEXT_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
906
956
|
if (!isLeader) {
|
|
907
957
|
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be initiated`);
|
|
908
|
-
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
909
|
-
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`);
|
|
910
|
-
await this._sessionState.clearCurrentVodCache();
|
|
911
|
-
}
|
|
912
|
-
this.waitingForNextVod = true;
|
|
913
958
|
}
|
|
914
959
|
return;
|
|
915
960
|
case SessionState.VOD_NEXT_INIT:
|
|
@@ -1014,6 +1059,77 @@ class Session {
|
|
|
1014
1059
|
}
|
|
1015
1060
|
}
|
|
1016
1061
|
break;
|
|
1062
|
+
case SessionState.VOD_RELOAD_INIT:
|
|
1063
|
+
try {
|
|
1064
|
+
debug(`[${this._sessionId}]: state=VOD_RELOAD_INIT`);
|
|
1065
|
+
if (isLeader) {
|
|
1066
|
+
|
|
1067
|
+
const startTS = Date.now();
|
|
1068
|
+
|
|
1069
|
+
// 1) To tell Follower that, Leader is working on it!
|
|
1070
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1071
|
+
|
|
1072
|
+
// 2) Set new 'offset' sequences, to carry on the continuity from session-live
|
|
1073
|
+
const mSeq = this.switchDataForSession.mediaSeq;
|
|
1074
|
+
const dSeq = this.switchDataForSession.discSeq;
|
|
1075
|
+
const mSeqOffset = this.switchDataForSession.mediaSeqOffset;
|
|
1076
|
+
const reloadBehind = this.switchDataForSession.reloadBehind;
|
|
1077
|
+
const segments = this.switchDataForSession.transitionSegments;
|
|
1078
|
+
|
|
1079
|
+
if ([mSeq,dSeq,mSeqOffset,reloadBehind,segments].includes(null)) {
|
|
1080
|
+
debug(`[${this._sessionId}]: LEADER: Cannot Reload VOD, missing switch-back data`);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
await this._sessionState.set("mediaSeq", mSeq);
|
|
1085
|
+
await this._playheadState.set("mediaSeq", mSeq);
|
|
1086
|
+
await this._sessionState.set("discSeq", dSeq);
|
|
1087
|
+
debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${mSeq}]:[${dSeq}]`);
|
|
1088
|
+
|
|
1089
|
+
// 3) Set new media segments/currentVod, to carry on the continuity from session-live
|
|
1090
|
+
debug(`[${this._sessionId}]: LEADER: making changes to current VOD. I will also update currentVod in store.`);
|
|
1091
|
+
const playheadState = await this._playheadState.getValues(["vodMediaSeqVideo"]);
|
|
1092
|
+
let currentVod = await this._sessionState.getCurrentVod();
|
|
1093
|
+
let currentMseq = playheadState.vodMediaSeqVideo + mSeqOffset;
|
|
1094
|
+
if (currentMseq > currentVod.getLiveMediaSequencesCount() - 1) {
|
|
1095
|
+
currentMseq = currentVod.getLiveMediaSequencesCount() - 1;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// TODO: Support reloading with audioSegments as well
|
|
1099
|
+
|
|
1100
|
+
await currentVod.reload(currentMseq, segments, null, reloadBehind);
|
|
1101
|
+
await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
1102
|
+
await this._sessionState.set("vodReloaded", 1);
|
|
1103
|
+
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
1104
|
+
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
1105
|
+
await this._playheadState.set("vodMediaSeqVideo", 0);
|
|
1106
|
+
await this._playheadState.set("vodMediaSeqAudio", 0);
|
|
1107
|
+
await this._playheadState.set("playheadRef", Date.now());
|
|
1108
|
+
debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`);
|
|
1109
|
+
// 4) emit cloudwatch event object
|
|
1110
|
+
cloudWatchLog(!this.cloudWatchLogging, "engine-session", { event: "switchback", channel: this._sessionId, reqTimeMs: Date.now() - startTS });
|
|
1111
|
+
|
|
1112
|
+
return;
|
|
1113
|
+
} else {
|
|
1114
|
+
debug(`[${this._sessionId}]: not a leader so will go directly to state VOD_RELOAD_INITIATING`);
|
|
1115
|
+
sessionState.state = await this._sessionState.set("state", SessionState.VOD_RELOAD_INITIATING);
|
|
1116
|
+
}
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
debug("Failed to init reload vod");
|
|
1119
|
+
throw err;
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
case SessionState.VOD_RELOAD_INITIATING:
|
|
1123
|
+
debug(`[${this._sessionId}]: state=VOD_RELOAD_INITIATING (${sessionState.vodMediaSeqVideo}_${sessionState.vodMediaSeqAudio}, ${currentVod.getLiveMediaSequencesCount()})`);
|
|
1124
|
+
if (!isLeader) {
|
|
1125
|
+
debug(`[${this._sessionId}]: not the leader so just waiting for the VOD to be reloaded`);
|
|
1126
|
+
if (sessionState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
1127
|
+
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`);
|
|
1128
|
+
await this._sessionState.clearCurrentVodCache();
|
|
1129
|
+
}
|
|
1130
|
+
this.waitingForNextVod = true;
|
|
1131
|
+
}
|
|
1132
|
+
return;
|
|
1017
1133
|
default:
|
|
1018
1134
|
throw new Error("Invalid state: " + sessionState.state);
|
|
1019
1135
|
}
|