eyevinn-channel-engine 4.3.8 → 4.3.10
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/dist/engine/session.d.ts +3 -1
- package/dist/engine/session.js +246 -291
- package/dist/examples/demux.js +1 -1
- package/dist/package.json +2 -2
- package/engine/session.js +219 -217
- package/examples/demux.ts +1 -1
- package/package.json +2 -2
package/dist/engine/session.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ declare class Session {
|
|
|
97
97
|
}>;
|
|
98
98
|
getCurrentMediaManifestAsync(bw: any, playbackSessionId: any): Promise<any>;
|
|
99
99
|
getCurrentAudioManifestAsync(audioGroupId: any, audioLanguage: any, playbackSessionId: any): Promise<any>;
|
|
100
|
-
getCurrentSubtitleManifestAsync(subtitleGroupId: any, subtitleLanguage: any): Promise<any>;
|
|
100
|
+
getCurrentSubtitleManifestAsync(subtitleGroupId: any, subtitleLanguage: any, playbackSessionId: any): Promise<any>;
|
|
101
101
|
incrementAsync(): Promise<any>;
|
|
102
102
|
getMediaManifestAsync(bw: any, opts: any): Promise<any>;
|
|
103
103
|
getAudioManifestAsync(audioGroupId: any, audioLanguage: any, opts: any): Promise<any>;
|
|
@@ -128,4 +128,6 @@ declare class Session {
|
|
|
128
128
|
position: number;
|
|
129
129
|
diff: string;
|
|
130
130
|
}>;
|
|
131
|
+
_getM3u8File(variantType: any, variantKey: any, playbackSessionId: any): Promise<any>;
|
|
132
|
+
_getCurrentVodData(dataName: any, variantType: any, vod: any): any;
|
|
131
133
|
}
|
package/dist/engine/session.js
CHANGED
|
@@ -794,300 +794,45 @@ var Session = /** @class */ (function () {
|
|
|
794
794
|
};
|
|
795
795
|
Session.prototype.getCurrentMediaManifestAsync = function (bw, playbackSessionId) {
|
|
796
796
|
return __awaiter(this, void 0, void 0, function () {
|
|
797
|
-
var
|
|
797
|
+
var m3u8;
|
|
798
798
|
return __generator(this, function (_a) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (!this._sessionState) {
|
|
802
|
-
throw new Error('Session not ready');
|
|
803
|
-
}
|
|
804
|
-
tries = 12;
|
|
805
|
-
_a.label = 1;
|
|
806
|
-
case 1:
|
|
807
|
-
if (!(tries > 0 && this.leaderIsSettingNextVod)) return [3 /*break*/, 3];
|
|
808
|
-
debug("[".concat(this._sessionId, "]: Leader is setting the next vod. Waiting 500ms_").concat(tries));
|
|
809
|
-
return [4 /*yield*/, timer(500)];
|
|
810
|
-
case 2:
|
|
811
|
-
_a.sent();
|
|
812
|
-
tries--;
|
|
813
|
-
return [3 /*break*/, 1];
|
|
814
|
-
case 3: return [4 /*yield*/, this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"])];
|
|
815
|
-
case 4:
|
|
816
|
-
sessionState = _a.sent();
|
|
817
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"])];
|
|
818
|
-
case 5:
|
|
819
|
-
playheadState = _a.sent();
|
|
820
|
-
if (this.prevVodMediaSeq.video === null) {
|
|
821
|
-
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
822
|
-
}
|
|
823
|
-
if (this.prevMediaSeqOffset.video === null) {
|
|
824
|
-
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
825
|
-
}
|
|
826
|
-
if (!(playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo || (playheadState.vodMediaSeqVideo < sessionState.vodMediaSeqVideo && playheadState.mediaSeq === this.prevMediaSeqOffset.video))) return [3 /*break*/, 9];
|
|
827
|
-
return [4 /*yield*/, this._sessionState.get("state")];
|
|
828
|
-
case 6:
|
|
829
|
-
state = _a.sent();
|
|
830
|
-
DELAY_TIME_MS = 1000;
|
|
831
|
-
ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
832
|
-
debug("[".concat(this._sessionId, "]: Recently ").concat(ACTION, " Vod. PlayheadState not up-to-date (").concat(playheadState.vodMediaSeqVideo, "_").concat(sessionState.vodMediaSeqVideo, "). Waiting ").concat(DELAY_TIME_MS, "ms before reading from store again"));
|
|
833
|
-
return [4 /*yield*/, timer(DELAY_TIME_MS)];
|
|
834
|
-
case 7:
|
|
835
|
-
_a.sent();
|
|
836
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"])];
|
|
837
|
-
case 8:
|
|
838
|
-
playheadState = _a.sent();
|
|
839
|
-
_a.label = 9;
|
|
840
|
-
case 9:
|
|
841
|
-
if (!(playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video)) return [3 /*break*/, 14];
|
|
842
|
-
debug("[".concat(this._sessionId, "]: current[").concat(playheadState.vodMediaSeqVideo, "]_prev[").concat(this.prevVodMediaSeq.video, "]"));
|
|
843
|
-
debug("[".concat(this._sessionId, "]: current-offset[").concat(playheadState.mediaSeq, "]_prev-offset[").concat(this.prevMediaSeqOffset.video, "]"));
|
|
844
|
-
// If true, then we have not updated the prev-values and not cleared the cache yet.
|
|
845
|
-
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
846
|
-
this.isAllowedToClearVodCache = true;
|
|
847
|
-
}
|
|
848
|
-
return [4 /*yield*/, this._sessionStateStore.isLeader(this._instanceId)];
|
|
849
|
-
case 10:
|
|
850
|
-
isLeader = _a.sent();
|
|
851
|
-
if (!(!isLeader && this.isAllowedToClearVodCache)) return [3 /*break*/, 13];
|
|
852
|
-
debug("[".concat(this._sessionId, "]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD."));
|
|
853
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
854
|
-
case 11:
|
|
855
|
-
_a.sent();
|
|
856
|
-
this.isAllowedToClearVodCache = false;
|
|
857
|
-
return [4 /*yield*/, this._playheadState.get("diffCompensation")];
|
|
858
|
-
case 12:
|
|
859
|
-
diffMs = _a.sent();
|
|
860
|
-
if (diffMs) {
|
|
861
|
-
this.diffCompensation = diffMs;
|
|
862
|
-
debug("[".concat(this._sessionId, "]: Setting diffCompensation->").concat(this.diffCompensation));
|
|
863
|
-
if (this.diffCompensation) {
|
|
864
|
-
this.timePositionOffset = this.diffCompensation;
|
|
865
|
-
cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
|
|
866
|
-
}
|
|
867
|
-
else {
|
|
868
|
-
this.timePositionOffset = 0;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
_a.label = 13;
|
|
872
|
-
case 13: return [3 /*break*/, 15];
|
|
873
|
-
case 14:
|
|
874
|
-
this.isAllowedToClearVodCache = true;
|
|
875
|
-
_a.label = 15;
|
|
876
|
-
case 15: return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
877
|
-
case 16:
|
|
878
|
-
currentVod = _a.sent();
|
|
879
|
-
if (!currentVod) return [3 /*break*/, 21];
|
|
880
|
-
_a.label = 17;
|
|
881
|
-
case 17:
|
|
882
|
-
_a.trys.push([17, 18, , 20]);
|
|
883
|
-
manifestMseq = playheadState.mediaSeq + playheadState.vodMediaSeqVideo;
|
|
884
|
-
manifestDseq = sessionState.discSeq + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
|
|
885
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
886
|
-
mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
887
|
-
debug("[".concat(this._sessionId, "]: {").concat(mediaSequenceValue, "}_{").concat(currentVod.getLastSequenceMediaSequenceValue(), "}"));
|
|
888
|
-
manifestMseq = playheadState.mediaSeq + mediaSequenceValue;
|
|
889
|
-
}
|
|
890
|
-
debug("[".concat(this._sessionId, "]: [").concat(playheadState.vodMediaSeqVideo, "]_[").concat(currentVod.getLiveMediaSequencesCount(), "]"));
|
|
891
|
-
m3u8_1 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
892
|
-
debug("[".concat(this._sessionId, "]: [").concat(manifestMseq, "][").concat(manifestDseq, "][+").concat(this.targetDurationPadding || 0, "] Current media manifest for ").concat(bw, " requested"));
|
|
893
|
-
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
894
|
-
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
895
|
-
return [2 /*return*/, m3u8_1];
|
|
896
|
-
case 18:
|
|
897
|
-
err_4 = _a.sent();
|
|
898
|
-
logerror(this._sessionId, err_4);
|
|
899
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
900
|
-
case 19:
|
|
901
|
-
_a.sent(); // force reading up from shared store
|
|
902
|
-
throw new Error("Failed to generate manifest: " + JSON.stringify(playheadState));
|
|
903
|
-
case 20: return [3 /*break*/, 22];
|
|
904
|
-
case 21: throw new Error("Engine not ready");
|
|
905
|
-
case 22: return [2 /*return*/];
|
|
799
|
+
if (!this._sessionState) {
|
|
800
|
+
throw new Error('Session not ready');
|
|
906
801
|
}
|
|
802
|
+
m3u8 = this._getM3u8File("video", bw, playbackSessionId);
|
|
803
|
+
return [2 /*return*/, m3u8];
|
|
907
804
|
});
|
|
908
805
|
});
|
|
909
806
|
};
|
|
910
807
|
Session.prototype.getCurrentAudioManifestAsync = function (audioGroupId, audioLanguage, playbackSessionId) {
|
|
911
808
|
return __awaiter(this, void 0, void 0, function () {
|
|
912
|
-
var
|
|
809
|
+
var variantKey, m3u8;
|
|
913
810
|
return __generator(this, function (_a) {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (!this._sessionState) {
|
|
917
|
-
throw new Error('Session not ready');
|
|
918
|
-
}
|
|
919
|
-
currentVod = null;
|
|
920
|
-
return [4 /*yield*/, this._sessionState.getValues(["discSeqAudio", "vodMediaSeqAudio"])];
|
|
921
|
-
case 1:
|
|
922
|
-
sessionState = _a.sent();
|
|
923
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio", "playheadRef"])];
|
|
924
|
-
case 2:
|
|
925
|
-
playheadState = _a.sent();
|
|
926
|
-
if (!(playheadState.vodMediaSeqAudio > sessionState.vodMediaSeqAudio || (playheadState.vodMediaSeqAudio < sessionState.vodMediaSeqAudio && playheadState.mediaSeqAudio === this.prevMediaSeqOffset.audio))) return [3 /*break*/, 6];
|
|
927
|
-
return [4 /*yield*/, this._sessionState.get("state")];
|
|
928
|
-
case 3:
|
|
929
|
-
state = _a.sent();
|
|
930
|
-
DELAY_TIME_MS = 1000;
|
|
931
|
-
ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
932
|
-
debug("[".concat(this._sessionId, "]: Recently ").concat(ACTION, " Vod. PlayheadState not up-to-date (").concat(playheadState.vodMediaSeqAudio, "_").concat(sessionState.vodMediaSeqAudio, "). Waiting ").concat(DELAY_TIME_MS, "ms before reading from store again"));
|
|
933
|
-
return [4 /*yield*/, timer(DELAY_TIME_MS)];
|
|
934
|
-
case 4:
|
|
935
|
-
_a.sent();
|
|
936
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio"])];
|
|
937
|
-
case 5:
|
|
938
|
-
playheadState = _a.sent();
|
|
939
|
-
_a.label = 6;
|
|
940
|
-
case 6:
|
|
941
|
-
// local store the prev values
|
|
942
|
-
if (this.prevVodMediaSeq.audio === null) {
|
|
943
|
-
this.prevVodMediaSeq.audio = playheadState.vodMediaSeqAudio;
|
|
944
|
-
}
|
|
945
|
-
if (this.prevMediaSeqOffset.audio === null) {
|
|
946
|
-
this.prevMediaSeqOffset.audio = playheadState.mediaSeqAudio;
|
|
947
|
-
}
|
|
948
|
-
return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
949
|
-
case 7:
|
|
950
|
-
currentVod = _a.sent();
|
|
951
|
-
if (!currentVod) return [3 /*break*/, 15];
|
|
952
|
-
if (!(playheadState.vodMediaSeqAudio < 2 || playheadState.mediaSeqAudio !== this.prevMediaSeqOffset.audio)) return [3 /*break*/, 11];
|
|
953
|
-
if (!(playheadState.playheadRef > this.currentPlayheadRef)) return [3 /*break*/, 11];
|
|
954
|
-
return [4 /*yield*/, timer(500)];
|
|
955
|
-
case 8:
|
|
956
|
-
_a.sent();
|
|
957
|
-
this.currentPlayheadRef = playheadState.playheadRef;
|
|
958
|
-
debug("[".concat(this._sessionId, "]: While requesting audio manifest for ").concat(audioGroupId, "-").concat(audioLanguage, ", (mseq=").concat(playheadState.vodMediaSeqAudio, ")"));
|
|
959
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
960
|
-
case 9:
|
|
961
|
-
_a.sent(); // force reading up from shared store
|
|
962
|
-
return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
963
|
-
case 10:
|
|
964
|
-
currentVod = _a.sent();
|
|
965
|
-
_a.label = 11;
|
|
966
|
-
case 11:
|
|
967
|
-
_a.trys.push([11, 12, , 14]);
|
|
968
|
-
manifestMseq = playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio;
|
|
969
|
-
manifestDseq = sessionState.discSeqAudio + currentVod.discontinuitiesAudio[playheadState.vodMediaSeqAudio];
|
|
970
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
971
|
-
mediaSequenceValue = currentVod.mediaSequenceValuesAudio[playheadState.vodMediaSeqAudio];
|
|
972
|
-
debug("[".concat(this._sessionId, "]: {").concat(mediaSequenceValue, "}_{").concat(currentVod.getLastSequenceMediaSequenceValueAudio(), "} AUDIO"));
|
|
973
|
-
manifestMseq = playheadState.mediaSeqAudio + mediaSequenceValue;
|
|
974
|
-
}
|
|
975
|
-
debug("[".concat(this._sessionId, "]: [").concat(playheadState.vodMediaSeqAudio, "]_[").concat(currentVod.getLiveMediaSequencesCount("audio"), "] AUDIO (").concat(audioGroupId, ")"));
|
|
976
|
-
m3u8_2 = currentVod.getLiveMediaAudioSequences(playheadState.mediaSeqAudio, audioGroupId, audioLanguage, playheadState.vodMediaSeqAudio, sessionState.discSeqAudio, this.targetDurationPadding, this.forceTargetDuration);
|
|
977
|
-
// # Case: current VOD does not have the selected track.
|
|
978
|
-
if (!m3u8_2) {
|
|
979
|
-
debug("[".concat(this._sessionId, "]: [").concat(playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio, "] Request Failed for current audio manifest for ").concat(audioGroupId, "-").concat(audioLanguage));
|
|
980
|
-
}
|
|
981
|
-
debug("[".concat(this._sessionId, "]: [").concat(manifestMseq, "][").concat(manifestDseq, "] Current audio manifest for ").concat(audioGroupId, "-").concat(audioLanguage, " requested"));
|
|
982
|
-
this.prevVodMediaSeq.audio = playheadState.vodMediaSeqAudio;
|
|
983
|
-
this.prevMediaSeqOffset.audio = playheadState.mediaSeqAudio;
|
|
984
|
-
return [2 /*return*/, m3u8_2];
|
|
985
|
-
case 12:
|
|
986
|
-
err_5 = _a.sent();
|
|
987
|
-
logerror(this._sessionId, err_5);
|
|
988
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
989
|
-
case 13:
|
|
990
|
-
_a.sent(); // force reading up from shared store
|
|
991
|
-
throw new Error("Failed to generate audio manifest: " + JSON.stringify(playheadState));
|
|
992
|
-
case 14: return [3 /*break*/, 16];
|
|
993
|
-
case 15: throw new Error("Engine not ready");
|
|
994
|
-
case 16: return [2 /*return*/];
|
|
811
|
+
if (!this._sessionState) {
|
|
812
|
+
throw new Error('Session not ready');
|
|
995
813
|
}
|
|
814
|
+
variantKey = JSON.stringify({ groupId: audioGroupId, lang: audioLanguage });
|
|
815
|
+
m3u8 = this._getM3u8File("audio", variantKey, playbackSessionId);
|
|
816
|
+
return [2 /*return*/, m3u8];
|
|
996
817
|
});
|
|
997
818
|
});
|
|
998
819
|
};
|
|
999
|
-
Session.prototype.getCurrentSubtitleManifestAsync = function (subtitleGroupId, subtitleLanguage) {
|
|
820
|
+
Session.prototype.getCurrentSubtitleManifestAsync = function (subtitleGroupId, subtitleLanguage, playbackSessionId) {
|
|
1000
821
|
return __awaiter(this, void 0, void 0, function () {
|
|
1001
|
-
var
|
|
822
|
+
var variantKey, m3u8;
|
|
1002
823
|
return __generator(this, function (_a) {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (!this._sessionState) {
|
|
1006
|
-
throw new Error('Session not ready');
|
|
1007
|
-
}
|
|
1008
|
-
currentVod = null;
|
|
1009
|
-
return [4 /*yield*/, this._sessionState.getValues(["discSeqSubtitle", "vodMediaSeqSubtitle"])];
|
|
1010
|
-
case 1:
|
|
1011
|
-
sessionState = _a.sent();
|
|
1012
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeqSubtitle", "vodMediaSeqSubtitle", "playheadRef"])];
|
|
1013
|
-
case 2:
|
|
1014
|
-
playheadState = _a.sent();
|
|
1015
|
-
if (!(playheadState.vodMediaSeqSubtitle > sessionState.vodMediaSeqSubtitle || (playheadState.vodMediaSeqSubtitle < sessionState.vodMediaSeqSubtitle && playheadState.mediaSeqSubtitle === this.prevMediaSeqOffset.subtitle))) return [3 /*break*/, 6];
|
|
1016
|
-
return [4 /*yield*/, this._sessionState.get("state")];
|
|
1017
|
-
case 3:
|
|
1018
|
-
state = _a.sent();
|
|
1019
|
-
DELAY_TIME_MS = 1000;
|
|
1020
|
-
ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
1021
|
-
debug("[".concat(this._sessionId, "]: Recently ").concat(ACTION, " Vod. PlayheadState not up-to-date (").concat(playheadState.vodMediaSeqSubtitle, "_").concat(sessionState.vodMediaSeqSubtitle, "). Waiting ").concat(DELAY_TIME_MS, "ms before reading from store again"));
|
|
1022
|
-
return [4 /*yield*/, timer(DELAY_TIME_MS)];
|
|
1023
|
-
case 4:
|
|
1024
|
-
_a.sent();
|
|
1025
|
-
return [4 /*yield*/, this._playheadState.getValues(["mediaSeqSubtitle", "vodMediaSeqSubtitle"])];
|
|
1026
|
-
case 5:
|
|
1027
|
-
playheadState = _a.sent();
|
|
1028
|
-
_a.label = 6;
|
|
1029
|
-
case 6:
|
|
1030
|
-
// local store the prev values
|
|
1031
|
-
if (this.prevVodMediaSeq.subtitle === null) {
|
|
1032
|
-
this.prevVodMediaSeq.subtitle = playheadState.vodMediaSeqSubtitle;
|
|
1033
|
-
}
|
|
1034
|
-
if (this.prevMediaSeqOffset.subtitle === null) {
|
|
1035
|
-
this.prevMediaSeqOffset.subtitle = playheadState.mediaSeqSubtitle;
|
|
1036
|
-
}
|
|
1037
|
-
return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
1038
|
-
case 7:
|
|
1039
|
-
currentVod = _a.sent();
|
|
1040
|
-
if (!currentVod) return [3 /*break*/, 15];
|
|
1041
|
-
if (!(playheadState.vodMediaSeqSubtitle < 2 || playheadState.mediaSeqSubtitle !== this.prevMediaSeqOffset.subtitle)) return [3 /*break*/, 11];
|
|
1042
|
-
if (!(playheadState.playheadRef > this.currentPlayheadRef)) return [3 /*break*/, 11];
|
|
1043
|
-
return [4 /*yield*/, timer(500)];
|
|
1044
|
-
case 8:
|
|
1045
|
-
_a.sent();
|
|
1046
|
-
this.currentPlayheadRef = playheadState.playheadRef;
|
|
1047
|
-
debug("[".concat(this._sessionId, "]: While requesting subtitle manifest for ").concat(subtitleGroupId, "-").concat(subtitleLanguage, ", (mseq=").concat(playheadState.vodMediaSeqSubtitle, ")"));
|
|
1048
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
1049
|
-
case 9:
|
|
1050
|
-
_a.sent(); // force reading up from shared store
|
|
1051
|
-
return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
1052
|
-
case 10:
|
|
1053
|
-
currentVod = _a.sent();
|
|
1054
|
-
_a.label = 11;
|
|
1055
|
-
case 11:
|
|
1056
|
-
_a.trys.push([11, 12, , 14]);
|
|
1057
|
-
manifestMseq = playheadState.mediaSeqSubtitle + playheadState.vodMediaSeqSubtitle;
|
|
1058
|
-
manifestDseq = sessionState.discSeqSubtitle + currentVod.discontinuitiesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
1059
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
1060
|
-
mediaSequenceValue = currentVod.mediaSequenceValuesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
1061
|
-
debug("[".concat(this._sessionId, "]: {").concat(mediaSequenceValue, "}_{").concat(currentVod.getLastSequenceMediaSequenceValueSubtitle(), "} SUBTITLES"));
|
|
1062
|
-
manifestMseq = playheadState.mediaSeqSubtitle + mediaSequenceValue;
|
|
1063
|
-
}
|
|
1064
|
-
debug("[".concat(this._sessionId, "]: [").concat(playheadState.vodMediaSeqSubtitle, "]_[").concat(currentVod.getLiveMediaSequencesCount("subtitle"), "] SUBTITLES (").concat(subtitleGroupId, ")"));
|
|
1065
|
-
m3u8_3 = currentVod.getLiveMediaSubtitleSequences(playheadState.mediaSeqSubtitle, subtitleGroupId, subtitleLanguage, playheadState.vodMediaSeqSubtitle, sessionState.discSeqSubtitle, this.targetDurationPadding, this.forceTargetDuration);
|
|
1066
|
-
// # Case: current VOD does not have the selected track.
|
|
1067
|
-
if (!m3u8_3) {
|
|
1068
|
-
debug("[".concat(this._sessionId, "]: [").concat(playheadState.mediaSeqSubtitle + playheadState.vodMediaSeqSubtitle, "] Request Failed for current subtitle manifest for ").concat(subtitleGroupId, "-").concat(subtitleLanguage));
|
|
1069
|
-
}
|
|
1070
|
-
debug("[".concat(this._sessionId, "]: [").concat(manifestMseq, "][").concat(manifestDseq, "] Current subtitle manifest for ").concat(subtitleGroupId, "-").concat(subtitleLanguage, " requested"));
|
|
1071
|
-
this.prevVodMediaSeq.subtitle = playheadState.vodMediaSeqSubtitle;
|
|
1072
|
-
this.prevMediaSeqOffset.subtitle = playheadState.mediaSeqSubtitle;
|
|
1073
|
-
return [2 /*return*/, m3u8_3];
|
|
1074
|
-
case 12:
|
|
1075
|
-
err_6 = _a.sent();
|
|
1076
|
-
logerror(this._sessionId, err_6);
|
|
1077
|
-
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
1078
|
-
case 13:
|
|
1079
|
-
_a.sent(); // force reading up from shared store
|
|
1080
|
-
throw new Error("Failed to generate subtitle manifest: " + JSON.stringify(playheadState));
|
|
1081
|
-
case 14: return [3 /*break*/, 16];
|
|
1082
|
-
case 15: throw new Error("Engine not ready");
|
|
1083
|
-
case 16: return [2 /*return*/];
|
|
824
|
+
if (!this._sessionState) {
|
|
825
|
+
throw new Error('Session not ready');
|
|
1084
826
|
}
|
|
827
|
+
variantKey = JSON.stringify({ groupId: subtitleGroupId, lang: subtitleLanguage });
|
|
828
|
+
m3u8 = this._getM3u8File("subtitle", variantKey, playbackSessionId);
|
|
829
|
+
return [2 /*return*/, m3u8];
|
|
1085
830
|
});
|
|
1086
831
|
});
|
|
1087
832
|
};
|
|
1088
833
|
Session.prototype.incrementAsync = function () {
|
|
1089
834
|
return __awaiter(this, void 0, void 0, function () {
|
|
1090
|
-
var isLeader, sessionState, playheadState, currentVod, _a, isOldVod, nextState, leaderAction, leaderState, _b,
|
|
835
|
+
var isLeader, sessionState, playheadState, currentVod, _a, isOldVod, nextState, leaderAction, leaderState, _b, m3u8_1, _c, playheadPosVideoMs, playheadAudio, playheadSubtitle, audioSeqLastIdx, sessionStateObj, _d, playheadPosVideo, _e, subtitleSeqLastIdx, sessionStateObj, _f, newSessionState, updatedValues, updatedPlayhead, mediaSequenceValue, audioInfo, mediaSequenceValueAudio, subsInfo, mediaSequenceValueSubtitle, diffMs, m3u8;
|
|
1091
836
|
return __generator(this, function (_g) {
|
|
1092
837
|
switch (_g.label) {
|
|
1093
838
|
case 0: return [4 /*yield*/, this._tickAsync()];
|
|
@@ -1145,9 +890,9 @@ var Session = /** @class */ (function () {
|
|
|
1145
890
|
debug("[".concat(this._sessionId, "]: Return the last generated m3u8 to give the leader some time"));
|
|
1146
891
|
return [4 /*yield*/, this._playheadState.getLastM3u8()];
|
|
1147
892
|
case 12:
|
|
1148
|
-
|
|
1149
|
-
if (
|
|
1150
|
-
return [2 /*return*/,
|
|
893
|
+
m3u8_1 = _g.sent();
|
|
894
|
+
if (m3u8_1) {
|
|
895
|
+
return [2 /*return*/, m3u8_1];
|
|
1151
896
|
}
|
|
1152
897
|
else {
|
|
1153
898
|
debug("[".concat(this._sessionId, "]: We don't have any previously generated m3u8"));
|
|
@@ -1252,9 +997,14 @@ var Session = /** @class */ (function () {
|
|
|
1252
997
|
mediaSequenceValueAudio = currentVod.mediaSequenceValuesAudio[playheadState.vodMediaSeqAudio];
|
|
1253
998
|
audioInfo = " mseq[A]={".concat(playheadState.mediaSeqAudio + mediaSequenceValueAudio, "}");
|
|
1254
999
|
}
|
|
1255
|
-
|
|
1000
|
+
subsInfo = "";
|
|
1001
|
+
if (this.use_vtt_subtitles) {
|
|
1002
|
+
mediaSequenceValueSubtitle = currentVod.mediaSequenceValuesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
1003
|
+
subsInfo = " mseq[S]={".concat(playheadState.mediaSeqSubtitle + mediaSequenceValueSubtitle, "}");
|
|
1004
|
+
}
|
|
1005
|
+
debug("[".concat(this._sessionId, "]: Session can now serve mseq[V]={").concat(playheadState.mediaSeq + mediaSequenceValue, "}") + audioInfo + subsInfo);
|
|
1256
1006
|
}
|
|
1257
|
-
debug("[".concat(this._sessionId, "]: INCREMENT (mseq=").concat(playheadState.mediaSeq + playheadState.vodMediaSeqVideo, ") vodMediaSeq=(").concat(playheadState.vodMediaSeqVideo, "_").concat(playheadState.vodMediaSeqAudio, " of ").concat(currentVod.getLiveMediaSequencesCount(), "_").concat(currentVod.getLiveMediaSequencesCount("audio"), ")"));
|
|
1007
|
+
debug("[".concat(this._sessionId, "]: INCREMENT (mseq=").concat(playheadState.mediaSeq + playheadState.vodMediaSeqVideo, ") vodMediaSeq=(").concat(playheadState.vodMediaSeqVideo, "_").concat(playheadState.vodMediaSeqAudio).concat(this.use_vtt_subtitles ? "_".concat(playheadState.vodMediaSeqSubtitle) : "", " of ").concat(currentVod.getLiveMediaSequencesCount(), "_").concat(currentVod.getLiveMediaSequencesCount("audio")).concat(this.use_vtt_subtitles ? "_".concat(currentVod.getLiveMediaSequencesCount("subtitle"), ")") : ")"));
|
|
1258
1008
|
if (!(playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video)) return [3 /*break*/, 35];
|
|
1259
1009
|
debug("[".concat(this._sessionId, "]: current[").concat(playheadState.vodMediaSeqVideo, "]_prev[").concat(this.prevVodMediaSeq.video, "]"));
|
|
1260
1010
|
debug("[".concat(this._sessionId, "]: current-offset[").concat(playheadState.mediaSeq, "]_prev-offset[").concat(this.prevMediaSeqOffset.video, "]"));
|
|
@@ -1751,7 +1501,7 @@ var Session = /** @class */ (function () {
|
|
|
1751
1501
|
};
|
|
1752
1502
|
Session.prototype._tickAsync = function () {
|
|
1753
1503
|
return __awaiter(this, void 0, void 0, function () {
|
|
1754
|
-
var newVod, sessionState, isLeader, currentVod, vodResponse, _a, nextVodPromise, nextVodStart, _b, loadPromise, hlsOpts, _c, mediaManifestLoader, audioManifestLoader, loadStart, updatedSessionState, _d, _e, _f, _g,
|
|
1504
|
+
var newVod, sessionState, isLeader, currentVod, vodResponse, _a, nextVodPromise, nextVodStart, _b, loadPromise, hlsOpts, _c, mediaManifestLoader, audioManifestLoader, loadStart, updatedSessionState, _d, _e, _f, _g, err_4, _h, length_1, endMseqValue, endMseqValueAudio, endMseqValueSubtitle, lastDiscontinuity, lastDiscontinuityAudio, lastDiscontinuitySubtitle, _j, vodPromise, gracePeriod, nextVodStart, _k, loadPromise, hlsOpts, _l, mediaManifestLoader, audioManifestLoader, loadStart, _m, _o, updatedSessionState, _p, _q, diffMs, err_5, startTS, _r, mSeq, currentVod_1, dSeq, mSeqOffset, reloadBehind, segments, playheadState, nextMseq, _s, _t, err_6;
|
|
1755
1505
|
var _this = this;
|
|
1756
1506
|
return __generator(this, function (_u) {
|
|
1757
1507
|
switch (_u.label) {
|
|
@@ -1905,13 +1655,13 @@ var Session = /** @class */ (function () {
|
|
|
1905
1655
|
return [2 /*return*/];
|
|
1906
1656
|
case 22: return [3 /*break*/, 26];
|
|
1907
1657
|
case 23:
|
|
1908
|
-
|
|
1658
|
+
err_4 = _u.sent();
|
|
1909
1659
|
console.error("[".concat(this._sessionId, "]: Failed to init first VOD"));
|
|
1910
1660
|
if (this._assetManager.handleError) {
|
|
1911
1661
|
this._assetManager.handleError(new Error("Failed to init first VOD"), vodResponse);
|
|
1912
1662
|
}
|
|
1913
|
-
cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'error', on: 'firstvod', channel: this._sessionId, err:
|
|
1914
|
-
debug(
|
|
1663
|
+
cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'error', on: 'firstvod', channel: this._sessionId, err: err_4, vod: vodResponse });
|
|
1664
|
+
debug(err_4);
|
|
1915
1665
|
return [4 /*yield*/, this._sessionState.remove("nextVod")];
|
|
1916
1666
|
case 24:
|
|
1917
1667
|
_u.sent();
|
|
@@ -1920,7 +1670,7 @@ var Session = /** @class */ (function () {
|
|
|
1920
1670
|
currentVod = _u.sent();
|
|
1921
1671
|
if (!currentVod) {
|
|
1922
1672
|
debug("No slate to load");
|
|
1923
|
-
throw
|
|
1673
|
+
throw err_4;
|
|
1924
1674
|
}
|
|
1925
1675
|
return [3 /*break*/, 26];
|
|
1926
1676
|
case 26:
|
|
@@ -2170,13 +1920,13 @@ var Session = /** @class */ (function () {
|
|
|
2170
1920
|
_u.label = 56;
|
|
2171
1921
|
case 56: return [3 /*break*/, 60];
|
|
2172
1922
|
case 57:
|
|
2173
|
-
|
|
1923
|
+
err_5 = _u.sent();
|
|
2174
1924
|
console.error("[".concat(this._sessionId, "]: Failed to init next VOD"));
|
|
2175
|
-
debug("[".concat(this._sessionId, "]: ").concat(
|
|
1925
|
+
debug("[".concat(this._sessionId, "]: ").concat(err_5));
|
|
2176
1926
|
if (this._assetManager.handleError) {
|
|
2177
1927
|
this._assetManager.handleError(new Error("Failed to init next VOD"), vodResponse);
|
|
2178
1928
|
}
|
|
2179
|
-
cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'error', on: 'nextvod', channel: this._sessionId, err:
|
|
1929
|
+
cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'error', on: 'nextvod', channel: this._sessionId, err: err_5, vod: vodResponse });
|
|
2180
1930
|
return [4 /*yield*/, this._sessionState.remove("nextVod")];
|
|
2181
1931
|
case 58:
|
|
2182
1932
|
_u.sent();
|
|
@@ -2185,7 +1935,7 @@ var Session = /** @class */ (function () {
|
|
|
2185
1935
|
currentVod = _u.sent();
|
|
2186
1936
|
if (!currentVod) {
|
|
2187
1937
|
debug("No slate to load");
|
|
2188
|
-
throw
|
|
1938
|
+
throw err_5;
|
|
2189
1939
|
}
|
|
2190
1940
|
// Allow Leader|Follower to clear vodCache...
|
|
2191
1941
|
this.isAllowedToClearVodCache = true;
|
|
@@ -2296,9 +2046,9 @@ var Session = /** @class */ (function () {
|
|
|
2296
2046
|
_u.label = 80;
|
|
2297
2047
|
case 80: return [3 /*break*/, 82];
|
|
2298
2048
|
case 81:
|
|
2299
|
-
|
|
2049
|
+
err_6 = _u.sent();
|
|
2300
2050
|
debug("Failed to init reload vod");
|
|
2301
|
-
throw
|
|
2051
|
+
throw err_6;
|
|
2302
2052
|
case 82: return [3 /*break*/, 88];
|
|
2303
2053
|
case 83:
|
|
2304
2054
|
debug("[".concat(this._sessionId, "]: state=VOD_RELOAD_INITIATING (").concat(sessionState.vodMediaSeqVideo, "_").concat(sessionState.vodMediaSeqAudio, "_").concat(sessionState.vodMediaSeqSubtitle, ", ").concat(currentVod.getLiveMediaSequencesCount(), "_").concat(currentVod.getLiveMediaSequencesCount("audio"), "_").concat(currentVod.getLiveMediaSequencesCount("subtitle"), ")"));
|
|
@@ -2725,7 +2475,7 @@ var Session = /** @class */ (function () {
|
|
|
2725
2475
|
debug("[".concat(this._sessionId, "]: About to determine ").concat(_extraType, " increment. Video increment has already been executed."));
|
|
2726
2476
|
extraMediaIncrement = 0;
|
|
2727
2477
|
positionV = _currentPosVideo ? _currentPosVideo / 1000 : 0;
|
|
2728
|
-
threshold = 0.
|
|
2478
|
+
threshold = 0.500;
|
|
2729
2479
|
_a.label = 1;
|
|
2730
2480
|
case 1:
|
|
2731
2481
|
if (!(extraMediaIncrement < _extraSeqFinalIndex)) return [3 /*break*/, 3];
|
|
@@ -2760,6 +2510,211 @@ var Session = /** @class */ (function () {
|
|
|
2760
2510
|
});
|
|
2761
2511
|
});
|
|
2762
2512
|
};
|
|
2513
|
+
Session.prototype._getM3u8File = function (variantType, variantKey, playbackSessionId) {
|
|
2514
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2515
|
+
var tries, delayMs, MSDKeys, currentVod, sessionState, playheadState, state, DELAY_TIME_MS, ACTION, isLeader, diffMs, manifestMseq, manifestDseq, mediaSequenceValue, m3u8_2, parsedGroupId, parsedLang, data, data, err_7;
|
|
2516
|
+
return __generator(this, function (_a) {
|
|
2517
|
+
switch (_a.label) {
|
|
2518
|
+
case 0:
|
|
2519
|
+
tries = 12;
|
|
2520
|
+
_a.label = 1;
|
|
2521
|
+
case 1:
|
|
2522
|
+
if (!(tries > 0 && this.leaderIsSettingNextVod)) return [3 /*break*/, 3];
|
|
2523
|
+
delayMs = 250;
|
|
2524
|
+
debug("[".concat(this._sessionId, "]: Leader is setting the next vod. Waiting ").concat(delayMs, "ms_").concat(tries));
|
|
2525
|
+
return [4 /*yield*/, timer(delayMs)];
|
|
2526
|
+
case 2:
|
|
2527
|
+
_a.sent();
|
|
2528
|
+
tries--;
|
|
2529
|
+
return [3 /*break*/, 1];
|
|
2530
|
+
case 3:
|
|
2531
|
+
MSDKeys = {
|
|
2532
|
+
mediaSeq: null,
|
|
2533
|
+
vodMediaSeq: null,
|
|
2534
|
+
discSeq: null
|
|
2535
|
+
};
|
|
2536
|
+
if (variantType === "video") {
|
|
2537
|
+
MSDKeys.mediaSeq = "mediaSeq";
|
|
2538
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqVideo";
|
|
2539
|
+
MSDKeys.discSeq = "discSeq";
|
|
2540
|
+
}
|
|
2541
|
+
else if (variantType === "audio") {
|
|
2542
|
+
MSDKeys.mediaSeq = "mediaSeqAudio";
|
|
2543
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqAudio";
|
|
2544
|
+
MSDKeys.discSeq = "discSeqAudio";
|
|
2545
|
+
}
|
|
2546
|
+
else if (variantType === "subtitle") {
|
|
2547
|
+
MSDKeys.mediaSeq = "mediaSeqSubtitle";
|
|
2548
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqSubtitle";
|
|
2549
|
+
MSDKeys.discSeq = "discSeqSubtitle";
|
|
2550
|
+
}
|
|
2551
|
+
currentVod = null;
|
|
2552
|
+
return [4 /*yield*/, this._sessionState.getValues([MSDKeys.vodMediaSeq, MSDKeys.discSeq])];
|
|
2553
|
+
case 4:
|
|
2554
|
+
sessionState = _a.sent();
|
|
2555
|
+
return [4 /*yield*/, this._playheadState.getValues([MSDKeys.mediaSeq, MSDKeys.vodMediaSeq, "playheadRef"])];
|
|
2556
|
+
case 5:
|
|
2557
|
+
playheadState = _a.sent();
|
|
2558
|
+
if (this.prevVodMediaSeq[variantType] === null) {
|
|
2559
|
+
this.prevVodMediaSeq[variantType] = playheadState[MSDKeys.vodMediaSeq];
|
|
2560
|
+
}
|
|
2561
|
+
if (this.prevMediaSeqOffset[variantType] === null) {
|
|
2562
|
+
this.prevMediaSeqOffset[variantType] = playheadState[MSDKeys.mediaSeq];
|
|
2563
|
+
}
|
|
2564
|
+
if (!(playheadState[MSDKeys.vodMediaSeq] > sessionState[MSDKeys.vodMediaSeq] ||
|
|
2565
|
+
(playheadState[MSDKeys.vodMediaSeq] < sessionState[MSDKeys.vodMediaSeq] &&
|
|
2566
|
+
playheadState[MSDKeys.mediaSeq] === this.prevMediaSeqOffset[variantType]))) return [3 /*break*/, 9];
|
|
2567
|
+
return [4 /*yield*/, this._sessionState.get("state")];
|
|
2568
|
+
case 6:
|
|
2569
|
+
state = _a.sent();
|
|
2570
|
+
DELAY_TIME_MS = 1000;
|
|
2571
|
+
ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
2572
|
+
debug("[".concat(this._sessionId, "]: Recently ").concat(ACTION, " Vod. PlayheadState not up-to-date (").concat(playheadState[MSDKeys.vodMediaSeq], "_").concat(sessionState[MSDKeys.vodMediaSeq], "). Waiting ").concat(DELAY_TIME_MS, "ms before reading from store again (").concat(variantType, ")"));
|
|
2573
|
+
return [4 /*yield*/, timer(DELAY_TIME_MS)];
|
|
2574
|
+
case 7:
|
|
2575
|
+
_a.sent();
|
|
2576
|
+
return [4 /*yield*/, this._playheadState.getValues([MSDKeys.mediaSeq, MSDKeys.vodMediaSeq])];
|
|
2577
|
+
case 8:
|
|
2578
|
+
playheadState = _a.sent();
|
|
2579
|
+
_a.label = 9;
|
|
2580
|
+
case 9:
|
|
2581
|
+
if (!(variantType === "video")) return [3 /*break*/, 15];
|
|
2582
|
+
if (!(playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video)) return [3 /*break*/, 14];
|
|
2583
|
+
debug("[".concat(this._sessionId, "]: current[").concat(playheadState.vodMediaSeqVideo, "]_prev[").concat(this.prevVodMediaSeq.video, "]"));
|
|
2584
|
+
debug("[".concat(this._sessionId, "]: current-offset[").concat(playheadState.mediaSeq, "]_prev-offset[").concat(this.prevMediaSeqOffset.video, "]"));
|
|
2585
|
+
// If true, then we have not updated the prev-values and not cleared the cache yet.
|
|
2586
|
+
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
2587
|
+
this.isAllowedToClearVodCache = true;
|
|
2588
|
+
}
|
|
2589
|
+
return [4 /*yield*/, this._sessionStateStore.isLeader(this._instanceId)];
|
|
2590
|
+
case 10:
|
|
2591
|
+
isLeader = _a.sent();
|
|
2592
|
+
if (!(!isLeader && this.isAllowedToClearVodCache)) return [3 /*break*/, 13];
|
|
2593
|
+
debug("[".concat(this._sessionId, "]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD."));
|
|
2594
|
+
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
2595
|
+
case 11:
|
|
2596
|
+
_a.sent();
|
|
2597
|
+
this.isAllowedToClearVodCache = false;
|
|
2598
|
+
return [4 /*yield*/, this._playheadState.get("diffCompensation")];
|
|
2599
|
+
case 12:
|
|
2600
|
+
diffMs = _a.sent();
|
|
2601
|
+
if (diffMs) {
|
|
2602
|
+
this.diffCompensation = diffMs;
|
|
2603
|
+
debug("[".concat(this._sessionId, "]: Setting diffCompensation->").concat(this.diffCompensation));
|
|
2604
|
+
if (this.diffCompensation) {
|
|
2605
|
+
this.timePositionOffset = this.diffCompensation;
|
|
2606
|
+
cloudWatchLog(!this.cloudWatchLogging, "engine-session", {
|
|
2607
|
+
event: "timePositionOffsetUpdated",
|
|
2608
|
+
channel: this._sessionId,
|
|
2609
|
+
offsetMs: this.timePositionOffset,
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
else {
|
|
2613
|
+
this.timePositionOffset = 0;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
_a.label = 13;
|
|
2617
|
+
case 13: return [3 /*break*/, 15];
|
|
2618
|
+
case 14:
|
|
2619
|
+
this.isAllowedToClearVodCache = true;
|
|
2620
|
+
_a.label = 15;
|
|
2621
|
+
case 15: return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
2622
|
+
case 16:
|
|
2623
|
+
currentVod = _a.sent();
|
|
2624
|
+
if (!currentVod) return [3 /*break*/, 24];
|
|
2625
|
+
if (!(playheadState[MSDKeys.vodMediaSeq] < 2 || playheadState[MSDKeys.mediaSeq] !== this.prevMediaSeqOffset.video)) return [3 /*break*/, 20];
|
|
2626
|
+
if (!(playheadState.playheadRef > this.currentPlayheadRef)) return [3 /*break*/, 20];
|
|
2627
|
+
return [4 /*yield*/, timer(500)];
|
|
2628
|
+
case 17:
|
|
2629
|
+
_a.sent();
|
|
2630
|
+
this.currentPlayheadRef = playheadState.playheadRef;
|
|
2631
|
+
debug("[".concat(this._sessionId, "]: While requesting ").concat(variantType, " manifest for ").concat(variantKey, ", (mseq=").concat(playheadState[MSDKeys.vodMediaSeq], ")"));
|
|
2632
|
+
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
2633
|
+
case 18:
|
|
2634
|
+
_a.sent(); // force reading up from shared store
|
|
2635
|
+
return [4 /*yield*/, this._sessionState.getCurrentVod()];
|
|
2636
|
+
case 19:
|
|
2637
|
+
currentVod = _a.sent();
|
|
2638
|
+
_a.label = 20;
|
|
2639
|
+
case 20:
|
|
2640
|
+
_a.trys.push([20, 21, , 23]);
|
|
2641
|
+
manifestMseq = playheadState[MSDKeys.mediaSeq] + playheadState[MSDKeys.vodMediaSeq];
|
|
2642
|
+
manifestDseq = sessionState[MSDKeys.discSeq] + this._getCurrentVodData("discontinuities", variantType, currentVod)[playheadState[MSDKeys.vodMediaSeq]];
|
|
2643
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
2644
|
+
mediaSequenceValue = this._getCurrentVodData("mseqValues", variantType, currentVod)[playheadState[MSDKeys.vodMediaSeq]];
|
|
2645
|
+
debug("[".concat(this._sessionId, "]: {").concat(mediaSequenceValue, "}_{").concat(this._getCurrentVodData("finalMseqValue", variantType, currentVod), "}"));
|
|
2646
|
+
manifestMseq = playheadState[MSDKeys.mediaSeq] + mediaSequenceValue;
|
|
2647
|
+
}
|
|
2648
|
+
debug("[".concat(this._sessionId, "]: [").concat(playheadState[MSDKeys.vodMediaSeq], "]_[").concat(this._getCurrentVodData("mseqCount", variantType, currentVod), "]"));
|
|
2649
|
+
parsedGroupId = void 0;
|
|
2650
|
+
parsedLang = void 0;
|
|
2651
|
+
if (variantType === "video") {
|
|
2652
|
+
m3u8_2 = currentVod.getLiveMediaSequences(playheadState[MSDKeys.mediaSeq], variantKey, playheadState[MSDKeys.vodMediaSeq], sessionState[MSDKeys.discSeq], this.targetDurationPadding, this.forceTargetDuration);
|
|
2653
|
+
}
|
|
2654
|
+
else if ((variantType === "audio")) {
|
|
2655
|
+
data = JSON.parse(variantKey);
|
|
2656
|
+
parsedGroupId = data.groupId;
|
|
2657
|
+
parsedLang = data.lang;
|
|
2658
|
+
m3u8_2 = currentVod.getLiveMediaAudioSequences(playheadState[MSDKeys.mediaSeq], parsedGroupId, parsedLang, playheadState[MSDKeys.vodMediaSeq], sessionState[MSDKeys.discSeq], this.targetDurationPadding, this.forceTargetDuration);
|
|
2659
|
+
}
|
|
2660
|
+
else if (variantType === "subtitle") {
|
|
2661
|
+
data = JSON.parse(variantKey);
|
|
2662
|
+
parsedGroupId = data.groupId;
|
|
2663
|
+
parsedLang = data.lang;
|
|
2664
|
+
m3u8_2 = currentVod.getLiveMediaSubtitleSequences(playheadState[MSDKeys.mediaSeq], parsedGroupId, parsedLang, playheadState[MSDKeys.vodMediaSeq], sessionState[MSDKeys.discSeq], this.targetDurationPadding, this.forceTargetDuration);
|
|
2665
|
+
}
|
|
2666
|
+
debug("[".concat(this._sessionId, "]:").concat(playbackSessionId ? "[".concat(playbackSessionId, "]:") : "", " [").concat(manifestMseq, "][").concat(manifestDseq, "][+").concat(this.targetDurationPadding || 0, "] Current ").concat(variantType, " manifest for ").concat(variantType === "video" ? variantKey : "".concat(parsedGroupId, "_").concat(parsedLang), " requested"));
|
|
2667
|
+
this.prevVodMediaSeq[variantType] = playheadState[MSDKeys.vodMediaSeq];
|
|
2668
|
+
this.prevMediaSeqOffset[variantType] = playheadState[MSDKeys.mediaSeq];
|
|
2669
|
+
return [2 /*return*/, m3u8_2];
|
|
2670
|
+
case 21:
|
|
2671
|
+
err_7 = _a.sent();
|
|
2672
|
+
logerror(this._sessionId, err_7);
|
|
2673
|
+
return [4 /*yield*/, this._sessionState.clearCurrentVodCache()];
|
|
2674
|
+
case 22:
|
|
2675
|
+
_a.sent(); // force reading up from shared store
|
|
2676
|
+
throw new Error("Failed to generate manifest: " + JSON.stringify(playheadState));
|
|
2677
|
+
case 23: return [3 /*break*/, 25];
|
|
2678
|
+
case 24: throw new Error("Engine not ready");
|
|
2679
|
+
case 25: return [2 /*return*/];
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
});
|
|
2683
|
+
};
|
|
2684
|
+
Session.prototype._getCurrentVodData = function (dataName, variantType, vod) {
|
|
2685
|
+
switch (dataName) {
|
|
2686
|
+
case "discontinuities":
|
|
2687
|
+
if (variantType === "video")
|
|
2688
|
+
return vod.discontinuities;
|
|
2689
|
+
if (variantType === "audio")
|
|
2690
|
+
return vod.discontinuitiesAudio;
|
|
2691
|
+
if (variantType === "subtitle")
|
|
2692
|
+
return vod.discontinuitiesSubtitle;
|
|
2693
|
+
case "mseqValues":
|
|
2694
|
+
if (variantType === "video")
|
|
2695
|
+
return vod.mediaSequenceValues;
|
|
2696
|
+
if (variantType === "audio")
|
|
2697
|
+
return vod.mediaSequenceValuesAudio;
|
|
2698
|
+
if (variantType === "subtitle")
|
|
2699
|
+
return vod.mediaSequenceValuesSubtitle;
|
|
2700
|
+
case "finalMseqValue":
|
|
2701
|
+
if (variantType === "video")
|
|
2702
|
+
return vod.getLastSequenceMediaSequenceValue();
|
|
2703
|
+
if (variantType === "audio")
|
|
2704
|
+
return vod.getLastSequenceMediaSequenceValueAudio();
|
|
2705
|
+
if (variantType === "subtitle")
|
|
2706
|
+
return vod.getLastSequenceMediaSequenceValueSubtitle();
|
|
2707
|
+
case "mseqCount":
|
|
2708
|
+
if (variantType === "video")
|
|
2709
|
+
return vod.getLiveMediaSequencesCount();
|
|
2710
|
+
if (variantType === "audio")
|
|
2711
|
+
return vod.getLiveMediaSequencesCount("audio");
|
|
2712
|
+
if (variantType === "subtitle")
|
|
2713
|
+
return vod.getLiveMediaSequencesCount("subtitle");
|
|
2714
|
+
default:
|
|
2715
|
+
console.log("WARNING! dataName:".concat(dataName, " is not valid"));
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2763
2718
|
return Session;
|
|
2764
2719
|
}());
|
|
2765
2720
|
module.exports = Session;
|
package/dist/examples/demux.js
CHANGED
|
@@ -78,7 +78,7 @@ var RefChannelManager = /** @class */ (function () {
|
|
|
78
78
|
RefChannelManager.prototype._getAudioTracks = function () {
|
|
79
79
|
return [
|
|
80
80
|
{ language: "en", name: "English", default: true },
|
|
81
|
-
{ language: "
|
|
81
|
+
{ language: "sp", name: "Spanish", default: false },
|
|
82
82
|
];
|
|
83
83
|
};
|
|
84
84
|
RefChannelManager.prototype._getSubtitleTracks = function () {
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.10",
|
|
4
4
|
"description": "OTT TV Channel Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@eyevinn/hls-repeat": "^0.2.0",
|
|
32
32
|
"@eyevinn/hls-truncate": "^0.3.0",
|
|
33
|
-
"@eyevinn/hls-vodtolive": "^4.1.
|
|
33
|
+
"@eyevinn/hls-vodtolive": "^4.1.1",
|
|
34
34
|
"@eyevinn/m3u8": "^0.5.3",
|
|
35
35
|
"abort-controller": "^3.0.0",
|
|
36
36
|
"debug": "^3.2.7",
|
package/engine/session.js
CHANGED
|
@@ -560,231 +560,26 @@ class Session {
|
|
|
560
560
|
if (!this._sessionState) {
|
|
561
561
|
throw new Error('Session not ready');
|
|
562
562
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
let tries = 12;
|
|
566
|
-
while (tries > 0 && this.leaderIsSettingNextVod) {
|
|
567
|
-
debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
|
|
568
|
-
await timer(500);
|
|
569
|
-
tries--;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
|
|
573
|
-
let playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
574
|
-
|
|
575
|
-
if (this.prevVodMediaSeq.video === null) {
|
|
576
|
-
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
577
|
-
}
|
|
578
|
-
if (this.prevMediaSeqOffset.video === null) {
|
|
579
|
-
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo || (playheadState.vodMediaSeqVideo < sessionState.vodMediaSeqVideo && playheadState.mediaSeq === this.prevMediaSeqOffset.video)) {
|
|
583
|
-
const state = await this._sessionState.get("state");
|
|
584
|
-
const DELAY_TIME_MS = 1000;
|
|
585
|
-
const ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
586
|
-
debug(`[${this._sessionId}]: Recently ${ACTION} Vod. PlayheadState not up-to-date (${playheadState.vodMediaSeqVideo}_${sessionState.vodMediaSeqVideo}). Waiting ${DELAY_TIME_MS}ms before reading from store again`);
|
|
587
|
-
await timer(DELAY_TIME_MS);
|
|
588
|
-
playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Force reading up from store, but only once if the condition is right
|
|
592
|
-
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
593
|
-
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
594
|
-
debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
|
|
595
|
-
// If true, then we have not updated the prev-values and not cleared the cache yet.
|
|
596
|
-
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
597
|
-
this.isAllowedToClearVodCache = true;
|
|
598
|
-
}
|
|
599
|
-
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
600
|
-
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
601
|
-
debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
|
|
602
|
-
await this._sessionState.clearCurrentVodCache();
|
|
603
|
-
this.isAllowedToClearVodCache = false;
|
|
604
|
-
const diffMs = await this._playheadState.get("diffCompensation");
|
|
605
|
-
if (diffMs) {
|
|
606
|
-
this.diffCompensation = diffMs;
|
|
607
|
-
debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
|
|
608
|
-
if (this.diffCompensation) {
|
|
609
|
-
this.timePositionOffset = this.diffCompensation;
|
|
610
|
-
cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
|
|
611
|
-
{ event: 'timePositionOffsetUpdated', channel: this._sessionId, offsetMs: this.timePositionOffset });
|
|
612
|
-
} else {
|
|
613
|
-
this.timePositionOffset = 0;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
} else {
|
|
618
|
-
this.isAllowedToClearVodCache = true;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const currentVod = await this._sessionState.getCurrentVod();
|
|
622
|
-
if (currentVod) {
|
|
623
|
-
try {
|
|
624
|
-
let manifestMseq = playheadState.mediaSeq + playheadState.vodMediaSeqVideo;
|
|
625
|
-
let manifestDseq = sessionState.discSeq + currentVod.discontinuities[playheadState.vodMediaSeqVideo];
|
|
626
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
627
|
-
const mediaSequenceValue = currentVod.mediaSequenceValues[playheadState.vodMediaSeqVideo];
|
|
628
|
-
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValue()}}`);
|
|
629
|
-
manifestMseq = playheadState.mediaSeq + mediaSequenceValue;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqVideo}]_[${currentVod.getLiveMediaSequencesCount()}]`);
|
|
633
|
-
const m3u8 = currentVod.getLiveMediaSequences(playheadState.mediaSeq, bw, playheadState.vodMediaSeqVideo, sessionState.discSeq, this.targetDurationPadding, this.forceTargetDuration);
|
|
634
|
-
debug(`[${this._sessionId}]: [${manifestMseq}][${manifestDseq}][+${this.targetDurationPadding || 0}] Current media manifest for ${bw} requested`);
|
|
635
|
-
this.prevVodMediaSeq.video = playheadState.vodMediaSeqVideo;
|
|
636
|
-
this.prevMediaSeqOffset.video = playheadState.mediaSeq;
|
|
637
|
-
return m3u8;
|
|
638
|
-
} catch (err) {
|
|
639
|
-
logerror(this._sessionId, err);
|
|
640
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
641
|
-
throw new Error("Failed to generate manifest: " + JSON.stringify(playheadState));
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
throw new Error("Engine not ready");
|
|
645
|
-
}
|
|
563
|
+
const m3u8 = this._getM3u8File("video", bw, playbackSessionId);
|
|
564
|
+
return m3u8;
|
|
646
565
|
}
|
|
647
566
|
|
|
648
567
|
async getCurrentAudioManifestAsync(audioGroupId, audioLanguage, playbackSessionId) {
|
|
649
568
|
if (!this._sessionState) {
|
|
650
569
|
throw new Error('Session not ready');
|
|
651
570
|
}
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (playheadState.vodMediaSeqAudio > sessionState.vodMediaSeqAudio || (playheadState.vodMediaSeqAudio < sessionState.vodMediaSeqAudio && playheadState.mediaSeqAudio === this.prevMediaSeqOffset.audio)) {
|
|
657
|
-
const state = await this._sessionState.get("state");
|
|
658
|
-
const DELAY_TIME_MS = 1000;
|
|
659
|
-
const ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
660
|
-
debug(`[${this._sessionId}]: Recently ${ACTION} Vod. PlayheadState not up-to-date (${playheadState.vodMediaSeqAudio}_${sessionState.vodMediaSeqAudio}). Waiting ${DELAY_TIME_MS}ms before reading from store again`);
|
|
661
|
-
await timer(DELAY_TIME_MS);
|
|
662
|
-
playheadState = await this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio"]);
|
|
663
|
-
}
|
|
664
|
-
// local store the prev values
|
|
665
|
-
if (this.prevVodMediaSeq.audio === null) {
|
|
666
|
-
this.prevVodMediaSeq.audio = playheadState.vodMediaSeqAudio;
|
|
667
|
-
}
|
|
668
|
-
if (this.prevMediaSeqOffset.audio === null) {
|
|
669
|
-
this.prevMediaSeqOffset.audio = playheadState.mediaSeqAudio;
|
|
670
|
-
}
|
|
671
|
-
currentVod = await this._sessionState.getCurrentVod();
|
|
672
|
-
if (currentVod) {
|
|
673
|
-
// condition suggesting that a new vod should exist
|
|
674
|
-
if (playheadState.vodMediaSeqAudio < 2 || playheadState.mediaSeqAudio !== this.prevMediaSeqOffset.audio) {
|
|
675
|
-
if (playheadState.playheadRef > this.currentPlayheadRef) {
|
|
676
|
-
await timer(500);
|
|
677
|
-
this.currentPlayheadRef = playheadState.playheadRef;
|
|
678
|
-
debug(`[${this._sessionId}]: While requesting audio manifest for ${audioGroupId}-${audioLanguage}, (mseq=${playheadState.vodMediaSeqAudio})`);
|
|
679
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
680
|
-
currentVod = await this._sessionState.getCurrentVod();
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
try {
|
|
684
|
-
let manifestMseq = playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio;
|
|
685
|
-
let manifestDseq = sessionState.discSeqAudio + currentVod.discontinuitiesAudio[playheadState.vodMediaSeqAudio];
|
|
686
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
687
|
-
const mediaSequenceValue = currentVod.mediaSequenceValuesAudio[playheadState.vodMediaSeqAudio];
|
|
688
|
-
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValueAudio()}} AUDIO`);
|
|
689
|
-
manifestMseq = playheadState.mediaSeqAudio + mediaSequenceValue;
|
|
690
|
-
}
|
|
691
|
-
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqAudio}]_[${currentVod.getLiveMediaSequencesCount("audio")}] AUDIO (${audioGroupId})`);
|
|
692
|
-
const m3u8 = currentVod.getLiveMediaAudioSequences(
|
|
693
|
-
playheadState.mediaSeqAudio,
|
|
694
|
-
audioGroupId,
|
|
695
|
-
audioLanguage,
|
|
696
|
-
playheadState.vodMediaSeqAudio,
|
|
697
|
-
sessionState.discSeqAudio,
|
|
698
|
-
this.targetDurationPadding,
|
|
699
|
-
this.forceTargetDuration
|
|
700
|
-
);
|
|
701
|
-
// # Case: current VOD does not have the selected track.
|
|
702
|
-
if (!m3u8) {
|
|
703
|
-
debug(`[${this._sessionId}]: [${playheadState.mediaSeqAudio + playheadState.vodMediaSeqAudio}] Request Failed for current audio manifest for ${audioGroupId}-${audioLanguage}`);
|
|
704
|
-
}
|
|
705
|
-
debug(`[${this._sessionId}]: [${manifestMseq}][${manifestDseq}] Current audio manifest for ${audioGroupId}-${audioLanguage} requested`);
|
|
706
|
-
this.prevVodMediaSeq.audio = playheadState.vodMediaSeqAudio;
|
|
707
|
-
this.prevMediaSeqOffset.audio = playheadState.mediaSeqAudio;
|
|
708
|
-
return m3u8;
|
|
709
|
-
} catch (err) {
|
|
710
|
-
logerror(this._sessionId, err);
|
|
711
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
712
|
-
throw new Error("Failed to generate audio manifest: " + JSON.stringify(playheadState));
|
|
713
|
-
}
|
|
714
|
-
} else {
|
|
715
|
-
throw new Error("Engine not ready");
|
|
716
|
-
}
|
|
571
|
+
const variantKey = JSON.stringify({groupId: audioGroupId, lang: audioLanguage});
|
|
572
|
+
const m3u8 = this._getM3u8File("audio", variantKey, playbackSessionId);
|
|
573
|
+
return m3u8;
|
|
717
574
|
}
|
|
718
575
|
|
|
719
|
-
async getCurrentSubtitleManifestAsync(subtitleGroupId, subtitleLanguage) {
|
|
576
|
+
async getCurrentSubtitleManifestAsync(subtitleGroupId, subtitleLanguage, playbackSessionId) {
|
|
720
577
|
if (!this._sessionState) {
|
|
721
578
|
throw new Error('Session not ready');
|
|
722
579
|
}
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (playheadState.vodMediaSeqSubtitle > sessionState.vodMediaSeqSubtitle || (playheadState.vodMediaSeqSubtitle < sessionState.vodMediaSeqSubtitle && playheadState.mediaSeqSubtitle === this.prevMediaSeqOffset.subtitle)) {
|
|
728
|
-
const state = await this._sessionState.get("state");
|
|
729
|
-
const DELAY_TIME_MS = 1000;
|
|
730
|
-
const ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
731
|
-
debug(`[${this._sessionId}]: Recently ${ACTION} Vod. PlayheadState not up-to-date (${playheadState.vodMediaSeqSubtitle}_${sessionState.vodMediaSeqSubtitle}). Waiting ${DELAY_TIME_MS}ms before reading from store again`);
|
|
732
|
-
await timer(DELAY_TIME_MS);
|
|
733
|
-
playheadState = await this._playheadState.getValues(["mediaSeqSubtitle", "vodMediaSeqSubtitle"]);
|
|
734
|
-
}
|
|
735
|
-
// local store the prev values
|
|
736
|
-
if (this.prevVodMediaSeq.subtitle === null) {
|
|
737
|
-
this.prevVodMediaSeq.subtitle = playheadState.vodMediaSeqSubtitle;
|
|
738
|
-
}
|
|
739
|
-
if (this.prevMediaSeqOffset.subtitle === null) {
|
|
740
|
-
this.prevMediaSeqOffset.subtitle = playheadState.mediaSeqSubtitle;
|
|
741
|
-
}
|
|
742
|
-
currentVod = await this._sessionState.getCurrentVod();
|
|
743
|
-
if (currentVod) {
|
|
744
|
-
// condition suggesting that a new vod should exist
|
|
745
|
-
if (playheadState.vodMediaSeqSubtitle < 2 || playheadState.mediaSeqSubtitle !== this.prevMediaSeqOffset.subtitle) {
|
|
746
|
-
if (playheadState.playheadRef > this.currentPlayheadRef) {
|
|
747
|
-
await timer(500);
|
|
748
|
-
this.currentPlayheadRef = playheadState.playheadRef;
|
|
749
|
-
debug(`[${this._sessionId}]: While requesting subtitle manifest for ${subtitleGroupId}-${subtitleLanguage}, (mseq=${playheadState.vodMediaSeqSubtitle})`);
|
|
750
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
751
|
-
currentVod = await this._sessionState.getCurrentVod();
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
try {
|
|
755
|
-
let manifestMseq = playheadState.mediaSeqSubtitle + playheadState.vodMediaSeqSubtitle;
|
|
756
|
-
let manifestDseq = sessionState.discSeqSubtitle + currentVod.discontinuitiesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
757
|
-
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
758
|
-
const mediaSequenceValue = currentVod.mediaSequenceValuesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
759
|
-
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${currentVod.getLastSequenceMediaSequenceValueSubtitle()}} SUBTITLES`);
|
|
760
|
-
manifestMseq = playheadState.mediaSeqSubtitle + mediaSequenceValue;
|
|
761
|
-
}
|
|
762
|
-
debug(`[${this._sessionId}]: [${playheadState.vodMediaSeqSubtitle}]_[${currentVod.getLiveMediaSequencesCount("subtitle")}] SUBTITLES (${subtitleGroupId})`);
|
|
763
|
-
const m3u8 = currentVod.getLiveMediaSubtitleSequences(
|
|
764
|
-
playheadState.mediaSeqSubtitle,
|
|
765
|
-
subtitleGroupId,
|
|
766
|
-
subtitleLanguage,
|
|
767
|
-
playheadState.vodMediaSeqSubtitle,
|
|
768
|
-
sessionState.discSeqSubtitle,
|
|
769
|
-
this.targetDurationPadding,
|
|
770
|
-
this.forceTargetDuration
|
|
771
|
-
);
|
|
772
|
-
// # Case: current VOD does not have the selected track.
|
|
773
|
-
if (!m3u8) {
|
|
774
|
-
debug(`[${this._sessionId}]: [${playheadState.mediaSeqSubtitle + playheadState.vodMediaSeqSubtitle}] Request Failed for current subtitle manifest for ${subtitleGroupId}-${subtitleLanguage}`);
|
|
775
|
-
}
|
|
776
|
-
debug(`[${this._sessionId}]: [${manifestMseq}][${manifestDseq}] Current subtitle manifest for ${subtitleGroupId}-${subtitleLanguage} requested`);
|
|
777
|
-
this.prevVodMediaSeq.subtitle = playheadState.vodMediaSeqSubtitle;
|
|
778
|
-
this.prevMediaSeqOffset.subtitle = playheadState.mediaSeqSubtitle;
|
|
779
|
-
return m3u8;
|
|
780
|
-
} catch (err) {
|
|
781
|
-
logerror(this._sessionId, err);
|
|
782
|
-
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
783
|
-
throw new Error("Failed to generate subtitle manifest: " + JSON.stringify(playheadState));
|
|
784
|
-
}
|
|
785
|
-
} else {
|
|
786
|
-
throw new Error("Engine not ready");
|
|
787
|
-
}
|
|
580
|
+
const variantKey = JSON.stringify({groupId: subtitleGroupId, lang: subtitleLanguage});
|
|
581
|
+
const m3u8 = this._getM3u8File("subtitle", variantKey, playbackSessionId);
|
|
582
|
+
return m3u8;
|
|
788
583
|
}
|
|
789
584
|
|
|
790
585
|
async incrementAsync() {
|
|
@@ -923,10 +718,19 @@ class Session {
|
|
|
923
718
|
const mediaSequenceValueAudio = currentVod.mediaSequenceValuesAudio[playheadState.vodMediaSeqAudio];
|
|
924
719
|
audioInfo = ` mseq[A]={${playheadState.mediaSeqAudio + mediaSequenceValueAudio}}`
|
|
925
720
|
}
|
|
926
|
-
|
|
721
|
+
let subsInfo = "";
|
|
722
|
+
if (this.use_vtt_subtitles) {
|
|
723
|
+
const mediaSequenceValueSubtitle = currentVod.mediaSequenceValuesSubtitle[playheadState.vodMediaSeqSubtitle];
|
|
724
|
+
subsInfo = ` mseq[S]={${playheadState.mediaSeqSubtitle + mediaSequenceValueSubtitle}}`
|
|
725
|
+
}
|
|
726
|
+
debug(`[${this._sessionId}]: Session can now serve mseq[V]={${playheadState.mediaSeq + mediaSequenceValue}}` + audioInfo + subsInfo);
|
|
927
727
|
}
|
|
928
728
|
|
|
929
|
-
debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio}
|
|
729
|
+
debug(`[${this._sessionId}]: INCREMENT (mseq=${playheadState.mediaSeq + playheadState.vodMediaSeqVideo}) vodMediaSeq=(${playheadState.vodMediaSeqVideo}_${playheadState.vodMediaSeqAudio}${
|
|
730
|
+
this.use_vtt_subtitles ? `_${playheadState.vodMediaSeqSubtitle}` : ""
|
|
731
|
+
} of ${currentVod.getLiveMediaSequencesCount()}_${currentVod.getLiveMediaSequencesCount("audio")}${
|
|
732
|
+
this.use_vtt_subtitles ? `_${currentVod.getLiveMediaSequencesCount("subtitle")})` : ")"
|
|
733
|
+
}`);
|
|
930
734
|
|
|
931
735
|
// As a FOLLOWER, we might need to read up from shared store...
|
|
932
736
|
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
@@ -2098,7 +1902,7 @@ class Session {
|
|
|
2098
1902
|
let positionV = _currentPosVideo ? _currentPosVideo / 1000 : 0;
|
|
2099
1903
|
let positionX;
|
|
2100
1904
|
let posDiff;
|
|
2101
|
-
const threshold = 0.
|
|
1905
|
+
const threshold = 0.500;
|
|
2102
1906
|
while (extraMediaIncrement < _extraSeqFinalIndex) {
|
|
2103
1907
|
const currentPosExtraMedia = (await _getExtraPlayheadPositionAsyncFn(_vodMediaSeqExtra + extraMediaIncrement)) * 1000;
|
|
2104
1908
|
positionX = currentPosExtraMedia ? currentPosExtraMedia / 1000 : 0;
|
|
@@ -2125,6 +1929,204 @@ class Session {
|
|
|
2125
1929
|
diff: posDiff
|
|
2126
1930
|
};
|
|
2127
1931
|
}
|
|
1932
|
+
|
|
1933
|
+
async _getM3u8File(variantType, variantKey, playbackSessionId) {
|
|
1934
|
+
// Be sure that the leader is not in the middle of setting new vod data in store.
|
|
1935
|
+
// Followers will never run this part...
|
|
1936
|
+
let tries = 12;
|
|
1937
|
+
while (tries > 0 && this.leaderIsSettingNextVod) {
|
|
1938
|
+
const delayMs = 250;
|
|
1939
|
+
debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting ${delayMs}ms_${tries}`);
|
|
1940
|
+
await timer(delayMs);
|
|
1941
|
+
tries--;
|
|
1942
|
+
}
|
|
1943
|
+
// Media Sequence Data Keys
|
|
1944
|
+
const MSDKeys = {
|
|
1945
|
+
mediaSeq: null,
|
|
1946
|
+
vodMediaSeq: null,
|
|
1947
|
+
discSeq: null
|
|
1948
|
+
};
|
|
1949
|
+
if (variantType === "video") {
|
|
1950
|
+
MSDKeys.mediaSeq = "mediaSeq";
|
|
1951
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqVideo";
|
|
1952
|
+
MSDKeys.discSeq = "discSeq";
|
|
1953
|
+
} else if (variantType === "audio") {
|
|
1954
|
+
MSDKeys.mediaSeq = "mediaSeqAudio";
|
|
1955
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqAudio";
|
|
1956
|
+
MSDKeys.discSeq = "discSeqAudio";
|
|
1957
|
+
} else if (variantType === "subtitle") {
|
|
1958
|
+
MSDKeys.mediaSeq = "mediaSeqSubtitle";
|
|
1959
|
+
MSDKeys.vodMediaSeq = "vodMediaSeqSubtitle";
|
|
1960
|
+
MSDKeys.discSeq = "discSeqSubtitle";
|
|
1961
|
+
}
|
|
1962
|
+
let currentVod = null;
|
|
1963
|
+
const sessionState = await this._sessionState.getValues([MSDKeys.vodMediaSeq, MSDKeys.discSeq]);
|
|
1964
|
+
let playheadState = await this._playheadState.getValues([MSDKeys.mediaSeq, MSDKeys.vodMediaSeq, "playheadRef"]);
|
|
1965
|
+
|
|
1966
|
+
if (this.prevVodMediaSeq[variantType] === null) {
|
|
1967
|
+
this.prevVodMediaSeq[variantType] = playheadState[MSDKeys.vodMediaSeq];
|
|
1968
|
+
}
|
|
1969
|
+
if (this.prevMediaSeqOffset[variantType] === null) {
|
|
1970
|
+
this.prevMediaSeqOffset[variantType] = playheadState[MSDKeys.mediaSeq];
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
if (
|
|
1974
|
+
playheadState[MSDKeys.vodMediaSeq] > sessionState[MSDKeys.vodMediaSeq] ||
|
|
1975
|
+
(playheadState[MSDKeys.vodMediaSeq] < sessionState[MSDKeys.vodMediaSeq] &&
|
|
1976
|
+
playheadState[MSDKeys.mediaSeq] === this.prevMediaSeqOffset[variantType])
|
|
1977
|
+
) {
|
|
1978
|
+
const state = await this._sessionState.get("state");
|
|
1979
|
+
const DELAY_TIME_MS = 1000;
|
|
1980
|
+
const ACTION = [SessionState.VOD_RELOAD_INIT, SessionState.VOD_RELOAD_INITIATING].includes(state) ? "Reloaded" : "Loaded Next";
|
|
1981
|
+
debug(
|
|
1982
|
+
`[${this._sessionId}]: Recently ${ACTION} Vod. PlayheadState not up-to-date (${playheadState[MSDKeys.vodMediaSeq]}_${
|
|
1983
|
+
sessionState[MSDKeys.vodMediaSeq]
|
|
1984
|
+
}). Waiting ${DELAY_TIME_MS}ms before reading from store again (${variantType})`
|
|
1985
|
+
);
|
|
1986
|
+
await timer(DELAY_TIME_MS);
|
|
1987
|
+
playheadState = await this._playheadState.getValues([MSDKeys.mediaSeq, MSDKeys.vodMediaSeq]);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
if (variantType === "video") {
|
|
1991
|
+
// Force reading up from store, but only once if the condition is right.
|
|
1992
|
+
if (playheadState.vodMediaSeqVideo < 2 || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
1993
|
+
debug(`[${this._sessionId}]: current[${playheadState.vodMediaSeqVideo}]_prev[${this.prevVodMediaSeq.video}]`);
|
|
1994
|
+
debug(`[${this._sessionId}]: current-offset[${playheadState.mediaSeq}]_prev-offset[${this.prevMediaSeqOffset.video}]`);
|
|
1995
|
+
// If true, then we have not updated the prev-values and not cleared the cache yet.
|
|
1996
|
+
if (playheadState.vodMediaSeqVideo < this.prevVodMediaSeq.video || playheadState.mediaSeq !== this.prevMediaSeqOffset.video) {
|
|
1997
|
+
this.isAllowedToClearVodCache = true;
|
|
1998
|
+
}
|
|
1999
|
+
const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
|
|
2000
|
+
if (!isLeader && this.isAllowedToClearVodCache) {
|
|
2001
|
+
debug(
|
|
2002
|
+
`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`
|
|
2003
|
+
);
|
|
2004
|
+
await this._sessionState.clearCurrentVodCache();
|
|
2005
|
+
this.isAllowedToClearVodCache = false;
|
|
2006
|
+
const diffMs = await this._playheadState.get("diffCompensation");
|
|
2007
|
+
if (diffMs) {
|
|
2008
|
+
this.diffCompensation = diffMs;
|
|
2009
|
+
debug(`[${this._sessionId}]: Setting diffCompensation->${this.diffCompensation}`);
|
|
2010
|
+
if (this.diffCompensation) {
|
|
2011
|
+
this.timePositionOffset = this.diffCompensation;
|
|
2012
|
+
cloudWatchLog(!this.cloudWatchLogging, "engine-session", {
|
|
2013
|
+
event: "timePositionOffsetUpdated",
|
|
2014
|
+
channel: this._sessionId,
|
|
2015
|
+
offsetMs: this.timePositionOffset,
|
|
2016
|
+
});
|
|
2017
|
+
} else {
|
|
2018
|
+
this.timePositionOffset = 0;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
} else {
|
|
2023
|
+
this.isAllowedToClearVodCache = true;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
currentVod = await this._sessionState.getCurrentVod();
|
|
2028
|
+
if (currentVod) {
|
|
2029
|
+
// condition suggesting that a new vod should exist
|
|
2030
|
+
if (playheadState[MSDKeys.vodMediaSeq] < 2 || playheadState[MSDKeys.mediaSeq] !== this.prevMediaSeqOffset.video) {
|
|
2031
|
+
if (playheadState.playheadRef > this.currentPlayheadRef) {
|
|
2032
|
+
await timer(500);
|
|
2033
|
+
this.currentPlayheadRef = playheadState.playheadRef;
|
|
2034
|
+
debug(`[${this._sessionId}]: While requesting ${variantType} manifest for ${variantKey}, (mseq=${playheadState[MSDKeys.vodMediaSeq]})`);
|
|
2035
|
+
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
2036
|
+
currentVod = await this._sessionState.getCurrentVod();
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
try {
|
|
2040
|
+
let manifestMseq = playheadState[MSDKeys.mediaSeq] + playheadState[MSDKeys.vodMediaSeq];
|
|
2041
|
+
let manifestDseq =
|
|
2042
|
+
sessionState[MSDKeys.discSeq] + this._getCurrentVodData("discontinuities", variantType, currentVod)[playheadState[MSDKeys.vodMediaSeq]];
|
|
2043
|
+
if (currentVod.sequenceAlwaysContainNewSegments) {
|
|
2044
|
+
const mediaSequenceValue = this._getCurrentVodData("mseqValues", variantType, currentVod)[playheadState[MSDKeys.vodMediaSeq]];
|
|
2045
|
+
debug(`[${this._sessionId}]: {${mediaSequenceValue}}_{${this._getCurrentVodData("finalMseqValue", variantType, currentVod)}}`);
|
|
2046
|
+
manifestMseq = playheadState[MSDKeys.mediaSeq] + mediaSequenceValue;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
debug(`[${this._sessionId}]: [${playheadState[MSDKeys.vodMediaSeq]}]_[${this._getCurrentVodData("mseqCount", variantType, currentVod)}]`);
|
|
2050
|
+
|
|
2051
|
+
let m3u8;
|
|
2052
|
+
let parsedGroupId;
|
|
2053
|
+
let parsedLang;
|
|
2054
|
+
if (variantType === "video") {
|
|
2055
|
+
m3u8 = currentVod.getLiveMediaSequences(
|
|
2056
|
+
playheadState[MSDKeys.mediaSeq],
|
|
2057
|
+
variantKey,
|
|
2058
|
+
playheadState[MSDKeys.vodMediaSeq],
|
|
2059
|
+
sessionState[MSDKeys.discSeq],
|
|
2060
|
+
this.targetDurationPadding,
|
|
2061
|
+
this.forceTargetDuration
|
|
2062
|
+
);
|
|
2063
|
+
} else if ((variantType === "audio")) {
|
|
2064
|
+
const data = JSON.parse(variantKey);
|
|
2065
|
+
parsedGroupId = data.groupId;
|
|
2066
|
+
parsedLang = data.lang;
|
|
2067
|
+
m3u8 = currentVod.getLiveMediaAudioSequences(
|
|
2068
|
+
playheadState[MSDKeys.mediaSeq],
|
|
2069
|
+
parsedGroupId,
|
|
2070
|
+
parsedLang,
|
|
2071
|
+
playheadState[MSDKeys.vodMediaSeq],
|
|
2072
|
+
sessionState[MSDKeys.discSeq],
|
|
2073
|
+
this.targetDurationPadding,
|
|
2074
|
+
this.forceTargetDuration
|
|
2075
|
+
);
|
|
2076
|
+
} else if (variantType === "subtitle") {
|
|
2077
|
+
const data = JSON.parse(variantKey);
|
|
2078
|
+
parsedGroupId = data.groupId;
|
|
2079
|
+
parsedLang = data.lang;
|
|
2080
|
+
m3u8 = currentVod.getLiveMediaSubtitleSequences(
|
|
2081
|
+
playheadState[MSDKeys.mediaSeq],
|
|
2082
|
+
parsedGroupId,
|
|
2083
|
+
parsedLang,
|
|
2084
|
+
playheadState[MSDKeys.vodMediaSeq],
|
|
2085
|
+
sessionState[MSDKeys.discSeq],
|
|
2086
|
+
this.targetDurationPadding,
|
|
2087
|
+
this.forceTargetDuration
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
debug(
|
|
2091
|
+
`[${this._sessionId}]:${playbackSessionId ? `[${playbackSessionId}]:` : ""} [${manifestMseq}][${manifestDseq}][+${this.targetDurationPadding || 0}] Current ${variantType} manifest for ${
|
|
2092
|
+
variantType === "video" ? variantKey : `${parsedGroupId}_${parsedLang}`
|
|
2093
|
+
} requested`
|
|
2094
|
+
);
|
|
2095
|
+
this.prevVodMediaSeq[variantType] = playheadState[MSDKeys.vodMediaSeq];
|
|
2096
|
+
this.prevMediaSeqOffset[variantType] = playheadState[MSDKeys.mediaSeq];
|
|
2097
|
+
return m3u8;
|
|
2098
|
+
} catch (err) {
|
|
2099
|
+
logerror(this._sessionId, err);
|
|
2100
|
+
await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
|
|
2101
|
+
throw new Error("Failed to generate manifest: " + JSON.stringify(playheadState));
|
|
2102
|
+
}
|
|
2103
|
+
} else {
|
|
2104
|
+
throw new Error("Engine not ready");
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
_getCurrentVodData(dataName, variantType, vod) {
|
|
2109
|
+
switch (dataName) {
|
|
2110
|
+
case "discontinuities":
|
|
2111
|
+
if (variantType === "video") return vod.discontinuities;
|
|
2112
|
+
if (variantType === "audio") return vod.discontinuitiesAudio;
|
|
2113
|
+
if (variantType === "subtitle") return vod.discontinuitiesSubtitle;
|
|
2114
|
+
case "mseqValues":
|
|
2115
|
+
if (variantType === "video") return vod.mediaSequenceValues;
|
|
2116
|
+
if (variantType === "audio") return vod.mediaSequenceValuesAudio;
|
|
2117
|
+
if (variantType === "subtitle") return vod.mediaSequenceValuesSubtitle;
|
|
2118
|
+
case "finalMseqValue":
|
|
2119
|
+
if (variantType === "video") return vod.getLastSequenceMediaSequenceValue();
|
|
2120
|
+
if (variantType === "audio") return vod.getLastSequenceMediaSequenceValueAudio();
|
|
2121
|
+
if (variantType === "subtitle") return vod.getLastSequenceMediaSequenceValueSubtitle();
|
|
2122
|
+
case "mseqCount":
|
|
2123
|
+
if (variantType === "video") return vod.getLiveMediaSequencesCount();
|
|
2124
|
+
if (variantType === "audio") return vod.getLiveMediaSequencesCount("audio");
|
|
2125
|
+
if (variantType === "subtitle") return vod.getLiveMediaSequencesCount("subtitle");
|
|
2126
|
+
default:
|
|
2127
|
+
console.log(`WARNING! dataName:${dataName} is not valid`);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2128
2130
|
}
|
|
2129
2131
|
|
|
2130
2132
|
module.exports = Session;
|
package/examples/demux.ts
CHANGED
|
@@ -89,7 +89,7 @@ class RefChannelManager implements IChannelManager {
|
|
|
89
89
|
_getAudioTracks(): AudioTracks[] {
|
|
90
90
|
return [
|
|
91
91
|
{ language: "en", name: "English", default: true },
|
|
92
|
-
{ language: "
|
|
92
|
+
{ language: "sp", name: "Spanish", default: false },
|
|
93
93
|
];
|
|
94
94
|
}
|
|
95
95
|
_getSubtitleTracks(): SubtitleTracks[] {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyevinn-channel-engine",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.10",
|
|
4
4
|
"description": "OTT TV Channel Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@eyevinn/hls-repeat": "^0.2.0",
|
|
32
32
|
"@eyevinn/hls-truncate": "^0.3.0",
|
|
33
|
-
"@eyevinn/hls-vodtolive": "^4.1.
|
|
33
|
+
"@eyevinn/hls-vodtolive": "^4.1.1",
|
|
34
34
|
"@eyevinn/m3u8": "^0.5.3",
|
|
35
35
|
"abort-controller": "^3.0.0",
|
|
36
36
|
"debug": "^3.2.7",
|