diff --git a/index.d.ts b/index.d.ts
index 6e0b72ffb7..303ce46fda 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1571,6 +1571,8 @@ declare namespace dashjs {
destroy(): void;
+ getManifest(): object;
+
}
interface MediaPlayerErrors {
@@ -3690,7 +3692,7 @@ declare namespace dashjs {
getFonts(): FontInfo[];
getFontsForTrackId(trackId: number): FontInfo[];
-
+
reset(): void;
}
diff --git a/samples/live-streaming/mpd-patching.html b/samples/live-streaming/mpd-patching.html
new file mode 100644
index 0000000000..729faaae6a
--- /dev/null
+++ b/samples/live-streaming/mpd-patching.html
@@ -0,0 +1,116 @@
+
+
+
+
+ Live stream with MPD Patching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Live stream with MPD Patching
+
Example showing how dash.js handles live streams with updates to the manifest file provided via
+ MPD patches.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/samples.json b/samples/samples.json
index 099ef40f80..5304b1de7e 100644
--- a/samples/samples.json
+++ b/samples/samples.json
@@ -165,6 +165,17 @@
"Video",
"Audio"
]
+ },
+ {
+ "title": "Live stream with MPD Patching",
+ "description": "Example showing how dash.js handles live streams with updates to the MPD provided via MPD patching",
+ "href": "live-streaming/mpd-patching.html",
+ "image": "lib/img/livesim-1.jpg",
+ "labels": [
+ "Live",
+ "Video",
+ "Audio"
+ ]
}
]
},
diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js
index 1dc73b4800..91e1fe627a 100644
--- a/src/streaming/MediaPlayer.js
+++ b/src/streaming/MediaPlayer.js
@@ -2171,6 +2171,13 @@ function MediaPlayer() {
}
}
+ /**
+ * Returns the current manifest
+ * @returns {object}
+ */
+ function getManifest() {
+ return manifestModel.getValue();
+ }
/**
* Returns all BaseURLs that are available including synthesized elements (e.g by content steering)
@@ -2642,6 +2649,7 @@ function MediaPlayer() {
getPlaybackRate,
getProtectionController,
getCurrentRepresentationForType,
+ getManifest,
getRepresentationsByType,
getSettings,
getSource,
diff --git a/test/functional/adapter/DashJsAdapter.js b/test/functional/adapter/DashJsAdapter.js
index 6b0b9e5c26..64b6cbac36 100644
--- a/test/functional/adapter/DashJsAdapter.js
+++ b/test/functional/adapter/DashJsAdapter.js
@@ -192,6 +192,10 @@ class DashJsAdapter {
return this.player.getTargetLiveDelay();
}
+ getManifest() {
+ return this.player.getManifest();
+ }
+
seek(value) {
this.player.seek(value);
}
@@ -478,6 +482,27 @@ class DashJsAdapter {
})
}
+ async waitForEventAndGetPayload(timeoutValue, event) {
+ return new Promise((resolve) => {
+ let timeout = null;
+
+ const _onComplete = (res) => {
+ clearTimeout(timeout);
+ timeout = null;
+ this.player.off(event, _onEvent);
+ resolve(res);
+ }
+ const _onTimeout = () => {
+ _onComplete(null);
+ }
+ const _onEvent = (e) => {
+ _onComplete(e);
+ }
+ timeout = setTimeout(_onTimeout, timeoutValue);
+ this.player.on(event, _onEvent);
+ })
+ }
+
async waitForMediaSegmentDownload(timeoutValue) {
return new Promise((resolve) => {
let timeout = null;
diff --git a/test/functional/config/karma.functional.conf.cjs b/test/functional/config/karma.functional.conf.cjs
index 1b690cbbac..b3c8d0edda 100644
--- a/test/functional/config/karma.functional.conf.cjs
+++ b/test/functional/config/karma.functional.conf.cjs
@@ -125,7 +125,7 @@ module.exports = function (config) {
autoWatch: false,
browserNoActivityTimeout: 180000,
- browserDisconnectTimeout: 10000,
+ browserDisconnectTimeout: 20000,
browserDisconnectTolerance: 3,
// start these browsers
diff --git a/test/functional/config/test-configurations/streams/all.json b/test/functional/config/test-configurations/streams/all.json
index d8eff6677d..d88e4abc37 100644
--- a/test/functional/config/test-configurations/streams/all.json
+++ b/test/functional/config/test-configurations/streams/all.json
@@ -419,6 +419,28 @@
}
]
}
+ },
+ {
+ "name": "MPD Patching with $time",
+ "url": "https://192-46-234-23.ip.linodeusercontent.com/livesim2/segtimeline_1/patch_60/testpic_2s/Manifest.mpd",
+ "type": "live",
+ "testdata": {
+ "mpdPatching": true
+ },
+ "includedTestfiles": [
+ "feature-support/mpd-patching"
+ ]
+ },
+ {
+ "name": "MPD Patching with $number",
+ "url": "https://192-46-234-23.ip.linodeusercontent.com/livesim2/segtimelinenr_1/patch_60/testpic_2s/Manifest.mpd",
+ "type": "live",
+ "testdata": {
+ "mpdPatching": true
+ },
+ "includedTestfiles": [
+ "feature-support/mpd-patching"
+ ]
}
]
}
diff --git a/test/functional/config/test-configurations/streams/single.json b/test/functional/config/test-configurations/streams/single.json
index 462f24d6fa..2c26ef27b1 100644
--- a/test/functional/config/test-configurations/streams/single.json
+++ b/test/functional/config/test-configurations/streams/single.json
@@ -9,11 +9,11 @@
},
"testvectors": [
{
- "name": "AWS Single Period $number$",
- "url": "https://d10gktn8v7end7.cloudfront.net/out/v1/6ee19df3afa24fe190a8ae16c2c88560/index.mpd",
- "type": "live",
+ "name": "Segment Base",
+ "type": "vod",
+ "url": "https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd",
"includedTestfiles": [
- "playback-advanced/cmcd"
+ "playback/*"
]
}
]
diff --git a/test/functional/config/test-configurations/streams/smoke.json b/test/functional/config/test-configurations/streams/smoke.json
index bbd94da482..1477d692a2 100644
--- a/test/functional/config/test-configurations/streams/smoke.json
+++ b/test/functional/config/test-configurations/streams/smoke.json
@@ -74,7 +74,7 @@
"playback/play",
"playback/pause",
"playback/seek",
- "playback-advanced/cmcd",
+ "feature-support/cmcd",
"playback-advanced/preload"
]
},
@@ -152,6 +152,17 @@
"includedTestfiles": [
"playback/*"
]
+ },
+ {
+ "name": "MPD Patching with $time",
+ "url": "https://192-46-234-23.ip.linodeusercontent.com/livesim2/segtimeline_1/patch_60/testpic_2s/Manifest.mpd",
+ "type": "live",
+ "testdata": {
+ "mpdPatching": true
+ },
+ "includedTestfiles": [
+ "feature-support/mpd-patching"
+ ]
}
]
}
diff --git a/test/functional/src/Constants.js b/test/functional/src/Constants.js
index c96b400a02..90062e0059 100644
--- a/test/functional/src/Constants.js
+++ b/test/functional/src/Constants.js
@@ -136,6 +136,8 @@ TESTCASES.BUFFER.INITIAL_TARGET = TESTCASES.CATEGORIES.BUFFER + 'initial-buffer-
TESTCASES.BUFFER.TARGET = TESTCASES.CATEGORIES.BUFFER + 'buffer-target';
TESTCASES.FEATURE_SUPPORT.EMSG_TRIGGERED = TESTCASES.CATEGORIES.FEATURE_SUPPORT + 'emsg-triggered';
+TESTCASES.FEATURE_SUPPORT.MPD_PATCHING = TESTCASES.CATEGORIES.FEATURE_SUPPORT + 'mpd-patching';
+TESTCASES.FEATURE_SUPPORT.CMCD = TESTCASES.CATEGORIES.FEATURE_SUPPORT + 'cmcd';
TESTCASES.LIVE.CATCHUP = TESTCASES.CATEGORIES.LIVE + 'latency-catchup';
TESTCASES.LIVE.DELAY = TESTCASES.CATEGORIES.LIVE + 'live-delay';
@@ -150,7 +152,6 @@ TESTCASES.PLAYBACK_ADVANCED.ATTACH_AT_NON_ZERO = TESTCASES.CATEGORIES.PLAYBACK_A
TESTCASES.PLAYBACK_ADVANCED.ATTACH_WITH_POSIX = TESTCASES.CATEGORIES.PLAYBACK_ADVANCED + 'attach-with-posix';
TESTCASES.PLAYBACK_ADVANCED.MPD_ANCHOR = TESTCASES.CATEGORIES.PLAYBACK_ADVANCED + 'mpd-anchor';
TESTCASES.PLAYBACK_ADVANCED.MULTIPERIOD_PLAYBACK = TESTCASES.CATEGORIES.PLAYBACK_ADVANCED + 'multiperiod-playback';
-TESTCASES.PLAYBACK_ADVANCED.CMCD = TESTCASES.CATEGORIES.PLAYBACK_ADVANCED + 'cmcd';
TESTCASES.PLAYBACK_ADVANCED.PRELOAD = TESTCASES.CATEGORIES.PLAYBACK_ADVANCED + 'preload';
TESTCASES.TEXT.INITIAL = TESTCASES.CATEGORIES.TEXT + 'initial-text';
diff --git a/test/functional/test/playback-advanced/cmcd.js b/test/functional/test/feature-support/cmcd.js
similarity index 97%
rename from test/functional/test/playback-advanced/cmcd.js
rename to test/functional/test/feature-support/cmcd.js
index b07cb2f16a..d9b4a250be 100644
--- a/test/functional/test/playback-advanced/cmcd.js
+++ b/test/functional/test/feature-support/cmcd.js
@@ -9,7 +9,7 @@ import {
} from '../common/common.js';
import {expect} from 'chai';
-const TESTCASE = Constants.TESTCASES.PLAYBACK_ADVANCED.CMCD;
+const TESTCASE = Constants.TESTCASES.FEATURE_SUPPORT.CMCD;
Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => {
const mpd = item.url;
diff --git a/test/functional/test/feature-support/emsg-triggered.js b/test/functional/test/feature-support/emsg-triggered.js
index 5920d30cbd..a417fb5f25 100644
--- a/test/functional/test/feature-support/emsg-triggered.js
+++ b/test/functional/test/feature-support/emsg-triggered.js
@@ -1,4 +1,3 @@
-import DashJsAdapter from '../../adapter/DashJsAdapter.js';
import Constants from '../../src/Constants.js';
import Utils from '../../src/Utils.js';
import {expect} from 'chai'
diff --git a/test/functional/test/feature-support/mpd-patching.js b/test/functional/test/feature-support/mpd-patching.js
new file mode 100644
index 0000000000..e9e0fe78b6
--- /dev/null
+++ b/test/functional/test/feature-support/mpd-patching.js
@@ -0,0 +1,55 @@
+import Constants from '../../src/Constants.js';
+import Utils from '../../src/Utils.js';
+import {expect} from 'chai'
+import {checkIsPlaying, checkIsProgressing, checkNoCriticalErrors, initializeDashJsAdapter} from '../common/common.js';
+
+const TESTCASE = Constants.TESTCASES.FEATURE_SUPPORT.MPD_PATCHING;
+
+Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => {
+ const mpd = item.url;
+
+ describe(`${TESTCASE} - ${item.name} - ${mpd}`, function () {
+
+ let playerAdapter
+
+ before(function () {
+ if (item.type === Constants.CONTENT_TYPES.VOD || !item.testdata || !item.testdata.mpdPatching) {
+ this.skip();
+ }
+ playerAdapter = initializeDashJsAdapter(item, mpd);
+ })
+
+ after(() => {
+ if (playerAdapter) {
+ playerAdapter.destroy();
+ }
+ })
+
+ it(`Checking playing state`, async () => {
+ await checkIsPlaying(playerAdapter, true);
+ })
+
+ it(`Checking progressing state`, async () => {
+ await checkIsProgressing(playerAdapter);
+ });
+
+ it(`Two consecutive manifest updates shall be of type Patch`, async () => {
+ const manifest = playerAdapter.getManifest();
+ const minimumUpdatePeriodInMs = parseInt(manifest.minimumUpdatePeriod) * 1000;
+
+ let manifestUpdateEvent = await playerAdapter.waitForEventAndGetPayload(minimumUpdatePeriodInMs * 2, 'internalManifestLoaded')
+ expect(manifestUpdateEvent.manifest.tagName).to.be.equal('Patch')
+ manifestUpdateEvent = await playerAdapter.waitForEventAndGetPayload(minimumUpdatePeriodInMs * 2, 'internalManifestLoaded')
+ expect(manifestUpdateEvent.manifest.tagName).to.be.equal('Patch')
+ });
+
+ it(`Should still be progressing`, async () => {
+ await checkIsProgressing(playerAdapter);
+ });
+
+ it(`Expect no critical errors to be thrown`, () => {
+ checkNoCriticalErrors(playerAdapter);
+ })
+
+ })
+})