From 03bc945e70b9f904b546863047c7e5e0959b2d39 Mon Sep 17 00:00:00 2001 From: Nfrederiksen Date: Fri, 11 Aug 2023 16:49:58 +0200 Subject: [PATCH] feat: handle new partialStoreHLSVod option --- engine/server.ts | 7 ++++++ engine/session.js | 50 +++++++++++++++++++++-------------------- engine/session_state.js | 14 +++++++++++- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/engine/server.ts b/engine/server.ts index bc1c908..3df7914 100644 --- a/engine/server.ts +++ b/engine/server.ts @@ -46,6 +46,7 @@ export interface ChannelEngineOpts { subtitleSliceEndpoint?: string; useVTTSubtitles?: boolean; alwaysNewSegments?: boolean; + partialStoreHLSVod?: boolean; alwaysMapBandwidthByNearest?: boolean; diffCompensationRate?: number; staticDirectory?: string; @@ -183,6 +184,7 @@ export class ChannelEngine { private subtitleSliceEndpoint: string; private useVTTSubtitles: boolean; private alwaysNewSegments: boolean; + private partialStoreHLSVod: boolean; private alwaysMapBandwidthByNearest: boolean; private defaultSlateUri?: string; private slateDuration?: number; @@ -223,6 +225,10 @@ export class ChannelEngine { if (options && options.alwaysNewSegments) { this.alwaysNewSegments = true; } + this.partialStoreHLSVod = false; + if (options && options.partialStoreHLSVod) { + this.partialStoreHLSVod = true; + } this.alwaysMapBandwidthByNearest = false; if (options && options.alwaysMapBandwidthByNearest) { this.alwaysMapBandwidthByNearest = true; @@ -460,6 +466,7 @@ export class ChannelEngine { subtitleSliceEndpoint: this.subtitleSliceEndpoint, useVTTSubtitles: this.useVTTSubtitles, alwaysNewSegments: options.alwaysNewSegments, + partialStoreHLSVod: options.partialStoreHLSVod, alwaysMapBandwidthByNearest: options.alwaysMapBandwidthByNearest, noSessionDataTags: options.noSessionDataTags, playheadDiffThreshold: channel.options && channel.options.playheadDiffThreshold ? channel.options.playheadDiffThreshold : this.streamerOpts.defaultPlayheadDiffThreshold, diff --git a/engine/session.js b/engine/session.js index fbe51fc..88247cf 100644 --- a/engine/session.js +++ b/engine/session.js @@ -73,11 +73,17 @@ class Session { this.isAllowedToClearVodCache = null; this.alwaysNewSegments = null; this.alwaysMapBandwidthByNearest = null; + this.partialStoreHLSVod = null; + this.currentPlayheadRef = null; if (config) { if (config.alwaysNewSegments) { this.alwaysNewSegments = config.alwaysNewSegments; } + if (config.partialStoreHLSVod) { + this.partialStoreHLSVod = config.partialStoreHLSVod; + } + if (config.alwaysMapBandwidthByNearest) { this.alwaysMapBandwidthByNearest = config.alwaysMapBandwidthByNearest; } @@ -637,7 +643,7 @@ class Session { } let currentVod = null; const sessionState = await this._sessionState.getValues(["discSeqAudio", "vodMediaSeqAudio"]); - let playheadState = await this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio"]); + let playheadState = await this._playheadState.getValues(["mediaSeqAudio", "vodMediaSeqAudio", "playheadRef"]); if (playheadState.vodMediaSeqAudio > sessionState.vodMediaSeqAudio || (playheadState.vodMediaSeqAudio < sessionState.vodMediaSeqAudio && playheadState.mediaSeqAudio === this.prevMediaSeqOffset.audio)) { const state = await this._sessionState.get("state"); @@ -658,14 +664,10 @@ class Session { if (currentVod) { // condition suggesting that a new vod should exist if (playheadState.vodMediaSeqAudio < 2 || playheadState.mediaSeqAudio !== this.prevMediaSeqOffset.audio) { - const AGE_THRESH = this.averageSegmentDuration * 2; - let cacheAge = null; - if (this._sessionState.cache && this._sessionState.cache.currentVod.ts) { - cacheAge = Date.now() - this._sessionState.cache.currentVod.ts; - } - if (cacheAge !== null && cacheAge > AGE_THRESH) { + if (playheadState.playheadRef > this.currentPlayheadRef) { await timer(500); - debug(`[${this._sessionId}]: While requesting audio manifest for ${audioGroupId}-${audioLanguage}, (mseq=${playheadState.vodMediaSeqAudio})(vod cache age=${cacheAge})`); + this.currentPlayheadRef = playheadState.playheadRef; + debug(`[${this._sessionId}]: While requesting audio manifest for ${audioGroupId}-${audioLanguage}, (mseq=${playheadState.vodMediaSeqAudio})`); await this._sessionState.clearCurrentVodCache(); // force reading up from shared store currentVod = await this._sessionState.getCurrentVod(); } @@ -712,7 +714,7 @@ class Session { } let currentVod = null; const sessionState = await this._sessionState.getValues(["discSeqSubtitle", "vodMediaSeqSubtitle"]); - let playheadState = await this._playheadState.getValues(["mediaSeqSubtitle", "vodMediaSeqSubtitle"]); + let playheadState = await this._playheadState.getValues(["mediaSeqSubtitle", "vodMediaSeqSubtitle", "playheadRef"]); if (playheadState.vodMediaSeqSubtitle > sessionState.vodMediaSeqSubtitle || (playheadState.vodMediaSeqSubtitle < sessionState.vodMediaSeqSubtitle && playheadState.mediaSeqSubtitle === this.prevMediaSeqOffset.subtitle)) { const state = await this._sessionState.get("state"); @@ -733,14 +735,10 @@ class Session { if (currentVod) { // condition suggesting that a new vod should exist if (playheadState.vodMediaSeqSubtitle < 2 || playheadState.mediaSeqSubtitle !== this.prevMediaSeqOffset.subtitle) { - const AGE_THRESH = this.averageSegmentDuration * 2; - let cacheAge = null; - if (this._sessionState.cache && this._sessionState.cache.currentVod.ts) { - cacheAge = Date.now() - this._sessionState.cache.currentVod.ts; - } - if (cacheAge !== null && cacheAge > AGE_THRESH) { + if (playheadState.playheadRef > this.currentPlayheadRef) { await timer(500); - debug(`[${this._sessionId}]: While requesting subtitle manifest for ${subtitleGroupId}-${subtitleLanguage}, (mseq=${playheadState.vodMediaSeqSubtitle})(vod cache age=${cacheAge})`); + this.currentPlayheadRef = playheadState.playheadRef; + debug(`[${this._sessionId}]: While requesting subtitle manifest for ${subtitleGroupId}-${subtitleLanguage}, (mseq=${playheadState.vodMediaSeqSubtitle})`); await this._sessionState.clearCurrentVodCache(); // force reading up from shared store currentVod = await this._sessionState.getCurrentVod(); } @@ -1275,7 +1273,7 @@ class Session { await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity); await this._sessionState.set("discSeqAudio", sessionState.discSeqAudio + lastDiscontinuityAudio); await this._sessionState.set("slateCount", sessionState.slateCount + 1); - await this._playheadState.set("playheadRef", Date.now(), isLeader); + this.currentPlayheadRef = await this._playheadState.set("playheadRef", Date.now(), isLeader); cloudWatchLog(!this.cloudWatchLogging, 'engine-session', { event: 'slateInserted', channel: this._sessionId }); @@ -1331,7 +1329,8 @@ class Session { subtitleSliceEndpoint: this.subtitleSliceEndpoint, shouldContainSubtitles: this.use_vtt_subtitles, expectedSubtitleTracks: this._subtitleTracks, - alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest + alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest, + skipSerializeMediaSequences: this.partialStoreHLSVod }; newVod = new HLSVod(vodResponse.uri, [], vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts); if (vodResponse.timedMetadata) { @@ -1375,7 +1374,6 @@ class Session { sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0); sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0); sessionState.vodMediaSeqSubtitle = await this._sessionState.set("vodMediaSeqSubtitle", 0); - await this._playheadState.set("playheadRef", Date.now(), isLeader); this.produceEvent({ type: 'NOW_PLAYING', data: { @@ -1385,6 +1383,7 @@ class Session { }); sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING); sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 }); + this.currentPlayheadRef = await this._playheadState.set("playheadRef", Date.now(), isLeader); await this._sessionState.remove("nextVod"); return; } else { @@ -1508,7 +1507,8 @@ class Session { subtitleSliceEndpoint: this.subtitleSliceEndpoint, shouldContainSubtitles: this.use_vtt_subtitles, expectedSubtitleTracks: this._subtitleTracks, - alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest + alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest, + skipSerializeMediaSequences: this.partialStoreHLSVod }; newVod = new HLSVod(vodResponse.uri, null, vodResponse.unixTs, vodResponse.offset * 1000, m3u8Header(this._instanceId), hlsOpts); if (vodResponse.timedMetadata) { @@ -1560,7 +1560,7 @@ class Session { currentVod = newVod; debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}; audio msequences=${currentVod.getLiveMediaSequencesCount("audio")}; subtitle msequences=${currentVod.getLiveMediaSequencesCount("subtitle")}`); sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 }); - await this._playheadState.set("playheadRef", Date.now(), isLeader); + this.currentPlayheadRef = await this._playheadState.set("playheadRef", Date.now(), isLeader); sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0); sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0); sessionState.vodMediaSeqSubtitle = await this._sessionState.set("vodMediaSeqSubtitle", 0); @@ -1675,7 +1675,7 @@ class Session { await this._playheadState.set("vodMediaSeqVideo", 0, isLeader); await this._playheadState.set("vodMediaSeqAudio", 0, isLeader); await this._playheadState.set("vodMediaSeqSubtitle", 0, isLeader); - await this._playheadState.set("playheadRef", Date.now(), isLeader); + this.currentPlayheadRef = await this._playheadState.set("playheadRef", Date.now(), isLeader); // 4) Log to debug and cloudwatch debug(`[${this._sessionId}]: LEADER: Set new Reloaded VOD and vodMediaSeq counts in store.`); debug(`[${this._sessionId}]: next VOD Reloaded (${currentVod.getDeltaTimes()})`); @@ -1756,7 +1756,8 @@ class Session { subtitleSliceEndpoint: this.subtitleSliceEndpoint, shouldContainSubtitles: this.use_vtt_subtitles, expectedSubtitleTracks: this._subtitleTracks, - alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest + alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest, + skipSerializeMediaSequences: this.partialStoreHLSVod }; const timestamp = Date.now(); hlsVod = new HLSVod(this.slateUri, null, timestamp, null, m3u8Header(this._instanceId), hlsOpts); @@ -1860,7 +1861,8 @@ class Session { subtitleSliceEndpoint: this.subtitleSliceEndpoint, shouldContainSubtitles: this.use_vtt_subtitles, expectedSubtitleTracks: this._subtitleTracks, - alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest + alwaysMapBandwidthByNearest: this.alwaysMapBandwidthByNearest, + skipSerializeMediaSequences: this.partialStoreHLSVod }; const timestamp = Date.now(); hlsVod = new HLSVod(nexVodUri, null, timestamp, null, m3u8Header(this._instanceId), hlsOpts); diff --git a/engine/session_state.js b/engine/session_state.js index a5c680f..e5a0c25 100644 --- a/engine/session_state.js +++ b/engine/session_state.js @@ -57,9 +57,18 @@ class SharedSessionState { let hlsVod = null; if (currentVod) { if (this.store.isShared()) { - debug(`[${this.sessionId}]: reading ${currentVod.length} characters from shared store (${Date.now()} < ${this.cache.currentVod.ts + this.cache.currentVod.ttl})`); + const strToMB = (str) => { + const bytesPerCharacter = 2; // Assuming each character takes around 2 bytes in memory + const stringSizeBytes = str.length * bytesPerCharacter; + const sizeInMegabytes = stringSizeBytes / (1024 * 1024); + return sizeInMegabytes.toFixed(1); + } + debug(`[${this.sessionId}]: reading ${currentVod.length} characters or (${strToMB(currentVod)} MB) from shared store (${Date.now()} < ${this.cache.currentVod.ts + this.cache.currentVod.ttl})`); hlsVod = new HLSVod(); hlsVod.fromJSON(currentVod); + if (hlsVod.skipSerializeMediaSequences) { + await hlsVod.generateMediaSequences(); + } } else { hlsVod = currentVod; } @@ -83,6 +92,9 @@ class SharedSessionState { const currentVod = await this.get("currentVod"); hlsVod = new HLSVod(); hlsVod.fromJSON(currentVod); + if (hlsVod.skipSerializeMediaSequences) { + await hlsVod.generateMediaSequences(); + } } } else { await this.set("currentVod", hlsVod);