eyevinn-channel-engine 3.0.5 → 3.1.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/.github/workflows/publish.yml +1 -1
- package/engine/session.js +23 -9
- package/engine/session_live.js +235 -152
- package/package.json +1 -1
- package/server-livemix.js +1 -1
- package/spec/engine/playhead_live_spec.js +30 -24
package/engine/session.js
CHANGED
|
@@ -276,7 +276,8 @@ class Session {
|
|
|
276
276
|
debug(`[${this._sessionId}]: FOLLOWER: WARNING! Attempts=0 - Risk of using wrong currentVod`);
|
|
277
277
|
}
|
|
278
278
|
if (!isLeader || vodReloaded) {
|
|
279
|
-
debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod`);
|
|
279
|
+
debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
|
|
280
|
+
await this._sessionState.clearCurrentVodCache();
|
|
280
281
|
return;
|
|
281
282
|
}
|
|
282
283
|
}
|
|
@@ -394,9 +395,20 @@ class Session {
|
|
|
394
395
|
if (!this._sessionState) {
|
|
395
396
|
throw new Error('Session not ready');
|
|
396
397
|
}
|
|
397
|
-
const sessionState = await this._sessionState.getValues(["discSeq"]);
|
|
398
|
+
const sessionState = await this._sessionState.getValues(["discSeq", "vodMediaSeqVideo"]);
|
|
398
399
|
const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
399
|
-
|
|
400
|
+
|
|
401
|
+
/*
|
|
402
|
+
Since sessionState is always newer. If it is a smaller value, then this signals that
|
|
403
|
+
a new vod has been loaded but playheadState has yet to get the new mseq value in 'incrementAsync()'.
|
|
404
|
+
Without this, we risk returning an m3u8 with an old mseq on a new vod to the client.
|
|
405
|
+
*/
|
|
406
|
+
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
|
|
407
|
+
playheadState.vodMediaSeqVideo = sessionState.vodMediaSeqVideo;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (playheadState.vodMediaSeqVideo === 0 || this.waitingForNextVod) {
|
|
411
|
+
this.waitingForNextVod = false;
|
|
400
412
|
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
401
413
|
if (!isLeader) {
|
|
402
414
|
debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
@@ -406,6 +418,7 @@ class Session {
|
|
|
406
418
|
const currentVod = await this._sessionState.getCurrentVod();
|
|
407
419
|
if (currentVod) {
|
|
408
420
|
try {
|
|
421
|
+
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
409
422
|
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
410
423
|
debug(`[${this._sessionId}]: [${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}][${sessionState.discSeq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
411
424
|
return m3u8;
|
|
@@ -726,10 +739,11 @@ class Session {
|
|
|
726
739
|
length = currentVod.getLiveMediaSequencesCount();
|
|
727
740
|
lastDiscontinuity = currentVod.getLastDiscontinuity();
|
|
728
741
|
}
|
|
742
|
+
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
743
|
+
await this._sessionState.setCurrentVod(slateVod);
|
|
744
|
+
|
|
729
745
|
await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
730
746
|
await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
731
|
-
await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
|
|
732
|
-
await this._sessionState.setCurrentVod(slateVod);
|
|
733
747
|
await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
|
|
734
748
|
await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
|
|
735
749
|
await this._sessionState.set("slateCount", sessionState.slateCount + 1);
|
|
@@ -813,10 +827,6 @@ class Session {
|
|
|
813
827
|
debug(`[${this._sessionId}]: ${currentVod.getDeltaTimes()}`);
|
|
814
828
|
debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
|
|
815
829
|
//debug(newVod);
|
|
816
|
-
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
|
|
817
|
-
sessionState.discSeq = await this._sessionState.set("discSeq", 0);
|
|
818
|
-
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
819
|
-
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
820
830
|
await this._playheadState.set("playheadRef", Date.now());
|
|
821
831
|
this.produceEvent({
|
|
822
832
|
type: 'NOW_PLAYING',
|
|
@@ -827,6 +837,10 @@ class Session {
|
|
|
827
837
|
});
|
|
828
838
|
sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
|
|
829
839
|
sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
|
|
840
|
+
sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
|
|
841
|
+
sessionState.discSeq = await this._sessionState.set("discSeq", 0);
|
|
842
|
+
sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
|
|
843
|
+
sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
|
|
830
844
|
await this._sessionState.remove("nextVod");
|
|
831
845
|
return;
|
|
832
846
|
} else {
|
package/engine/session_live.js
CHANGED
|
@@ -15,6 +15,7 @@ const daterangeAttribute = (key, attr) => {
|
|
|
15
15
|
return key.toUpperCase() + "=" + `"${attr}"`;
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
+
const TARGET_PLAYLIST_DURATION_SEC = 60;
|
|
18
19
|
const RESET_DELAY = 5000;
|
|
19
20
|
const FAIL_TIMEOUT = 4000;
|
|
20
21
|
const PlayheadState = Object.freeze({
|
|
@@ -30,7 +31,9 @@ class SessionLive {
|
|
|
30
31
|
this.sessionLiveStateStore = sessionLiveStore.sessionLiveStateStore;
|
|
31
32
|
this.instanceId = sessionLiveStore.instanceId;
|
|
32
33
|
this.mediaSeqCount = 0;
|
|
34
|
+
this.prevMediaSeqCount = 0;
|
|
33
35
|
this.discSeqCount = 0;
|
|
36
|
+
this.prevDiscSeqCount = 0;
|
|
34
37
|
this.targetDuration = 0;
|
|
35
38
|
this.masterManifestUri = null;
|
|
36
39
|
this.vodSegments = {};
|
|
@@ -106,6 +109,7 @@ class SessionLive {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
this.mediaSeqCount = 0;
|
|
112
|
+
this.prevMediaSeqCount = 0;
|
|
109
113
|
this.discSeqCount = 0;
|
|
110
114
|
this.targetDuration = 0;
|
|
111
115
|
this.masterManifestUri = null;
|
|
@@ -136,7 +140,7 @@ class SessionLive {
|
|
|
136
140
|
if (!this.masterManifestUri) {
|
|
137
141
|
await timer(3000);
|
|
138
142
|
continue;
|
|
139
|
-
}
|
|
143
|
+
}
|
|
140
144
|
if (this.playheadState === PlayheadState.STOPPED) {
|
|
141
145
|
debug(`[${this.sessionId}]: Playhead has Stopped, clearing local session and store.`);
|
|
142
146
|
this.waitForPlayhead = false;
|
|
@@ -208,7 +212,7 @@ class SessionLive {
|
|
|
208
212
|
while (!this.masterManifestUri && attempts > 0) {
|
|
209
213
|
attempts--;
|
|
210
214
|
try {
|
|
211
|
-
debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`)
|
|
215
|
+
debug(`[${this.instanceId}][${this.sessionId}]: Going to fetch Live Master Manifest!`);
|
|
212
216
|
// Load & Parse all Media Manifest URIs from Master
|
|
213
217
|
await this._loadMasterManifest(masterManifestUri);
|
|
214
218
|
this.masterManifestUri = masterManifestUri;
|
|
@@ -303,7 +307,7 @@ class SessionLive {
|
|
|
303
307
|
const leadersMediaSeqCount = liveCounts.mediaSeqCount;
|
|
304
308
|
const leadersDiscSeqCount = liveCounts.discSeqCount;
|
|
305
309
|
if (leadersMediaSeqCount !== null) {
|
|
306
|
-
this.mediaSeqCount = leadersMediaSeqCount
|
|
310
|
+
this.mediaSeqCount = leadersMediaSeqCount;
|
|
307
311
|
debug(`[${this.sessionId}]: Setting mediaSeqCount to: [${this.mediaSeqCount}]`);
|
|
308
312
|
}
|
|
309
313
|
if (leadersDiscSeqCount !== null) {
|
|
@@ -342,7 +346,7 @@ class SessionLive {
|
|
|
342
346
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
343
347
|
|
|
344
348
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
345
|
-
const vodTargetBandwidth = this.
|
|
349
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
346
350
|
|
|
347
351
|
// Remove segments and disc-tag if they are on top
|
|
348
352
|
if (this.vodSegments[vodTargetBandwidth].length > 0 && this.vodSegments[vodTargetBandwidth][0].discontinuity) {
|
|
@@ -390,7 +394,7 @@ class SessionLive {
|
|
|
390
394
|
// Generate manifest to give to client
|
|
391
395
|
async getCurrentMediaManifestAsync(bw) {
|
|
392
396
|
if (!this.sessionLiveState) {
|
|
393
|
-
throw new Error(
|
|
397
|
+
throw new Error("SessionLive not ready");
|
|
394
398
|
}
|
|
395
399
|
if (bw === null) {
|
|
396
400
|
debug(`[${this.sessionId}]: No bandwidth provided`);
|
|
@@ -481,60 +485,28 @@ class SessionLive {
|
|
|
481
485
|
// FOLLOWER only function
|
|
482
486
|
_updateLiveSegQueue() {
|
|
483
487
|
if (Object.keys(this.liveSegsForFollowers).length === 0) {
|
|
484
|
-
debug(`[${this.sessionId}]: Error No Segments found at all.`);
|
|
488
|
+
debug(`[${this.sessionId}]: FOLLOWER: Error No Segments found at all.`);
|
|
485
489
|
}
|
|
486
|
-
const vodBws = Object.keys(this.vodSegments);
|
|
487
490
|
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
488
491
|
const size = this.liveSegsForFollowers[liveBws[0]].length;
|
|
489
492
|
|
|
490
|
-
|
|
491
|
-
this.liveSegsForFollowers[liveBws[0]].map((seg) => {if (seg.uri) { newSegCount++ }});
|
|
492
|
-
|
|
493
|
-
debug(`[${this.sessionId}]: FOLLOWER: liveSegsForFollowers Bws=${Object.keys(this.liveSegsForFollowers)}_newSegCount=${newSegCount} `);
|
|
494
|
-
// Remove transitional segs & add live source segs collected from store
|
|
495
|
-
for (let k = 0; k < newSegCount; k++) {
|
|
496
|
-
let incrementDiscSeqCount = false;
|
|
497
|
-
// Shift the top vod segment on all variants
|
|
498
|
-
for (let i = 0; i < vodBws.length; i++) {
|
|
499
|
-
let seg = this.vodSegments[vodBws[i]].shift();
|
|
500
|
-
if (seg && seg.discontinuity) {
|
|
501
|
-
incrementDiscSeqCount = true;
|
|
502
|
-
this.vodSegments[vodBws[i]].shift();
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (incrementDiscSeqCount) {
|
|
506
|
-
this.discSeqCount++;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
// Push to bottom, new live source segment on all variants
|
|
493
|
+
// Push the New Live Segments to All Variants
|
|
510
494
|
for (let segIdx = 0; segIdx < size; segIdx++) {
|
|
511
|
-
let
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (!this.liveSegQueue[
|
|
515
|
-
this.liveSegQueue[
|
|
516
|
-
}
|
|
517
|
-
this.liveSegQueue[bw].push(liveSegFromLeader);
|
|
518
|
-
|
|
519
|
-
let segCount = 0;
|
|
520
|
-
this.liveSegQueue[bw].map((seg) => {if (seg.uri) { segCount++ }});
|
|
521
|
-
if (segCount > this.targetNumSeg) {
|
|
522
|
-
let seg = this.liveSegQueue[bw].shift();
|
|
523
|
-
if (seg && seg.discontinuity) {
|
|
524
|
-
incrementDiscSeqCount = true;
|
|
525
|
-
seg = this.liveSegQueue[bw].shift();
|
|
526
|
-
}
|
|
495
|
+
for (let i = 0; i < liveBws.length; i++) {
|
|
496
|
+
const liveBw = liveBws[i];
|
|
497
|
+
const liveSegFromLeader = this.liveSegsForFollowers[liveBw][segIdx];
|
|
498
|
+
if (!this.liveSegQueue[liveBw]) {
|
|
499
|
+
this.liveSegQueue[liveBw] = [];
|
|
527
500
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (incrementDiscSeqCount) {
|
|
531
|
-
this.discSeqCount++;
|
|
501
|
+
this.liveSegQueue[liveBw].push(liveSegFromLeader);
|
|
502
|
+
debug(`[${this.sessionId}]: FOLLOWER: Pushed segment (${liveSegFromLeader.uri ? liveSegFromLeader.uri : "Disc-tag"}) to 'liveSegQueue' (${liveBw})`);
|
|
532
503
|
}
|
|
533
504
|
}
|
|
534
|
-
|
|
535
|
-
this.
|
|
536
|
-
|
|
537
|
-
|
|
505
|
+
// Remove older segments and update counts
|
|
506
|
+
const newTotalDuration = this._incrementAndShift("FOLLOWER");
|
|
507
|
+
if (newTotalDuration) {
|
|
508
|
+
debug(`[${this.sessionId}]: FOLLOWER: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
509
|
+
}
|
|
538
510
|
}
|
|
539
511
|
|
|
540
512
|
/**
|
|
@@ -651,7 +623,7 @@ class SessionLive {
|
|
|
651
623
|
}
|
|
652
624
|
|
|
653
625
|
// Handle if any promise got rejected
|
|
654
|
-
if (manifestList.some(result => result.status === "rejected")) {
|
|
626
|
+
if (manifestList.some((result) => result.status === "rejected")) {
|
|
655
627
|
debug(`[${this.sessionId}]: ALERT! Promises I: Failed, Rejection Found! Trying again...`);
|
|
656
628
|
continue;
|
|
657
629
|
}
|
|
@@ -663,16 +635,21 @@ class SessionLive {
|
|
|
663
635
|
return item.value.mediaSeq;
|
|
664
636
|
});
|
|
665
637
|
|
|
666
|
-
|
|
667
638
|
// Handle if mediaSeqCounts are NOT synced up!
|
|
668
639
|
if (!allMediaSeqCounts.every((val, i, arr) => val === arr[0])) {
|
|
669
|
-
|
|
670
640
|
debug(`[${this.sessionId}]: Live Mseq counts=[${allMediaSeqCounts}]`);
|
|
671
641
|
// Decrement fetch counter
|
|
672
642
|
FETCH_ATTEMPTS--;
|
|
643
|
+
// Calculate retry delay time. Default=1500
|
|
644
|
+
let retryDelayMs = 1500;
|
|
645
|
+
if (Object.keys(this.liveSegQueue).length > 0) {
|
|
646
|
+
const firstBw = Object.keys(this.liveSegQueue)[0];
|
|
647
|
+
const lastIdx = this.liveSegQueue[firstBw].length - 1;
|
|
648
|
+
retryDelayMs = this.liveSegQueue[firstBw][lastIdx].duration * 1000 * 0.25;
|
|
649
|
+
}
|
|
673
650
|
// Wait a little before trying again
|
|
674
|
-
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after
|
|
675
|
-
await timer(
|
|
651
|
+
debug(`[${this.sessionId}]: ALERT! Live Source Data NOT in sync! Will try again after ${retryDelayMs}ms`);
|
|
652
|
+
await timer(retryDelayMs);
|
|
676
653
|
this.timerCompensation = false;
|
|
677
654
|
continue;
|
|
678
655
|
}
|
|
@@ -681,7 +658,7 @@ class SessionLive {
|
|
|
681
658
|
let leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
|
|
682
659
|
let tries = 20;
|
|
683
660
|
|
|
684
|
-
while (!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0 ||
|
|
661
|
+
while ((!leadersFirstSeqCounts.liveSourceMseqCount && tries > 0) || leadersFirstSeqCounts.liveSourceMseqCount === 0) {
|
|
685
662
|
debug(`[${this.sessionId}]: NEW FOLLOWER: Waiting for LEADER to add 'firstCounts' in store! Will look again after 1000ms (tries left=${tries})`);
|
|
686
663
|
await timer(1000);
|
|
687
664
|
leadersFirstSeqCounts = await this.sessionLiveState.get("firstCounts");
|
|
@@ -705,8 +682,9 @@ class SessionLive {
|
|
|
705
682
|
|
|
706
683
|
// Respawners never do this, only starter followers.
|
|
707
684
|
// Edge Case: FOLLOWER transitioned from session with different segments from LEADER
|
|
708
|
-
|
|
709
|
-
|
|
685
|
+
const leaderStartedOnCount = leadersFirstSeqCounts.mediaSeqCount;
|
|
686
|
+
if (leaderStartedOnCount !== this.mediaSeqCount) {
|
|
687
|
+
this.mediaSeqCount = leaderStartedOnCount;
|
|
710
688
|
const transitSegs = await this.sessionLiveState.get("transitSegs");
|
|
711
689
|
if (!this._isEmpty(transitSegs)) {
|
|
712
690
|
this.vodSegments = transitSegs;
|
|
@@ -722,12 +700,12 @@ class SessionLive {
|
|
|
722
700
|
}
|
|
723
701
|
|
|
724
702
|
// Prepare to load segments...
|
|
725
|
-
debug(`[${this.instanceId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
703
|
+
debug(`[${this.instanceId}][${this.sessionId}]: Newest mseq from LIVE=${allMediaSeqCounts[0]} First mseq in store=${leadersFirstSeqCounts.liveSourceMseqCount}`);
|
|
726
704
|
if (allMediaSeqCounts[0] === leadersFirstSeqCounts.liveSourceMseqCount) {
|
|
727
705
|
this.pushAmount = 1; // Follower from start
|
|
728
706
|
} else {
|
|
729
707
|
// RESPAWNED NODES
|
|
730
|
-
this.pushAmount =
|
|
708
|
+
this.pushAmount = allMediaSeqCounts[0] - leadersFirstSeqCounts.liveSourceMseqCount + 1;
|
|
731
709
|
|
|
732
710
|
const transitSegs = await this.sessionLiveState.get("transitSegs");
|
|
733
711
|
//debug(`[${this.sessionId}]: NEW FOLLOWER: I tried to get 'transitSegs'. This is what I found ${JSON.stringify(transitSegs)}`);
|
|
@@ -760,7 +738,7 @@ class SessionLive {
|
|
|
760
738
|
}
|
|
761
739
|
|
|
762
740
|
if (FETCH_ATTEMPTS === 0) {
|
|
763
|
-
debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`)
|
|
741
|
+
debug(`[${this.sessionId}]: Fetching from Live-Source did not work! Returning to Playhead Loop...`);
|
|
764
742
|
return;
|
|
765
743
|
}
|
|
766
744
|
|
|
@@ -781,8 +759,7 @@ class SessionLive {
|
|
|
781
759
|
this.firstTime = false;
|
|
782
760
|
debug(`[${this.sessionId}]: Got all needed segments from live-source (read from store).\nWe are now able to build Live Manifest: [${this.mediaSeqCount}]`);
|
|
783
761
|
return;
|
|
784
|
-
}
|
|
785
|
-
else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
|
|
762
|
+
} else if (leadersCurrentMseqRaw < this.lastRequestedMediaSeqRaw) {
|
|
786
763
|
// WE ARE A RESPAWN-NODE, and we are ahead of leader.
|
|
787
764
|
this.blockGenerateManifest = true;
|
|
788
765
|
}
|
|
@@ -793,34 +770,34 @@ class SessionLive {
|
|
|
793
770
|
let pushPromises = [];
|
|
794
771
|
for (let i = 0; i < Object.keys(this.mediaManifestURIs).length; i++) {
|
|
795
772
|
let bw = Object.keys(this.mediaManifestURIs)[i];
|
|
773
|
+
// will add new segments to live seg queue
|
|
796
774
|
pushPromises.push(this._parseMediaManifest(this.liveSourceM3Us[bw].M3U, this.mediaManifestURIs[bw], bw));
|
|
797
775
|
debug(`[${this.sessionId}]: Pushed pushPromise for bw=${bw}`);
|
|
798
776
|
}
|
|
799
777
|
// Segment Pushing
|
|
800
778
|
debug(`[${this.sessionId}]: Executing Promises II: Segment Pushing`);
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
});
|
|
808
|
-
if (removedDiscSeqList.every((val, i, arr) => val === arr[0])) {
|
|
809
|
-
// if all variants removed equal amounts of disc-tags
|
|
810
|
-
this.discSeqCount += removedDiscSeqList[0];
|
|
779
|
+
await allSettled(pushPromises);
|
|
780
|
+
|
|
781
|
+
// UPDATE COUNTS, & Shift Segments in vodSegments and liveSegQueue if needed.
|
|
782
|
+
const newTotalDuration = this._incrementAndShift("LEADER");
|
|
783
|
+
if (newTotalDuration) {
|
|
784
|
+
debug(`[${this.sessionId}]: New Adjusted Playlist Duration=${newTotalDuration}s`);
|
|
811
785
|
}
|
|
812
|
-
// UPDATE COUNTS, & Shift Segments in vodSegments
|
|
813
|
-
await this._incrementAndShift();
|
|
814
786
|
}
|
|
815
787
|
|
|
816
788
|
// -----------------------------------------------------
|
|
817
789
|
// Leader writes to store so that Followers can read.
|
|
818
790
|
// -----------------------------------------------------
|
|
819
791
|
if (isLeader) {
|
|
820
|
-
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
821
792
|
if (this.allowedToSet) {
|
|
822
|
-
|
|
823
|
-
|
|
793
|
+
const liveBws = Object.keys(this.liveSegsForFollowers);
|
|
794
|
+
const segListSize = this.liveSegsForFollowers[liveBws[0]].length;
|
|
795
|
+
// Do not replace old data with empty data
|
|
796
|
+
if (segListSize > 0) {
|
|
797
|
+
debug(`[${this.sessionId}]: LEADER: Adding data to store!`);
|
|
798
|
+
await this.sessionLiveState.set("liveSegsForFollowers", this.liveSegsForFollowers);
|
|
799
|
+
await this.sessionLiveState.set("lastRequestedMediaSeqRaw", this.lastRequestedMediaSeqRaw);
|
|
800
|
+
}
|
|
824
801
|
}
|
|
825
802
|
|
|
826
803
|
// [LASTLY]: LEADER does this for respawned-FOLLOWERS' sake.
|
|
@@ -829,8 +806,8 @@ class SessionLive {
|
|
|
829
806
|
await timer(1000); // maybe remove
|
|
830
807
|
const firstCounts = {
|
|
831
808
|
liveSourceMseqCount: this.lastRequestedMediaSeqRaw,
|
|
832
|
-
mediaSeqCount: this.
|
|
833
|
-
discSeqCount: this.
|
|
809
|
+
mediaSeqCount: this.prevMediaSeqCount,
|
|
810
|
+
discSeqCount: this.prevDiscSeqCount,
|
|
834
811
|
};
|
|
835
812
|
debug(`[${this.sessionId}]: LEADER: I am adding 'firstCounts'=${JSON.stringify(firstCounts)} to Store for future followers`);
|
|
836
813
|
await this.sessionLiveState.set("firstCounts", firstCounts);
|
|
@@ -846,39 +823,148 @@ class SessionLive {
|
|
|
846
823
|
return;
|
|
847
824
|
}
|
|
848
825
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
826
|
+
_shiftSegments(opt) {
|
|
827
|
+
let _totalDur = 0;
|
|
828
|
+
let _segments = {};
|
|
829
|
+
let _name = "";
|
|
830
|
+
let _removedSegments = 0;
|
|
831
|
+
let _removedDiscontinuities = 0;
|
|
832
|
+
|
|
833
|
+
if (opt && opt.totalDur) {
|
|
834
|
+
_totalDur = opt.totalDur;
|
|
835
|
+
}
|
|
836
|
+
if (opt && opt.segments) {
|
|
837
|
+
_segments = JSON.parse(JSON.stringify(opt.segments)); // clone it
|
|
838
|
+
}
|
|
839
|
+
if (opt && opt.name) {
|
|
840
|
+
_name = opt.name || "NONE";
|
|
841
|
+
}
|
|
842
|
+
if (opt && opt.removedSegments) {
|
|
843
|
+
_removedSegments = opt.removedSegments;
|
|
844
|
+
}
|
|
845
|
+
if (opt && opt.removedDiscontinuities) {
|
|
846
|
+
_removedDiscontinuities = opt.removedDiscontinuities;
|
|
847
|
+
}
|
|
848
|
+
const bws = Object.keys(_segments);
|
|
849
|
+
|
|
850
|
+
/* When Total Duration is past the Limit, start Shifting V2L|LIVE segments if found */
|
|
851
|
+
while (_totalDur > TARGET_PLAYLIST_DURATION_SEC) {
|
|
852
|
+
// Skip loop if there are no more segments to remove...
|
|
853
|
+
if (_segments[bws[0]].length === 0) {
|
|
854
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
855
|
+
}
|
|
856
|
+
debug(`[${this.sessionId}]: ${_name}: (${_totalDur})s/(${TARGET_PLAYLIST_DURATION_SEC})s - Playlist Duration is Over the Target. Shift needed!`);
|
|
857
|
+
let timeToRemove = 0;
|
|
852
858
|
let incrementDiscSeqCount = false;
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
859
|
+
|
|
860
|
+
// Shift Segments for each variant...
|
|
861
|
+
for (let i = 0; i < bws.length; i++) {
|
|
862
|
+
let seg = _segments[bws[i]].shift();
|
|
863
|
+
if (i === 0) {
|
|
864
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
865
|
+
}
|
|
866
|
+
if (seg && seg.discontinuity) {
|
|
867
|
+
incrementDiscSeqCount = true;
|
|
868
|
+
if (_segments[bws[i]].length > 0) {
|
|
869
|
+
seg = _segments[bws[i]].shift();
|
|
870
|
+
if (i === 0) {
|
|
871
|
+
debug(`[${this.sessionId}]: ${_name}: (${bws[i]}) Ejected from playlist->: ${JSON.stringify(seg, null, 2)}`);
|
|
872
|
+
}
|
|
860
873
|
}
|
|
861
874
|
}
|
|
875
|
+
if (seg && seg.duration) {
|
|
876
|
+
timeToRemove = seg.duration;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (timeToRemove) {
|
|
880
|
+
_totalDur -= timeToRemove;
|
|
881
|
+
// Increment number of removed segments...
|
|
882
|
+
_removedSegments++;
|
|
862
883
|
}
|
|
863
884
|
if (incrementDiscSeqCount) {
|
|
864
|
-
|
|
885
|
+
// Update Session Live Discontinuity Sequence Count
|
|
886
|
+
_removedDiscontinuities++;
|
|
865
887
|
}
|
|
866
|
-
this.mediaSeqCount++;
|
|
867
888
|
}
|
|
889
|
+
return { totalDuration: _totalDur, removedSegments: _removedSegments, removedDiscontinuities: _removedDiscontinuities, shiftedSegments: _segments };
|
|
890
|
+
}
|
|
868
891
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
892
|
+
/**
|
|
893
|
+
* Shifts V2L or LIVE items if total segment duration (V2L+LIVE) are over the target duration.
|
|
894
|
+
* It will also update and increment SessionLive's MediaSeqCount and DiscSeqCount based
|
|
895
|
+
* on what was shifted.
|
|
896
|
+
* @param {string} instanceName Name of instance "LEADER" | "FOLLOWER"
|
|
897
|
+
* @returns {number} The new total duration in seconds
|
|
898
|
+
*/
|
|
899
|
+
_incrementAndShift(instanceName) {
|
|
900
|
+
if (!instanceName) {
|
|
901
|
+
instanceName = "UNKNOWN";
|
|
902
|
+
}
|
|
903
|
+
const vodBws = Object.keys(this.vodSegments);
|
|
904
|
+
const liveBws = Object.keys(this.liveSegQueue);
|
|
905
|
+
let vodTotalDur = 0;
|
|
906
|
+
let liveTotalDur = 0;
|
|
907
|
+
let totalDur = 0;
|
|
908
|
+
let removedSegments = 0;
|
|
909
|
+
let removedDiscontinuities = 0;
|
|
910
|
+
|
|
911
|
+
// Calculate Playlist Total Duration
|
|
912
|
+
this.vodSegments[vodBws[0]].forEach((seg) => {
|
|
913
|
+
if (seg.duration) {
|
|
914
|
+
vodTotalDur += seg.duration;
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
this.liveSegQueue[liveBws[0]].forEach((seg) => {
|
|
918
|
+
if (seg.duration) {
|
|
919
|
+
liveTotalDur += seg.duration;
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
totalDur = vodTotalDur + liveTotalDur;
|
|
923
|
+
debug(`[${this.sessionId}]: ${instanceName}: L2L dur->: ${liveTotalDur}s | V2L dur->: ${vodTotalDur}s | Total dur->: ${totalDur}s`);
|
|
924
|
+
|
|
925
|
+
/** --- SHIFT then INCREMENT --- **/
|
|
926
|
+
|
|
927
|
+
// Shift V2L Segments
|
|
928
|
+
const outputV2L = this._shiftSegments({
|
|
929
|
+
name: instanceName,
|
|
930
|
+
totalDur: totalDur,
|
|
931
|
+
segments: this.vodSegments,
|
|
932
|
+
removedSegments: removedSegments,
|
|
933
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
934
|
+
});
|
|
935
|
+
// Update V2L Segments
|
|
936
|
+
this.vodSegments = outputV2L.shiftedSegments;
|
|
937
|
+
// Update values
|
|
938
|
+
totalDur = outputV2L.totalDuration;
|
|
939
|
+
removedSegments = outputV2L.removedSegments;
|
|
940
|
+
removedDiscontinuities = outputV2L.removedDiscontinuities;
|
|
941
|
+
// Shift LIVE Segments
|
|
942
|
+
const outputLIVE = this._shiftSegments({
|
|
943
|
+
name: instanceName,
|
|
944
|
+
totalDur: totalDur,
|
|
945
|
+
segments: this.liveSegQueue,
|
|
946
|
+
removedSegments: removedSegments,
|
|
947
|
+
removedDiscontinuities: removedDiscontinuities,
|
|
948
|
+
});
|
|
949
|
+
// Update LIVE Segments
|
|
950
|
+
this.liveSegQueue = outputLIVE.shiftedSegments;
|
|
951
|
+
// Update values
|
|
952
|
+
totalDur = outputLIVE.totalDuration;
|
|
953
|
+
removedSegments = outputLIVE.removedSegments;
|
|
954
|
+
removedDiscontinuities = outputLIVE.removedDiscontinuities;
|
|
955
|
+
|
|
956
|
+
// Update Session Live Discontinuity Sequence Count...
|
|
957
|
+
this.prevDiscSeqCount = this.discSeqCount;
|
|
958
|
+
this.discSeqCount += removedDiscontinuities;
|
|
959
|
+
// Update Session Live Media Sequence Count...
|
|
960
|
+
this.prevMediaSeqCount = this.mediaSeqCount;
|
|
961
|
+
this.mediaSeqCount += removedSegments;
|
|
962
|
+
if (this.discSeqCount !== this.prevDiscSeqCount) {
|
|
963
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Dseq Count from {${this.prevDiscSeqCount}} -> {${this.discSeqCount}}`);
|
|
964
|
+
}
|
|
965
|
+
debug(`[${this.sessionId}]: ${instanceName}: Incrementing Mseq Count from [${this.prevMediaSeqCount}] -> [${this.mediaSeqCount}]`);
|
|
966
|
+
debug(`[${this.sessionId}]: ${instanceName}: Finished updating all Counts and Segment Queues!`);
|
|
967
|
+
return totalDur;
|
|
882
968
|
}
|
|
883
969
|
|
|
884
970
|
async _loadMediaManifest(bw) {
|
|
@@ -945,26 +1031,17 @@ class SessionLive {
|
|
|
945
1031
|
//debug(`[${this.sessionId}]: Current RAW Mseq: [${m3u.get("mediaSequence")}]`);
|
|
946
1032
|
//debug(`[${this.sessionId}]: Previous RAW Mseq: [${this.lastRequestedMediaSeqRaw}]`);
|
|
947
1033
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
// this.targetNumSeg = m3u.items.PlaylistItem.length;
|
|
952
|
-
// }
|
|
953
|
-
|
|
954
|
-
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
1034
|
+
if (this.pushAmount >= 0) {
|
|
1035
|
+
this.lastRequestedMediaSeqRaw = m3u.get("mediaSequence");
|
|
1036
|
+
}
|
|
955
1037
|
this.targetDuration = m3u.get("targetDuration");
|
|
956
|
-
let amountRemovedDiscSeqs = 0;
|
|
957
1038
|
let startIdx = m3u.items.PlaylistItem.length - this.pushAmount;
|
|
958
1039
|
startIdx = startIdx < 0 ? 0 : startIdx;
|
|
959
1040
|
if (mediaManifestUri) {
|
|
960
1041
|
// push segments
|
|
961
|
-
|
|
1042
|
+
this._addLiveSegmentsToQueue(startIdx, m3u.items.PlaylistItem, baseUrl, liveTargetBandwidth);
|
|
962
1043
|
}
|
|
963
|
-
|
|
964
|
-
removedDiscSeqs: amountRemovedDiscSeqs,
|
|
965
|
-
bw: liveTargetBandwidth
|
|
966
|
-
}
|
|
967
|
-
resolve(resolveObj);
|
|
1044
|
+
resolve();
|
|
968
1045
|
} catch (exc) {
|
|
969
1046
|
console.error("ERROR: " + exc);
|
|
970
1047
|
reject(exc);
|
|
@@ -972,8 +1049,15 @@ class SessionLive {
|
|
|
972
1049
|
});
|
|
973
1050
|
}
|
|
974
1051
|
|
|
1052
|
+
/**
|
|
1053
|
+
* Collects 'new' PlaylistItems and converts them into custom SegmentItems,
|
|
1054
|
+
* then Pushes them to the LiveSegQueue for all variants.
|
|
1055
|
+
* @param {number} startIdx
|
|
1056
|
+
* @param {m3u8.Item.PlaylistItem} playlistItems
|
|
1057
|
+
* @param {string} baseUrl
|
|
1058
|
+
* @param {string} liveTargetBandwidth
|
|
1059
|
+
*/
|
|
975
1060
|
_addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
|
|
976
|
-
let amountRemovedDiscSeqs = 0;
|
|
977
1061
|
for (let i = startIdx; i < playlistItems.length; i++) {
|
|
978
1062
|
debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
|
|
979
1063
|
let seg = {};
|
|
@@ -998,7 +1082,7 @@ class SessionLive {
|
|
|
998
1082
|
}
|
|
999
1083
|
cueData["out"] = true;
|
|
1000
1084
|
cueData["duration"] = attributes["cueout"];
|
|
1001
|
-
|
|
1085
|
+
}
|
|
1002
1086
|
if ("cuecont" in attributes) {
|
|
1003
1087
|
if (!cueData) {
|
|
1004
1088
|
cueData = {};
|
|
@@ -1042,26 +1126,11 @@ class SessionLive {
|
|
|
1042
1126
|
if (daterangeData) {
|
|
1043
1127
|
seg["daterange"] = daterangeData;
|
|
1044
1128
|
}
|
|
1045
|
-
|
|
1129
|
+
// Push new Live Segments!
|
|
1046
1130
|
this.liveSegQueue[liveTargetBandwidth].push(seg);
|
|
1047
|
-
this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
|
|
1048
|
-
let segCount = 0;
|
|
1049
|
-
this.liveSegQueue[liveTargetBandwidth].map((seg) => {if (seg.uri) { segCount++ }});
|
|
1050
|
-
debug(`[${this.sessionId}]: size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
|
|
1051
|
-
if (segCount > this.targetNumSeg) {
|
|
1052
|
-
debug(`[${this.sessionId}]: liveTargetBandwidth=${liveTargetBandwidth}_seg=${JSON.stringify(seg,null,2)}`);
|
|
1053
|
-
seg = this.liveSegQueue[liveTargetBandwidth].shift();
|
|
1054
|
-
if (seg) {
|
|
1055
|
-
if (seg.discontinuity) {
|
|
1056
|
-
this.liveSegQueue[liveTargetBandwidth].shift();
|
|
1057
|
-
amountRemovedDiscSeqs++;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
debug(`[${this.sessionId}]: After Shifting -> size of queue=${segCount}_targetNumseg=${this.targetNumSeg}`);
|
|
1061
|
-
}
|
|
1131
|
+
this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
|
|
1062
1132
|
}
|
|
1063
1133
|
}
|
|
1064
|
-
return amountRemovedDiscSeqs;
|
|
1065
1134
|
}
|
|
1066
1135
|
|
|
1067
1136
|
/*
|
|
@@ -1078,9 +1147,8 @@ class SessionLive {
|
|
|
1078
1147
|
throw new Error("No bandwidth provided");
|
|
1079
1148
|
}
|
|
1080
1149
|
const liveTargetBandwidth = this._findNearestBw(bw, Object.keys(this.mediaManifestURIs));
|
|
1081
|
-
const vodTargetBandwidth = this.
|
|
1082
|
-
debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`)
|
|
1083
|
-
|
|
1150
|
+
const vodTargetBandwidth = this._getNearestBandwidth(bw, Object.keys(this.vodSegments));
|
|
1151
|
+
debug(`[${this.sessionId}]: Client requesting manifest for bw=(${bw}). Nearest LiveBw=(${liveTargetBandwidth})`);
|
|
1084
1152
|
|
|
1085
1153
|
if (this.blockGenerateManifest) {
|
|
1086
1154
|
debug(`[${this.sessionId}]: FOLLOWER: Cannot Generate Manifest! Waiting to sync-up with Leader...`);
|
|
@@ -1154,7 +1222,7 @@ class SessionLive {
|
|
|
1154
1222
|
m3u8 += "#EXT-X-DISCONTINUITY\n";
|
|
1155
1223
|
}
|
|
1156
1224
|
if (seg.cue) {
|
|
1157
|
-
if(seg.cue.out) {
|
|
1225
|
+
if (seg.cue.out) {
|
|
1158
1226
|
if (seg.cue.scteData) {
|
|
1159
1227
|
m3u8 += "#EXT-OATCLS-SCTE35:" + seg.cue.scteData + "\n";
|
|
1160
1228
|
}
|
|
@@ -1166,8 +1234,7 @@ class SessionLive {
|
|
|
1166
1234
|
if (seg.cue.cont) {
|
|
1167
1235
|
if (seg.cue.scteData) {
|
|
1168
1236
|
m3u8 += "#EXT-X-CUE-OUT-CONT:ElapsedTime=" + seg.cue.cont + ",Duration=" + seg.cue.duration + ",SCTE35=" + seg.cue.scteData + "\n";
|
|
1169
|
-
}
|
|
1170
|
-
else {
|
|
1237
|
+
} else {
|
|
1171
1238
|
m3u8 += "#EXT-X-CUE-OUT-CONT:" + seg.cue.cont + "/" + seg.cue.duration + "\n";
|
|
1172
1239
|
}
|
|
1173
1240
|
}
|
|
@@ -1176,14 +1243,16 @@ class SessionLive {
|
|
|
1176
1243
|
m3u8 += `#EXT-X-PROGRAM-DATE-TIME:${seg.datetime}\n`;
|
|
1177
1244
|
}
|
|
1178
1245
|
if (seg.daterange) {
|
|
1179
|
-
const dateRangeAttributes = Object.keys(seg.daterange)
|
|
1180
|
-
|
|
1181
|
-
|
|
1246
|
+
const dateRangeAttributes = Object.keys(seg.daterange)
|
|
1247
|
+
.map((key) => daterangeAttribute(key, seg.daterange[key]))
|
|
1248
|
+
.join(",");
|
|
1249
|
+
if (!seg.datetime && seg.daterange["start-date"]) {
|
|
1250
|
+
m3u8 += "#EXT-X-PROGRAM-DATE-TIME:" + seg.daterange["start-date"] + "\n";
|
|
1182
1251
|
}
|
|
1183
1252
|
m3u8 += "#EXT-X-DATERANGE:" + dateRangeAttributes + "\n";
|
|
1184
1253
|
}
|
|
1185
1254
|
// Mimick logic used in hls-vodtolive
|
|
1186
|
-
if (seg.cue && seg.cue.in){
|
|
1255
|
+
if (seg.cue && seg.cue.in) {
|
|
1187
1256
|
m3u8 += "#EXT-X-CUE-IN" + "\n";
|
|
1188
1257
|
}
|
|
1189
1258
|
if (seg.uri) {
|
|
@@ -1201,6 +1270,20 @@ class SessionLive {
|
|
|
1201
1270
|
});
|
|
1202
1271
|
}
|
|
1203
1272
|
|
|
1273
|
+
_getNearestBandwidth(bandwidthToMatch, array) {
|
|
1274
|
+
const sortedBandwidths = array.sort((a, b) => a - b);
|
|
1275
|
+
const exactMatch = sortedBandwidths.find((a) => a == bandwidthToMatch);
|
|
1276
|
+
if (exactMatch) {
|
|
1277
|
+
return exactMatch;
|
|
1278
|
+
}
|
|
1279
|
+
for (let i = 0; i < sortedBandwidths.length; i++) {
|
|
1280
|
+
if (Number(bandwidthToMatch) <= Number(sortedBandwidths[i])) {
|
|
1281
|
+
return sortedBandwidths[i];
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return sortedBandwidths[sortedBandwidths.length - 1];
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1204
1287
|
_getFirstBwWithSegmentsInList(allSegments) {
|
|
1205
1288
|
const bandwidths = Object.keys(allSegments);
|
|
1206
1289
|
for (let i = 0; i < bandwidths.length; i++) {
|
|
@@ -1234,13 +1317,13 @@ class SessionLive {
|
|
|
1234
1317
|
const profiles = this.sessionLiveProfile;
|
|
1235
1318
|
const toKeep = new Set();
|
|
1236
1319
|
let newItem = {};
|
|
1237
|
-
profiles.forEach(profile => {
|
|
1238
|
-
let bwToKeep = this.
|
|
1320
|
+
profiles.forEach((profile) => {
|
|
1321
|
+
let bwToKeep = this._getNearestBandwidth(profile.bw, Object.keys(this.mediaManifestURIs));
|
|
1239
1322
|
toKeep.add(bwToKeep);
|
|
1240
1323
|
});
|
|
1241
1324
|
toKeep.forEach((bw) => {
|
|
1242
1325
|
newItem[bw] = this.mediaManifestURIs[bw];
|
|
1243
|
-
})
|
|
1326
|
+
});
|
|
1244
1327
|
this.mediaManifestURIs = newItem;
|
|
1245
1328
|
}
|
|
1246
1329
|
|
package/package.json
CHANGED
package/server-livemix.js
CHANGED
|
@@ -155,7 +155,7 @@ class StreamSwitchManager {
|
|
|
155
155
|
type: StreamType.LIVE,
|
|
156
156
|
start_time: startOffset,
|
|
157
157
|
end_time: endTime,
|
|
158
|
-
uri: "https://
|
|
158
|
+
uri: "https://d2fz24s2fts31b.cloudfront.net/out/v1/2e5b17b7dd0548c7b844eb1b311b6434/manifest.m3u8",
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
eventId: this.generateID(),
|
|
@@ -202,36 +202,42 @@ segment_0_8.ts
|
|
|
202
202
|
|
|
203
203
|
const mockLiveSegments = {
|
|
204
204
|
180000: [
|
|
205
|
-
{ duration:
|
|
206
|
-
{ duration:
|
|
207
|
-
{ duration:
|
|
208
|
-
{ duration:
|
|
209
|
-
{ duration:
|
|
210
|
-
{ duration:
|
|
211
|
-
{ duration:
|
|
212
|
-
{ duration:
|
|
205
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg09.ts" },
|
|
206
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg10.ts" },
|
|
207
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg11.ts" },
|
|
208
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg12.ts" },
|
|
209
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg13.ts" },
|
|
210
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg14.ts" },
|
|
211
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg15.ts" },
|
|
212
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg16.ts" },
|
|
213
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg17.ts" },
|
|
214
|
+
{ duration: 6, uri: "http://mock.mock.com/180000/seg18.ts" },
|
|
213
215
|
{ discontinuity: true },
|
|
214
216
|
],
|
|
215
217
|
1258000: [
|
|
216
|
-
{ duration:
|
|
217
|
-
{ duration:
|
|
218
|
-
{ duration:
|
|
219
|
-
{ duration:
|
|
220
|
-
{ duration:
|
|
221
|
-
{ duration:
|
|
222
|
-
{ duration:
|
|
223
|
-
{ duration:
|
|
218
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg09.ts" },
|
|
219
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg10.ts" },
|
|
220
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg11.ts" },
|
|
221
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg12.ts" },
|
|
222
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg13.ts" },
|
|
223
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg14.ts" },
|
|
224
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg15.ts" },
|
|
225
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg16.ts" },
|
|
226
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg17.ts" },
|
|
227
|
+
{ duration: 6, uri: "http://mock.mock.com/1258000/seg18.ts" },
|
|
224
228
|
{ discontinuity: true },
|
|
225
229
|
],
|
|
226
230
|
2488000: [
|
|
227
|
-
{ duration:
|
|
228
|
-
{ duration:
|
|
229
|
-
{ duration:
|
|
230
|
-
{ duration:
|
|
231
|
-
{ duration:
|
|
232
|
-
{ duration:
|
|
233
|
-
{ duration:
|
|
234
|
-
{ duration:
|
|
231
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg09.ts" },
|
|
232
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg10.ts" },
|
|
233
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg11.ts" },
|
|
234
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg12.ts" },
|
|
235
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg13.ts" },
|
|
236
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg14.ts" },
|
|
237
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg15.ts" },
|
|
238
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg16.ts" },
|
|
239
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg17.ts" },
|
|
240
|
+
{ duration: 6, uri: "http://mock.mock.com/2488000/seg18.ts" },
|
|
235
241
|
{ discontinuity: true },
|
|
236
242
|
],
|
|
237
243
|
};
|