From c062b44e180d90869c569d85040375c1641e5e6d Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Tue, 21 May 2024 19:35:27 +0300 Subject: [PATCH 01/26] upgrade shaka --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 37bed62..ce516fd 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "karma-webpack": "^5.0.0", "mocha": "^10.0.0", "prettier": "^3.0.3", - "shaka-player": "4.7.0", + "shaka-player": "4.8.4", "sinon": "^14.0.0", "sinon-chai": "^3.7.0", "standard-version": "^6.0.1", diff --git a/yarn.lock b/yarn.lock index d7425a1..2dae980 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5534,10 +5534,10 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shaka-player@4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-4.7.0.tgz#5bb0a60c1b7c2a8a3c2d1ff82e632c3c000219c3" - integrity sha512-utR9hKMt8GiGv7EDC8/nh8F1c4KeVGa4Wd8k6h+g2Ylks0m9//kvxvXkQnYAGJRtdql/CJC9Ur8YQ/G+kTwoiQ== +shaka-player@4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-4.8.4.tgz#dee88b29122da78aee02e6ce6ca76ff6c17435ab" + integrity sha512-LtPUioN0/kwLi5ewSFaoSUpQgA01XxaSa7vneCiXP8AMIdIWVM+pk/lFetwW0br26H8Lb79djiU+5vhJujEBjQ== dependencies: eme-encryption-scheme-polyfill "^2.1.1" From 1792bdd4376bdf737b54eb36e252fb2b19da15ec Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Tue, 21 May 2024 19:35:46 +0300 Subject: [PATCH 02/26] add cachedUrls API --- src/dash-adapter.ts | 129 ++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 1a0dcdd..2e15fe7 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -14,7 +14,15 @@ import { ImageTrack, ThumbnailInfo, PKABRRestrictionObject, - filterTracksByRestriction, PKDrmDataObject, PKMediaSourceObject, IMediaSourceAdapter, FakeEvent, IDrmProtocol, PKResponseObject, PKRequestObject, PKDrmConfigObject + filterTracksByRestriction, + PKDrmDataObject, + PKMediaSourceObject, + IMediaSourceAdapter, + FakeEvent, + IDrmProtocol, + PKResponseObject, + PKRequestObject, + PKDrmConfigObject } from '@playkit-js/playkit-js'; import {Widevine} from './drm/widevine'; import {PlayReady} from './drm/playready'; @@ -22,6 +30,7 @@ import DefaultConfig from './default-config.json'; import './assets/style.css'; import {DashManifestParser} from './parser/dash-manifest-parser'; import {DashThumbnailController} from './dash-thumbnail-controller'; +import {AssetCache} from './cache/asset-cache'; type ShakaEventType = {[event: string]: string}; @@ -38,8 +47,7 @@ const ShakaEvent: ShakaEventType = { EMSG: 'emsg' }; -type ErrorEventsType = {[errorCode: string]: {'timeStamp': number, 'count': number}}; - +type ErrorEventsType = {[errorCode: string]: {timeStamp: number; count: number}}; /** * the interval in which to sample player size changes @@ -89,7 +97,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ protected static _logger = BaseMediaSourceAdapter.getLogger(DashAdapter.id); - public static textContainerClass = "shaka-text-container"; + public static textContainerClass = 'shaka-text-container'; /** * The supported mime type by the dash adapter @@ -218,7 +226,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @type ErrorEventsType * @private */ - private _errorCounter: ErrorEventsType= {} + private _errorCounter: ErrorEventsType = {}; /** * Dash thumbnail controller. * @type {DashThumbnailController} @@ -233,12 +241,16 @@ export default class DashAdapter extends BaseMediaSourceAdapter { private _selectedVideoTrack: VideoTrack | undefined | null = null; private _playbackActualUri: string | undefined; + private _loaderPromiseMap = new Map(); + + private static _assetCache = new AssetCache(); + public applyTextTrackStyles(sheet: CSSStyleSheet, styles: any, containerId: string): void { const flexAlignment = { left: 'flex-start', center: 'center', - right: 'flex-end', - } + right: 'flex-end' + }; sheet.insertRule(`#${containerId} .${DashAdapter.textContainerClass} { align-items: ${flexAlignment[styles.textAlign]}!important; }`, 0); sheet.insertRule(`#${containerId} .${DashAdapter.textContainerClass} > * { ${styles.toCSS()} }`, 0); } @@ -340,7 +352,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } } - private _getSortedTracks(): Array<{id: number, bandwidth: number, active: boolean}> { + private _getSortedTracks(): Array<{id: number; bandwidth: number; active: boolean}> { const tracks = this._shaka.getVariantTracks(); const sortedTracks = tracks .map(obj => ({ @@ -429,6 +441,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._maybeBreakStalls(); this._shaka.configure(this._config.shakaConfig); this._addBindings(); + + DashAdapter._assetCache.init(this._shaka); } private _clearStallInterval(): void { @@ -534,6 +548,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._shaka.getNetworkingEngine()?.registerResponseFilter((type, response) => { if (Object.values(RequestType).includes(type)) { const {uri: url, data, headers} = response; + + // TODO const pkResponse: PKResponseObject = {url, originalUrl: this._sourceObj!.url, data, headers}; let responseFilterPromise; try { @@ -769,28 +785,28 @@ export default class DashAdapter extends BaseMediaSourceAdapter { // called when a resource is downloaded this._shaka.getNetworkingEngine()?.registerResponseFilter((type, response) => { switch (type) { - case shaka.net.NetworkingEngine.RequestType.SEGMENT: - this._trigger(EventType.FRAG_LOADED, { - miliSeconds: response.timeMs, - bytes: response.data.byteLength, - url: response.uri - }); - if (this.isLive()) { - this._dispatchNativeEvent(EventType.DURATION_CHANGE); - } - break; - case shaka.net.NetworkingEngine.RequestType.MANIFEST: - this._parseManifest(response.data); - this._playbackActualUri = response.uri; - this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); - setTimeout(() => { - this._isLive = this._isLive || this._shaka?.isLive() as boolean; - if (this._isLive && !this._shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + case shaka.net.NetworkingEngine.RequestType.SEGMENT: + this._trigger(EventType.FRAG_LOADED, { + miliSeconds: response.timeMs, + bytes: response.data.byteLength, + url: response.uri + }); + if (this.isLive()) { + this._dispatchNativeEvent(EventType.DURATION_CHANGE); + } + break; + case shaka.net.NetworkingEngine.RequestType.MANIFEST: + this._parseManifest(response.data); + this._playbackActualUri = response.uri; + this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); + setTimeout(() => { + this._isLive = this._isLive || (this._shaka?.isLive() as boolean); + if (this._isLive && !this._shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); - } - }); - break; + } + }); + break; } }); } @@ -799,12 +815,15 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._setLowLatencyMode(); const segmentDuration = this.getSegmentDuration(); this._seekRangeStart = this._shaka.seekRange().start; - this._startOverTimeout = window.setTimeout(() => { - if (this._shaka.seekRange().start - this._seekRangeStart >= segmentDuration) { - // in start over the seekRange().start should be permanent - this._isStartOver = false; - } - }, (segmentDuration + 1) * 1000); + this._startOverTimeout = window.setTimeout( + () => { + if (this._shaka.seekRange().start - this._seekRangeStart >= segmentDuration) { + // in start over the seekRange().start should be permanent + this._isStartOver = false; + } + }, + (segmentDuration + 1) * 1000 + ); } public async _switchFromDynamicToStatic(): Promise { @@ -867,7 +886,9 @@ export default class DashAdapter extends BaseMediaSourceAdapter { public async load(startTime?: number): Promise { if (!this._loadPromise) { await this._removeMediaKeys(); + // TODO do not reattach ? this._shaka.attach(this._videoElement); + this._loadPromise = new Promise((resolve, reject) => { if (this._sourceObj && this._sourceObj.url) { this._trigger(EventType.ABR_MODE_CHANGED, {mode: this.isAdaptiveBitrateEnabled() ? 'auto' : 'manual'}); @@ -875,8 +896,15 @@ export default class DashAdapter extends BaseMediaSourceAdapter { shakaStartTime = isNaN(this._lastTimeDetach) ? shakaStartTime : this._lastTimeDetach; this._lastTimeDetach = NaN; this._maybeGetRedirectedUrl(this._sourceObj.url) - .then(url => { - return this._shaka.load(url, shakaStartTime); + .then(async url => { + debugger; + const assetPromise = DashAdapter._assetCache.get(url); + if (!assetPromise) { + return this._shaka.load(url, shakaStartTime); + } else { + DashAdapter._assetCache.remove(url); + return this._shaka.load(await assetPromise, shakaStartTime); + } }) .then(() => { const data = {tracks: this._getParsedTracks()}; @@ -1010,7 +1038,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {Array} - Array of objects with unique language and label. * @private */ - private _getAudioTracks(): (shaka.extern.LanguageRole & { active: boolean, id: number })[] { + private _getAudioTracks(): (shaka.extern.LanguageRole & {active: boolean; id: number})[] { const variantTracks = this._shaka.getVariantTracks(); const audioTracks = this._shaka.getAudioLanguagesAndRoles(); audioTracks.forEach(track => { @@ -1021,7 +1049,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { track.label = sameLangAudioVariants[0].label; track['active'] = active; }); - return audioTracks as (shaka.extern.LanguageRole & { active: boolean, id: number })[]; + return audioTracks as (shaka.extern.LanguageRole & {active: boolean; id: number})[]; } /** @@ -1348,16 +1376,16 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } private _shouldErrorChangeSeverity(errorCode): boolean { - const secondsErrorRepeat = 30 + const secondsErrorRepeat = 30; const errorsCounterToChangeSeverity = 3; const getCurrentTimeInSeconds = (): number => { return Date.now() / 1000; }; - if(!(errorCode in this._errorCounter)){ + if (!(errorCode in this._errorCounter)) { this._errorCounter[errorCode] = { count: 1, timeStamp: getCurrentTimeInSeconds() - } + }; return false; } @@ -1514,4 +1542,23 @@ export default class DashAdapter extends BaseMediaSourceAdapter { return drmDataObject; } } + + public set cachedUrls(urls: string[]) { + if (!urls || !Array.isArray(urls)) urls = []; + + const cachedUrls = DashAdapter._assetCache.list(); + + const newUrlsSet = new Set(urls); + const cachedUrlsSet = new Set(cachedUrls); + + const toRemove = cachedUrls.filter(url => !newUrlsSet.has(url)); + for (const url of toRemove) { + DashAdapter._assetCache.remove(url); + } + + const toAdd = urls.filter(url => !cachedUrlsSet.has(url)); + for (const url of toAdd) { + DashAdapter._assetCache.add(url); + } + } } From 9cf28a10ff30fafdaeed996634b9e80bc7d22b84 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Tue, 21 May 2024 19:36:02 +0300 Subject: [PATCH 03/26] add tests for cachedUrls API --- src/cache/asset-cache.ts | 68 ++++++++++++++++++++++++++ tests/src/dash-adapter.spec.js | 88 ++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/cache/asset-cache.ts diff --git a/src/cache/asset-cache.ts b/src/cache/asset-cache.ts new file mode 100644 index 0000000..959b358 --- /dev/null +++ b/src/cache/asset-cache.ts @@ -0,0 +1,68 @@ +class AssetCache { + private shakaInstance: shaka.Player | null = null; + + private preloadQueue: string[] = []; + private preloadPromiseMap = new Map(); + + public init(shakaInstance: shaka.Player) { + this.reset(); + this.shakaInstance = shakaInstance; + this.preloadAssets(); + } + + public add(assetUrl: string) { + // TODO dont add the same url twice + + this.preloadQueue.push(assetUrl); + this.preloadAssets(); + } + + public get(assetUrl: string): Promise | null { + return this.preloadPromiseMap.get(assetUrl) || null; + } + + public list(): string[] { + if (this.preloadQueue.length) { + return this.preloadQueue; + } + return [...this.preloadPromiseMap.keys()]; + } + + public remove(assetUrl: string, destroy: boolean = false) { + const index = this.preloadQueue.findIndex(item => item === assetUrl); + if (index !== -1) { + this.preloadQueue.splice(index, 1); + } else if (this.preloadPromiseMap.has(assetUrl)) { + const assetPromise = this.preloadPromiseMap.get(assetUrl); + // TODO + if (!destroy) { + assetPromise.then(loader => loader.destroy()); + } + this.preloadPromiseMap.delete(assetUrl); + } + } + + public removeAll() { + this.preloadQueue = []; + const assetUrls = this.preloadPromiseMap.keys(); + for (const assetUrl of assetUrls) { + this.remove(assetUrl, true); + } + } + + public reset() { + this.removeAll(); + this.shakaInstance = null; + } + + private preloadAssets() { + if (!this.shakaInstance) return; + + while (this.preloadQueue.length) { + const assetUrl = this.preloadQueue.pop() as string; + this.preloadPromiseMap.set(assetUrl, this.shakaInstance.preload(assetUrl)); + } + } +} + +export {AssetCache}; diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 6775e3c..9769aea 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -6,6 +6,7 @@ import {PlayReady} from '../../src/drm/playready'; import {wwDrmData, prDrmData} from './drm/fake-drm-data'; import shaka from 'shaka-player'; import {ImageTrack, ThumbnailInfo} from '@playkit-js/playkit-js'; +import { expect } from 'chai'; const targetId = 'player-placeholder_dash-adapter.spec'; @@ -1951,3 +1952,90 @@ describe('DashAdapter: on emsg', () => { }); }); }); + +describe.only('DashAdapter: cachedUrls', () => { + // TODO stub shaka + + let video, dashInstance, config, sandbox, assetCache; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + video = document.createElement('video'); + config = {playback: {options: {html5: {dash: {}}}}}; + + dashInstance = DashAdapter.createAdapter(video, vodSource, config); + }); + + afterEach(() => { + dashInstance.destroy(); + dashInstance = null; + sandbox.restore(); + + DashAdapter._assetCache.reset(); + }); + + after(() => { + TestUtils.removeVideoElementsFromTestPage(); + }); + + describe('setting value of cachedUrls', () => { + describe('on initial call', () => { + let addAsset; + + beforeEach(() => { + addAsset = sandbox.spy(DashAdapter._assetCache, "add"); + }); + + it('should not cache asset url on empty call', () => { + dashInstance.cachedUrls = []; + addAsset.should.not.have.been.called; + }); + + it('should not add loaders on non-array call', () => { + dashInstance.cachedUrls = "abc"; + addAsset.should.not.have.been.called; + }); + + it('should cache aseet url on array call', () => { + dashInstance.cachedUrls = ["entry_id"]; + addAsset.should.have.been.calledOnceWith("entry_id"); + }) + }); + + describe("on consecutive calls", () => { + + let addAsset, removeAsset; + + beforeEach(() => { + addAsset = sandbox.spy(DashAdapter._assetCache, "add"); + removeAsset = sandbox.stub(DashAdapter._assetCache, "remove"); + }); + + it("should not add a url the same url twice on conscutive calls", () => { + dashInstance.cachedUrls = ["entry_id"]; + dashInstance.cachedUrls = ["entry_id"]; + addAsset.should.have.been.calledOnceWith("entry_id"); + }); + + it("should add new urls on consecutive calls", () => { + dashInstance.cachedUrls = ["entry_id"]; + addAsset.should.have.been.calledWith("entry_id"); + dashInstance.cachedUrls = ["entry_id", "entry_id_2"]; + addAsset.should.have.been.calledWith("entry_id_2"); + }); + + it("should remove asset urls that were initially added and are missing on consecutive calls", () => { + dashInstance.cachedUrls = ["entry_id"]; + dashInstance.cachedUrls = ["entry_id_2"]; + removeAsset.should.have.been.calledOnceWith("entry_id"); + }); + }); + }); + + // xdescribe('using value of cachedUrls', () => { + // it('should do stuff', () => { + // expect(false).to.equal(true); + // }); + // }); +}); From a9d32c0896c51efe0840e4ee3923e024f463866e Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 22 May 2024 18:22:05 +0300 Subject: [PATCH 04/26] add tests for load cached url --- src/dash-adapter.ts | 4 +-- tests/src/dash-adapter.spec.js | 53 +++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 2e15fe7..c5aa3e8 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -897,13 +897,13 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._lastTimeDetach = NaN; this._maybeGetRedirectedUrl(this._sourceObj.url) .then(async url => { - debugger; const assetPromise = DashAdapter._assetCache.get(url); if (!assetPromise) { return this._shaka.load(url, shakaStartTime); } else { DashAdapter._assetCache.remove(url); - return this._shaka.load(await assetPromise, shakaStartTime); + const preloadMgr = await assetPromise; + return this._shaka.load(preloadMgr, shakaStartTime); } }) .then(() => { diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 9769aea..da1d2cf 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -1956,22 +1956,16 @@ describe('DashAdapter: on emsg', () => { describe.only('DashAdapter: cachedUrls', () => { // TODO stub shaka - let video, dashInstance, config, sandbox, assetCache; + let video, config, sandbox; beforeEach(() => { sandbox = sinon.createSandbox(); - video = document.createElement('video'); config = {playback: {options: {html5: {dash: {}}}}}; - - dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); afterEach(() => { - dashInstance.destroy(); - dashInstance = null; sandbox.restore(); - DashAdapter._assetCache.reset(); }); @@ -1979,7 +1973,15 @@ describe.only('DashAdapter: cachedUrls', () => { TestUtils.removeVideoElementsFromTestPage(); }); - describe('setting value of cachedUrls', () => { + describe('cachedUrls setting', () => { + let dashInstance; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + video = document.createElement('video'); + dashInstance = DashAdapter.createAdapter(video, vodSource, config); + }); + describe('on initial call', () => { let addAsset; @@ -2004,7 +2006,6 @@ describe.only('DashAdapter: cachedUrls', () => { }); describe("on consecutive calls", () => { - let addAsset, removeAsset; beforeEach(() => { @@ -2033,9 +2034,33 @@ describe.only('DashAdapter: cachedUrls', () => { }); }); - // xdescribe('using value of cachedUrls', () => { - // it('should do stuff', () => { - // expect(false).to.equal(true); - // }); - // }); + describe.only('cachedUrls usage', () => { + it('should initialize assetCache', () => { + const init = sandbox.spy(DashAdapter._assetCache, "init"); + const dashInstance = DashAdapter.createAdapter(video, vodSource, config); + + init.should.have.been.calledOnceWith(dashInstance._shaka); + }); + + it("should check if url is cached", done => { + const get = sandbox.spy(DashAdapter._assetCache, "get"); + const dashInstance = DashAdapter.createAdapter(video, vodSource, config); + + dashInstance.load().then(() => { + get.should.have.been.calledOnceWith(vodSource.url); + done(); + }) + }); + + it('should use cached url if url is cached', () => { + const dashInstance = DashAdapter.createAdapter(video, dvrSource, config); + const load = sandbox.spy(dashInstance._shaka, "load"); + sandbox.stub(DashAdapter._assetCache, "get").resolves(vodSource.url); + + dashInstance.load().then(() => { + load.should.have.been.calledWith(vodSource.url, undefined); + done(); + }) + }); + }); }); From eea8891effb10b8756a7ca66200d58478300e9e7 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Tue, 28 May 2024 17:05:04 +0300 Subject: [PATCH 05/26] add tests for cache add and list --- src/cache/asset-cache.ts | 45 ++++++++++--------- tests/src/cache/asset-cache.spec.js | 70 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 tests/src/cache/asset-cache.spec.js diff --git a/src/cache/asset-cache.ts b/src/cache/asset-cache.ts index 959b358..e0bacb1 100644 --- a/src/cache/asset-cache.ts +++ b/src/cache/asset-cache.ts @@ -1,50 +1,52 @@ class AssetCache { private shakaInstance: shaka.Player | null = null; - private preloadQueue: string[] = []; - private preloadPromiseMap = new Map(); + private cacheQueue = new Set(); + private cache = new Map(); public init(shakaInstance: shaka.Player) { + // TODO when to reset shaka ? when video element is destroyed ? this.reset(); this.shakaInstance = shakaInstance; this.preloadAssets(); } public add(assetUrl: string) { - // TODO dont add the same url twice + console.log('>>> asset cache add', assetUrl); + if (this.cache.has(assetUrl)) return; - this.preloadQueue.push(assetUrl); + this.cacheQueue.add(assetUrl); this.preloadAssets(); } public get(assetUrl: string): Promise | null { - return this.preloadPromiseMap.get(assetUrl) || null; + console.log('>>> asset cache get', assetUrl); + + return this.cache.get(assetUrl) || null; } public list(): string[] { - if (this.preloadQueue.length) { - return this.preloadQueue; + if (this.cacheQueue.size) { + return [...this.cacheQueue]; } - return [...this.preloadPromiseMap.keys()]; + return [...this.cache.keys()]; } public remove(assetUrl: string, destroy: boolean = false) { - const index = this.preloadQueue.findIndex(item => item === assetUrl); - if (index !== -1) { - this.preloadQueue.splice(index, 1); - } else if (this.preloadPromiseMap.has(assetUrl)) { - const assetPromise = this.preloadPromiseMap.get(assetUrl); - // TODO - if (!destroy) { + if (this.cacheQueue.has(assetUrl)) { + this.cacheQueue.delete(assetUrl); + } else if (this.cache.has(assetUrl)) { + const assetPromise = this.cache.get(assetUrl); + if (destroy) { assetPromise.then(loader => loader.destroy()); } - this.preloadPromiseMap.delete(assetUrl); + this.cache.delete(assetUrl); } } public removeAll() { - this.preloadQueue = []; - const assetUrls = this.preloadPromiseMap.keys(); + this.cacheQueue.clear(); + const assetUrls = this.cache.keys(); for (const assetUrl of assetUrls) { this.remove(assetUrl, true); } @@ -58,9 +60,10 @@ class AssetCache { private preloadAssets() { if (!this.shakaInstance) return; - while (this.preloadQueue.length) { - const assetUrl = this.preloadQueue.pop() as string; - this.preloadPromiseMap.set(assetUrl, this.shakaInstance.preload(assetUrl)); + // TODO test this + for (const assetUrl of this.cacheQueue) { + this.cache.set(assetUrl, this.shakaInstance.preload(assetUrl)); + this.cacheQueue.delete(assetUrl); } } } diff --git a/tests/src/cache/asset-cache.spec.js b/tests/src/cache/asset-cache.spec.js new file mode 100644 index 0000000..1815e06 --- /dev/null +++ b/tests/src/cache/asset-cache.spec.js @@ -0,0 +1,70 @@ +import { AssetCache } from "../../../src/cache/asset-cache"; + +describe.only('AssetCache', () => { + let assetCache, shakaInstance; + + beforeEach(() => { + assetCache = new AssetCache(); + shakaInstance = { + preload: () => {}, + destroy: () => {} + } + }); + afterEach(() => { + assetCache = null; + sinon.restore(); + }); + + describe('get', () => { + let get; + beforeEach(() => { + get = sinon.spy(assetCache, "get"); + }); + it('should return null if asset was not added', () => { + expect(assetCache.get("abc")).to.equal(null); + }); + it('should return null if asset was added but shaka is not set', () => { + assetCache.add("abc"); + expect(assetCache.get("abc")).to.equal(null); + }); + it('should return the asset promise if asset was added and shaka is set', done => { + sinon.stub(shakaInstance, "preload").resolves("def"); + assetCache.init(shakaInstance); + assetCache.add("abc"); + assetCache.get("abc").then(result => { + expect(result).to.equal("def"); + done(); + }); + }) + }); + + describe('list', () => { + it('should list all queued assets, without duplicates, if shaka is not set', () => { + assetCache.add("abc"); + assetCache.add("abc"); + assetCache.add("def"); + + const listResult = assetCache.list(); + expect(listResult.length).to.equal(2); + expect(listResult.findIndex(i => i === "abc")).to.not.equal(-1); + expect(listResult.findIndex(i => i === "def")).to.not.equal(-1); + }); + it('should list all cached assets, without duplicates, if shaka is set', () => { + sinon.stub(shakaInstance, "preload").resolves({}); + assetCache.init(shakaInstance); + + assetCache.add("abc"); + assetCache.add("abc"); + assetCache.add("def"); + + const listResult = assetCache.list(); + expect(listResult.length).to.equal(2); + expect(listResult.findIndex(i => i === "abc")).to.not.equal(-1); + expect(listResult.findIndex(i => i === "def")).to.not.equal(-1); + }); + }) + + // describe('remove'); + // describe('removeAll'); + // describe('reset'); +}); From fab0bce9bfdb5fac47e89036bcc9b3e2092dca84 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Sun, 2 Jun 2024 13:34:22 +0300 Subject: [PATCH 06/26] add tests for asset cache remove --- src/cache/asset-cache.ts | 19 ++--- tests/src/cache/asset-cache.spec.js | 123 ++++++++++++++++++++++++---- 2 files changed, 112 insertions(+), 30 deletions(-) diff --git a/src/cache/asset-cache.ts b/src/cache/asset-cache.ts index e0bacb1..56f5fc9 100644 --- a/src/cache/asset-cache.ts +++ b/src/cache/asset-cache.ts @@ -6,7 +6,8 @@ class AssetCache { public init(shakaInstance: shaka.Player) { // TODO when to reset shaka ? when video element is destroyed ? - this.reset(); + this.shakaInstance = null; + this.clearCache(); this.shakaInstance = shakaInstance; this.preloadAssets(); } @@ -26,9 +27,9 @@ class AssetCache { } public list(): string[] { - if (this.cacheQueue.size) { - return [...this.cacheQueue]; - } + // if (this.cacheQueue.size) { + // return [...this.cacheQueue]; + // } return [...this.cache.keys()]; } @@ -38,25 +39,19 @@ class AssetCache { } else if (this.cache.has(assetUrl)) { const assetPromise = this.cache.get(assetUrl); if (destroy) { - assetPromise.then(loader => loader.destroy()); + assetPromise.then(preloadMgr => preloadMgr.destroy()); } this.cache.delete(assetUrl); } } - public removeAll() { - this.cacheQueue.clear(); + private clearCache() { const assetUrls = this.cache.keys(); for (const assetUrl of assetUrls) { this.remove(assetUrl, true); } } - public reset() { - this.removeAll(); - this.shakaInstance = null; - } - private preloadAssets() { if (!this.shakaInstance) return; diff --git a/tests/src/cache/asset-cache.spec.js b/tests/src/cache/asset-cache.spec.js index 1815e06..dd46a06 100644 --- a/tests/src/cache/asset-cache.spec.js +++ b/tests/src/cache/asset-cache.spec.js @@ -1,8 +1,9 @@ +import { assert } from "chai"; import { AssetCache } from "../../../src/cache/asset-cache"; describe.only('AssetCache', () => { let assetCache, shakaInstance; - + beforeEach(() => { assetCache = new AssetCache(); shakaInstance = { @@ -14,7 +15,7 @@ describe.only('AssetCache', () => { assetCache = null; sinon.restore(); }); - + describe('get', () => { let get; beforeEach(() => { @@ -27,44 +28,130 @@ describe.only('AssetCache', () => { assetCache.add("abc"); expect(assetCache.get("abc")).to.equal(null); }); - it('should return the asset promise if asset was added and shaka is set', done => { - sinon.stub(shakaInstance, "preload").resolves("def"); + it('should return the asset promise if shaka was set first, and then asset was added', done => { + const preload = sinon.stub(shakaInstance, "preload").resolves("def"); assetCache.init(shakaInstance); assetCache.add("abc"); assetCache.get("abc").then(result => { + expect(preload).to.have.been.calledOnceWith("abc"); + expect(result).to.equal("def"); + done(); + }); + }); + it('should return the asset promise if asset was added first, and then shaka was set', done => { + const preload = sinon.stub(shakaInstance, "preload").resolves("def"); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.equal(null); + assetCache.init(shakaInstance); + assetCache.get("abc").then(result => { + expect(preload).to.have.been.calledOnceWith("abc"); expect(result).to.equal("def"); done(); }); - }) + }); }); - + describe('list', () => { - it('should list all queued assets, without duplicates, if shaka is not set', () => { + it('should not list queued assets', () => { assetCache.add("abc"); assetCache.add("abc"); assetCache.add("def"); - - const listResult = assetCache.list(); - expect(listResult.length).to.equal(2); - expect(listResult.findIndex(i => i === "abc")).to.not.equal(-1); - expect(listResult.findIndex(i => i === "def")).to.not.equal(-1); + + expect(assetCache.list().length).to.equal(0); }); - it('should list all cached assets, without duplicates, if shaka is set', () => { + it('should list all cached assets, without duplicates', () => { sinon.stub(shakaInstance, "preload").resolves({}); assetCache.init(shakaInstance); - + assetCache.add("abc"); assetCache.add("abc"); assetCache.add("def"); - + const listResult = assetCache.list(); expect(listResult.length).to.equal(2); expect(listResult.findIndex(i => i === "abc")).to.not.equal(-1); expect(listResult.findIndex(i => i === "def")).to.not.equal(-1); }); }) + + describe('remove', () => { + it('should remove queued assets, so they will not become cached when shaka is set', () => { + assetCache.add("abc"); + expect(assetCache.get("abc")).to.equal(null); + assetCache.remove("abc"); + assetCache.init(shakaInstance); + expect(assetCache.get("abc")).to.equal(null); + }); + it('should remove cached assets, if they were added before shaka was set', () => { + sinon.stub(shakaInstance, "preload").resolves({}); - // describe('remove'); - // describe('removeAll'); - // describe('reset'); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.equal(null); + assetCache.init(shakaInstance); + expect(assetCache.get("abc")).to.not.equal(null); + assetCache.remove("abc"); + expect(assetCache.get("abc")).to.equal(null); + }); + it('should remove assets while they are cached, if they were added after shaka was set', () => { + sinon.stub(shakaInstance, "preload").resolves({}); + + assetCache.init(shakaInstance); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.not.equal(null); + assetCache.remove("abc"); + expect(assetCache.get("abc")).to.equal(null); + }); + it('should not destroy cached asset by default', done => { + const preloadResult = { destroy: () => {} }; + const destroy = sinon.spy(preloadResult, "destroy"); + const preload = sinon.stub(shakaInstance, "preload").resolves(preloadResult); + + assetCache.init(shakaInstance); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.not.equal(null); + + assetCache.remove("abc"); + expect(assetCache.get("abc")).to.equal(null); + + setTimeout(() => { + expect(destroy).not.to.have.been.calledOnce; + done(); + }); + }); + it('should not destroy cached asset if called with destroy = false', done => { + const preloadResult = { destroy: () => {} }; + const destroy = sinon.spy(preloadResult, "destroy"); + const preload = sinon.stub(shakaInstance, "preload").resolves(preloadResult); + + assetCache.init(shakaInstance); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.not.equal(null); + + assetCache.remove("abc", false); + expect(assetCache.get("abc")).to.equal(null); + + setTimeout(() => { + expect(destroy).not.to.have.been.calledOnce; + done(); + }); + }); + it('should destroy cached asset if called with destroy = true', done => { + const preloadResult = { destroy: () => {} }; + const destroy = sinon.spy(preloadResult, "destroy"); + const preload = sinon.stub(shakaInstance, "preload").resolves(preloadResult); + + assetCache.init(shakaInstance); + assetCache.add("abc"); + expect(assetCache.get("abc")).to.not.equal(null); + + assetCache.remove("abc", true); + expect(assetCache.get("abc")).to.equal(null); + + setTimeout(() => { + expect(destroy).to.have.been.calledOnce; + done(); + }); + }); + }); + // describe('init'); }); From 3c919aa5de5f51ed07023049ee6d4ce456a9766c Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:12:40 +0300 Subject: [PATCH 07/26] add tests for asset cache init --- tests/src/cache/asset-cache.spec.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/src/cache/asset-cache.spec.js b/tests/src/cache/asset-cache.spec.js index dd46a06..673a94c 100644 --- a/tests/src/cache/asset-cache.spec.js +++ b/tests/src/cache/asset-cache.spec.js @@ -153,5 +153,23 @@ describe.only('AssetCache', () => { }); }); }); - // describe('init'); + describe.only('init', () => { + it('should add queued items to cache', () => { + const preload = sinon.stub(shakaInstance, "preload").resolves({}); + assetCache.add("abc"); + assetCache.add("def"); + expect(assetCache.list().length).to.equal(0); + assetCache.init(shakaInstance); + expect(assetCache.list().length).to.equal(2); + }); + it('should remove all items from cache', () => { + const preload = sinon.stub(shakaInstance, "preload").resolves({}); + assetCache.init(shakaInstance); + assetCache.add("abc"); + assetCache.add("def"); + expect(assetCache.list().length).to.equal(2); + assetCache.init(shakaInstance); + expect(assetCache.list().length).to.equal(0); + }); + }); }); From c6d656aeff118d7adbd1011e72cd6251fc536dde Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:13:23 +0300 Subject: [PATCH 08/26] add tests for asset cache init --- src/cache/asset-cache.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/cache/asset-cache.ts b/src/cache/asset-cache.ts index 56f5fc9..a11af70 100644 --- a/src/cache/asset-cache.ts +++ b/src/cache/asset-cache.ts @@ -5,15 +5,12 @@ class AssetCache { private cache = new Map(); public init(shakaInstance: shaka.Player) { - // TODO when to reset shaka ? when video element is destroyed ? - this.shakaInstance = null; this.clearCache(); this.shakaInstance = shakaInstance; this.preloadAssets(); } public add(assetUrl: string) { - console.log('>>> asset cache add', assetUrl); if (this.cache.has(assetUrl)) return; this.cacheQueue.add(assetUrl); @@ -21,15 +18,10 @@ class AssetCache { } public get(assetUrl: string): Promise | null { - console.log('>>> asset cache get', assetUrl); - return this.cache.get(assetUrl) || null; } public list(): string[] { - // if (this.cacheQueue.size) { - // return [...this.cacheQueue]; - // } return [...this.cache.keys()]; } @@ -55,7 +47,6 @@ class AssetCache { private preloadAssets() { if (!this.shakaInstance) return; - // TODO test this for (const assetUrl of this.cacheQueue) { this.cache.set(assetUrl, this.shakaInstance.preload(assetUrl)); this.cacheQueue.delete(assetUrl); From 062719cef272e9b32c7b39ac02cfa462f83f66c6 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:57:51 +0300 Subject: [PATCH 09/26] add setCachedUrls tests --- tests/src/cache/asset-cache.spec.js | 4 +- tests/src/dash-adapter.spec.js | 95 +++++++++++++---------------- 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/tests/src/cache/asset-cache.spec.js b/tests/src/cache/asset-cache.spec.js index 673a94c..ab16eca 100644 --- a/tests/src/cache/asset-cache.spec.js +++ b/tests/src/cache/asset-cache.spec.js @@ -1,7 +1,7 @@ import { assert } from "chai"; import { AssetCache } from "../../../src/cache/asset-cache"; -describe.only('AssetCache', () => { +describe('AssetCache', () => { let assetCache, shakaInstance; beforeEach(() => { @@ -153,7 +153,7 @@ describe.only('AssetCache', () => { }); }); }); - describe.only('init', () => { + describe('init', () => { it('should add queued items to cache', () => { const preload = sinon.stub(shakaInstance, "preload").resolves({}); assetCache.add("abc"); diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index da1d2cf..6d2cdd7 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -1953,112 +1953,105 @@ describe('DashAdapter: on emsg', () => { }); }); -describe.only('DashAdapter: cachedUrls', () => { - // TODO stub shaka - - let video, config, sandbox; +describe('DashAdapter: setCachedUrls', () => { + let video, config, sandbox, dashInstance; beforeEach(() => { - sandbox = sinon.createSandbox(); + sandbox = sinon.createSandbox(); + video = document.createElement('video'); + video.id = `test_id_${Date.now()}`; + config = {playback: {options: {html5: {dash: {}}}}}; + + dashInstance = DashAdapter.createAdapter(video, vodSource, config); + sandbox.stub(dashInstance.shaka, "preload").resolves("def"); }); afterEach(() => { sandbox.restore(); - DashAdapter._assetCache.reset(); }); after(() => { TestUtils.removeVideoElementsFromTestPage(); }); - describe('cachedUrls setting', () => { - let dashInstance; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - video = document.createElement('video'); - dashInstance = DashAdapter.createAdapter(video, vodSource, config); - }); + describe('setCachedUrls setting', () => { describe('on initial call', () => { - let addAsset; + let add; beforeEach(() => { - addAsset = sandbox.spy(DashAdapter._assetCache, "add"); + add = sandbox.spy(dashInstance.assetCache, "add"); }); it('should not cache asset url on empty call', () => { - dashInstance.cachedUrls = []; - addAsset.should.not.have.been.called; + dashInstance.setCachedUrls([]); + add.should.not.have.been.called; }); it('should not add loaders on non-array call', () => { - dashInstance.cachedUrls = "abc"; - addAsset.should.not.have.been.called; + dashInstance.setCachedUrls("abc"); + add.should.not.have.been.called; }); - it('should cache aseet url on array call', () => { - dashInstance.cachedUrls = ["entry_id"]; - addAsset.should.have.been.calledOnceWith("entry_id"); + it('should cache asset url on array call', () => { + dashInstance.setCachedUrls(["abc"]); + add.should.have.been.calledOnceWith("abc"); }) }); describe("on consecutive calls", () => { - let addAsset, removeAsset; + let add, remove; beforeEach(() => { - addAsset = sandbox.spy(DashAdapter._assetCache, "add"); - removeAsset = sandbox.stub(DashAdapter._assetCache, "remove"); + add = sandbox.spy(dashInstance.assetCache, "add"); + remove = sandbox.stub(dashInstance.assetCache, "remove"); }); - it("should not add a url the same url twice on conscutive calls", () => { - dashInstance.cachedUrls = ["entry_id"]; - dashInstance.cachedUrls = ["entry_id"]; - addAsset.should.have.been.calledOnceWith("entry_id"); + it("should not add the same url twice on consecutive calls", () => { + dashInstance.setCachedUrls(["abc"]); + dashInstance.setCachedUrls(["abc"]); + add.should.have.been.calledOnceWith("abc"); }); it("should add new urls on consecutive calls", () => { - dashInstance.cachedUrls = ["entry_id"]; - addAsset.should.have.been.calledWith("entry_id"); - dashInstance.cachedUrls = ["entry_id", "entry_id_2"]; - addAsset.should.have.been.calledWith("entry_id_2"); + dashInstance.setCachedUrls(["abc"]); + add.should.have.been.calledWith("abc"); + dashInstance.setCachedUrls(["abc", "def"]); + add.should.have.been.calledWith("def"); }); it("should remove asset urls that were initially added and are missing on consecutive calls", () => { - dashInstance.cachedUrls = ["entry_id"]; - dashInstance.cachedUrls = ["entry_id_2"]; - removeAsset.should.have.been.calledOnceWith("entry_id"); + dashInstance.setCachedUrls(["abc"]); + add.should.have.been.calledWith("abc"); + dashInstance.setCachedUrls(["def"]); + remove.should.have.been.calledWith("abc"); }); }); }); - describe.only('cachedUrls usage', () => { - it('should initialize assetCache', () => { - const init = sandbox.spy(DashAdapter._assetCache, "init"); - const dashInstance = DashAdapter.createAdapter(video, vodSource, config); + describe('setCachedUrls usage', () => { + let get, load; - init.should.have.been.calledOnceWith(dashInstance._shaka); + beforeEach(() => { + get = sandbox.stub(dashInstance.assetCache, "get"); + load = sandbox.stub(dashInstance.shaka, "load").resolves({}); }); it("should check if url is cached", done => { - const get = sandbox.spy(DashAdapter._assetCache, "get"); - const dashInstance = DashAdapter.createAdapter(video, vodSource, config); - dashInstance.load().then(() => { get.should.have.been.calledOnceWith(vodSource.url); + load.should.have.been.calledOnceWith(vodSource.url, undefined); done(); }) }); - it('should use cached url if url is cached', () => { - const dashInstance = DashAdapter.createAdapter(video, dvrSource, config); - const load = sandbox.spy(dashInstance._shaka, "load"); - sandbox.stub(DashAdapter._assetCache, "get").resolves(vodSource.url); - + it('should use cached url if url is cached', done => { + get.resolves("abc"); + dashInstance.setCachedUrls([vodSource.url]); dashInstance.load().then(() => { - load.should.have.been.calledWith(vodSource.url, undefined); + load.should.have.been.calledWith("abc", undefined); done(); }) }); From dcfa3c81705347fe70aa7761470d067d600b787d Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:11:57 +0300 Subject: [PATCH 10/26] update tests --- tests/src/dash-adapter.spec.js | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 6d2cdd7..e7f6202 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -200,7 +200,7 @@ describe('DashAdapter: load', () => { .load() .then(() => { try { - dashInstance._shaka.should.exist; + dashInstance.shaka.should.exist; dashInstance._config.should.exist; dashInstance._videoElement.should.exist; dashInstance._sourceObj.should.exist; @@ -218,7 +218,7 @@ describe('DashAdapter: load', () => { try { dashInstance = DashAdapter.createAdapter(video, vodSource, config); video.addEventListener(EventType.LOADED_DATA, () => { - dashInstance._shaka.getConfiguration().streaming.lowLatencyMode.should.equal(false); + dashInstance.shaka.getConfiguration().streaming.lowLatencyMode.should.equal(false); done(); }); @@ -234,7 +234,7 @@ describe('DashAdapter: load', () => { try { dashInstance = DashAdapter.createAdapter(video, liveSource, config); video.addEventListener(EventType.LOADED_DATA, () => { - dashInstance._shaka.getConfiguration().streaming.lowLatencyMode.should.equal(true); + dashInstance.shaka.getConfiguration().streaming.lowLatencyMode.should.equal(true); done(); }); @@ -251,7 +251,7 @@ describe('DashAdapter: load', () => { const playerConfig = {...config, streaming: {lowLatencyMode: true}}; dashInstance = DashAdapter.createAdapter(video, vodSource, playerConfig); video.addEventListener(EventType.LOADED_DATA, () => { - dashInstance._shaka.getConfiguration().streaming.lowLatencyMode.should.equal(true); + dashInstance.shaka.getConfiguration().streaming.lowLatencyMode.should.equal(true); done(); }); @@ -268,7 +268,7 @@ describe('DashAdapter: load', () => { const playerConfig = {...config, streaming: {lowLatencyMode: false}}; dashInstance = DashAdapter.createAdapter(video, vodSource, playerConfig); video.addEventListener(EventType.LOADED_DATA, () => { - dashInstance._shaka.getConfiguration().streaming.lowLatencyMode.should.equal(false); + dashInstance.shaka.getConfiguration().streaming.lowLatencyMode.should.equal(false); done(); }); @@ -369,8 +369,8 @@ describe('DashAdapter: targetBuffer', () => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); video.addEventListener(EventType.PLAYING, () => { const targetBufferVal = - dashInstance._shaka.getConfiguration().streaming.bufferingGoal + - dashInstance._shaka.getManifest().presentationTimeline.getMaxSegmentDuration(); + dashInstance.shaka.getConfiguration().streaming.bufferingGoal + + dashInstance.shaka.getManifest().presentationTimeline.getMaxSegmentDuration(); Math.round(dashInstance.targetBuffer - targetBufferVal).should.equal(0); done(); @@ -531,7 +531,7 @@ describe('DashAdapter: _getParsedTracks', () => { .then(data => { let videoTracks = dashInstance._getVideoTracks(); let audioTracks = dashInstance._getAudioTracks(); - let textTracks = dashInstance._shaka.getTextTracks(); + let textTracks = dashInstance.shaka.getTextTracks(); let totalTracksLength = videoTracks.length + audioTracks.length + textTracks.length; try { data.tracks.length.should.be.equal(totalTracksLength); @@ -922,7 +922,7 @@ describe('DashAdapter: selectTextTrack', () => { return !track.active; })[0]; dashInstance.selectTextTrack(inactiveTrack); - let activeTrack = dashInstance._shaka.getTextTracks().filter(track => { + let activeTrack = dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0]; activeTrack.language.should.be.equal(inactiveTrack.language); @@ -940,7 +940,7 @@ describe('DashAdapter: selectTextTrack', () => { dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(activeTrack); activeTrack.language.should.be.equal( - dashInstance._shaka.getTextTracks().filter(track => { + dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0].language ); @@ -962,7 +962,7 @@ describe('DashAdapter: selectTextTrack', () => { dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(new VideoTrack({index: 0})); activeTrack.language.should.be.equal( - dashInstance._shaka.getTextTracks().filter(track => { + dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0].language ); @@ -984,7 +984,7 @@ describe('DashAdapter: selectTextTrack', () => { dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(); activeTrack.language.should.be.equal( - dashInstance._shaka.getTextTracks().filter(track => { + dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0].language ); @@ -1006,7 +1006,7 @@ describe('DashAdapter: selectTextTrack', () => { dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(new TextTrack({kind: 'metadata'})); activeTrack.language.should.be.equal( - dashInstance._shaka.getTextTracks().filter(track => { + dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0].language ); @@ -1042,9 +1042,9 @@ describe('DashAdapter: enableAdaptiveBitrate', () => { dashInstance .load() .then(() => { - dashInstance._shaka.getConfiguration().abr.enabled.should.be.false; + dashInstance.shaka.getConfiguration().abr.enabled.should.be.false; dashInstance.enableAdaptiveBitrate(); - dashInstance._shaka.getConfiguration().abr.enabled.should.be.true; + dashInstance.shaka.getConfiguration().abr.enabled.should.be.true; dashInstance.isAdaptiveBitrateEnabled().should.be.true; done(); }) @@ -1176,7 +1176,7 @@ describe('DashAdapter: _getLiveEdge', () => { .load() .then(() => { try { - Math.floor(Math.abs(dashInstance._getLiveEdge() - dashInstance._shaka.seekRange().end)).should.equal(0); + Math.floor(Math.abs(dashInstance._getLiveEdge() - dashInstance.shaka.seekRange().end)).should.equal(0); done(); } catch (e) { done(e); @@ -1214,10 +1214,10 @@ describe('DashAdapter: seekToLiveEdge', () => { .then(() => { try { video.play().then(() => { - video.currentTime = dashInstance._shaka.seekRange().start; - const initialTimeShift = dashInstance._shaka.seekRange().end - video.currentTime; + video.currentTime = dashInstance.shaka.seekRange().start; + const initialTimeShift = dashInstance.shaka.seekRange().end - video.currentTime; dashInstance.seekToLiveEdge(); - const timeShift = dashInstance._shaka.seekRange().end - video.currentTime; + const timeShift = dashInstance.shaka.seekRange().end - video.currentTime; timeShift.should.be.lessThan(3); timeShift.should.be.lessThan(initialTimeShift); done(); @@ -1237,10 +1237,10 @@ describe('DashAdapter: seekToLiveEdge', () => { .load() .then(() => { try { - video.currentTime = dashInstance._shaka.seekRange().start; - (dashInstance._shaka.seekRange().end - video.currentTime > 30).should.be.true; + video.currentTime = dashInstance.shaka.seekRange().start; + (dashInstance.shaka.seekRange().end - video.currentTime > 30).should.be.true; dashInstance.seekToLiveEdge(); - (dashInstance._shaka.seekRange().end - video.currentTime < 1).should.be.true; + (dashInstance.shaka.seekRange().end - video.currentTime < 1).should.be.true; done(); } catch (e) { done(e); @@ -1422,7 +1422,7 @@ describe('DashAdapter: getStartTimeOfDvrWindow', () => { setTimeout(() => { try { Math.floor(dashInstance.getStartTimeOfDvrWindow()).should.equal( - Math.floor(dashInstance._shaka.seekRange().start + dashInstance._shaka.getConfiguration().streaming.safeSeekOffset) + Math.floor(dashInstance.shaka.seekRange().start + dashInstance.shaka.getConfiguration().streaming.safeSeekOffset) ); done(); } catch (e) { @@ -1852,7 +1852,7 @@ describe('DashAdapter: in-stream thumbnails', () => { .load() .then(result => { try { - dashInstance._shaka.should.exist; + dashInstance.shaka.should.exist; dashInstance._config.should.exist; dashInstance._videoElement.should.exist; dashInstance._sourceObj.should.exist; @@ -1878,7 +1878,7 @@ describe('DashAdapter: in-stream thumbnails', () => { .load() .then(result => { try { - dashInstance._shaka.should.exist; + dashInstance.shaka.should.exist; dashInstance._config.should.exist; dashInstance._videoElement.should.exist; dashInstance._sourceObj.should.exist; From 02f50091340061ac4fff2a46542ce4f3d4fd22d8 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:12:29 +0300 Subject: [PATCH 11/26] update karma timeout setting --- karma.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index da9a493..0bff688 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -41,7 +41,7 @@ module.exports = function (config) { client: { mocha: { reporter: 'html', - timeout: 5000 + timeout: 50000 } } }); From f047d771ae015add894f399b975fc1f8494fbc2a Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:20:00 +0300 Subject: [PATCH 12/26] add tests for dash adapter destroy --- tests/src/dash-adapter.spec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index e7f6202..4fb9b7a 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -498,6 +498,22 @@ describe('DashAdapter: destroy', () => { done(e); }); }); + + it('should destroy shaka instance if there are no cached urls', done => { + const destroy = sinon.spy(dashInstance.shaka, "destroy"); + dashInstance.destroy().then(() => { + destroy.should.have.been.calledOnce; + done(); + }); + }); + it('should not destroy shaka instance if there are cached urls', done => { + const destroy = sinon.spy(dashInstance.shaka, "destroy"); + dashInstance.setCachedUrls(["abc"]); + dashInstance.destroy().then(() => { + destroy.should.not.have.been.called; + done(); + }) + }); }); describe('DashAdapter: _getParsedTracks', () => { From 5334c24e06561c58fa1afa0f7a6371849d9983e7 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:01:02 +0300 Subject: [PATCH 13/26] manage one instance of shaka per player --- src/dash-adapter.ts | 212 +++++++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 90 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index c5aa3e8..d9a2985 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -126,12 +126,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ private _shakaLib: typeof shaka = shaka; - /** - * The shaka player instance - * @member {any} _shaka - * @private - */ - private _shaka!: shaka.Player; + /** * an object containing all the events we bind and unbind to. * @member {Object} - _adapterEventsBindings @@ -241,9 +236,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { private _selectedVideoTrack: VideoTrack | undefined | null = null; private _playbackActualUri: string | undefined; - private _loaderPromiseMap = new Map(); - - private static _assetCache = new AssetCache(); + private static _assetCacheMap = new Map(); + private static _shakaInstanceMap = new Map(); public applyTextTrackStyles(sheet: CSSStyleSheet, styles: any, containerId: string): void { const flexAlignment = { @@ -348,12 +342,12 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public setMaxBitrate(bitrate: number): void { if (this._hasLowerOrEqualBitrate(bitrate)) { - this._shaka.configure({abr: {restrictions: {maxBandwidth: bitrate}}}); + this.shaka!.configure({abr: {restrictions: {maxBandwidth: bitrate}}}); } } private _getSortedTracks(): Array<{id: number; bandwidth: number; active: boolean}> { - const tracks = this._shaka.getVariantTracks(); + const tracks = this.shaka!.getVariantTracks(); const sortedTracks = tracks .map(obj => ({ id: obj.id, @@ -431,18 +425,27 @@ export default class DashAdapter extends BaseMediaSourceAdapter { private _init(): void { //Need to call this again cause we are uninstalling the VTTCue polyfill to avoid collisions with other libs shaka.polyfill.installAll(); - this._shaka = new shaka.Player(); + + if (!this.shaka! || !this.assetCache?.list().length) { + const shakaInstance = new shaka.Player(); + const assetCache = new AssetCache(); + assetCache.init(shakaInstance); + + DashAdapter._assetCacheMap.set(this._videoElement.id, assetCache); + DashAdapter._shakaInstanceMap.set(this._videoElement.id, shakaInstance); + } + // This will force the player to use shaka UITextDisplayer plugin to render text tracks. if (this._config.useShakaTextTrackDisplay) { - this._shaka.setVideoContainer(Utils.Dom.getElementBySelector('.playkit-subtitles')); + this.shaka!.setVideoContainer(Utils.Dom.getElementBySelector('.playkit-subtitles')); } this._maybeSetFilters(); this._maybeSetDrmConfig(); this._maybeBreakStalls(); - this._shaka.configure(this._config.shakaConfig); - this._addBindings(); - DashAdapter._assetCache.init(this._shaka); + this.shaka!.configure(this._config.shakaConfig); + + this._addBindings(); } private _clearStallInterval(): void { @@ -513,7 +516,10 @@ export default class DashAdapter extends BaseMediaSourceAdapter { private _maybeSetFilters(): void { if (typeof Utils.Object.getPropertyPath(this._config, 'network.requestFilter') === 'function') { DashAdapter._logger.debug('Register request filter'); - this._shaka.getNetworkingEngine()?.registerRequestFilter((type, request) => { + + this.shaka!.getNetworkingEngine()?.clearAllRequestFilters(); + this.shaka!.getNetworkingEngine()?.clearAllResponseFilters(); + this.shaka!.getNetworkingEngine()?.registerRequestFilter((type, request) => { if (Object.values(RequestType).includes(type)) { const pkRequest: PKRequestObject = {url: request.uris[0], body: request.body, headers: request.headers}; let requestFilterPromise; @@ -545,11 +551,10 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } if (typeof Utils.Object.getPropertyPath(this._config, 'network.responseFilter') === 'function') { DashAdapter._logger.debug('Register response filter'); - this._shaka.getNetworkingEngine()?.registerResponseFilter((type, response) => { + this.shaka!.getNetworkingEngine()?.registerResponseFilter((type, response) => { if (Object.values(RequestType).includes(type)) { const {uri: url, data, headers} = response; - // TODO const pkResponse: PKResponseObject = {url, originalUrl: this._sourceObj!.url, data, headers}; let responseFilterPromise; try { @@ -635,7 +640,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ private _updateRestriction(restrictions: PKABRRestrictionObject): void { const shakaRestrictionsConfig = this._getRestrictionShakaConfig(restrictions); - this._shaka.configure({ + this.shaka!.configure({ abr: { restrictions: shakaRestrictionsConfig } @@ -687,7 +692,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {void} */ public attachMediaSource(): void { - if (!this._shaka) { + if (!this.shaka!) { if (this._videoElement && this._videoElement.src) { Utils.Dom.setAttribute(this._videoElement, 'src', ''); Utils.Dom.removeAttribute(this._videoElement, 'src'); @@ -702,7 +707,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {Promise} - detach promise */ public detachMediaSource(): Promise { - if (this._shaka) { + if (this.shaka!) { // 1 second different between duration and current time will signal as end - will enable replay button // @ts-expect-error - ???? if (Math.floor(this.duration - this.currentTime) === 0) { @@ -712,7 +717,6 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._lastTimeDetach = this.currentTime; } return this._reset().then(() => { - this._shaka = null as unknown as shaka.Player; this._loadPromise = undefined; }); } else { @@ -769,21 +773,21 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {void} */ private _addBindings(): void { - this._eventManager.listen(this._shaka, ShakaEvent.ADAPTATION, this._adapterEventsBindings.adaptation); - this._eventManager.listen(this._shaka, ShakaEvent.ERROR, this._adapterEventsBindings.error); - this._eventManager.listen(this._shaka, ShakaEvent.DRM_SESSION_UPDATE, this._adapterEventsBindings.drmsessionupdate); + this._eventManager.listen(this.shaka!, ShakaEvent.ADAPTATION, this._adapterEventsBindings.adaptation); + this._eventManager.listen(this.shaka!, ShakaEvent.ERROR, this._adapterEventsBindings.error); + this._eventManager.listen(this.shaka!, ShakaEvent.DRM_SESSION_UPDATE, this._adapterEventsBindings.drmsessionupdate); this._eventManager.listen(this._videoElement, EventType.WAITING, this._adapterEventsBindings.waiting); this._eventManager.listen(this._videoElement, EventType.PLAYING, this._adapterEventsBindings.playing); this._eventManager.listen(this._videoElement, EventType.LOADED_DATA, () => this._onLoadedData()); this._eventManager.listenOnce(this._videoElement, EventType.PLAYING, () => { - this._eventManager.listen(this._shaka, ShakaEvent.BUFFERING, this._adapterEventsBindings.buffering); + this._eventManager.listen(this.shaka!, ShakaEvent.BUFFERING, this._adapterEventsBindings.buffering); }); if (this._config.trackEmsgEvents) { - this._eventManager.listen(this._shaka, ShakaEvent.EMSG, this._adapterEventsBindings.emsg); + this._eventManager.listen(this.shaka!, ShakaEvent.EMSG, this._adapterEventsBindings.emsg); } // called when a resource is downloaded - this._shaka.getNetworkingEngine()?.registerResponseFilter((type, response) => { + this.shaka!.getNetworkingEngine()?.registerResponseFilter((type, response) => { switch (type) { case shaka.net.NetworkingEngine.RequestType.SEGMENT: this._trigger(EventType.FRAG_LOADED, { @@ -800,8 +804,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._playbackActualUri = response.uri; this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); setTimeout(() => { - this._isLive = this._isLive || (this._shaka?.isLive() as boolean); - if (this._isLive && !this._shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + this._isLive = this._isLive || (this.shaka!?.isLive() as boolean); + if (this._isLive && !this.shaka!?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); } @@ -814,10 +818,10 @@ export default class DashAdapter extends BaseMediaSourceAdapter { private _onLoadedData(): void { this._setLowLatencyMode(); const segmentDuration = this.getSegmentDuration(); - this._seekRangeStart = this._shaka.seekRange().start; + this._seekRangeStart = this.shaka!.seekRange().start; this._startOverTimeout = window.setTimeout( () => { - if (this._shaka.seekRange().start - this._seekRangeStart >= segmentDuration) { + if (this.shaka!.seekRange().start - this._seekRangeStart >= segmentDuration) { // in start over the seekRange().start should be permanent this._isStartOver = false; } @@ -855,7 +859,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } private _setLowLatencyMode(): void { - this._shaka.configure({ + this.shaka!.configure({ streaming: { lowLatencyMode: typeof this._config.lowLatencyMode === 'boolean' ? this._config.lowLatencyMode : this.isLive() } @@ -886,8 +890,10 @@ export default class DashAdapter extends BaseMediaSourceAdapter { public async load(startTime?: number): Promise { if (!this._loadPromise) { await this._removeMediaKeys(); - // TODO do not reattach ? - this._shaka.attach(this._videoElement); + + if (!this.shaka!?.getMediaElement()) { + this.shaka!.attach(this._videoElement); + } this._loadPromise = new Promise((resolve, reject) => { if (this._sourceObj && this._sourceObj.url) { @@ -897,13 +903,13 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._lastTimeDetach = NaN; this._maybeGetRedirectedUrl(this._sourceObj.url) .then(async url => { - const assetPromise = DashAdapter._assetCache.get(url); + const assetPromise = this.assetCache!.get(url); if (!assetPromise) { - return this._shaka.load(url, shakaStartTime); + return this.shaka!.load(url, shakaStartTime); } else { - DashAdapter._assetCache.remove(url); + this.assetCache!.remove(url); const preloadMgr = await assetPromise; - return this._shaka.load(preloadMgr, shakaStartTime); + return this.shaka!.load(preloadMgr, shakaStartTime); } }) .then(() => { @@ -929,12 +935,21 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public destroy(): Promise { this._isDestroyInProgress = true; + + let shakaInstanceToDestroy; + if (this.shaka! && !this.assetCache?.list().length) { + shakaInstanceToDestroy = this.shaka!; + + DashAdapter._shakaInstanceMap.delete(this._videoElement.id); + DashAdapter._assetCacheMap.delete(this._videoElement.id); + } + return new Promise((resolve, reject) => { super.destroy().then(() => { DashAdapter._logger.debug('destroy'); this._loadPromise = undefined; this._adapterEventsBindings = {}; - this._reset() + this._reset(shakaInstanceToDestroy) .then(resetResult => { this._isDestroyInProgress = false; resolve(resetResult); @@ -966,7 +981,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private * @returns {Promise<*>} - The destroy promise. */ - private _reset(): Promise { + private _reset(shakaInstance?: shaka.Player): Promise { this._buffering = false; this._waitingSent = false; this._playingSent = false; @@ -983,8 +998,9 @@ export default class DashAdapter extends BaseMediaSourceAdapter { if (this._eventManager) { this._eventManager.removeAll(); } - if (this._shaka) { - return this._shaka.destroy(); + + if (shakaInstance) { + return shakaInstance.destroy(); } return Promise.resolve(); } @@ -1020,7 +1036,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ private _getVideoTracks(): Array { - const variantTracks = this._shaka.getVariantTracks(); + const variantTracks = this.shaka!.getVariantTracks(); const activeVariantTrack = this._getActiveTrack(); const videoTracks = variantTracks.filter(variantTrack => { return variantTrack.audioId === activeVariantTrack.audioId; @@ -1029,7 +1045,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } private _getActiveTrack(): shaka.extern.Track { - return this._shaka.getVariantTracks().find(variantTrack => variantTrack.active)!; + return this.shaka!.getVariantTracks().find(variantTrack => variantTrack.active)!; } /** @@ -1039,8 +1055,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ private _getAudioTracks(): (shaka.extern.LanguageRole & {active: boolean; id: number})[] { - const variantTracks = this._shaka.getVariantTracks(); - const audioTracks = this._shaka.getAudioLanguagesAndRoles(); + const variantTracks = this.shaka!.getVariantTracks(); + const audioTracks = this.shaka!.getAudioLanguagesAndRoles(); audioTracks.forEach(track => { const sameLangAudioVariants = variantTracks.filter(vt => vt.language === track.language); const id = sameLangAudioVariants.map(variant => variant.id).join('_'); @@ -1059,7 +1075,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ private _getParsedTracks(): Array { - if (this._shaka) { + if (this.shaka!) { const videoTracks = this._getParsedVideoTracks(); const audioTracks = this._getParsedAudioTracks(); const textTracks = this._getParsedTextTracks(); @@ -1126,7 +1142,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ private _getParsedTextTracks(): Array { const parsedTracks: PKTextTrack[] = []; - for (const textTrack of this._shaka.getTextTracks()) { + for (const textTrack of this.shaka!.getTextTracks()) { let kind = textTrack.kind ? textTrack.kind + 's' : ''; kind = kind === '' && this._config.useShakaTextTrackDisplay ? 'captions' : kind; const settings = { @@ -1166,18 +1182,18 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectVideoTrack(videoTrack: VideoTrack): void { - if (this._shaka) { + if (this.shaka!) { const videoTracks = this._getVideoTracks(); if (videoTrack instanceof VideoTrack && videoTracks) { const selectedVideoTrack = videoTracks[videoTrack.index]; if (selectedVideoTrack) { if (this.isAdaptiveBitrateEnabled()) { - this._shaka.configure({abr: {enabled: false}}); + this.shaka!.configure({abr: {enabled: false}}); this._trigger(EventType.ABR_MODE_CHANGED, {mode: 'manual'}); } if (!selectedVideoTrack.active) { this._selectedVideoTrack = videoTrack; - this._shaka.selectVariantTrack(videoTracks[videoTrack.index], true); + this.shaka!.selectVariantTrack(videoTracks[videoTrack.index], true); this._onTrackChanged(videoTrack); } } @@ -1193,8 +1209,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectAudioTrack(audioTrack: AudioTrack): void { - if (this._shaka && audioTrack instanceof AudioTrack && !audioTrack.active) { - this._shaka.selectAudioLanguage(audioTrack.language); + if (this.shaka! && audioTrack instanceof AudioTrack && !audioTrack.active) { + this.shaka!.selectAudioLanguage(audioTrack.language); this._onTrackChanged(audioTrack); } } @@ -1207,15 +1223,15 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectTextTrack(textTrack: PKTextTrack): void { - if (this._shaka && textTrack instanceof PKTextTrack && !textTrack.active && (textTrack.kind === 'subtitles' || textTrack.kind === 'captions')) { - this._shaka.setTextTrackVisibility(this._config.textTrackVisibile); - this._shaka.selectTextLanguage(textTrack.language); + if (this.shaka! && textTrack instanceof PKTextTrack && !textTrack.active && (textTrack.kind === 'subtitles' || textTrack.kind === 'captions')) { + this.shaka!.setTextTrackVisibility(this._config.textTrackVisibile); + this.shaka!.selectTextLanguage(textTrack.language); this._onTrackChanged(textTrack); } } public selectImageTrack(imageTrack: ImageTrack): void { - if (this._shaka && this._thumbnailController && imageTrack instanceof ImageTrack && !imageTrack.active) { + if (this.shaka! && this._thumbnailController && imageTrack instanceof ImageTrack && !imageTrack.active) { this._thumbnailController.selectTrack(imageTrack); this._onTrackChanged(imageTrack); } @@ -1228,8 +1244,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public hideTextTrack(): void { - if (this._shaka) { - this._shaka.setTextTrackVisibility(false); + if (this.shaka!) { + this.shaka!.setTextTrackVisibility(false); } } @@ -1240,9 +1256,9 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public enableAdaptiveBitrate(): void { - if (this._shaka && !this.isAdaptiveBitrateEnabled()) { + if (this.shaka! && !this.isAdaptiveBitrateEnabled()) { this._trigger(EventType.ABR_MODE_CHANGED, {mode: 'auto'}); - this._shaka.configure({abr: {enabled: true}}); + this.shaka!.configure({abr: {enabled: true}}); } } @@ -1253,8 +1269,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public isAdaptiveBitrateEnabled(): boolean { - if (this._shaka) { - const shakaConfig = this._shaka.getConfiguration(); + if (this.shaka!) { + const shakaConfig = this.shaka!.getConfiguration(); return shakaConfig.abr.enabled; } return false; @@ -1288,7 +1304,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ protected _getLiveEdge(): number { - return this._shaka ? this._shaka.seekRange().end : NaN; + return this.shaka! ? this.shaka!.seekRange().end : NaN; } /** @@ -1298,7 +1314,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public seekToLiveEdge(): void { - if (this._shaka && this._videoElement.readyState > 0) { + if (this.shaka! && this._videoElement.readyState > 0) { this._videoElement.currentTime = this._getLiveEdge(); } } @@ -1310,7 +1326,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public isLive(): boolean { - return this._shaka?.isLive() || this._isLive; + return this.shaka!?.isLive() || this._isLive; } /** @@ -1326,8 +1342,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @return {number} - live duration */ public getSegmentDuration(): number { - if (this._shaka) { - return this._shaka.getStats().maxSegmentDuration; + if (this.shaka!) { + return this.shaka!.getStats().maxSegmentDuration; } return 0; } @@ -1446,8 +1462,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ private _onDrmSessionUpdate(): void { this._trigger(EventType.DRM_LICENSE_LOADED, { - licenseTime: this._shaka.getStats().licenseTime, - scheme: this._shaka.drmInfo()?.keySystem + licenseTime: this.shaka!.getStats().licenseTime, + scheme: this.shaka!.drmInfo()?.keySystem }); } @@ -1504,8 +1520,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public getStartTimeOfDvrWindow(): number { - if (this.isLive() && this._shaka) { - return (this._isStartOver ? this._seekRangeStart : this._shaka.seekRange().start) + this._shaka.getConfiguration().streaming.safeSeekOffset; + if (this.isLive() && this.shaka!) { + return (this._isStartOver ? this._seekRangeStart : this.shaka!.seekRange().start) + this.shaka!.getConfiguration().streaming.safeSeekOffset; } return 0; } @@ -1516,7 +1532,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public get targetBuffer(): number { let targetBufferVal = NaN; - if (!this._shaka) return NaN; + if (!this.shaka!) return NaN; if (this.isLive()) { targetBufferVal = this._getLiveEdge() - this._videoElement.currentTime; } else { @@ -1524,12 +1540,12 @@ export default class DashAdapter extends BaseMediaSourceAdapter { targetBufferVal = this._videoElement.duration - this._videoElement.currentTime; } - targetBufferVal = Math.min(targetBufferVal, this._shaka.getConfiguration().streaming.bufferingGoal + this._shaka.getStats().maxSegmentDuration); + targetBufferVal = Math.min(targetBufferVal, this.shaka!.getConfiguration().streaming.bufferingGoal + this.shaka!.getStats().maxSegmentDuration); return targetBufferVal; } public getDrmInfo(): PKDrmDataObject | null { - const drmInfo = this._shaka.drmInfo(); + const drmInfo = this.shaka!.drmInfo(); if (!drmInfo) { return null; } else { @@ -1543,22 +1559,38 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } } - public set cachedUrls(urls: string[]) { - if (!urls || !Array.isArray(urls)) urls = []; - - const cachedUrls = DashAdapter._assetCache.list(); - - const newUrlsSet = new Set(urls); - const cachedUrlsSet = new Set(cachedUrls); + public setCachedUrls(cachedUrls: string[]) { + if (!Array.isArray(cachedUrls)) return; + const newUrls = new Set(cachedUrls); + const existingUrls = new Set(this.assetCache?.list()); + for (const url of newUrls) { + if (!existingUrls.has(url)) { + this.assetCache?.add(url); + } + } + for (const url of existingUrls) { + if (!newUrls.has(url)) { + this.assetCache?.remove(url, true); + } + } + } - const toRemove = cachedUrls.filter(url => !newUrlsSet.has(url)); - for (const url of toRemove) { - DashAdapter._assetCache.remove(url); + private get assetCache() { + const assetCache = DashAdapter._assetCacheMap.get(this._videoElement.id); + if (!assetCache) { + DashAdapter._logger.warn('Failed to fetch asset cache for video element ', this._videoElement.id); + return null; } + return assetCache; + } - const toAdd = urls.filter(url => !cachedUrlsSet.has(url)); - for (const url of toAdd) { - DashAdapter._assetCache.add(url); + private get shaka() { + const shakaInstance = DashAdapter._shakaInstanceMap.get(this._videoElement.id); + if (!shakaInstance) { + DashAdapter._logger.warn('Failed to fetch shaka instance for video element ', this._videoElement.id); + return null; } + + return shakaInstance; } } From e7781bf8e7e1bd9a208fe49f1aaec2f84536e129 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:09:05 +0300 Subject: [PATCH 14/26] upgrade shaka --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce516fd..135d1e6 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "karma-webpack": "^5.0.0", "mocha": "^10.0.0", "prettier": "^3.0.3", - "shaka-player": "4.8.4", + "shaka-player": "4.8.11", "sinon": "^14.0.0", "sinon-chai": "^3.7.0", "standard-version": "^6.0.1", diff --git a/yarn.lock b/yarn.lock index 2dae980..4184773 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5534,10 +5534,10 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shaka-player@4.8.4: - version "4.8.4" - resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-4.8.4.tgz#dee88b29122da78aee02e6ce6ca76ff6c17435ab" - integrity sha512-LtPUioN0/kwLi5ewSFaoSUpQgA01XxaSa7vneCiXP8AMIdIWVM+pk/lFetwW0br26H8Lb79djiU+5vhJujEBjQ== +shaka-player@4.8.11: + version "4.8.11" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-4.8.11.tgz#f67df889ac859875a2f53645840c39b67b568192" + integrity sha512-PXrizP6bWi6Gzjk5B7aSfBiU81di1kPd8LEBIzdaNvwINjZSMYL+lDzEWeLTIoyZCjb3l1WoJJTGLex8mD3dmg== dependencies: eme-encryption-scheme-polyfill "^2.1.1" From 92f791fdfcc89477592d003bf60f137f29ec56ce Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:12:07 +0300 Subject: [PATCH 15/26] add return types to asset cache api --- src/cache/asset-cache.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cache/asset-cache.ts b/src/cache/asset-cache.ts index a11af70..d2d66c0 100644 --- a/src/cache/asset-cache.ts +++ b/src/cache/asset-cache.ts @@ -4,13 +4,13 @@ class AssetCache { private cacheQueue = new Set(); private cache = new Map(); - public init(shakaInstance: shaka.Player) { + public init(shakaInstance: shaka.Player): void { this.clearCache(); this.shakaInstance = shakaInstance; this.preloadAssets(); } - public add(assetUrl: string) { + public add(assetUrl: string): void { if (this.cache.has(assetUrl)) return; this.cacheQueue.add(assetUrl); @@ -25,7 +25,7 @@ class AssetCache { return [...this.cache.keys()]; } - public remove(assetUrl: string, destroy: boolean = false) { + public remove(assetUrl: string, destroy: boolean = false): void { if (this.cacheQueue.has(assetUrl)) { this.cacheQueue.delete(assetUrl); } else if (this.cache.has(assetUrl)) { @@ -37,14 +37,14 @@ class AssetCache { } } - private clearCache() { + private clearCache(): void { const assetUrls = this.cache.keys(); for (const assetUrl of assetUrls) { this.remove(assetUrl, true); } } - private preloadAssets() { + private preloadAssets(): void { if (!this.shakaInstance) return; for (const assetUrl of this.cacheQueue) { From 31a8ad8714ed95195f20eaf965d22a49ea9c35bd Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:16:15 +0300 Subject: [PATCH 16/26] pr fix --- src/dash-adapter.ts | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index d9a2985..d216717 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -514,11 +514,12 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } private _maybeSetFilters(): void { + this.shaka!.getNetworkingEngine()?.clearAllRequestFilters(); + this.shaka!.getNetworkingEngine()?.clearAllResponseFilters(); + if (typeof Utils.Object.getPropertyPath(this._config, 'network.requestFilter') === 'function') { DashAdapter._logger.debug('Register request filter'); - this.shaka!.getNetworkingEngine()?.clearAllRequestFilters(); - this.shaka!.getNetworkingEngine()?.clearAllResponseFilters(); this.shaka!.getNetworkingEngine()?.registerRequestFilter((type, request) => { if (Object.values(RequestType).includes(type)) { const pkRequest: PKRequestObject = {url: request.uris[0], body: request.body, headers: request.headers}; @@ -707,7 +708,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {Promise} - detach promise */ public detachMediaSource(): Promise { - if (this.shaka!) { + if (this.shaka) { // 1 second different between duration and current time will signal as end - will enable replay button // @ts-expect-error - ???? if (Math.floor(this.duration - this.currentTime) === 0) { @@ -937,8 +938,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._isDestroyInProgress = true; let shakaInstanceToDestroy; - if (this.shaka! && !this.assetCache?.list().length) { - shakaInstanceToDestroy = this.shaka!; + if (this.shaka && !this.assetCache?.list().length) { + shakaInstanceToDestroy = this.shaka; DashAdapter._shakaInstanceMap.delete(this._videoElement.id); DashAdapter._assetCacheMap.delete(this._videoElement.id); @@ -1075,7 +1076,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ private _getParsedTracks(): Array { - if (this.shaka!) { + if (this.shaka) { const videoTracks = this._getParsedVideoTracks(); const audioTracks = this._getParsedAudioTracks(); const textTracks = this._getParsedTextTracks(); @@ -1182,7 +1183,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectVideoTrack(videoTrack: VideoTrack): void { - if (this.shaka!) { + if (this.shaka) { const videoTracks = this._getVideoTracks(); if (videoTrack instanceof VideoTrack && videoTracks) { const selectedVideoTrack = videoTracks[videoTrack.index]; @@ -1209,7 +1210,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectAudioTrack(audioTrack: AudioTrack): void { - if (this.shaka! && audioTrack instanceof AudioTrack && !audioTrack.active) { + if (this.shaka && audioTrack instanceof AudioTrack && !audioTrack.active) { this.shaka!.selectAudioLanguage(audioTrack.language); this._onTrackChanged(audioTrack); } @@ -1223,7 +1224,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public selectTextTrack(textTrack: PKTextTrack): void { - if (this.shaka! && textTrack instanceof PKTextTrack && !textTrack.active && (textTrack.kind === 'subtitles' || textTrack.kind === 'captions')) { + if (this.shaka && textTrack instanceof PKTextTrack && !textTrack.active && (textTrack.kind === 'subtitles' || textTrack.kind === 'captions')) { this.shaka!.setTextTrackVisibility(this._config.textTrackVisibile); this.shaka!.selectTextLanguage(textTrack.language); this._onTrackChanged(textTrack); @@ -1231,7 +1232,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } public selectImageTrack(imageTrack: ImageTrack): void { - if (this.shaka! && this._thumbnailController && imageTrack instanceof ImageTrack && !imageTrack.active) { + if (this.shaka && this._thumbnailController && imageTrack instanceof ImageTrack && !imageTrack.active) { this._thumbnailController.selectTrack(imageTrack); this._onTrackChanged(imageTrack); } @@ -1244,7 +1245,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public hideTextTrack(): void { - if (this.shaka!) { + if (this.shaka) { this.shaka!.setTextTrackVisibility(false); } } @@ -1256,7 +1257,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public enableAdaptiveBitrate(): void { - if (this.shaka! && !this.isAdaptiveBitrateEnabled()) { + if (this.shaka && !this.isAdaptiveBitrateEnabled()) { this._trigger(EventType.ABR_MODE_CHANGED, {mode: 'auto'}); this.shaka!.configure({abr: {enabled: true}}); } @@ -1269,7 +1270,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public isAdaptiveBitrateEnabled(): boolean { - if (this.shaka!) { + if (this.shaka) { const shakaConfig = this.shaka!.getConfiguration(); return shakaConfig.abr.enabled; } @@ -1314,7 +1315,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public seekToLiveEdge(): void { - if (this.shaka! && this._videoElement.readyState > 0) { + if (this.shaka && this._videoElement.readyState > 0) { this._videoElement.currentTime = this._getLiveEdge(); } } @@ -1342,7 +1343,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @return {number} - live duration */ public getSegmentDuration(): number { - if (this.shaka!) { + if (this.shaka) { return this.shaka!.getStats().maxSegmentDuration; } return 0; From 5f5cdfd2fd3ff1fb499d13c59563a9abb61cd0c3 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:26:31 +0300 Subject: [PATCH 17/26] add test for setCachedUrls with empty array --- tests/src/dash-adapter.spec.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 4fb9b7a..252a362 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -7,6 +7,7 @@ import {wwDrmData, prDrmData} from './drm/fake-drm-data'; import shaka from 'shaka-player'; import {ImageTrack, ThumbnailInfo} from '@playkit-js/playkit-js'; import { expect } from 'chai'; +import sinonChai from 'sinon-chai'; const targetId = 'player-placeholder_dash-adapter.spec'; @@ -2025,25 +2026,36 @@ describe('DashAdapter: setCachedUrls', () => { remove = sandbox.stub(dashInstance.assetCache, "remove"); }); - it("should not add the same url twice on consecutive calls", () => { + it("should not add the same url twice on a consecutive call", () => { dashInstance.setCachedUrls(["abc"]); dashInstance.setCachedUrls(["abc"]); add.should.have.been.calledOnceWith("abc"); }); - it("should add new urls on consecutive calls", () => { + it("should add new urls on a consecutive call", () => { dashInstance.setCachedUrls(["abc"]); add.should.have.been.calledWith("abc"); dashInstance.setCachedUrls(["abc", "def"]); add.should.have.been.calledWith("def"); }); - it("should remove asset urls that were initially added and are missing on consecutive calls", () => { + it("should remove asset urls that were initially added and are missing on a consecutive call", () => { dashInstance.setCachedUrls(["abc"]); add.should.have.been.calledWith("abc"); dashInstance.setCachedUrls(["def"]); remove.should.have.been.calledWith("abc"); }); + + it("should remove all asset urls when receiving an empty array on a consecutive call", () => { + dashInstance.assetCache.list().length.should.equal(0); + dashInstance.setCachedUrls(["abc"]); + add.should.have.been.calledWith("abc"); + dashInstance.setCachedUrls(["def"]); + add.should.have.been.calledWith("def"); + dashInstance.setCachedUrls([]); + remove.should.have.been.calledWith("abc"); + remove.should.have.been.calledWith("def"); + }); }); }); From 03761caad614d364c200247061aed43f9de904f7 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:12:27 +0300 Subject: [PATCH 18/26] lint fix --- src/dash-adapter.ts | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index d216717..f254a3a 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -341,8 +341,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {void} */ public setMaxBitrate(bitrate: number): void { - if (this._hasLowerOrEqualBitrate(bitrate)) { - this.shaka!.configure({abr: {restrictions: {maxBandwidth: bitrate}}}); + if (this.shaka && this._hasLowerOrEqualBitrate(bitrate)) { + this.shaka?.configure({abr: {restrictions: {maxBandwidth: bitrate}}}); } } @@ -426,7 +426,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { //Need to call this again cause we are uninstalling the VTTCue polyfill to avoid collisions with other libs shaka.polyfill.installAll(); - if (!this.shaka! || !this.assetCache?.list().length) { + if (!this.shaka || !this.assetCache?.list().length) { const shakaInstance = new shaka.Player(); const assetCache = new AssetCache(); assetCache.init(shakaInstance); @@ -641,7 +641,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ private _updateRestriction(restrictions: PKABRRestrictionObject): void { const shakaRestrictionsConfig = this._getRestrictionShakaConfig(restrictions); - this.shaka!.configure({ + this.shaka?.configure({ abr: { restrictions: shakaRestrictionsConfig } @@ -693,7 +693,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @returns {void} */ public attachMediaSource(): void { - if (!this.shaka!) { + if (this.shaka) { if (this._videoElement && this._videoElement.src) { Utils.Dom.setAttribute(this._videoElement, 'src', ''); Utils.Dom.removeAttribute(this._videoElement, 'src'); @@ -805,8 +805,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._playbackActualUri = response.uri; this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); setTimeout(() => { - this._isLive = this._isLive || (this.shaka!?.isLive() as boolean); - if (this._isLive && !this.shaka!?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + this._isLive = this._isLive || (this.shaka?.isLive() as boolean); + if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); } @@ -860,7 +860,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } private _setLowLatencyMode(): void { - this.shaka!.configure({ + this.shaka?.configure({ streaming: { lowLatencyMode: typeof this._config.lowLatencyMode === 'boolean' ? this._config.lowLatencyMode : this.isLive() } @@ -889,11 +889,13 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @override */ public async load(startTime?: number): Promise { + if (!this.shaka) return Promise.reject('Shaka instance not set'); + if (!this._loadPromise) { await this._removeMediaKeys(); - if (!this.shaka!?.getMediaElement()) { - this.shaka!.attach(this._videoElement); + if (!this.shaka.getMediaElement()) { + this.shaka.attach(this._videoElement); } this._loadPromise = new Promise((resolve, reject) => { @@ -1189,12 +1191,12 @@ export default class DashAdapter extends BaseMediaSourceAdapter { const selectedVideoTrack = videoTracks[videoTrack.index]; if (selectedVideoTrack) { if (this.isAdaptiveBitrateEnabled()) { - this.shaka!.configure({abr: {enabled: false}}); + this.shaka.configure({abr: {enabled: false}}); this._trigger(EventType.ABR_MODE_CHANGED, {mode: 'manual'}); } if (!selectedVideoTrack.active) { this._selectedVideoTrack = videoTrack; - this.shaka!.selectVariantTrack(videoTracks[videoTrack.index], true); + this.shaka.selectVariantTrack(videoTracks[videoTrack.index], true); this._onTrackChanged(videoTrack); } } @@ -1211,7 +1213,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public selectAudioTrack(audioTrack: AudioTrack): void { if (this.shaka && audioTrack instanceof AudioTrack && !audioTrack.active) { - this.shaka!.selectAudioLanguage(audioTrack.language); + this.shaka.selectAudioLanguage(audioTrack.language); this._onTrackChanged(audioTrack); } } @@ -1225,8 +1227,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public selectTextTrack(textTrack: PKTextTrack): void { if (this.shaka && textTrack instanceof PKTextTrack && !textTrack.active && (textTrack.kind === 'subtitles' || textTrack.kind === 'captions')) { - this.shaka!.setTextTrackVisibility(this._config.textTrackVisibile); - this.shaka!.selectTextLanguage(textTrack.language); + this.shaka.setTextTrackVisibility(this._config.textTrackVisibile); + this.shaka.selectTextLanguage(textTrack.language); this._onTrackChanged(textTrack); } } @@ -1246,7 +1248,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public hideTextTrack(): void { if (this.shaka) { - this.shaka!.setTextTrackVisibility(false); + this.shaka.setTextTrackVisibility(false); } } @@ -1259,7 +1261,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { public enableAdaptiveBitrate(): void { if (this.shaka && !this.isAdaptiveBitrateEnabled()) { this._trigger(EventType.ABR_MODE_CHANGED, {mode: 'auto'}); - this.shaka!.configure({abr: {enabled: true}}); + this.shaka.configure({abr: {enabled: true}}); } } @@ -1271,7 +1273,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public isAdaptiveBitrateEnabled(): boolean { if (this.shaka) { - const shakaConfig = this.shaka!.getConfiguration(); + const shakaConfig = this.shaka.getConfiguration(); return shakaConfig.abr.enabled; } return false; @@ -1305,7 +1307,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private */ protected _getLiveEdge(): number { - return this.shaka! ? this.shaka!.seekRange().end : NaN; + return this.shaka ? this.shaka.seekRange().end : NaN; } /** @@ -1327,7 +1329,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public isLive(): boolean { - return this.shaka!?.isLive() || this._isLive; + return this.shaka?.isLive() || this._isLive; } /** @@ -1344,7 +1346,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public getSegmentDuration(): number { if (this.shaka) { - return this.shaka!.getStats().maxSegmentDuration; + return this.shaka.getStats().maxSegmentDuration; } return 0; } @@ -1463,8 +1465,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ private _onDrmSessionUpdate(): void { this._trigger(EventType.DRM_LICENSE_LOADED, { - licenseTime: this.shaka!.getStats().licenseTime, - scheme: this.shaka!.drmInfo()?.keySystem + licenseTime: this.shaka?.getStats().licenseTime, + scheme: this.shaka?.drmInfo()?.keySystem }); } @@ -1521,8 +1523,8 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @public */ public getStartTimeOfDvrWindow(): number { - if (this.isLive() && this.shaka!) { - return (this._isStartOver ? this._seekRangeStart : this.shaka!.seekRange().start) + this.shaka!.getConfiguration().streaming.safeSeekOffset; + if (this.isLive() && this.shaka) { + return (this._isStartOver ? this._seekRangeStart : this.shaka.seekRange().start) + this.shaka.getConfiguration().streaming.safeSeekOffset; } return 0; } @@ -1533,7 +1535,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { */ public get targetBuffer(): number { let targetBufferVal = NaN; - if (!this.shaka!) return NaN; + if (!this.shaka) return NaN; if (this.isLive()) { targetBufferVal = this._getLiveEdge() - this._videoElement.currentTime; } else { @@ -1541,12 +1543,12 @@ export default class DashAdapter extends BaseMediaSourceAdapter { targetBufferVal = this._videoElement.duration - this._videoElement.currentTime; } - targetBufferVal = Math.min(targetBufferVal, this.shaka!.getConfiguration().streaming.bufferingGoal + this.shaka!.getStats().maxSegmentDuration); + targetBufferVal = Math.min(targetBufferVal, this.shaka.getConfiguration().streaming.bufferingGoal + this.shaka.getStats().maxSegmentDuration); return targetBufferVal; } public getDrmInfo(): PKDrmDataObject | null { - const drmInfo = this.shaka!.drmInfo(); + const drmInfo = this.shaka?.drmInfo(); if (!drmInfo) { return null; } else { From 5f1820bf3df5fd03c63cb69ff2bde3e0dee3e82c Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:15:32 +0300 Subject: [PATCH 19/26] lint fix --- src/dash-adapter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index f254a3a..be67715 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -1562,7 +1562,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } } - public setCachedUrls(cachedUrls: string[]) { + public setCachedUrls(cachedUrls: string[]): void { if (!Array.isArray(cachedUrls)) return; const newUrls = new Set(cachedUrls); const existingUrls = new Set(this.assetCache?.list()); @@ -1578,7 +1578,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } } - private get assetCache() { + private get assetCache(): AssetCache | null { const assetCache = DashAdapter._assetCacheMap.get(this._videoElement.id); if (!assetCache) { DashAdapter._logger.warn('Failed to fetch asset cache for video element ', this._videoElement.id); @@ -1587,7 +1587,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { return assetCache; } - private get shaka() { + private get shaka(): shaka.Player | null { const shakaInstance = DashAdapter._shakaInstanceMap.get(this._videoElement.id); if (!shakaInstance) { DashAdapter._logger.warn('Failed to fetch shaka instance for video element ', this._videoElement.id); From b2e2d1887e3c01f210cd38697c058e8c7a266551 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:17:44 +0300 Subject: [PATCH 20/26] lint fix --- src/dash-adapter.ts | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index be67715..4740a42 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -790,28 +790,28 @@ export default class DashAdapter extends BaseMediaSourceAdapter { // called when a resource is downloaded this.shaka!.getNetworkingEngine()?.registerResponseFilter((type, response) => { switch (type) { - case shaka.net.NetworkingEngine.RequestType.SEGMENT: - this._trigger(EventType.FRAG_LOADED, { - miliSeconds: response.timeMs, - bytes: response.data.byteLength, - url: response.uri - }); - if (this.isLive()) { - this._dispatchNativeEvent(EventType.DURATION_CHANGE); - } - break; - case shaka.net.NetworkingEngine.RequestType.MANIFEST: - this._parseManifest(response.data); - this._playbackActualUri = response.uri; - this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); - setTimeout(() => { - this._isLive = this._isLive || (this.shaka?.isLive() as boolean); - if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + case shaka.net.NetworkingEngine.RequestType.SEGMENT: + this._trigger(EventType.FRAG_LOADED, { + miliSeconds: response.timeMs, + bytes: response.data.byteLength, + url: response.uri + }); + if (this.isLive()) { + this._dispatchNativeEvent(EventType.DURATION_CHANGE); + } + break; + case shaka.net.NetworkingEngine.RequestType.MANIFEST: + this._parseManifest(response.data); + this._playbackActualUri = response.uri; + this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); + setTimeout(() => { + this._isLive = this._isLive || (this.shaka?.isLive() as boolean); + if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); - } - }); - break; + } + }); + break; } }); } From d0664d282931c7aad9f8995e2e1d191b50a99f56 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:15:48 +0300 Subject: [PATCH 21/26] use array includes --- src/dash-adapter.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 4740a42..14de99f 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -1563,17 +1563,18 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } public setCachedUrls(cachedUrls: string[]): void { - if (!Array.isArray(cachedUrls)) return; - const newUrls = new Set(cachedUrls); - const existingUrls = new Set(this.assetCache?.list()); - for (const url of newUrls) { - if (!existingUrls.has(url)) { - this.assetCache?.add(url); + if (!Array.isArray(cachedUrls) || !this.assetCache) return; + + const existingUrls = this.assetCache.list(); + + for (const url of cachedUrls) { + if (!existingUrls.includes(url)) { + this.assetCache.add(url); } } for (const url of existingUrls) { - if (!newUrls.has(url)) { - this.assetCache?.remove(url, true); + if (!cachedUrls.includes(url)) { + this.assetCache.remove(url, true); } } } From 300961a858d2ebbb10e7ac688431ea3bd6718a60 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:09:02 +0300 Subject: [PATCH 22/26] fix tests --- tests/src/dash-adapter.spec.js | 174 +++++++++++++++++---------------- 1 file changed, 89 insertions(+), 85 deletions(-) diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 252a362..50310fb 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -40,7 +40,7 @@ const dvrInStreamThumbnailSource = { describe.skip('DashAdapter [debugging and testing manually]', () => { let player, tracks, videoTracks, textTracks, audioTracks; - before(function () { + before(() => { TestUtils.createElement('DIV', targetId); }); @@ -92,7 +92,7 @@ describe('DashAdapter: canPlayDrm', () => { sandbox.restore(); }); - it('should return true since widevine configured', function () { + it('should return true since widevine configured', () => { sandbox.stub(Widevine, 'canPlayDrm').value(() => true); sandbox.stub(Widevine, 'isConfigured').value(() => true); sandbox.stub(PlayReady, 'canPlayDrm').value(() => false); @@ -102,7 +102,7 @@ describe('DashAdapter: canPlayDrm', () => { (DashAdapter._availableDrmProtocol.find(entry => entry === Widevine) !== null).should.be.true; }); - it('should return true since playready configured', function () { + it('should return true since playready configured', () => { sandbox.stub(Widevine, 'canPlayDrm').value(() => false); sandbox.stub(Widevine, 'isConfigured').value(() => false); sandbox.stub(PlayReady, 'canPlayDrm').value(() => true); @@ -112,21 +112,21 @@ describe('DashAdapter: canPlayDrm', () => { (DashAdapter._availableDrmProtocol.find(entry => entry === PlayReady) !== null).should.be.true; }); - it('should return true for widevine and playready sources without config', function () { + it('should return true for widevine and playready sources without config', () => { sandbox.stub(Widevine, 'isConfigured').value(() => false); sandbox.stub(PlayReady, 'isConfigured').value(() => false); DashAdapter.canPlayDrm(wwDrmData.concat(prDrmData)).should.be.true; DashAdapter._availableDrmProtocol.length.should.equal(2); }); - it('should return true for widevine source only', function () { + it('should return true for widevine source only', () => { sandbox.stub(Widevine, 'isConfigured').value(() => false); sandbox.stub(PlayReady, 'isConfigured').value(() => false); DashAdapter.canPlayDrm(wwDrmData).should.be.true; DashAdapter._availableDrmProtocol.length.should.equal(1); }); - it('should return true for playready source only', function () { + it('should return true for playready source only', () => { sandbox.stub(Widevine, 'isConfigured').value(() => false); sandbox.stub(PlayReady, 'isConfigured').value(() => false); DashAdapter.canPlayDrm(prDrmData).should.be.true; @@ -143,35 +143,35 @@ describe('DashAdapter: canPlayType', () => { DashAdapter.canPlayType('APPLICATION/DASH+XML').should.be.true; }); - it('should return false to video/mp4', function () { + it('should return false to video/mp4', () => { DashAdapter.canPlayType('video/mp4').should.be.false; }); - it('should return false to invalid mimetype', function () { + it('should return false to invalid mimetype', () => { DashAdapter.canPlayType('dummy').should.be.false; }); - it('should return false to null mimetype', function () { + it('should return false to null mimetype', () => { DashAdapter.canPlayType(null).should.be.false; }); - it('should return false to empty mimetype', function () { + it('should return false to empty mimetype', () => { DashAdapter.canPlayType('').should.be.false; }); - it('should return false to no mimetype', function () { + it('should return false to no mimetype', () => { DashAdapter.canPlayType().should.be.false; }); }); describe('DashAdapter: isSupported', () => { - it('should return true', function () { + it('should return true', () => { DashAdapter.isSupported().should.be.true; }); }); describe('DashAdapter: id', () => { - it('should be named DashAdapter', function () { + it('should be named DashAdapter', () => { DashAdapter.id.should.equal('DashAdapter'); }); }); @@ -411,7 +411,7 @@ describe('DashAdapter: targetBuffer', () => { Utils.Object.mergeDeep(config, {playback: {options: {html5: {dash: {streaming: {bufferingGoal: 120}}}}}}) ); video.addEventListener(EventType.PLAYING, () => { - let targetBufferVal = dashInstance._getLiveEdge() - video.currentTime; + const targetBufferVal = dashInstance._getLiveEdge() - video.currentTime; Math.round(dashInstance.targetBuffer - targetBufferVal).should.equal(0); done(); }); @@ -432,7 +432,7 @@ describe('DashAdapter: targetBuffer', () => { Utils.Object.mergeDeep(config, {playback: {options: {html5: {dash: {streaming: {bufferingGoal: 10}}}}}}) ); video.addEventListener(EventType.PLAYING, () => { - let targetBufferVal = dashInstance._getLiveEdge() - video.currentTime; + const targetBufferVal = dashInstance._getLiveEdge() - video.currentTime; Math.round(dashInstance.targetBuffer - targetBufferVal).should.equal(0); done(); }); @@ -455,15 +455,19 @@ describe('DashAdapter: destroy', () => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); - afterEach(() => { - dashInstance = null; + afterEach(done => { + dashInstance.setCachedUrls([]); + dashInstance.destroy().then(() => { + dashInstance = null; + done(); + }) }); - after(function () { + after(() => { TestUtils.removeVideoElementsFromTestPage(); }); - it('should preform cleanup', done => { + it('should perform cleanup', done => { dashInstance .load() .then(() => { @@ -501,15 +505,15 @@ describe('DashAdapter: destroy', () => { }); it('should destroy shaka instance if there are no cached urls', done => { - const destroy = sinon.spy(dashInstance.shaka, "destroy"); + const destroy = sinon.spy(dashInstance.shaka, 'destroy'); dashInstance.destroy().then(() => { destroy.should.have.been.calledOnce; done(); }); }); it('should not destroy shaka instance if there are cached urls', done => { - const destroy = sinon.spy(dashInstance.shaka, "destroy"); - dashInstance.setCachedUrls(["abc"]); + const destroy = sinon.spy(dashInstance.shaka, 'destroy'); + dashInstance.setCachedUrls(['abc']); dashInstance.destroy().then(() => { destroy.should.not.have.been.called; done(); @@ -546,10 +550,10 @@ describe('DashAdapter: _getParsedTracks', () => { dashInstance .load() .then(data => { - let videoTracks = dashInstance._getVideoTracks(); - let audioTracks = dashInstance._getAudioTracks(); - let textTracks = dashInstance.shaka.getTextTracks(); - let totalTracksLength = videoTracks.length + audioTracks.length + textTracks.length; + const videoTracks = dashInstance._getVideoTracks(); + const audioTracks = dashInstance._getAudioTracks(); + const textTracks = dashInstance.shaka.getTextTracks(); + const totalTracksLength = videoTracks.length + audioTracks.length + textTracks.length; try { data.tracks.length.should.be.equal(totalTracksLength); data.tracks.map(track => { @@ -582,7 +586,7 @@ describe('DashAdapter: _getParsedTracks', () => { }); it('should return empty array before loading', () => { - let tracks = dashInstance._getParsedTracks(); + const tracks = dashInstance._getParsedTracks(); tracks.length.should.be.equal(0); }); }); @@ -640,7 +644,7 @@ describe('DashAdapter: selectVideoTrack', () => { it('should select a new video track', done => { let error = false; let inactiveTrack; - let onVideoTrackChanged = event => { + const onVideoTrackChanged = event => { try { if (!error) { dashInstance.removeEventListener('videotrackchanged', onVideoTrackChanged); @@ -659,7 +663,7 @@ describe('DashAdapter: selectVideoTrack', () => { return !track.active; })[0]; dashInstance.selectVideoTrack(inactiveTrack); - let activeTrack = dashInstance._getVideoTracks().filter(track => { + const activeTrack = dashInstance._getVideoTracks().filter(track => { return track.active; })[0]; try { @@ -680,7 +684,7 @@ describe('DashAdapter: selectVideoTrack', () => { dashInstance.addEventListener('videotrackchanged', () => { eventIsFired = true; }); - let activeTrack = dashInstance._getParsedVideoTracks().filter(track => { + const activeTrack = dashInstance._getParsedVideoTracks().filter(track => { return track.active; })[0]; let eventIsFired = false; @@ -702,7 +706,7 @@ describe('DashAdapter: selectVideoTrack', () => { dashInstance.addEventListener('videotrackchanged', () => { eventIsFired = true; }); - let activeTrack = dashInstance._getParsedVideoTracks().filter(track => { + const activeTrack = dashInstance._getParsedVideoTracks().filter(track => { return track.active; })[0]; let eventIsFired = false; @@ -724,7 +728,7 @@ describe('DashAdapter: selectVideoTrack', () => { dashInstance.addEventListener('videotrackchanged', () => { eventIsFired = true; }); - let activeTrack = dashInstance._getParsedVideoTracks().filter(track => { + const activeTrack = dashInstance._getParsedVideoTracks().filter(track => { return track.active; })[0]; let eventIsFired = false; @@ -746,7 +750,7 @@ describe('DashAdapter: selectVideoTrack', () => { dashInstance.addEventListener('videotrackchanged', () => { eventIsFired = true; }); - let activeTrack = dashInstance._getParsedVideoTracks().filter(track => { + const activeTrack = dashInstance._getParsedVideoTracks().filter(track => { return track.active; })[0]; let eventIsFired = false; @@ -796,7 +800,7 @@ describe('DashAdapter: selectAudioTrack', () => { done(e); } }); - let inactiveTrack = dashInstance._getParsedAudioTracks().filter(track => { + const inactiveTrack = dashInstance._getParsedAudioTracks().filter(track => { return !track.active; })[0]; dashInstance.selectAudioTrack(inactiveTrack); @@ -935,11 +939,11 @@ describe('DashAdapter: selectTextTrack', () => { event.payload.selectedTextTrack.language.should.be.equal(inactiveTrack.language); done(); }); - let inactiveTrack = dashInstance._getParsedTextTracks().filter(track => { + const inactiveTrack = dashInstance._getParsedTextTracks().filter(track => { return !track.active; })[0]; dashInstance.selectTextTrack(inactiveTrack); - let activeTrack = dashInstance.shaka.getTextTracks().filter(track => { + const activeTrack = dashInstance.shaka.getTextTracks().filter(track => { return track.active; })[0]; activeTrack.language.should.be.equal(inactiveTrack.language); @@ -953,7 +957,7 @@ describe('DashAdapter: selectTextTrack', () => { eventCounter++; activeTrack.active = true; }); - let activeTrack = dashInstance._getParsedTextTracks()[0]; + const activeTrack = dashInstance._getParsedTextTracks()[0]; dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(activeTrack); activeTrack.language.should.be.equal( @@ -975,7 +979,7 @@ describe('DashAdapter: selectTextTrack', () => { eventCounter++; activeTrack.active = true; }); - let activeTrack = dashInstance._getParsedTextTracks()[0]; + const activeTrack = dashInstance._getParsedTextTracks()[0]; dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(new VideoTrack({index: 0})); activeTrack.language.should.be.equal( @@ -997,7 +1001,7 @@ describe('DashAdapter: selectTextTrack', () => { eventCounter++; activeTrack.active = true; }); - let activeTrack = dashInstance._getParsedTextTracks()[0]; + const activeTrack = dashInstance._getParsedTextTracks()[0]; dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(); activeTrack.language.should.be.equal( @@ -1019,7 +1023,7 @@ describe('DashAdapter: selectTextTrack', () => { eventCounter++; activeTrack.active = true; }); - let activeTrack = dashInstance._getParsedTextTracks()[0]; + const activeTrack = dashInstance._getParsedTextTracks()[0]; dashInstance.selectTextTrack(activeTrack); dashInstance.selectTextTrack(new TextTrack({kind: 'metadata'})); activeTrack.language.should.be.equal( @@ -1086,7 +1090,7 @@ describe('DashAdapter: enableAdaptiveBitrate', () => { .then(() => { mode = 'auto'; dashInstance.enableAdaptiveBitrate(); - let inactiveTrack = dashInstance._getParsedVideoTracks().filter(track => { + const inactiveTrack = dashInstance._getParsedVideoTracks().filter(track => { return !track.active; })[0]; mode = 'manual'; @@ -1312,7 +1316,7 @@ describe('DashAdapter: _onBuffering', () => { it('should dispatch playing event when buffering is false and video is playing after waiting event', done => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); let hasPlaying = false; - let onPlaying = () => { + const onPlaying = () => { if (hasPlaying) { dashInstance._videoElement.removeEventListener(EventType.PLAYING, onPlaying); done(); @@ -1340,7 +1344,7 @@ describe('DashAdapter: _onBuffering', () => { it('should not dispatch playing event when buffering is false and video is playing but it has already been sent by the video element', done => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); sandbox.stub(dashInstance._videoElement, 'paused').get(() => false); - let t = setTimeout(done, 0); + const t = setTimeout(done, 0); dashInstance._videoElement.addEventListener(EventType.PLAYING, () => { done(new Error('test fail')); clearTimeout(t); @@ -1351,7 +1355,7 @@ describe('DashAdapter: _onBuffering', () => { it('should not dispatch playing event when buffering is false but video is paused', done => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); - let t = setTimeout(done, 0); + const t = setTimeout(done, 0); dashInstance._videoElement.addEventListener(EventType.PLAYING, () => { done(new Error('test fail')); clearTimeout(t); @@ -1388,7 +1392,7 @@ describe('DashAdapter: _onPlaying', () => { it('should not dispatch waiting event when buffering is false', done => { dashInstance = DashAdapter.createAdapter(video, vodSource, config); - let t = setTimeout(done, 0); + const t = setTimeout(done, 0); dashInstance._videoElement.addEventListener(EventType.WAITING, () => { done(new Error('test fail')); clearTimeout(t); @@ -1974,15 +1978,15 @@ describe('DashAdapter: setCachedUrls', () => { let video, config, sandbox, dashInstance; beforeEach(() => { - sandbox = sinon.createSandbox(); - + sandbox = sinon.createSandbox(); + video = document.createElement('video'); video.id = `test_id_${Date.now()}`; - + config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); - sandbox.stub(dashInstance.shaka, "preload").resolves("def"); + sandbox.stub(dashInstance.shaka, 'preload').resolves('def'); }); afterEach(() => { @@ -1996,66 +2000,66 @@ describe('DashAdapter: setCachedUrls', () => { describe('setCachedUrls setting', () => { describe('on initial call', () => { - let add; + let add; beforeEach(() => { - add = sandbox.spy(dashInstance.assetCache, "add"); + add = sandbox.spy(dashInstance.assetCache, 'add'); }); - + it('should not cache asset url on empty call', () => { dashInstance.setCachedUrls([]); add.should.not.have.been.called; }); it('should not add loaders on non-array call', () => { - dashInstance.setCachedUrls("abc"); + dashInstance.setCachedUrls('abc'); add.should.not.have.been.called; }); it('should cache asset url on array call', () => { - dashInstance.setCachedUrls(["abc"]); - add.should.have.been.calledOnceWith("abc"); + dashInstance.setCachedUrls(['abc']); + add.should.have.been.calledOnceWith('abc'); }) }); - describe("on consecutive calls", () => { + describe('on consecutive calls', () => { let add, remove; beforeEach(() => { - add = sandbox.spy(dashInstance.assetCache, "add"); - remove = sandbox.stub(dashInstance.assetCache, "remove"); + add = sandbox.spy(dashInstance.assetCache, 'add'); + remove = sandbox.stub(dashInstance.assetCache, 'remove'); }); - it("should not add the same url twice on a consecutive call", () => { - dashInstance.setCachedUrls(["abc"]); - dashInstance.setCachedUrls(["abc"]); - add.should.have.been.calledOnceWith("abc"); + it('should not add the same url twice on a consecutive call', () => { + dashInstance.setCachedUrls(['abc']); + dashInstance.setCachedUrls(['abc']); + add.should.have.been.calledOnceWith('abc'); }); - it("should add new urls on a consecutive call", () => { - dashInstance.setCachedUrls(["abc"]); - add.should.have.been.calledWith("abc"); - dashInstance.setCachedUrls(["abc", "def"]); - add.should.have.been.calledWith("def"); + it('should add new urls on a consecutive call', () => { + dashInstance.setCachedUrls(['abc']); + add.should.have.been.calledWith('abc'); + dashInstance.setCachedUrls(['abc', 'def']); + add.should.have.been.calledWith('def'); }); - it("should remove asset urls that were initially added and are missing on a consecutive call", () => { - dashInstance.setCachedUrls(["abc"]); - add.should.have.been.calledWith("abc"); - dashInstance.setCachedUrls(["def"]); - remove.should.have.been.calledWith("abc"); + it('should remove asset urls that were initially added and are missing on a consecutive call', () => { + dashInstance.setCachedUrls(['abc']); + add.should.have.been.calledWith('abc'); + dashInstance.setCachedUrls(['def']); + remove.should.have.been.calledWith('abc'); }); - it("should remove all asset urls when receiving an empty array on a consecutive call", () => { + it('should remove all asset urls when receiving an empty array on a consecutive call', () => { dashInstance.assetCache.list().length.should.equal(0); - dashInstance.setCachedUrls(["abc"]); - add.should.have.been.calledWith("abc"); - dashInstance.setCachedUrls(["def"]); - add.should.have.been.calledWith("def"); + dashInstance.setCachedUrls(['abc']); + add.should.have.been.calledWith('abc'); + dashInstance.setCachedUrls(['def']); + add.should.have.been.calledWith('def'); dashInstance.setCachedUrls([]); - remove.should.have.been.calledWith("abc"); - remove.should.have.been.calledWith("def"); - }); + remove.should.have.been.calledWith('abc'); + remove.should.have.been.calledWith('def'); + }); }); }); @@ -2063,11 +2067,11 @@ describe('DashAdapter: setCachedUrls', () => { let get, load; beforeEach(() => { - get = sandbox.stub(dashInstance.assetCache, "get"); - load = sandbox.stub(dashInstance.shaka, "load").resolves({}); + get = sandbox.stub(dashInstance.assetCache, 'get'); + load = sandbox.stub(dashInstance.shaka, 'load').resolves({}); }); - it("should check if url is cached", done => { + it('should check if url is cached', done => { dashInstance.load().then(() => { get.should.have.been.calledOnceWith(vodSource.url); load.should.have.been.calledOnceWith(vodSource.url, undefined); @@ -2076,10 +2080,10 @@ describe('DashAdapter: setCachedUrls', () => { }); it('should use cached url if url is cached', done => { - get.resolves("abc"); + get.resolves('abc'); dashInstance.setCachedUrls([vodSource.url]); dashInstance.load().then(() => { - load.should.have.been.calledWith("abc", undefined); + load.should.have.been.calledWith('abc', undefined); done(); }) }); From de15dbcd7012e8e9d7791fc0d53f0669308425c8 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:51:27 +0300 Subject: [PATCH 23/26] unload shaka on destroy --- src/dash-adapter.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 14de99f..67d0eaa 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -940,11 +940,15 @@ export default class DashAdapter extends BaseMediaSourceAdapter { this._isDestroyInProgress = true; let shakaInstanceToDestroy; - if (this.shaka && !this.assetCache?.list().length) { - shakaInstanceToDestroy = this.shaka; + if (this.shaka) { + this.shaka.unload(); + + if (!this.assetCache?.list().length) { + shakaInstanceToDestroy = this.shaka; - DashAdapter._shakaInstanceMap.delete(this._videoElement.id); - DashAdapter._assetCacheMap.delete(this._videoElement.id); + DashAdapter._shakaInstanceMap.delete(this._videoElement.id); + DashAdapter._assetCacheMap.delete(this._videoElement.id); + } } return new Promise((resolve, reject) => { From dea51c9c4d861117ef6e74d33a9ba9591be4ce62 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:36:15 +0300 Subject: [PATCH 24/26] fix unit tests --- src/dash-adapter.ts | 64 ++++++++++--------- tests/src/dash-adapter.spec.js | 113 +++++++++++++++++++-------------- tests/src/utils/test-utils.js | 20 +++--- 3 files changed, 110 insertions(+), 87 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 67d0eaa..2ec8eb2 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -790,28 +790,28 @@ export default class DashAdapter extends BaseMediaSourceAdapter { // called when a resource is downloaded this.shaka!.getNetworkingEngine()?.registerResponseFilter((type, response) => { switch (type) { - case shaka.net.NetworkingEngine.RequestType.SEGMENT: - this._trigger(EventType.FRAG_LOADED, { - miliSeconds: response.timeMs, - bytes: response.data.byteLength, - url: response.uri - }); - if (this.isLive()) { - this._dispatchNativeEvent(EventType.DURATION_CHANGE); - } - break; - case shaka.net.NetworkingEngine.RequestType.MANIFEST: - this._parseManifest(response.data); - this._playbackActualUri = response.uri; - this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); - setTimeout(() => { - this._isLive = this._isLive || (this.shaka?.isLive() as boolean); - if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + case shaka.net.NetworkingEngine.RequestType.SEGMENT: + this._trigger(EventType.FRAG_LOADED, { + miliSeconds: response.timeMs, + bytes: response.data.byteLength, + url: response.uri + }); + if (this.isLive()) { + this._dispatchNativeEvent(EventType.DURATION_CHANGE); + } + break; + case shaka.net.NetworkingEngine.RequestType.MANIFEST: + this._parseManifest(response.data); + this._playbackActualUri = response.uri; + this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); + setTimeout(() => { + this._isLive = this._isLive || (this.shaka?.isLive() as boolean); + if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); - } - }); - break; + } + }); + break; } }); } @@ -939,19 +939,25 @@ export default class DashAdapter extends BaseMediaSourceAdapter { public destroy(): Promise { this._isDestroyInProgress = true; - let shakaInstanceToDestroy; - if (this.shaka) { - this.shaka.unload(); + return new Promise(async (resolve, reject) => { + let shakaInstanceToDestroy; - if (!this.assetCache?.list().length) { - shakaInstanceToDestroy = this.shaka; + if (this.shaka) { + // explicitly pass undefined to restore the default drm configuration + this.shaka.configure('drm', undefined); - DashAdapter._shakaInstanceMap.delete(this._videoElement.id); - DashAdapter._assetCacheMap.delete(this._videoElement.id); + if (this.assetCache?.list().length) { + if (this._videoElement.src) { + await this.shaka.unload(); + } + await this.shaka.detach(); + } else { + shakaInstanceToDestroy = this.shaka; + DashAdapter._shakaInstanceMap.delete(this._videoElement.id); + DashAdapter._assetCacheMap.delete(this._videoElement.id); + } } - } - return new Promise((resolve, reject) => { super.destroy().then(() => { DashAdapter._logger.debug('destroy'); this._loadPromise = undefined; diff --git a/tests/src/dash-adapter.spec.js b/tests/src/dash-adapter.spec.js index 50310fb..9736f2d 100644 --- a/tests/src/dash-adapter.spec.js +++ b/tests/src/dash-adapter.spec.js @@ -6,8 +6,6 @@ import {PlayReady} from '../../src/drm/playready'; import {wwDrmData, prDrmData} from './drm/fake-drm-data'; import shaka from 'shaka-player'; import {ImageTrack, ThumbnailInfo} from '@playkit-js/playkit-js'; -import { expect } from 'chai'; -import sinonChai from 'sinon-chai'; const targetId = 'player-placeholder_dash-adapter.spec'; @@ -37,6 +35,10 @@ const dvrInStreamThumbnailSource = { url: 'http://pf5.broadpeak-vcdn.com/bpk-tv/tvr/default/index.mpd' }; +function createVideoElement() { + return TestUtils.createElement('video', 'test_id_1'); +} + describe.skip('DashAdapter [debugging and testing manually]', () => { let player, tracks, videoTracks, textTracks, audioTracks; @@ -180,7 +182,7 @@ describe('DashAdapter: load', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -353,7 +355,7 @@ describe('DashAdapter: targetBuffer', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -447,20 +449,17 @@ describe('DashAdapter: targetBuffer', () => { }); describe('DashAdapter: destroy', () => { - let video, dashInstance, config; + let video, dashInstance, config, sandbox; beforeEach(() => { - video = document.createElement('video'); + sandbox = sinon.createSandbox(); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); - afterEach(done => { - dashInstance.setCachedUrls([]); - dashInstance.destroy().then(() => { - dashInstance = null; - done(); - }) + afterEach(() => { + sandbox.restore(); }); after(() => { @@ -505,27 +504,36 @@ describe('DashAdapter: destroy', () => { }); it('should destroy shaka instance if there are no cached urls', done => { - const destroy = sinon.spy(dashInstance.shaka, 'destroy'); + const destroy = sandbox.spy(dashInstance.shaka, 'destroy'); + + dashInstance.setCachedUrls([]); dashInstance.destroy().then(() => { - destroy.should.have.been.calledOnce; + destroy.should.have.been.called; done(); }); }); - it('should not destroy shaka instance if there are cached urls', done => { - const destroy = sinon.spy(dashInstance.shaka, 'destroy'); - dashInstance.setCachedUrls(['abc']); + + it('should detach shaka instance if there are cached urls', done => { + const destroy = sandbox.spy(dashInstance.shaka, 'destroy'); + const detach = sandbox.spy(dashInstance.shaka, 'detach'); + + dashInstance.setCachedUrls(['https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd']); dashInstance.destroy().then(() => { destroy.should.not.have.been.called; + detach.should.have.been.called; done(); }) }); + + // Note - we don't call setCachedUrls([]) here on purpose, + // to verify that on the following tests, working with cached entries does not break the adapter behavior }); describe('DashAdapter: _getParsedTracks', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); @@ -598,7 +606,7 @@ describe('DashAdapter: check config overriding', () => { }; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); }); afterEach(done => { @@ -625,7 +633,7 @@ describe('DashAdapter: selectVideoTrack', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {abr: {enabled: false}}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); @@ -772,7 +780,7 @@ describe('DashAdapter: selectAudioTrack', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); @@ -917,7 +925,7 @@ describe('DashAdapter: selectTextTrack', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); @@ -1043,7 +1051,7 @@ describe('DashAdapter: enableAdaptiveBitrate', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {abr: {enabled: false}}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); }); @@ -1107,7 +1115,7 @@ describe('DashAdapter: isLive', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -1171,7 +1179,7 @@ describe('DashAdapter: _getLiveEdge', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -1213,7 +1221,7 @@ describe('DashAdapter: seekToLiveEdge', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -1277,14 +1285,16 @@ describe('DashAdapter: _onBuffering', () => { let video, dashInstance, config, sandbox; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; sandbox = sinon.createSandbox(); }); - afterEach(() => { - dashInstance.destroy(); - dashInstance = null; + afterEach(done => { + dashInstance.destroy().then(() => { + dashInstance = null; + done(); + }); sandbox.restore(); }); @@ -1368,13 +1378,15 @@ describe('DashAdapter: _onPlaying', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); - afterEach(() => { - dashInstance.destroy(); - dashInstance = null; + afterEach(done => { + dashInstance.destroy().then(() => { + dashInstance = null; + done(); + }); }); after(() => { @@ -1402,10 +1414,11 @@ describe('DashAdapter: _onPlaying', () => { }); describe('DashAdapter: getStartTimeOfDvrWindow', () => { - let video, dashInstance, config; + let video, dashInstance, config, sandbox; beforeEach(() => { - video = document.createElement('video'); + sandbox = sinon.createSandbox(); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -1414,6 +1427,7 @@ describe('DashAdapter: getStartTimeOfDvrWindow', () => { dashInstance = null; done(); }); + sandbox.restore(); }); after(() => { @@ -1438,7 +1452,9 @@ describe('DashAdapter: getStartTimeOfDvrWindow', () => { }); it('should return the start time of Dvr window for live', done => { - dashInstance = DashAdapter.createAdapter(video, liveSource, config); + dashInstance = DashAdapter.createAdapter(video, vodSource, config); + dashInstance._isLive = true; + video.addEventListener(EventType.LOADED_DATA, () => { setTimeout(() => { try { @@ -1459,7 +1475,7 @@ describe('DashAdapter: request filter', () => { let video, dashInstance, config, sandbox; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; sandbox = sinon.createSandbox(); }); @@ -1672,7 +1688,7 @@ describe('DashAdapter: response filter', () => { let video, dashInstance, config, sandbox; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; sandbox = sinon.createSandbox(); }); @@ -1852,7 +1868,7 @@ describe('DashAdapter: in-stream thumbnails', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); @@ -1924,13 +1940,15 @@ describe('DashAdapter: on emsg', () => { let video, dashInstance, config; beforeEach(() => { - video = document.createElement('video'); + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; }); - afterEach(() => { - dashInstance.destroy(); - dashInstance = null; + afterEach(done => { + dashInstance.destroy().then(() => { + dashInstance = null; + done(); + }); }); after(() => { @@ -1979,13 +1997,12 @@ describe('DashAdapter: setCachedUrls', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - - video = document.createElement('video'); - video.id = `test_id_${Date.now()}`; - + video = createVideoElement(); config = {playback: {options: {html5: {dash: {}}}}}; dashInstance = DashAdapter.createAdapter(video, vodSource, config); + dashInstance.setCachedUrls([]); + sandbox.stub(dashInstance.shaka, 'preload').resolves('def'); }); diff --git a/tests/src/utils/test-utils.js b/tests/src/utils/test-utils.js index b155096..7ad8440 100644 --- a/tests/src/utils/test-utils.js +++ b/tests/src/utils/test-utils.js @@ -6,12 +6,12 @@ * @returns {HTMLDivElement} */ function createElement(type, id, opt_parentId) { - let element = document.createElement(type); + const element = document.createElement(type); element.id = id; if (!opt_parentId) { document.body.appendChild(element); } else { - let parent = document.getElementById(opt_parentId); + const parent = document.getElementById(opt_parentId); if (parent) { parent.appendChild(element); } else { @@ -27,7 +27,7 @@ function createElement(type, id, opt_parentId) { * @returns {void} */ function removeElement(id) { - let element = document.getElementById(id); + const element = document.getElementById(id); element.parentNode.removeChild(element); } @@ -36,7 +36,7 @@ function removeElement(id) { * @returns {void} */ function removeVideoElementsFromTestPage() { - let element = document.getElementsByTagName('video'); + const element = document.getElementsByTagName('video'); for (let i = element.length - 1; i >= 0; i--) { element[i].parentNode.removeChild(element[i]); } @@ -48,8 +48,8 @@ function removeVideoElementsFromTestPage() { * @returns {void} */ function createTitle(title) { - let header = document.createElement('header'); - let h4 = document.createElement('h4'); + const header = document.createElement('header'); + const h4 = document.createElement('h4'); h4.textContent = title; header.appendChild(h4); document.body.appendChild(header); @@ -62,7 +62,7 @@ function createTitle(title) { * @returns {Element} - The track button element. */ function createTrackButton(innerText, id) { - let element = document.createElement('BUTTON'); + const element = document.createElement('BUTTON'); element.innerText = innerText; element.id = id; document.body.appendChild(element); @@ -78,7 +78,7 @@ function createTrackButton(innerText, id) { function createVideoTrackButtons(player, videoTracks) { createTitle('Video Tracks'); for (let i = 0; i < videoTracks.length; i++) { - let element = createTrackButton(videoTracks[i].label || videoTracks[i].bandwidth || videoTracks[i].language, videoTracks[i].index); + const element = createTrackButton(videoTracks[i].label || videoTracks[i].bandwidth || videoTracks[i].language, videoTracks[i].index); element.onclick = function () { player.selectTrack(videoTracks[i]); }; @@ -94,7 +94,7 @@ function createVideoTrackButtons(player, videoTracks) { function createAudioTrackButtons(player, audioTracks) { createTitle('Audio Tracks'); for (let i = 0; i < audioTracks.length; i++) { - let element = createTrackButton(audioTracks[i].label || audioTracks[i].language, audioTracks[i].index); + const element = createTrackButton(audioTracks[i].label || audioTracks[i].language, audioTracks[i].index); element.onclick = function () { player.selectTrack(audioTracks[i]); }; @@ -110,7 +110,7 @@ function createAudioTrackButtons(player, audioTracks) { function createTextTrackButtons(player, textTracks) { createTitle('Text Tracks'); for (let i = 0; i < textTracks.length; i++) { - let element = createTrackButton(textTracks[i].label || textTracks[i].language, textTracks[i].index); + const element = createTrackButton(textTracks[i].label || textTracks[i].language, textTracks[i].index); element.onclick = function () { player.selectTrack(textTracks[i]); }; From 13ba13dbc53ca2a0b80b22a0335f0a7e837241b4 Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:44:48 +0300 Subject: [PATCH 25/26] lint fix --- src/dash-adapter.ts | 59 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/dash-adapter.ts b/src/dash-adapter.ts index 2ec8eb2..63c25fd 100644 --- a/src/dash-adapter.ts +++ b/src/dash-adapter.ts @@ -790,28 +790,28 @@ export default class DashAdapter extends BaseMediaSourceAdapter { // called when a resource is downloaded this.shaka!.getNetworkingEngine()?.registerResponseFilter((type, response) => { switch (type) { - case shaka.net.NetworkingEngine.RequestType.SEGMENT: - this._trigger(EventType.FRAG_LOADED, { - miliSeconds: response.timeMs, - bytes: response.data.byteLength, - url: response.uri - }); - if (this.isLive()) { - this._dispatchNativeEvent(EventType.DURATION_CHANGE); - } - break; - case shaka.net.NetworkingEngine.RequestType.MANIFEST: - this._parseManifest(response.data); - this._playbackActualUri = response.uri; - this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); - setTimeout(() => { - this._isLive = this._isLive || (this.shaka?.isLive() as boolean); - if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { + case shaka.net.NetworkingEngine.RequestType.SEGMENT: + this._trigger(EventType.FRAG_LOADED, { + miliSeconds: response.timeMs, + bytes: response.data.byteLength, + url: response.uri + }); + if (this.isLive()) { + this._dispatchNativeEvent(EventType.DURATION_CHANGE); + } + break; + case shaka.net.NetworkingEngine.RequestType.MANIFEST: + this._parseManifest(response.data); + this._playbackActualUri = response.uri; + this._trigger(EventType.MANIFEST_LOADED, {miliSeconds: response.timeMs}); + setTimeout(() => { + this._isLive = this._isLive || (this.shaka?.isLive() as boolean); + if (this._isLive && !this.shaka?.isLive() && !this._isStaticLive && this._config.switchDynamicToStatic) { this._sourceObj!.url = response.uri; this._switchFromDynamicToStatic(); - } - }); - break; + } + }); + break; } }); } @@ -939,20 +939,19 @@ export default class DashAdapter extends BaseMediaSourceAdapter { public destroy(): Promise { this._isDestroyInProgress = true; - return new Promise(async (resolve, reject) => { - let shakaInstanceToDestroy; + return new Promise((resolve, reject) => { + let shakaInstance; + let cleanUpFunction; if (this.shaka) { // explicitly pass undefined to restore the default drm configuration this.shaka.configure('drm', undefined); + shakaInstance = this.shaka; if (this.assetCache?.list().length) { - if (this._videoElement.src) { - await this.shaka.unload(); - } - await this.shaka.detach(); + cleanUpFunction = 'detach'; } else { - shakaInstanceToDestroy = this.shaka; + cleanUpFunction = 'destroy'; DashAdapter._shakaInstanceMap.delete(this._videoElement.id); DashAdapter._assetCacheMap.delete(this._videoElement.id); } @@ -962,7 +961,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { DashAdapter._logger.debug('destroy'); this._loadPromise = undefined; this._adapterEventsBindings = {}; - this._reset(shakaInstanceToDestroy) + this._reset(shakaInstance, cleanUpFunction) .then(resetResult => { this._isDestroyInProgress = false; resolve(resetResult); @@ -994,7 +993,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { * @private * @returns {Promise<*>} - The destroy promise. */ - private _reset(shakaInstance?: shaka.Player): Promise { + private _reset(shakaInstance?: shaka.Player, cleanUpFunctionName: string = 'destroy'): Promise { this._buffering = false; this._waitingSent = false; this._playingSent = false; @@ -1013,7 +1012,7 @@ export default class DashAdapter extends BaseMediaSourceAdapter { } if (shakaInstance) { - return shakaInstance.destroy(); + return shakaInstance[cleanUpFunctionName](); } return Promise.resolve(); } From 3dc1ba517551fd180408effe4880e46ae5eda5ec Mon Sep 17 00:00:00 2001 From: SivanA-Kaltura <88330203+SivanA-Kaltura@users.noreply.github.com> Date: Sun, 23 Jun 2024 13:44:14 +0300 Subject: [PATCH 26/26] update playkit-js version --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 135d1e6..0dc5626 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@babel/runtime": "^7.23.6", "@microsoft/api-extractor": "^7.38.0", "@playkit-js/browserslist-config": "1.0.8", - "@playkit-js/playkit-js": "canary", + "@playkit-js/playkit-js": "0.84.10-canary.0-fa40833", "@types/chai": "^4.3.3", "@types/mocha": "^9.1.1", "@types/sinon": "^10.0.20", @@ -89,7 +89,7 @@ "webpack-dev-server": "^4.15.1" }, "peerDependencies": { - "@playkit-js/playkit-js": "canary", + "@playkit-js/playkit-js": "0.84.10-canary.0-fa40833", "shaka-player": "4.7.0" }, "browserslist": [ diff --git a/yarn.lock b/yarn.lock index 4184773..75283d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1175,10 +1175,10 @@ resolved "https://registry.yarnpkg.com/@playkit-js/browserslist-config/-/browserslist-config-1.0.8.tgz#735256ba560063d397d4b8776acb865e8697a287" integrity sha512-BeiDM72c6GP8dZ6b2qScEpxT4sGECIJzjVGsanaTvXeFOkw3MoplAyz6HPKdrcLmidcinSl4yna5Yc9/ObwZow== -"@playkit-js/playkit-js@canary": - version "0.84.2-canary.0-cceb0c7" - resolved "https://registry.yarnpkg.com/@playkit-js/playkit-js/-/playkit-js-0.84.2-canary.0-cceb0c7.tgz#a3b47545b2710acaf55811f48115bb75b8ff7972" - integrity sha512-pa2JDuNA1MD5cxYLT65GXVw8SLgL1QuTyklT88cxHa4Ir7XMbWJBV+1lp9RS363jNX1+pgMQT5lCWthEb8pqXg== +"@playkit-js/playkit-js@0.84.10-canary.0-fa40833": + version "0.84.10-canary.0-fa40833" + resolved "https://registry.yarnpkg.com/@playkit-js/playkit-js/-/playkit-js-0.84.10-canary.0-fa40833.tgz#15a2e7a2927b81f62d292753250d48f3c6af1365" + integrity sha512-qDaZWIz363kScuBjhg9P3zc2dG0yewQ5vj/+XsJYJMtIycE1WThhPi1gX/3AG/uBf/5oABYjq2Fi2AoQbRfvSA== dependencies: js-logger "^1.6.0" ua-parser-js "^1.0.36"