From 73f0e5430d7af003d23ff74f2f7134974de77731 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 09:47:18 +0200 Subject: [PATCH 1/6] Adds a functional testsuite based on the Karma framework. --- .gitignore | 4 + karma.functional.conf.js | 124 +++++ package-lock.json | 81 +++ package.json | 3 + .../functional-karma/adapter/DashJsAdapter.js | 461 ++++++++++++++++++ .../adapter/GoogleAdManagerAdapter.js | 142 ++++++ test/functional-karma/config/content.js | 265 ++++++++++ test/functional-karma/config/single-vector.js | 20 + test/functional-karma/config/subtitle.js | 107 ++++ test/functional-karma/config/vendor.js | 10 + .../gap/audio_gap_at_start_timeline.mpd | 30 ++ .../content/gap/audio_negative_ept_delta.mpd | 31 ++ .../content/gap/audio_positive_ept_delta.mpd | 31 ++ .../gap/video_gap_at_start_timeline.mpd | 31 ++ .../content/gap/video_negative_ept_delta.mpd | 31 ++ .../content/gap/video_positive_ept_delta.mpd | 31 ++ test/functional-karma/helper/Constants.js | 119 +++++ test/functional-karma/helper/Utils.js | 42 ++ test/functional-karma/index.md | 34 ++ .../advanced/abr-switch-fully-buffered.js | 0 .../no-reload-after-non-replacement-switch.js | 0 .../test/advanced/no-reload-after-seek.js | 81 +++ .../test/advanced/seek-in-gaps.js | 64 +++ .../test/simple/apply-service-description.js | 0 .../test/simple/attach-at-non-zero.js | 86 ++++ .../test/simple/attach-with-posix.js | 48 ++ .../test/simple/buffer-cleanup.js | 50 ++ .../test/simple/buffer-target.js | 0 .../test/simple/emsg-triggered.js | 58 +++ test/functional-karma/test/simple/ended.js | 56 +++ .../test/simple/initial-buffer-target.js | 49 ++ .../test/simple/latency-catchup.js | 68 +++ .../test/simple/limit-by-portal-size.js | 0 .../test/simple/live-delay.js | 57 +++ .../test/simple/max-min-bitrate.js | 0 .../test/simple/mpd-anchor.js | 0 .../test/simple/multiperiod-playback.js | 58 +++ test/functional-karma/test/simple/pause.js | 51 ++ test/functional-karma/test/simple/play.js | 42 ++ test/functional-karma/test/simple/preload.js | 0 test/functional-karma/test/simple/seek.js | 96 ++++ .../test/simple/switch-audio.js | 57 +++ .../test/simple/switch-quality.js | 0 .../test/simple/switch-text.js | 82 ++++ .../test/simple/switch-video.js | 57 +++ .../test/vendor/google-ad-manager-emsg.js | 96 ++++ test/functional-karma/view/index.html | 41 ++ 47 files changed, 2794 insertions(+) create mode 100644 karma.functional.conf.js create mode 100644 test/functional-karma/adapter/DashJsAdapter.js create mode 100644 test/functional-karma/adapter/GoogleAdManagerAdapter.js create mode 100644 test/functional-karma/config/content.js create mode 100644 test/functional-karma/config/single-vector.js create mode 100644 test/functional-karma/config/subtitle.js create mode 100644 test/functional-karma/config/vendor.js create mode 100644 test/functional-karma/content/gap/audio_gap_at_start_timeline.mpd create mode 100644 test/functional-karma/content/gap/audio_negative_ept_delta.mpd create mode 100644 test/functional-karma/content/gap/audio_positive_ept_delta.mpd create mode 100644 test/functional-karma/content/gap/video_gap_at_start_timeline.mpd create mode 100644 test/functional-karma/content/gap/video_negative_ept_delta.mpd create mode 100644 test/functional-karma/content/gap/video_positive_ept_delta.mpd create mode 100644 test/functional-karma/helper/Constants.js create mode 100644 test/functional-karma/helper/Utils.js create mode 100644 test/functional-karma/index.md create mode 100644 test/functional-karma/test/advanced/abr-switch-fully-buffered.js create mode 100644 test/functional-karma/test/advanced/no-reload-after-non-replacement-switch.js create mode 100644 test/functional-karma/test/advanced/no-reload-after-seek.js create mode 100644 test/functional-karma/test/advanced/seek-in-gaps.js create mode 100644 test/functional-karma/test/simple/apply-service-description.js create mode 100644 test/functional-karma/test/simple/attach-at-non-zero.js create mode 100644 test/functional-karma/test/simple/attach-with-posix.js create mode 100644 test/functional-karma/test/simple/buffer-cleanup.js create mode 100644 test/functional-karma/test/simple/buffer-target.js create mode 100644 test/functional-karma/test/simple/emsg-triggered.js create mode 100644 test/functional-karma/test/simple/ended.js create mode 100644 test/functional-karma/test/simple/initial-buffer-target.js create mode 100644 test/functional-karma/test/simple/latency-catchup.js create mode 100644 test/functional-karma/test/simple/limit-by-portal-size.js create mode 100644 test/functional-karma/test/simple/live-delay.js create mode 100644 test/functional-karma/test/simple/max-min-bitrate.js create mode 100644 test/functional-karma/test/simple/mpd-anchor.js create mode 100644 test/functional-karma/test/simple/multiperiod-playback.js create mode 100644 test/functional-karma/test/simple/pause.js create mode 100644 test/functional-karma/test/simple/play.js create mode 100644 test/functional-karma/test/simple/preload.js create mode 100644 test/functional-karma/test/simple/seek.js create mode 100644 test/functional-karma/test/simple/switch-audio.js create mode 100644 test/functional-karma/test/simple/switch-quality.js create mode 100644 test/functional-karma/test/simple/switch-text.js create mode 100644 test/functional-karma/test/simple/switch-video.js create mode 100644 test/functional-karma/test/vendor/google-ad-manager-emsg.js create mode 100644 test/functional-karma/view/index.html diff --git a/.gitignore b/.gitignore index 3ea6845672..9eb8f2c602 100644 --- a/.gitignore +++ b/.gitignore @@ -188,3 +188,7 @@ build/typings/ # Vim .vimrc + +#Karma Functional tests +test/functional-karma/coverage +test/functional-karma/results diff --git a/karma.functional.conf.js b/karma.functional.conf.js new file mode 100644 index 0000000000..012496972d --- /dev/null +++ b/karma.functional.conf.js @@ -0,0 +1,124 @@ +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: 'test/functional-karma', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha', 'chai', 'webpack'], + + plugins: [ + 'karma-webpack', + 'karma-mocha', + 'karma-chai', + 'karma-coverage', + 'karma-mocha-reporter', + 'karma-junit-reporter', + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-htmlfile-reporter' + ], + + // list of files / patterns to load in the browser + // https://github.com/webpack-contrib/karma-webpack#alternative-usage + files: [ + { pattern: 'https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js', watched: false, nocache: true }, + { pattern: '../../dist/dash.all.debug.js', watched: false, nocache: true }, + { pattern: '../../dist/dash.mss.min.js', watched: false, nocache: true }, + { pattern: 'test/**/*.js', watched: false }, + { pattern: 'content/**/*.mpd', watched: false, included: false, served: true } + ], + + // list of files / patterns to exclude + // exclude: ['test/vendor/*.js'], + + customContextFile: 'view/index.html', + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['mocha', 'html', 'progress', 'junit'], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + // add webpack as preprocessor + 'test/**/*.js': ['webpack'] + }, + + junitReporter: { + outputDir: 'results/karma/junit', // results will be saved as $outputDir/$browserName.xml + outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile + suite: '', // suite will become the package name attribute in xml testsuite element + useBrowserName: true, // add browser name to report and classes names + nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element + classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element + properties: {}, // key value pair of properties to add to the section of the report + xmlVersion: null // use '1' if reporting to be per SonarQube 6.2 XML format + }, + + htmlReporter: { + outputFile: 'results/karma/htmlreporter/out.html', + + // Optional + pageTitle: 'dash.js', + subPageTitle: 'Functional Tests', + groupSuites: true, + useCompactStyle: true, + useLegacyStyle: true, + showOnlyFailed: false + }, + + webpack: {}, + + client: { + useIframe: false, + mocha: { + timeout: 180000 + } + }, + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + browserNoActivityTimeout: 180000, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['chrome_custom'], + + customLaunchers: { + chrome_custom: { + base: 'Chrome', + flags: ['--disable-web-security', '--autoplay-policy=no-user-gesture-required', '--disable-popup-blocking'] + }, + firefox_custom: { + base: 'Firefox', + prefs: {} + } + }, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: 2 + }) +} diff --git a/package-lock.json b/package-lock.json index a4c7c609d1..58b765a748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "karma-chrome-launcher": "^3.1.1", "karma-coverage": "^2.2.0", "karma-firefox-launcher": "^2.1.2", + "karma-htmlfile-reporter": "^0.3.8", + "karma-junit-reporter": "^2.0.1", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.8", @@ -10341,6 +10343,43 @@ "node": ">= 8" } }, + "node_modules/karma-htmlfile-reporter": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/karma-htmlfile-reporter/-/karma-htmlfile-reporter-0.3.8.tgz", + "integrity": "sha512-Hd4c/vqPXYjdNYXeDJRMMq2DMMxPxqOR+TPeiLz2qbqO0qCCQMeXwFGhNDFr+GsvYhcOyn7maTbWusUFchS/4A==", + "dev": true, + "dependencies": { + "xmlbuilder": "^10.0.0" + }, + "peerDependencies": { + "karma": ">=0.10" + } + }, + "node_modules/karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "dependencies": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-junit-reporter/node_modules/xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/karma-mocha": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", @@ -16965,6 +17004,15 @@ } } }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlcreate": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", @@ -25913,6 +25961,33 @@ } } }, + "karma-htmlfile-reporter": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/karma-htmlfile-reporter/-/karma-htmlfile-reporter-0.3.8.tgz", + "integrity": "sha512-Hd4c/vqPXYjdNYXeDJRMMq2DMMxPxqOR+TPeiLz2qbqO0qCCQMeXwFGhNDFr+GsvYhcOyn7maTbWusUFchS/4A==", + "dev": true, + "requires": { + "xmlbuilder": "^10.0.0" + } + }, + "karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "requires": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + }, + "dependencies": { + "xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true + } + } + }, "karma-mocha": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", @@ -30739,6 +30814,12 @@ "dev": true, "requires": {} }, + "xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "dev": true + }, "xmlcreate": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", diff --git a/package.json b/package.json index ac35435954..21ed8a3199 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test": "karma start karma.unit.conf.js", "test-browserunit": "karma start build/karma.conf.js", "test-functional": "node test/functional/runTests.js --selenium=remote --app=remote", + "test-functional-mocha": "karma start karma.functional.conf.js --testsuite=content.js", "prepare": "node githook.js", "prepack": "npm run build" }, @@ -39,6 +40,8 @@ "karma-chrome-launcher": "^3.1.1", "karma-coverage": "^2.2.0", "karma-firefox-launcher": "^2.1.2", + "karma-htmlfile-reporter": "^0.3.8", + "karma-junit-reporter": "^2.0.1", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.8", diff --git a/test/functional-karma/adapter/DashJsAdapter.js b/test/functional-karma/adapter/DashJsAdapter.js new file mode 100644 index 0000000000..46f14e284e --- /dev/null +++ b/test/functional-karma/adapter/DashJsAdapter.js @@ -0,0 +1,461 @@ +import Constants from '../helper/Constants'; + +class DashJsAdapter { + + constructor() { + this.player = null; + this.videoElement = document.getElementById('video-element'); + this.ttmlRenderingDiv = document.getElementById('ttml-rendering-div'); + this.startedFragmentDownloads = []; + this.logEvents = {}; + this._onFragmentLoadedHandler = this._onFragmentLoaded.bind(this); + this._onLogEvent = this._onLogEvent.bind(this); + } + + /** + * Initialize the player + * @param autoplay + */ + init(autoplay = true) { + this.logEvents[dashjs.Debug.LOG_LEVEL_NONE] = []; + this.logEvents[dashjs.Debug.LOG_LEVEL_FATAL] = []; + this.logEvents[dashjs.Debug.LOG_LEVEL_ERROR] = []; + this.logEvents[dashjs.Debug.LOG_LEVEL_WARNING] = []; + this.logEvents[dashjs.Debug.LOG_LEVEL_INFO] = []; + this.logEvents[dashjs.Debug.LOG_LEVEL_DEBUG] = []; + this.player = dashjs.MediaPlayer().create(); + this.player.updateSettings({ + debug: { + logLevel: 3, + dispatchEvent: true + } + }) + this.player.initialize(this.videoElement, null, autoplay); + this.attachTtmlRenderingDiv(); + this._registerInternalEvents(); + } + + getLogEvents() { + return this.logEvents; + } + + getVideoElement() { + return this.videoElement + } + + attachTtmlRenderingDiv() { + this.player.attachTTMLRenderingDiv(this.ttmlRenderingDiv); + } + + destroy() { + this.logEvents = {}; + if (this.player) { + this.player.resetSettings(); + this.player.destroy(); + } + } + + updateSettings(settings) { + if (this.player) { + this.player.updateSettings(settings); + } + } + + getSettings() { + return this.player.getSettings(); + } + + /** + * + * @private + */ + _registerInternalEvents() { + this.player.on(dashjs.MediaPlayer.events.FRAGMENT_LOADING_STARTED, this._onFragmentLoadedHandler) + this.player.on(dashjs.MediaPlayer.events.LOG, this._onLogEvent) + } + + /** + * + * @private + */ + _unregisterInternalEvents() { + this.player.off(dashjs.MediaPlayer.events.FRAGMENT_LOADING_STARTED, this._onFragmentLoadedHandler) + this.player.off(dashjs.MediaPlayer.events.LOG, this._onLogEvent) + } + + /** + * + * @param type + * @param callback + */ + registerEvent(type, callback) { + this.player.on(type, callback) + } + + /** + * + * @param type + * @param callback + */ + unregisterEvent(type, callback) { + this.player.off(type, callback) + } + + _onFragmentLoaded(e) { + if (e && e.request && e.request.type === Constants.SEGMENT_TYPES.MEDIA) { + let targetString = e.request.url; + if (e.request.range && e.request.range !== '') { + targetString += e.request.range; + } + this.startedFragmentDownloads.push(targetString); + } + } + + _onLogEvent(e) { + this.logEvents[e.level].push(e.message); + } + + /** + * Attach a new MPD for playback + * @param mpd + * @param startTime + */ + attachSource(mpd, startTime = null) { + this.startedFragmentDownloads = []; + this.player.attachSource(mpd, startTime); + } + + setDrmData(data) { + if (data) { + this.player.setProtectionData(data); + } + } + + /** + * Pause playback + */ + pause() { + this.player.pause(); + } + + /** + * + */ + play() { + this.player.play(); + } + + /** + * + */ + getCurrentTime() { + return this.player.time(); + } + + getBufferLengthByType(type) { + return this.player.getBufferLength(type); + } + + getTargetLiveDelay() { + return this.player.getTargetLiveDelay(); + } + + seek(value) { + this.player.seek(value); + } + + getCurrentTextTrackIndex() { + return this.player.getCurrentTextTrackIndex(); + } + + setTextTrack(idx) { + this.player.setTextTrack(idx) + } + + /** + * Returns the target buffer level that the player keeps in the backwards buffer + * @return {number|*} + */ + getTargetBackwardsBuffer() { + return this.player.getSettings().streaming.buffer.bufferToKeep; + } + + /** + * Checks whether the provided time is within a threshold compared to the current playback time + * @param time + * @param threshold + * @return {boolean} + */ + timeIsWithinThreshold(time, threshold) { + const currentTime = this.getCurrentTime(); + return currentTime + threshold > time && currentTime - threshold < time; + } + + /** + * Check if any segment downloads have been triggered multiple times + * @returns {boolean} + */ + hasDuplicateFragmentDownloads() { + return new Set(this.startedFragmentDownloads).size !== this.startedFragmentDownloads.length; + } + + generateValidSeekPosition(duration = NaN) { + duration = isNaN(duration) ? this.player.duration() : duration; + const targetDuration = this.player.isDynamic() ? duration - this.getCurrentLiveLatency() : duration - Constants.TEST_INPUTS.SEEK.VOD_RANDOM_SEEK_DURATION_SUBTRACT_OFFSET; + return Math.random() * targetDuration; + } + + generateValidStartPosition() { + if (this.isDynamic()) { + // getDVRSeekOffset of 0 gives us the start of the DVR window relative to AST. To that number we add a random start time within the DVR window + return (Math.random() * (this.player.getDVRWindowSize() - this.player.getTargetLiveDelay())) + this.player.getDVRSeekOffset(0); + } else { + return Math.random() * this.getDuration(); + } + } + + getDuration() { + return this.player.duration(); + } + + getDvrSeekOffset(value) { + return this.player.getDVRSeekOffset(value); + } + + getCurrentLiveLatency() { + return this.player.getCurrentLiveLatency(); + } + + isDynamic() { + return this.player.isDynamic(); + } + + getTracksFor(type) { + return this.player.getTracksFor(type); + } + + getCurrentTrackFor(type) { + return this.player.getCurrentTrackFor(type); + } + + setCurrentTrack(track) { + this.player.setCurrentTrack(track); + } + + /** + * Checks if the player is in playing state when calling this function or after the configured threshold has been reached + * @return {Promise} + */ + async isInPlayingState(timeoutValue) { + return new Promise((resolve) => { + if (!this.player.isPaused()) { + resolve(true); + } else { + let timeout = null; + const _onComplete = (res) => { + clearTimeout(timeout); + timeout = null; + this.player.off(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, _onPlaying); + resolve(res); + } + const _onTimeout = () => { + _onComplete(false); + } + const _onPlaying = () => { + _onComplete(true); + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, _onPlaying); + } + }) + } + + /** + * Checks if the player is progressing by the provided minimum time in the provided timeout value + * @param timeoutValue + * @param minimumProgress + * @return {Promise} + */ + async isProgressing(timeoutValue, minimumProgress) { + return new Promise((resolve) => { + let startTime = -1; + let timeout = null; + + const _onComplete = (res) => { + clearTimeout(timeout); + timeout = null; + this.player.off(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated); + resolve(res); + } + const _onTimeout = () => { + _onComplete(false); + } + const _onPlaybackTimeUpdated = (e) => { + if (startTime < 0) { + startTime = e.time; + } else { + if (e.time > startTime + minimumProgress) { + _onComplete(true); + } + } + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated); + }) + } + + async reachedPlaybackPosition(timeoutValue, targetTime) { + return new Promise((resolve) => { + let timeout = null; + + const _onComplete = (res) => { + clearTimeout(timeout); + timeout = null; + this.player.off(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated); + resolve(res); + } + const _onTimeout = () => { + _onComplete(false); + } + const _onPlaybackTimeUpdated = (e) => { + if (e.time >= targetTime) { + _onComplete(true); + } + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated); + }) + } + + async performedPeriodTransitions(timeoutValue) { + return new Promise((resolve) => { + let timeout = null; + let periodSwitches = 0; + + const _onComplete = () => { + clearTimeout(timeout); + timeout = null; + this.player.off(dashjs.MediaPlayer.events.PERIOD_SWITCH_COMPLETED, _onPeriodSwitched); + resolve(periodSwitches); + } + const _onTimeout = () => { + _onComplete(); + } + const _onPeriodSwitched = () => { + periodSwitches += 1; + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(dashjs.MediaPlayer.events.PERIOD_SWITCH_COMPLETED, _onPeriodSwitched); + }) + } + + async isKeepingBackwardsBufferTarget(timeoutValue, target, tolerance) { + return new Promise((resolve) => { + let timeout = null; + + const _onComplete = (res) => { + clearTimeout(timeout); + timeout = null; + this.player.off(dashjs.MediaPlayer.events.BUFFER_LEVEL_UPDATED, _onBufferLevelUpdated); + resolve(res); + } + const _onTimeout = () => { + _onComplete(true); + } + const _onBufferLevelUpdated = (e) => { + if (e.mediaType === Constants.DASH_JS.MEDIA_TYPES.VIDEO || e.mediaType === Constants.DASH_JS.MEDIA_TYPES.AUDIO) { + try { + const currentTime = this.videoElement.currentTime; + const bufferStart = this.videoElement.buffered.start(0); + if (currentTime - bufferStart > target + tolerance) { + _onComplete(false); + } + } catch (e) { + } + } + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(dashjs.MediaPlayer.events.BUFFER_LEVEL_UPDATED, _onBufferLevelUpdated); + }) + } + + async emsgEvents(timeoutValue, schemeIdUri) { + return new Promise((resolve) => { + let timeout = null; + let eventCounter = { onReceive: 0, onStart: 0 }; + + const _onComplete = () => { + clearTimeout(timeout); + timeout = null; + this.player.off(schemeIdUri, _onStartEvent); + this.player.off(schemeIdUri, _onReceiveEvent); + resolve(eventCounter); + } + const _onTimeout = () => { + _onComplete(); + } + const _onStartEvent = () => { + eventCounter.onStart += 1; + } + const _onReceiveEvent = () => { + eventCounter.onReceive += 1; + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(schemeIdUri, _onStartEvent, null); /* Default mode is onStart, no need to specify a mode */ + this.player.on(schemeIdUri, _onReceiveEvent, null, { mode: dashjs.MediaPlayer.events.EVENT_MODE_ON_RECEIVE }); + }) + } + + async waitForEvent(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(false); + } + const _onEvent = (e) => { + _onComplete(true); + } + timeout = setTimeout(_onTimeout, timeoutValue); + this.player.on(event, _onEvent); + }) + } + + async reachedTargetDelay(timeoutValue, targetDelay, tolerance) { + return new Promise((resolve) => { + let timeout = null; + let delayPollInterval = null; + + const _onComplete = (res) => { + clearTimeout(timeout); + clearInterval(delayPollInterval); + delayPollInterval = null; + timeout = null; + resolve(res); + } + const _onTimeout = () => { + _onComplete(false); + } + const _onEvent = (e) => { + _onComplete(true); + } + + const _checkDelay = () => { + const delay = this.getCurrentLiveLatency(); + if (Math.abs(delay - targetDelay) <= tolerance) { + _onComplete(true); + } + }; + timeout = setTimeout(_onTimeout, timeoutValue); + delayPollInterval = setInterval(_checkDelay, 100); + }) + } +} + +export default DashJsAdapter; diff --git a/test/functional-karma/adapter/GoogleAdManagerAdapter.js b/test/functional-karma/adapter/GoogleAdManagerAdapter.js new file mode 100644 index 0000000000..a347336fbd --- /dev/null +++ b/test/functional-karma/adapter/GoogleAdManagerAdapter.js @@ -0,0 +1,142 @@ +import Constants from '../helper/Constants'; + +const NETWORK_CODE = '51636543'; +const CUSTOM_ASSET_KEY = 'dash-pod-serving-manifest-1'; +const SCHEME_ID_URI = 'https://developer.apple.com/streaming/emsg-id3'; +const POD_DURATION = 90000; +const VAST_EVENTS_TO_VERIFY = {} + +try { + VAST_EVENTS_TO_VERIFY[google.ima.dai.api.StreamEvent.Type.STARTED] = { + position: 0 + }; + VAST_EVENTS_TO_VERIFY[google.ima.dai.api.StreamEvent.Type.FIRST_QUARTILE] = { + position: 1 + }; + VAST_EVENTS_TO_VERIFY[google.ima.dai.api.StreamEvent.Type.MIDPOINT] = { + position: 2 + }; + VAST_EVENTS_TO_VERIFY[google.ima.dai.api.StreamEvent.Type.THIRD_QUARTILE] = { + position: 3 + }; + VAST_EVENTS_TO_VERIFY[google.ima.dai.api.StreamEvent.Type.COMPLETE] = { + position: 4 + }; +} +catch(e) { + console.log(e); +} + + +class GoogleAdManagerAdapter { + constructor(playerAdapter) { + this.playerAdapter = playerAdapter; + this.adUiElement = document.getElementById('ad-ui'); + this.streamManager = null; + this.streamData = null; + this.adData = {} + this._onEmsgEvent = this._onEmsgEvent.bind(this); + this._onVastEvent = this._onVastEvent.bind(this); + } + + reset() { + this.streamManager = null; + this.streamData = null; + this.playerAdapter.unregisterEvent(SCHEME_ID_URI, this._onEmsgEvent); + this.adData = {} + } + + init() { + const videoElement = this.playerAdapter.getVideoElement(); + this.streamManager = new google.ima.dai.api.StreamManager(videoElement, this.adUiElement); + this.playerAdapter.registerEvent(SCHEME_ID_URI, this._onEmsgEvent); + } + + registerVastEventListener() { + this.streamManager.addEventListener(Object.keys(VAST_EVENTS_TO_VERIFY), + this._onVastEvent, false); + } + + _onVastEvent(event) { + console.log(`Received ${event.type} event at playback time ${this.playerAdapter.getCurrentTime()}`); + switch (event.type) { + case google.ima.dai.api.StreamEvent.Type.STARTED: + case google.ima.dai.api.StreamEvent.Type.FIRST_QUARTILE: + case google.ima.dai.api.StreamEvent.Type.MIDPOINT: + case google.ima.dai.api.StreamEvent.Type.THIRD_QUARTILE: + case google.ima.dai.api.StreamEvent.Type.COMPLETE: + const ad = event.getAd(); + const adId = ad.getAdId(); + const adPodInfo = ad.getAdPodInfo(); + if (!this.adData[adId]) { + this.adData[adId] = { + events: {}, + adPodInfo, + ad, + eventCounter: 0 + }; + } + this.adData[adId].events[event.type] = { + time: this.playerAdapter.getCurrentTime(), + position: this.adData[adId].eventCounter + } + this.adData[adId].eventCounter += 1; + //console.log(`Ad ID ${adId}, Ad ${adPodInfo.getAdPosition()} / ${adPodInfo.getTotalAds()}, Duration: ${ad.getDuration()}s`) + default: + break; + } + } + + _onEmsgEvent(event) { + const data = event.event.messageData; + const pts = event.event.calculatedPresentationTime; + if ((data instanceof Uint8Array) && data.byteLength > 0) { + console.log(`Called streamManager.processMetadata using EMSG event at playback time ${this.playerAdapter.getCurrentTime()}`) + this.streamManager.processMetadata('ID3', data, pts); + } + } + + requestStream() { + return new Promise((resolve, reject) => { + const streamRequest = new google.ima.dai.api.PodStreamRequest(); + streamRequest.networkCode = NETWORK_CODE; + streamRequest.customAssetKey = CUSTOM_ASSET_KEY; + const _onStreamInitialized = (event) => { + this.streamData = event.getStreamData(); + this.streamManager.removeEventListener(google.ima.dai.api.StreamEvent.Type.STREAM_INITIALIZED, _onStreamInitialized); + resolve(); + } + + this.streamManager.addEventListener(google.ima.dai.api.StreamEvent.Type.STREAM_INITIALIZED, _onStreamInitialized, false); + + this.streamManager.requestStream(streamRequest); + }) + } + + getAdData() { + return this.adData; + } + + getVastEventsToVerify() { + return VAST_EVENTS_TO_VERIFY; + } + + + getAdPodManifest() { + if (!this.streamData) { + console.log('No stream Data'); + return; + } + + return this.streamData.getStandalonePodManifestUrl(this._getPodId(), + POD_DURATION); + + } + + _getPodId() { + return Math.trunc(new Date().getTime() / 60000); + } + +} + +export default GoogleAdManagerAdapter \ No newline at end of file diff --git a/test/functional-karma/config/content.js b/test/functional-karma/config/content.js new file mode 100644 index 0000000000..337d6bf63a --- /dev/null +++ b/test/functional-karma/config/content.js @@ -0,0 +1,265 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Segment Base', + type: 'vod', + url: 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Template, number based', + type: 'vod', + url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Timeline, time based', + type: 'vod', + url: 'https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'TTML segmented subtitles', + type: 'vod', + url: 'https://livesim2.dashif.org/vod/testpic_2s/multi_subs.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Multi audio', + type: 'vod', + url: 'http://refapp.hbbtv.org/videos/02_gran_dillama_1080p_ma_25f75g6sv5/manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: '1080p with PlayReady and Widevine DRM, single key', + type: 'vod', + url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd', + drm: { + 'com.widevine.alpha': { + 'serverURL': 'https://drm-widevine-licensing.axtest.net/AcquireLicense', + 'httpRequestHeaders': { + 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' + }, + 'httpTimeout': 5000 + }, + 'com.microsoft.playready': { + 'serverURL': 'https://drm-playready-licensing.axtest.net/AcquireLicense', + 'httpRequestHeaders': { + 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' + }, + 'httpTimeout': 5000 + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: '1080p with W3C Clear Key, single key', + type: 'vod', + url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd', + drm: { + 'org.w3.clearkey': { + 'clearkeys': { + 'nrQFDeRLSAKTLifXUIPiZg': 'FmY0xnWCPCNaSpRG-tUuTQ' + } + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Big Buck Bunny', + type: 'vod', + url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'External VTT', + type: 'vod', + url: 'https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + }, + { + name: 'CEA-608 + TTML', + type: 'vod', + url: 'https://livesim2.dashif.org/vod/testpic_2s/cea608_and_segs.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + }, + { + name: 'Shaka Demo Assets: Angel-One Widevine', + type: 'vod', + url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + drm: { + 'com.widevine.alpha': { + serverURL: 'https://cwip-shaka-proxy.appspot.com/no_auth' + } + } + }, + { + name: 'MSS', + type: 'vod', + url: 'http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Timeline with missing audio segment in MPD for time 0', + type: 'vod', + url: '/base/content/gap/audio_gap_at_start_timeline.mpd', + testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], + testdata: { + gaps: [ + { + start: 0, + end: 5.97 + }] + } + }, + { + name: 'Segment Timeline with missing video segment in MPD for time 0', + type: 'vod', + url: '/base/content/gap/video_gap_at_start_timeline.mpd', + testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], + testdata: { + gaps: [ + { + start: 0, + end: 6 + }] + } + }, + { + name: 'Segment Timeline with negative video EPT Delta', + type: 'vod', + url: '/base/content/gap/video_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with negative audio EPT Delta', + type: 'vod', + url: '/base/content/gap/audio_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with positive video EPT Delta', + type: 'vod', + url: '/base/content/gap/video_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with positive audio EPT Delta', + type: 'vod', + url: '/base/content/gap/audio_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Axinom 3 Audio 3 Text', + type: 'vod', + url: 'https://media.axprod.net/TestVectors/Cmaf/clear_1080p_h264/manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'BBC Testcard', + type: 'vod', + url: 'https://rdmedia.bbc.co.uk/testcard/vod/manifests/avc-full.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'DASH-IF Live Sim - Segment Template without manifest updates', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + url: 'https://livesim2.dashif.org/livesim2/segtimeline_1/testpic_2s/Manifest.mpd', + name: 'Segment Timeline with $time$', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + url: 'https://livesim2.dashif.org/livesim2/segtimelinenr_1/testpic_2s/Manifest.mpd', + name: 'Segment Timeline with $number$', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'livesim2 SCTE35', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/scte35_2/testpic_2s/Manifest.mpd', + testcases: [Constants.TESTCASES.SIMPLE.EMSG_TRIGGERED], + testdata: { + emsg: { + minimumNumberOfEvents: 2, + runtime: 65000, + schemeIdUri: 'urn:scte:scte35:2013:xml' + } + } + }, + { + name: 'CEA-608 + TTML - Live', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/testpic_2s/cea608_and_segs.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + }, + { + name: 'AWS Multiperiod unencrypted', + type: 'live', + url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams/4577dca5f8a44756875ab5cc913cd1f1/index.mpd', + testdata: { + periods: { + waitingTimeForPeriodSwitches: 70000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 15, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK] + }, + + { + name: 'Multiperiod - Number + Timeline - Compact manifest - Thumbnails (1 track) - Encryption (2 keys : audio + video) - No key rotation', + type: 'live', + url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams//6e16c26536564c2f9dbc5f725a820cff/index.mpd', + drm: { + 'com.widevine.alpha': { + 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/?specConform=true', + 'httpRequestHeaders': { + 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' + } + }, + 'com.microsoft.playready': { + 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', + 'httpRequestHeaders': { + 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' + } + } + }, + testdata: { + periods: { + waitingTimeForPeriodSwitches: 70000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 15, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], + }, + { + name: 'Multiperiod DASH-IF livesim2', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/periods_60/continuous_1/testpic_2s/Manifest.mpd', + testdata: { + periods: { + waitingTimeForPeriodSwitches: 60000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 2, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], + } +] + diff --git a/test/functional-karma/config/single-vector.js b/test/functional-karma/config/single-vector.js new file mode 100644 index 0000000000..7edf5be0b5 --- /dev/null +++ b/test/functional-karma/config/single-vector.js @@ -0,0 +1,20 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Livesim SCTE35', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/scte35_2/testpic_2s/Manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.SIMPLE_ALL], + testdata: { + emsg: { + minimumNumberOfEvents: 2, + runtime: 65000, + schemeIdUri: 'urn:scte:scte35:2013:xml' + } + }, + excludedTestcases: [] + }, +] + + diff --git a/test/functional-karma/config/subtitle.js b/test/functional-karma/config/subtitle.js new file mode 100644 index 0000000000..3b652943eb --- /dev/null +++ b/test/functional-karma/config/subtitle.js @@ -0,0 +1,107 @@ +import Constants from '../helper/Constants'; + +export default [ + { + 'url': 'https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd', + 'name': 'External VTT subtitle file', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'TTML Segmented Subtitles VoD', + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/multi_subs.mpd', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'TTML Segmented Subtitles Live (livesim2)', + 'url': 'https://livesim2.dashif.org/livesim2/testpic_2s/multi_subs.mpd', + 'provider': 'dashif', + type: 'live', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'TTML Sideloaded XML Subtitles', + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/xml_subs.mpd', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'Embedded CEA-608 Closed Captions', + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/cea608.mpd', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'Embedded CEA-608 Closed Captions (livesim2)', + 'url': 'https://livesim2.dashif.org/livesim2/testpic_2s/cea608.mpd', + 'provider': 'dashif', + type: 'live', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'Embedded CEA-608 Closed Captions and TTML segments VoD', + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/cea608_and_segs.mpd', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'Embedded CEA-608 Closed Captions and TTML segments Live (livesim2)', + 'url': 'https://livesim2.dashif.org/livesim2/testpic_2s/cea608_and_segs.mpd', + 'provider': 'dashif', + type: 'live', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/imsc1_img.mpd', + 'name': 'IMSC1 (CMAF) Image Subtitles', + 'moreInfo': 'https://livesim2.dashif.org/dash/vod/testpic_2s/imsc1_img_subs_info.html', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'TTML Image Subtitles embedded (VoD)', + 'url': 'https://livesim2.dashif.org/dash/vod/testpic_2s/img_subs.mpd', + 'moreInfo': 'https://livesim2.dashif.org/dash/vod/testpic_2s/img_subs_info.html', + 'provider': 'dashif', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'TTML Segmented \'snaking\' subtitles (with random text) (Ondemand)', + 'url': 'https://rdmedia.bbc.co.uk/elephants_dream/1/client_manifest-snake.mpd', + 'moreInfo': 'https://rdmedia.bbc.co.uk/elephants_dream/', + 'provider': 'bbc', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'name': 'BBC R&D EBU-TT-D Subtitling Test', + 'url': 'https://rdmedia.bbc.co.uk/elephants_dream/1/client_manifest-all.mpd', + 'moreInfo': 'https://rdmedia.bbc.co.uk/elephants_dream/', + 'provider': 'bbc', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'url': 'https://dash.akamaized.net/dash264/CTA/imsc1/IT1-20171027_dash.mpd', + 'name': 'IMSC1 Text Subtitles via sidecar file', + 'provider': 'cta', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + }, + { + 'url': 'https://storage.googleapis.com/shaka-demo-assets/sintel-many-subs/dash.mpd', + 'name': 'Shaka 44 differenr subtitles', + type: 'vod', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], + } +] + + diff --git a/test/functional-karma/config/vendor.js b/test/functional-karma/config/vendor.js new file mode 100644 index 0000000000..24c5493376 --- /dev/null +++ b/test/functional-karma/config/vendor.js @@ -0,0 +1,10 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Google Ad Manager Test', + type: 'live', + url: '', + testcases: [Constants.TESTCASES.VENDOR.GOOGLE_AD_MANAGER_EMSG] + }, +] \ No newline at end of file diff --git a/test/functional-karma/content/gap/audio_gap_at_start_timeline.mpd b/test/functional-karma/content/gap/audio_gap_at_start_timeline.mpd new file mode 100644 index 0000000000..59337262f8 --- /dev/null +++ b/test/functional-karma/content/gap/audio_gap_at_start_timeline.mpd @@ -0,0 +1,30 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/content/gap/audio_negative_ept_delta.mpd b/test/functional-karma/content/gap/audio_negative_ept_delta.mpd new file mode 100644 index 0000000000..2af5acc056 --- /dev/null +++ b/test/functional-karma/content/gap/audio_negative_ept_delta.mpd @@ -0,0 +1,31 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/content/gap/audio_positive_ept_delta.mpd b/test/functional-karma/content/gap/audio_positive_ept_delta.mpd new file mode 100644 index 0000000000..23a24ef170 --- /dev/null +++ b/test/functional-karma/content/gap/audio_positive_ept_delta.mpd @@ -0,0 +1,31 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/content/gap/video_gap_at_start_timeline.mpd b/test/functional-karma/content/gap/video_gap_at_start_timeline.mpd new file mode 100644 index 0000000000..37b516238c --- /dev/null +++ b/test/functional-karma/content/gap/video_gap_at_start_timeline.mpd @@ -0,0 +1,31 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/content/gap/video_negative_ept_delta.mpd b/test/functional-karma/content/gap/video_negative_ept_delta.mpd new file mode 100644 index 0000000000..99c0610e61 --- /dev/null +++ b/test/functional-karma/content/gap/video_negative_ept_delta.mpd @@ -0,0 +1,31 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/content/gap/video_positive_ept_delta.mpd b/test/functional-karma/content/gap/video_positive_ept_delta.mpd new file mode 100644 index 0000000000..13b3ab424d --- /dev/null +++ b/test/functional-karma/content/gap/video_positive_ept_delta.mpd @@ -0,0 +1,31 @@ + + + http://refapp.hbbtv.org/videos/01_llama_drama_1080p_25f75g6sv3/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional-karma/helper/Constants.js b/test/functional-karma/helper/Constants.js new file mode 100644 index 0000000000..ae4ab8c717 --- /dev/null +++ b/test/functional-karma/helper/Constants.js @@ -0,0 +1,119 @@ +export default { + DASH_JS: { + MEDIA_TYPES: { + AUDIO: 'audio', + VIDEO: 'video', + TEXT: 'text' + } + }, + TESTCASES: { + ADVANCED: { + NO_RELOAD_AFTER_SEEK: 'advanced_no_reload_after_seek', + SEEK_IN_GAPS: 'advanced_seek_in_gaps', + PREFIX: 'advanced_' + }, + SIMPLE: { + PLAY: 'simple_play', + PAUSE: 'simple_pause', + SEEK: 'simple_seek', + ENDED: 'simple_ended', + SWITCH_AUDIO: 'simple_switch_audio', + SWITCH_VIDEO: 'simple_switch_video', + SWITCH_TEXT: 'simple_switch_text', + ATTACH_AT_NON_ZERO: 'simple_attach_at_non_zero', + MULTIPERIOD_PLAYBACK: 'simple_multiperiod_playback', + EMSG_TRIGGERED: 'simple_emsg_triggered', + INITIAL_BUFFER_TARGET: 'simple_initial_buffer_target', + BUFFER_CLEANUP: 'simple_buffer_cleanup', + LIVE_DELAY: 'simple_live_delay', + LIVE_CATCHUP: 'simple_live_catchup', + ATTACH_WITH_POSIX: 'simple_attach_with_posix', + PREFIX: 'simple_' + }, + VENDOR: { + GOOGLE_AD_MANAGER_EMSG: 'vendor_google_ad_manager_emsg', + PREFIX: 'vendor_' + }, + GENERIC: { + ALL: 'all_testcases', + SIMPLE_ALL: 'all_simple_testcases', + ADVANCED_ALL: 'all_advanced_testcases', + VENDOR_ALL: 'all_vendor_testcases' + }, + REQUIRED_CAPABILITIES: {} + }, + TEST_TIMEOUT_THRESHOLDS: { + IS_PLAYING: 5000, + IS_PROGRESSING: 10000, + IS_NOT_PROGRESSING: 3000, + TO_REACH_TARGET_OFFSET: 10000, + EVENT_WAITING_TIME: 10000, + BUFFER_CLEANUP: 45000, + TARGET_DELAY_REACHED: 20000, + ENDED_EVENT_OFFSET: 2000, + IS_FINISHED_OFFSET_TO_DURATION: 5000 + }, + TEST_INPUTS: { + GENERAL: { + MINIMUM_PROGRESS_WHEN_PLAYING: 0.5, + MAXIMUM_PROGRESS_WHEN_PAUSED: 0, + MAXIMUM_ALLOWED_SEEK_DIFFERENCE: 0.5, + MAXIMUM_ALLOWED_SEEK_DIFFERENCE_LIVE_EDGE: 2, + }, + SEEK: { + NUMBER_OF_RANDOM_SEEKS: 3, + VOD_RANDOM_SEEK_DURATION_SUBTRACT_OFFSET: 5 + }, + ENDED: { + SEEK_END_OFFSET: 8, + }, + NO_RELOAD_AFTER_SEEK: { + TIME_TO_REACH_FOR_REDUNDANT_SEGMENT_FETCH: 10, + TIME_TO_SEEK_BACK_FOR_REDUNDANT_SEGMENT_FETCH: 2, + OFFSET_TO_REACH_WHEN_PLAYING: 2 + }, + SEEK_IN_GAPS: { + OFFSET_BEFORE_GAP: 0.1, + OFFSET_BEFORE_END_GAP: 0.1, + MAXIMUM_ALLOWED_PLAYING_DIFFERENCE_TO_GAP_END: 0.5, + }, + ATTACH_AT_NON_ZERO: { + NUMBER_OF_RANDOM_ATTACHES: 3, + VOD_RANDOM_ATTACH_SUBTRACT_OFFSET: 5, + LIVE_RANDOM_ATTACH_SUBTRACT_OFFSET: 5 + }, + INITIAL_BUFFER_TARGET: { + VALUE: 10, + TOLERANCE: 1, + }, + BUFFER_CLEANUP: { + INTERVAL: 2, + TO_KEEP: 10, + TOLERANCE: 3 + }, + LIVE_DELAY: { + VALUE: 10, + TOLERANCE: 2 + }, + LATENCY_CATCHUP: { + DELAY: 12, + TOLERANCE: 0.1 + }, + ATTACH_WITH_POSIX: { + DELAY: 30, + TOLERANCE: 5 + } + }, + CONTENT_TYPES: { + VOD: 'vod', + LIVE: 'live' + }, + SEGMENT_TYPES: { + INIT: 'InitializationSegment', + MEDIA: 'MediaSegment' + }, + DRM_SYSTEMS: { + WIDEVINE: 'com.widevine.alpha', + PLAYREADY: 'com.microsoft.playready' + } +} diff --git a/test/functional-karma/helper/Utils.js b/test/functional-karma/helper/Utils.js new file mode 100644 index 0000000000..4198e54ee7 --- /dev/null +++ b/test/functional-karma/helper/Utils.js @@ -0,0 +1,42 @@ +import Constants from '../helper/Constants'; +import content from '../config/content'; + + +class Utils { + + static getTestvectorsForTestcase(testcase) { + return content.filter((item) => { + return Utils._filterNonIncluded(item, testcase) + }) + } + + static _filterNonIncluded(item, testcase) { + if (!item.testcases) { + return false; + } + + // Vendor Testcases have to be explicitly enabled + if (testcase.includes(Constants.TESTCASES.VENDOR.PREFIX)) { + return item.testcases.indexOf(Constants.TESTCASES.GENERIC.VENDOR_ALL) !== -1 || item.testcases.indexOf(testcase) !== -1 + } + + if (item.excludedTestcases && item.excludedTestcases.indexOf(testcase) !== -1) { + return false; + } + if (item.testcases.indexOf(Constants.TESTCASES.GENERIC.ALL) !== -1) { + return true; + } + if ((testcase.includes(Constants.TESTCASES.ADVANCED.PREFIX) && item.testcases.indexOf(Constants.TESTCASES.GENERIC.ADVANCED_ALL) !== -1) + || (testcase.includes(Constants.TESTCASES.SIMPLE.PREFIX) && item.testcases.indexOf(Constants.TESTCASES.GENERIC.SIMPLE_ALL) !== -1)) { + return true; + } + return item.testcases && item.testcases.indexOf(testcase) !== -1; + } + + static _filterByRequiredCapabilities() { + + } + +} + +export default Utils diff --git a/test/functional-karma/index.md b/test/functional-karma/index.md new file mode 100644 index 0000000000..76824280ce --- /dev/null +++ b/test/functional-karma/index.md @@ -0,0 +1,34 @@ +# Description + +The `functional-karma` testsuite implements functional tests using the Karma Testrunner. Functional tests are used to +test player functionality such as play, pause and seek. + +# Structure + +The source files are placed in multiple folders: + +* `adapter`: Adapter classes that implement additional logic to run a test. For instance the `DashJsAdapter.js` serves + as a wrapper around dash.js functionality. +* `config`: Test configuration files that define a set of testcases to be executed. The target configuration file is + imported in `Utils.js` +* `content`: Contains static MPDs that serve as input for testcases. +* `helper`: Helper classes that define constant values and filter the relevant testvectors for a specific testcase. +* `results`: The summary of the test results is placed in this folder. +* `test`: The implementation of the testcases. +* `view`: Customized view for the test execution including a video element. + +# Configuration + +The main configuration for the test execution is defined in `karma.functional.conf.js`. To adjust the list of +testvectors or the testcases the existing `config/content.js` can be adjusted. As an alternative, include a different +configuration file in `helper/Utils.js`. Future additions to the test framework should allow definition of the testfile +to be used directly via command line parameters. + +# Test Execution +To execute the functional tests run the following steps: + +1. `npm install` to install all dependencies +2. `npm run build` to build the `dist` files of dash.js. +3. `npm run test-functional-mocha` to execute the tests. +4. The results will be available after the test execution in `test/functional-karma/results` + diff --git a/test/functional-karma/test/advanced/abr-switch-fully-buffered.js b/test/functional-karma/test/advanced/abr-switch-fully-buffered.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/advanced/no-reload-after-non-replacement-switch.js b/test/functional-karma/test/advanced/no-reload-after-non-replacement-switch.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/advanced/no-reload-after-seek.js b/test/functional-karma/test/advanced/no-reload-after-seek.js new file mode 100644 index 0000000000..04eea8fbbb --- /dev/null +++ b/test/functional-karma/test/advanced/no-reload-after-seek.js @@ -0,0 +1,81 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Advanced - No reload after seek - ${item.name} -${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Wait till the player has enough backwards buffer`, async () => { + const reachedTargetTime = await playerAdapter.reachedPlaybackPosition(Constants.TEST_TIMEOUT_THRESHOLDS.TO_REACH_TARGET_OFFSET, Constants.TEST_INPUTS.NO_RELOAD_AFTER_SEEK.TIME_TO_REACH_FOR_REDUNDANT_SEGMENT_FETCH); + expect(reachedTargetTime).to.be.true; + }); + + it(`Pause and seek back`, async () => { + playerAdapter.pause(); + const previousTime = playerAdapter.getCurrentTime(); + const seekTime = previousTime - Constants.TEST_INPUTS.NO_RELOAD_AFTER_SEEK.TIME_TO_SEEK_BACK_FOR_REDUNDANT_SEGMENT_FETCH; + playerAdapter.seek(seekTime); + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(seekTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + }); + + it(`Start playback`, () => { + playerAdapter.play(); + }); + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it('Play for some time', async () => { + const currentTime = playerAdapter.getCurrentTime(); + const reachedTargetTime = await playerAdapter.reachedPlaybackPosition(Constants.TEST_TIMEOUT_THRESHOLDS.TO_REACH_TARGET_OFFSET, Constants.TEST_INPUTS.NO_RELOAD_AFTER_SEEK.OFFSET_TO_REACH_WHEN_PLAYING + currentTime); + expect(reachedTargetTime).to.be.true; + }) + + it(`Do not expect any redundant segment downloads`, async () => { + const redundantSegmentsFetched = playerAdapter.hasDuplicateFragmentDownloads(); + expect(redundantSegmentsFetched).to.be.false; + }) + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + + }) +}) diff --git a/test/functional-karma/test/advanced/seek-in-gaps.js b/test/functional-karma/test/advanced/seek-in-gaps.js new file mode 100644 index 0000000000..2bff024b52 --- /dev/null +++ b/test/functional-karma/test/advanced/seek-in-gaps.js @@ -0,0 +1,64 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Advanced - Seek in gaps - ${item.name} -${mpd}`, function () { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + + if (!item.testdata || !item.testdata.gaps) { + this.skip(); + } + + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + if (item && item.testdata && item.testdata.gaps && item.testdata.gaps.length > 0) { + item.testdata.gaps.forEach((gap) => { + const midGap = (gap.end - gap.start) / 2; + it(`Seeking in the middle of the gap to ${midGap}`, async () => { + playerAdapter.seek(midGap); + + const reachedTargetTime = await playerAdapter.reachedPlaybackPosition(Constants.TEST_TIMEOUT_THRESHOLDS.TO_REACH_TARGET_OFFSET, gap.end + Constants.TEST_INPUTS.SEEK_IN_GAPS.MAXIMUM_ALLOWED_PLAYING_DIFFERENCE_TO_GAP_END); + expect(reachedTargetTime).to.be.true; + }); + + const beforeGap = Math.max(gap.start - Constants.TEST_INPUTS.SEEK_IN_GAPS.OFFSET_BEFORE_GAP, 0); + it(`Seeking right before the beginning of the gap to ${beforeGap}`, async () => { + playerAdapter.seek(beforeGap); + + const reachedTargetTime = await playerAdapter.reachedPlaybackPosition(Constants.TEST_TIMEOUT_THRESHOLDS.TO_REACH_TARGET_OFFSET, gap.end + Constants.TEST_INPUTS.SEEK_IN_GAPS.MAXIMUM_ALLOWED_PLAYING_DIFFERENCE_TO_GAP_END); + expect(reachedTargetTime).to.be.true; + }); + + const beforeEndGap = gap.end - Constants.TEST_INPUTS.SEEK_IN_GAPS.OFFSET_BEFORE_END_GAP; + it(`Seeking right before the end of the gap to ${beforeEndGap}`, async () => { + playerAdapter.seek(beforeEndGap); + + const reachedTargetTime = await playerAdapter.reachedPlaybackPosition(Constants.TEST_TIMEOUT_THRESHOLDS.TO_REACH_TARGET_OFFSET, gap.end + Constants.TEST_INPUTS.SEEK_IN_GAPS.MAXIMUM_ALLOWED_PLAYING_DIFFERENCE_TO_GAP_END); + expect(reachedTargetTime).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) + } + }) +}) diff --git a/test/functional-karma/test/simple/apply-service-description.js b/test/functional-karma/test/simple/apply-service-description.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/attach-at-non-zero.js b/test/functional-karma/test/simple/attach-at-non-zero.js new file mode 100644 index 0000000000..740067105d --- /dev/null +++ b/test/functional-karma/test/simple/attach-at-non-zero.js @@ -0,0 +1,86 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Attach source non zero - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Attach null as starttime and expect content to play from start`, async () => { + playerAdapter.attachSource(mpd, null); + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(0, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }) + + it(`Attach negative value as starttime and expect content to play from start`, async () => { + playerAdapter.attachSource(mpd, -10); + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(0, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }) + + it(`Attach string as starttime and expect content to play`, async () => { + playerAdapter.attachSource(mpd, 'foobar'); + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(0, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }) + + for (let i = 0; i < Constants.TEST_INPUTS.ATTACH_AT_NON_ZERO.NUMBER_OF_RANDOM_ATTACHES; i++) { + it(`Generate random start time and use in attachSource() call`, async () => { + playerAdapter.attachSource(mpd); + + let metadataLoaded = await playerAdapter.waitForEvent(Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED); + expect(metadataLoaded).to.be.true; + + let startTime = playerAdapter.generateValidStartPosition(); + startTime = playerAdapter.isDynamic() ? startTime - Constants.TEST_INPUTS.ATTACH_AT_NON_ZERO.LIVE_RANDOM_ATTACH_SUBTRACT_OFFSET : startTime - Constants.TEST_INPUTS.ATTACH_AT_NON_ZERO.VOD_RANDOM_ATTACH_SUBTRACT_OFFSET; + startTime = Math.max(startTime, 0); + playerAdapter.attachSource(mpd, startTime); + + let seeked = await playerAdapter.waitForEvent(Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_SEEKED); + expect(seeked).to.be.true; + + const targetTime = playerAdapter.isDynamic() ? startTime - playerAdapter.getDvrSeekOffset(0) : startTime; + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(targetTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + }); + } + + }) +}) diff --git a/test/functional-karma/test/simple/attach-with-posix.js b/test/functional-karma/test/simple/attach-with-posix.js new file mode 100644 index 0000000000..a3ed7f2f9c --- /dev/null +++ b/test/functional-karma/test/simple/attach-with-posix.js @@ -0,0 +1,48 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.ATTACH_WITH_POSIX; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Attach with posix - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + if (item.type === Constants.CONTENT_TYPES.VOD) { + this.skip(); + } + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Attach with posix and expect live delay to correspond`, async () => { + const starttime = new Date().getTime() / 1000 - Constants.TEST_INPUTS.ATTACH_WITH_POSIX.DELAY; + playerAdapter.attachSource(mpd, `posix:${starttime}`); /* start from UTC time */ + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + + const liveDelay = playerAdapter.getCurrentLiveLatency(); + expect(liveDelay).to.be.at.least(Constants.TEST_INPUTS.ATTACH_WITH_POSIX.DELAY - Constants.TEST_INPUTS.ATTACH_WITH_POSIX.TOLERANCE); + expect(liveDelay).to.be.below(Constants.TEST_INPUTS.ATTACH_WITH_POSIX.DELAY + Constants.TEST_INPUTS.ATTACH_WITH_POSIX.TOLERANCE); + }) + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/buffer-cleanup.js b/test/functional-karma/test/simple/buffer-cleanup.js new file mode 100644 index 0000000000..af0466fc0e --- /dev/null +++ b/test/functional-karma/test/simple/buffer-cleanup.js @@ -0,0 +1,50 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.BUFFER_CLEANUP; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Buffer Cleanup - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Setting buffer cleanup values`, async () => { + playerAdapter.updateSettings({ + streaming: { + buffer: { + bufferPruningInterval: Constants.TEST_INPUTS.BUFFER_CLEANUP.INTERVAL, + bufferToKeep: Constants.TEST_INPUTS.BUFFER_CLEANUP.TO_KEEP + } + } + }) + }) + + it(`Attach source`, async () => { + playerAdapter.attachSource(mpd); + }) + + it(`Play for some time and expect buffer level to stay within tolerance`, async () => { + const isKeepingBackwardsBufferTarget = await playerAdapter.isKeepingBackwardsBufferTarget(Constants.TEST_TIMEOUT_THRESHOLDS.BUFFER_CLEANUP, Constants.TEST_INPUTS.BUFFER_CLEANUP.TO_KEEP, Constants.TEST_INPUTS.BUFFER_CLEANUP.TOLERANCE); + expect(isKeepingBackwardsBufferTarget).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/buffer-target.js b/test/functional-karma/test/simple/buffer-target.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/emsg-triggered.js b/test/functional-karma/test/simple/emsg-triggered.js new file mode 100644 index 0000000000..681ca99a52 --- /dev/null +++ b/test/functional-karma/test/simple/emsg-triggered.js @@ -0,0 +1,58 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.EMSG_TRIGGERED; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Check if emsg events are dispatched - ${item.name} - ${mpd}`, function () { + + let playerAdapter + + before(function () { + playerAdapter = new DashJsAdapter(); + + if (!item.testdata || !item.testdata.emsg || isNaN(item.testdata.emsg.minimumNumberOfEvents) || isNaN(item.testdata.emsg.runtime) || !item.testdata.emsg.schemeIdUri) { + this.skip(); + } + + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Dispatches events in receive and start mode`, async () => { + const eventData = await playerAdapter.emsgEvents(item.testdata.emsg.runtime, item.testdata.emsg.schemeIdUri); + expect(eventData.onStart).to.be.at.least(item.testdata.emsg.minimumNumberOfEvents); + expect(eventData.onReceive).to.be.at.least(item.testdata.emsg.minimumNumberOfEvents); + }); + + it(`Should still be progressing`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + + }) +}) diff --git a/test/functional-karma/test/simple/ended.js b/test/functional-karma/test/simple/ended.js new file mode 100644 index 0000000000..4d8055a4f2 --- /dev/null +++ b/test/functional-karma/test/simple/ended.js @@ -0,0 +1,56 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.ENDED; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Ended - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + if (item.type === Constants.CONTENT_TYPES.LIVE) { + this.skip(); + } + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Seek close to playback end`, async () => { + const targetTime = playerAdapter.getDuration() - Constants.TEST_INPUTS.ENDED.SEEK_END_OFFSET; + playerAdapter.seek(targetTime); + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(targetTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + }) + + it(`Check if ended event is thrown`, async () => { + const ended = await playerAdapter.waitForEvent(Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE * 1000 + Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_ENDED); + expect(ended).to.be.true; + }) + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/initial-buffer-target.js b/test/functional-karma/test/simple/initial-buffer-target.js new file mode 100644 index 0000000000..ad3c155ed9 --- /dev/null +++ b/test/functional-karma/test/simple/initial-buffer-target.js @@ -0,0 +1,49 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.INITIAL_BUFFER_TARGET; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Initial buffer target - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Setting initial buffer target to ${Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.VALUE} seconds`, async () => { + playerAdapter.updateSettings({ streaming: { buffer: { initialBufferLevel: Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.VALUE } } }) + const settings = playerAdapter.getSettings(); + expect(settings.streaming.buffer.initialBufferLevel).to.be.equal(Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.VALUE); + }) + + it(`Attach source`, async () => { + playerAdapter.attachSource(mpd); + }) + + it(`Expect buffer level to be within the initial target or the live delay once progressing`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + const videoBuffer = playerAdapter.getBufferLengthByType(Constants.DASH_JS.MEDIA_TYPES.VIDEO); + const liveDelay = playerAdapter.getTargetLiveDelay(); + const minimumTarget = liveDelay > 0 ? Math.min(liveDelay - Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.TOLERANCE, Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.VALUE) : Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.VALUE; + expect(videoBuffer).to.be.above(minimumTarget - Constants.TEST_INPUTS.INITIAL_BUFFER_TARGET.TOLERANCE); + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/latency-catchup.js b/test/functional-karma/test/simple/latency-catchup.js new file mode 100644 index 0000000000..0c242feb26 --- /dev/null +++ b/test/functional-karma/test/simple/latency-catchup.js @@ -0,0 +1,68 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.LIVE_CATCHUP; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Latency catchup - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + if (item.type === Constants.CONTENT_TYPES.VOD) { + this.skip(); + } + playerAdapter.init(true); + playerAdapter.updateSettings({ + streaming: { + delay: { + liveDelay: Constants.TEST_INPUTS.LATENCY_CATCHUP.DELAY + }, + liveCatchup: { + enabled: true + } + } + }) + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + + it(`Attach source`, () => { + playerAdapter.attachSource(mpd); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Check if playback rate is adjusted`, async () => { + const playbackRateChanged = await playerAdapter.waitForEvent(Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_RATE_CHANGED) + expect(playbackRateChanged).to.be.true; + }) + + it(`Checking if target live delay is reached`, async () => { + const liveDelayReached = await playerAdapter.reachedTargetDelay(Constants.TEST_TIMEOUT_THRESHOLDS.TARGET_DELAY_REACHED, Constants.TEST_INPUTS.LATENCY_CATCHUP.DELAY, Constants.TEST_INPUTS.LATENCY_CATCHUP.TOLERANCE); + expect(liveDelayReached).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/limit-by-portal-size.js b/test/functional-karma/test/simple/limit-by-portal-size.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/live-delay.js b/test/functional-karma/test/simple/live-delay.js new file mode 100644 index 0000000000..58794d98e2 --- /dev/null +++ b/test/functional-karma/test/simple/live-delay.js @@ -0,0 +1,57 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.LIVE_DELAY; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Live Delay - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + if (item.type === Constants.CONTENT_TYPES.VOD) { + this.skip(); + } + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Setting initial live delay`, () => { + playerAdapter.updateSettings({ streaming: { delay: { liveDelay: Constants.TEST_INPUTS.LIVE_DELAY.VALUE } } }) + }) + + it(`Attach source`, () => { + playerAdapter.attachSource(mpd); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Checking if live delay is correct`, async () => { + const liveDelay = playerAdapter.getCurrentLiveLatency(); + expect(liveDelay).to.be.at.least(Constants.TEST_INPUTS.LIVE_DELAY.VALUE); + expect(liveDelay).to.be.below(Constants.TEST_INPUTS.LIVE_DELAY.VALUE + Constants.TEST_INPUTS.LIVE_DELAY.TOLERANCE); + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/max-min-bitrate.js b/test/functional-karma/test/simple/max-min-bitrate.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/mpd-anchor.js b/test/functional-karma/test/simple/mpd-anchor.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/multiperiod-playback.js b/test/functional-karma/test/simple/multiperiod-playback.js new file mode 100644 index 0000000000..b84310022c --- /dev/null +++ b/test/functional-karma/test/simple/multiperiod-playback.js @@ -0,0 +1,58 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.MULTIPERIOD_PLAYBACK; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Multiperiod Playback with period transition - ${item.name} - ${mpd}`, function () { + + let playerAdapter + + before(function () { + playerAdapter = new DashJsAdapter(); + + if (!item.testdata || !item.testdata.periods || isNaN(item.testdata.periods.waitingTimeForPeriodSwitches) + || isNaN(item.testdata.periods.minimumNumberOfPeriodSwitches) || isNaN(item.testdata.periods.maximumNumberOfPeriodSwitches)) { + this.skip(); + } + + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Transitions to next periods`, async () => { + const numberOfPeriodSwitches = await playerAdapter.performedPeriodTransitions(item.testdata.periods.waitingTimeForPeriodSwitches); + expect(numberOfPeriodSwitches).to.be.at.most(item.testdata.periods.maximumNumberOfPeriodSwitches); + expect(numberOfPeriodSwitches).to.be.at.least(item.testdata.periods.minimumNumberOfPeriodSwitches); + }); + + it(`Should still be progressing`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/pause.js b/test/functional-karma/test/simple/pause.js new file mode 100644 index 0000000000..313bd7628b --- /dev/null +++ b/test/functional-karma/test/simple/pause.js @@ -0,0 +1,51 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.PAUSE; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Pause - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Pause the playback`, async () => { + playerAdapter.pause(); + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.false; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_NOT_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MAXIMUM_PROGRESS_WHEN_PAUSED); + expect(isProgressing).to.be.false; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + + }) +}) diff --git a/test/functional-karma/test/simple/play.js b/test/functional-karma/test/simple/play.js new file mode 100644 index 0000000000..d76a21ddeb --- /dev/null +++ b/test/functional-karma/test/simple/play.js @@ -0,0 +1,42 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.PLAY; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Play - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + + }) +}) diff --git a/test/functional-karma/test/simple/preload.js b/test/functional-karma/test/simple/preload.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/seek.js b/test/functional-karma/test/simple/seek.js new file mode 100644 index 0000000000..67ea4f50af --- /dev/null +++ b/test/functional-karma/test/simple/seek.js @@ -0,0 +1,96 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.SEEK; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Seek - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Checking seek to 0`, async () => { + playerAdapter.seek(0); + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(0, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Checking seek to negative value`, async () => { + playerAdapter.pause(); + playerAdapter.seek(-10); + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(0, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + playerAdapter.play(); + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Checking seek to high value`, async () => { + playerAdapter.pause(); + playerAdapter.seek(999999999999); + + // For live we expect to be playing close to the live edge, For VoD we are at the end of the stream. + const targetTime = playerAdapter.isDynamic() ? playerAdapter.getDuration() - playerAdapter.getCurrentLiveLatency() : playerAdapter.getDuration(); + const allowedDifference = playerAdapter.isDynamic() ? Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE_LIVE_EDGE : Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE; + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(targetTime, allowedDifference); + expect(timeIsWithinThreshold).to.be.true; + }); + + for (let i = 0; i < Constants.TEST_INPUTS.SEEK.NUMBER_OF_RANDOM_SEEKS; i++) { + it(`Checking seek to random time`, async () => { + const targetTime = playerAdapter.generateValidSeekPosition(); + playerAdapter.pause(); + playerAdapter.seek(targetTime); + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(targetTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + + playerAdapter.play(); + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + } + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) + diff --git a/test/functional-karma/test/simple/switch-audio.js b/test/functional-karma/test/simple/switch-audio.js new file mode 100644 index 0000000000..1e66daa0c1 --- /dev/null +++ b/test/functional-karma/test/simple/switch-audio.js @@ -0,0 +1,57 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.SWITCH_AUDIO; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Switch audio - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Switch audio languages`, async () => { + const availableAudioTracks = playerAdapter.getTracksFor(Constants.DASH_JS.MEDIA_TYPES.AUDIO); + for (let i = 0; i < availableAudioTracks.length; i++) { + const track = availableAudioTracks[i]; + + playerAdapter.setCurrentTrack(track); + + const currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.AUDIO); + expect(currentTrack.lang).to.be.equal(track.lang); + expect(currentTrack.bitrateList.bandwidth).to.be.equal(track.bitrateList.bandwidth); + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + } + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/switch-quality.js b/test/functional-karma/test/simple/switch-quality.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional-karma/test/simple/switch-text.js b/test/functional-karma/test/simple/switch-text.js new file mode 100644 index 0000000000..163e4e04b6 --- /dev/null +++ b/test/functional-karma/test/simple/switch-text.js @@ -0,0 +1,82 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.SWITCH_TEXT; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Switch text - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Switch texttracks`, async () => { + const availableTracks = playerAdapter.getTracksFor(Constants.DASH_JS.MEDIA_TYPES.TEXT); + for (let i = 0; i < availableTracks.length; i++) { + const track = availableTracks[i]; + + playerAdapter.setTextTrack(i); + + const currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.TEXT); + expect(currentTrack.lang).to.be.equal(track.lang); + expect(currentTrack.index).to.be.equal(track.index); + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + } + }); + + it(`on/off/on`, async () => { + const availableTracks = playerAdapter.getTracksFor(Constants.DASH_JS.MEDIA_TYPES.TEXT); + if (availableTracks && availableTracks.length > 0) { + const targetTrack = availableTracks[0]; + + playerAdapter.setCurrentTrack(targetTrack); + let currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.TEXT); + expect(currentTrack.lang).to.be.equal(targetTrack.lang); + expect(currentTrack.index).to.be.equal(targetTrack.index); + let isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + + playerAdapter.setTextTrack(-1); + const currentIndex = playerAdapter.getCurrentTextTrackIndex(); + expect(currentIndex).to.be.equal(-1); + + playerAdapter.setCurrentTrack(targetTrack); + currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.TEXT); + expect(currentTrack.lang).to.be.equal(targetTrack.lang); + expect(currentTrack.index).to.be.equal(targetTrack.index); + isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + } + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/simple/switch-video.js b/test/functional-karma/test/simple/switch-video.js new file mode 100644 index 0000000000..113075abed --- /dev/null +++ b/test/functional-karma/test/simple/switch-video.js @@ -0,0 +1,57 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.SWITCH_VIDEO; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Switch video - ${item.name} -${mpd}`, () => { + + let playerAdapter; + + before(() => { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + playerAdapter.attachSource(mpd); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Checking playing state`, async () => { + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + }) + + it(`Checking progressing state`, async () => { + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + }); + + it(`Switch video track`, async () => { + const availableTracks = playerAdapter.getTracksFor(Constants.DASH_JS.MEDIA_TYPES.VIDEO); + for (let i = 0; i < availableTracks.length; i++) { + const track = availableTracks[i]; + + playerAdapter.setCurrentTrack(track); + + const currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.VIDEO); + expect(currentTrack.type).to.be.equal(Constants.DASH_JS.MEDIA_TYPES.VIDEO); + expect(currentTrack.codec).to.be.equal(track.codec); + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + } + }); + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) diff --git a/test/functional-karma/test/vendor/google-ad-manager-emsg.js b/test/functional-karma/test/vendor/google-ad-manager-emsg.js new file mode 100644 index 0000000000..a471548e0b --- /dev/null +++ b/test/functional-karma/test/vendor/google-ad-manager-emsg.js @@ -0,0 +1,96 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import GoogleAdManagerAdapter from '../../adapter/GoogleAdManagerAdapter'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.VENDOR.GOOGLE_AD_MANAGER_EMSG; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Vendor - Google Ad Manager EMSG - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + let googleAdManagerAdapter; + let mpd; + + before(() => { + playerAdapter = new DashJsAdapter(); + googleAdManagerAdapter = new GoogleAdManagerAdapter(playerAdapter) + playerAdapter.init(true); + googleAdManagerAdapter.init() + }) + + after(() => { + mpd = null; + playerAdapter.destroy(); + googleAdManagerAdapter.reset(); + }) + + it('Register DAI pod session', async () => { + await googleAdManagerAdapter.requestStream() + }) + + + it('Request Ad Manifest and start playback', () => { + mpd = googleAdManagerAdapter.getAdPodManifest(); + expect(mpd).to.be.a('string'); + expect(mpd).to.not.be.empty; + }) + + it('Register for ID3 events', () => { + googleAdManagerAdapter.registerVastEventListener() + }); + + it(' Wait for playback to be finished', async () => { + playerAdapter.attachSource(mpd); + console.log(`MPD URL ${mpd}`); + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isFinished = await playerAdapter.waitForEvent(playerAdapter.getDuration() * 1000 + Constants.TEST_TIMEOUT_THRESHOLDS.IS_FINISHED_OFFSET_TO_DURATION, dashjs.MediaPlayer.events.PLAYBACK_ENDED) + expect(isFinished).to.be.true; + }) + + it(`Expect all events to be triggered`, () => { + const adData = googleAdManagerAdapter.getAdData(); + const vastEventsToVerify = googleAdManagerAdapter.getVastEventsToVerify(); + const adIds = Object.keys(adData); + + adIds.forEach((adId) => { + const entry = adData[adId]; + const events = Object.keys(entry.events); + expect(Object.keys(vastEventsToVerify).every(v => events.includes(v))).to.be.true + }) + }) + + + it(`Expect all events to have the right order`, () => { + const adData = googleAdManagerAdapter.getAdData(); + const vastEventsToVerify = googleAdManagerAdapter.getVastEventsToVerify(); + const adIds = Object.keys(adData); + + adIds.forEach((adId) => { + const entry = adData[adId]; + const events = Object.keys(entry.events); + + events.forEach((event) => { + console.log(`event ${event} with position ${entry.events[event].position} should be at position ${vastEventsToVerify[event].position}`); + expect(entry.events[event].position).to.be.equal(vastEventsToVerify[event].position) + }) + }) + }) + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + + }) +}) + diff --git a/test/functional-karma/view/index.html b/test/functional-karma/view/index.html new file mode 100644 index 0000000000..585786db6e --- /dev/null +++ b/test/functional-karma/view/index.html @@ -0,0 +1,41 @@ + + + + + dash.js test + + + + +
+
+ + + + + +%SCRIPTS% + + + + + From fb23a26a13876a543265237013cb4f23844377fb Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 10:37:11 +0200 Subject: [PATCH 2/6] Add test for MPD anchors --- test/functional-karma/helper/Constants.js | 7 ++ .../test/simple/mpd-anchor.js | 71 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/test/functional-karma/helper/Constants.js b/test/functional-karma/helper/Constants.js index ae4ab8c717..afe68cdb5a 100644 --- a/test/functional-karma/helper/Constants.js +++ b/test/functional-karma/helper/Constants.js @@ -28,6 +28,7 @@ export default { LIVE_DELAY: 'simple_live_delay', LIVE_CATCHUP: 'simple_live_catchup', ATTACH_WITH_POSIX: 'simple_attach_with_posix', + MPD_ANCHOR: 'simple_mpd_anchor', PREFIX: 'simple_' }, VENDOR: { @@ -82,6 +83,12 @@ export default { VOD_RANDOM_ATTACH_SUBTRACT_OFFSET: 5, LIVE_RANDOM_ATTACH_SUBTRACT_OFFSET: 5 }, + MPD_ANCHOR: { + NUMBER_OF_RANDOM_ATTACHES: 3, + VOD_RANDOM_SUBTRACT_OFFSET: 5, + LIVE_RANDOM_POSIX_DELAY: 20, + LIVE_TOLERANCE: 5 + }, INITIAL_BUFFER_TARGET: { VALUE: 10, TOLERANCE: 1, diff --git a/test/functional-karma/test/simple/mpd-anchor.js b/test/functional-karma/test/simple/mpd-anchor.js index e69de29bb2..09edb27b40 100644 --- a/test/functional-karma/test/simple/mpd-anchor.js +++ b/test/functional-karma/test/simple/mpd-anchor.js @@ -0,0 +1,71 @@ +import DashJsAdapter from '../../adapter/DashJsAdapter'; +import Constants from '../../helper/Constants'; +import Utils from '../../helper/Utils'; +import {expect} from 'chai' + +const TESTCASE = Constants.TESTCASES.SIMPLE.MPD_ANCHOR; + +Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { + const mpd = item.url; + + describe(`Simple - Attach with posix - ${item.name} - ${mpd}`, () => { + + let playerAdapter; + + before(function () { + playerAdapter = new DashJsAdapter(); + playerAdapter.init(true); + playerAdapter.setDrmData(item.drm); + }) + + after(() => { + playerAdapter.destroy(); + }) + + it(`Attach with #t and expect current time to correspond`, async function () { + if (item.type === Constants.CONTENT_TYPES.LIVE) { + this.skip(); + } + + playerAdapter.attachSource(mpd); + + let metadataLoaded = await playerAdapter.waitForEvent(Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED); + expect(metadataLoaded).to.be.true; + + let startTime = playerAdapter.generateValidStartPosition(); + startTime -= Constants.TEST_INPUTS.MPD_ANCHOR.VOD_RANDOM_SUBTRACT_OFFSET; + startTime = Math.max(startTime, 0); + playerAdapter.attachSource(`${mpd}#t=${startTime}`); + + let seeked = await playerAdapter.waitForEvent(Constants.TEST_TIMEOUT_THRESHOLDS.EVENT_WAITING_TIME, dashjs.MediaPlayer.events.PLAYBACK_SEEKED); + expect(seeked).to.be.true; + + const timeIsWithinThreshold = playerAdapter.timeIsWithinThreshold(startTime, Constants.TEST_INPUTS.GENERAL.MAXIMUM_ALLOWED_SEEK_DIFFERENCE); + expect(timeIsWithinThreshold).to.be.true; + }); + + it(`Attach with #posix and expect live delay to correspond`, async function (){ + if (item.type === Constants.CONTENT_TYPES.VOD) { + this.skip(); + } + + const starttime = new Date().getTime() / 1000 - Constants.TEST_INPUTS.MPD_ANCHOR.LIVE_RANDOM_POSIX_DELAY; + playerAdapter.attachSource(`${mpd}#t=posix:${starttime}`); /* start from UTC time */ + + const isPlaying = await playerAdapter.isInPlayingState(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PLAYING); + expect(isPlaying).to.be.true; + + const isProgressing = await playerAdapter.isProgressing(Constants.TEST_TIMEOUT_THRESHOLDS.IS_PROGRESSING, Constants.TEST_INPUTS.GENERAL.MINIMUM_PROGRESS_WHEN_PLAYING); + expect(isProgressing).to.be.true; + + const liveDelay = playerAdapter.getCurrentLiveLatency(); + expect(liveDelay).to.be.at.least(Constants.TEST_INPUTS.MPD_ANCHOR.LIVE_RANDOM_POSIX_DELAY - Constants.TEST_INPUTS.MPD_ANCHOR.LIVE_TOLERANCE); + expect(liveDelay).to.be.below(Constants.TEST_INPUTS.MPD_ANCHOR.LIVE_RANDOM_POSIX_DELAY + Constants.TEST_INPUTS.MPD_ANCHOR.LIVE_TOLERANCE); + }) + + it(`Expect no critical errors to be thrown`, () => { + const logEvents = playerAdapter.getLogEvents(); + expect(logEvents[dashjs.Debug.LOG_LEVEL_ERROR]).to.be.empty; + }) + }) +}) From a8a792e25fc1efc9a653e855f683c9108dd856d3 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 13:53:03 +0200 Subject: [PATCH 3/6] Fix wording --- test/functional-karma/helper/Utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional-karma/helper/Utils.js b/test/functional-karma/helper/Utils.js index 4198e54ee7..5161b559f2 100644 --- a/test/functional-karma/helper/Utils.js +++ b/test/functional-karma/helper/Utils.js @@ -1,6 +1,6 @@ import Constants from '../helper/Constants'; import content from '../config/content'; - +import singleVector from '../config/single-vector'; class Utils { From 4833caca46224883d5c424c55398dd995c454f56 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 13:53:10 +0200 Subject: [PATCH 4/6] Fix wording --- test/functional-karma/test/simple/mpd-anchor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional-karma/test/simple/mpd-anchor.js b/test/functional-karma/test/simple/mpd-anchor.js index 09edb27b40..6dfa0a41a2 100644 --- a/test/functional-karma/test/simple/mpd-anchor.js +++ b/test/functional-karma/test/simple/mpd-anchor.js @@ -8,7 +8,7 @@ const TESTCASE = Constants.TESTCASES.SIMPLE.MPD_ANCHOR; Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { const mpd = item.url; - describe(`Simple - Attach with posix - ${item.name} - ${mpd}`, () => { + describe(`Simple - Attach with MPD anchor - ${item.name} - ${mpd}`, () => { let playerAdapter; From 2dc06458e4f5efdb510f56fbb4eff628f2027ec9 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 14:32:07 +0200 Subject: [PATCH 5/6] Fix schemeIdUri for EMSG events --- test/functional-karma/config/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional-karma/config/content.js b/test/functional-karma/config/content.js index 337d6bf63a..c7c4ff51e5 100644 --- a/test/functional-karma/config/content.js +++ b/test/functional-karma/config/content.js @@ -194,7 +194,7 @@ export default [ emsg: { minimumNumberOfEvents: 2, runtime: 65000, - schemeIdUri: 'urn:scte:scte35:2013:xml' + schemeIdUri: 'urn:scte:scte35:2013:bin' } } }, From dcb91431b350e13a9fc1a909d85be91da8574e54 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 13 Sep 2023 15:36:03 +0200 Subject: [PATCH 6/6] Put streams into individual JavaScript files. That way we can later enable/disable specific tests via commandline --- test/functional-karma/config/content.js | 265 ------------------ test/functional-karma/config/drm.js | 52 ++++ test/functional-karma/config/emsg.js | 19 ++ test/functional-karma/config/ept-delta.js | 34 +++ test/functional-karma/config/gaps.js | 32 +++ test/functional-karma/config/live-basic.js | 36 +++ test/functional-karma/config/low-latency.js | 12 + test/functional-karma/config/mss.js | 10 + test/functional-karma/config/multi-audio.js | 23 ++ test/functional-karma/config/multiperiod.js | 63 +++++ test/functional-karma/config/single-vector.js | 9 +- test/functional-karma/config/subtitle.js | 2 +- test/functional-karma/config/vod-basic.js | 24 ++ test/functional-karma/helper/Utils.js | 18 +- test/functional-karma/index.md | 1 - 15 files changed, 325 insertions(+), 275 deletions(-) delete mode 100644 test/functional-karma/config/content.js create mode 100644 test/functional-karma/config/drm.js create mode 100644 test/functional-karma/config/emsg.js create mode 100644 test/functional-karma/config/ept-delta.js create mode 100644 test/functional-karma/config/gaps.js create mode 100644 test/functional-karma/config/live-basic.js create mode 100644 test/functional-karma/config/low-latency.js create mode 100644 test/functional-karma/config/mss.js create mode 100644 test/functional-karma/config/multi-audio.js create mode 100644 test/functional-karma/config/multiperiod.js create mode 100644 test/functional-karma/config/vod-basic.js diff --git a/test/functional-karma/config/content.js b/test/functional-karma/config/content.js deleted file mode 100644 index c7c4ff51e5..0000000000 --- a/test/functional-karma/config/content.js +++ /dev/null @@ -1,265 +0,0 @@ -import Constants from '../helper/Constants'; - -export default [ - { - name: 'Segment Base', - type: 'vod', - url: 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'Segment Template, number based', - type: 'vod', - url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'Segment Timeline, time based', - type: 'vod', - url: 'https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'TTML segmented subtitles', - type: 'vod', - url: 'https://livesim2.dashif.org/vod/testpic_2s/multi_subs.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'Multi audio', - type: 'vod', - url: 'http://refapp.hbbtv.org/videos/02_gran_dillama_1080p_ma_25f75g6sv5/manifest.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: '1080p with PlayReady and Widevine DRM, single key', - type: 'vod', - url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd', - drm: { - 'com.widevine.alpha': { - 'serverURL': 'https://drm-widevine-licensing.axtest.net/AcquireLicense', - 'httpRequestHeaders': { - 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' - }, - 'httpTimeout': 5000 - }, - 'com.microsoft.playready': { - 'serverURL': 'https://drm-playready-licensing.axtest.net/AcquireLicense', - 'httpRequestHeaders': { - 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' - }, - 'httpTimeout': 5000 - } - }, - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: '1080p with W3C Clear Key, single key', - type: 'vod', - url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd', - drm: { - 'org.w3.clearkey': { - 'clearkeys': { - 'nrQFDeRLSAKTLifXUIPiZg': 'FmY0xnWCPCNaSpRG-tUuTQ' - } - } - }, - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'Big Buck Bunny', - type: 'vod', - url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'External VTT', - type: 'vod', - url: 'https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - }, - { - name: 'CEA-608 + TTML', - type: 'vod', - url: 'https://livesim2.dashif.org/vod/testpic_2s/cea608_and_segs.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - }, - { - name: 'Shaka Demo Assets: Angel-One Widevine', - type: 'vod', - url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - drm: { - 'com.widevine.alpha': { - serverURL: 'https://cwip-shaka-proxy.appspot.com/no_auth' - } - } - }, - { - name: 'MSS', - type: 'vod', - url: 'http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'Segment Timeline with missing audio segment in MPD for time 0', - type: 'vod', - url: '/base/content/gap/audio_gap_at_start_timeline.mpd', - testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], - testdata: { - gaps: [ - { - start: 0, - end: 5.97 - }] - } - }, - { - name: 'Segment Timeline with missing video segment in MPD for time 0', - type: 'vod', - url: '/base/content/gap/video_gap_at_start_timeline.mpd', - testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], - testdata: { - gaps: [ - { - start: 0, - end: 6 - }] - } - }, - { - name: 'Segment Timeline with negative video EPT Delta', - type: 'vod', - url: '/base/content/gap/video_negative_ept_delta.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], - }, - { - name: 'Segment Timeline with negative audio EPT Delta', - type: 'vod', - url: '/base/content/gap/audio_negative_ept_delta.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], - }, - { - name: 'Segment Timeline with positive video EPT Delta', - type: 'vod', - url: '/base/content/gap/video_negative_ept_delta.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], - }, - { - name: 'Segment Timeline with positive audio EPT Delta', - type: 'vod', - url: '/base/content/gap/audio_negative_ept_delta.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], - }, - { - name: 'Axinom 3 Audio 3 Text', - type: 'vod', - url: 'https://media.axprod.net/TestVectors/Cmaf/clear_1080p_h264/manifest.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'BBC Testcard', - type: 'vod', - url: 'https://rdmedia.bbc.co.uk/testcard/vod/manifests/avc-full.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'DASH-IF Live Sim - Segment Template without manifest updates', - type: 'live', - url: 'https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - url: 'https://livesim2.dashif.org/livesim2/segtimeline_1/testpic_2s/Manifest.mpd', - name: 'Segment Timeline with $time$', - type: 'live', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - url: 'https://livesim2.dashif.org/livesim2/segtimelinenr_1/testpic_2s/Manifest.mpd', - name: 'Segment Timeline with $number$', - type: 'live', - testcases: [Constants.TESTCASES.GENERIC.ALL] - }, - { - name: 'livesim2 SCTE35', - type: 'live', - url: 'https://livesim2.dashif.org/livesim2/scte35_2/testpic_2s/Manifest.mpd', - testcases: [Constants.TESTCASES.SIMPLE.EMSG_TRIGGERED], - testdata: { - emsg: { - minimumNumberOfEvents: 2, - runtime: 65000, - schemeIdUri: 'urn:scte:scte35:2013:bin' - } - } - }, - { - name: 'CEA-608 + TTML - Live', - type: 'live', - url: 'https://livesim2.dashif.org/livesim2/testpic_2s/cea608_and_segs.mpd', - testcases: [Constants.TESTCASES.GENERIC.ALL], - }, - { - name: 'AWS Multiperiod unencrypted', - type: 'live', - url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams/4577dca5f8a44756875ab5cc913cd1f1/index.mpd', - testdata: { - periods: { - waitingTimeForPeriodSwitches: 70000, - minimumNumberOfPeriodSwitches: 1, - maximumNumberOfPeriodSwitches: 15, - } - }, - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK] - }, - - { - name: 'Multiperiod - Number + Timeline - Compact manifest - Thumbnails (1 track) - Encryption (2 keys : audio + video) - No key rotation', - type: 'live', - url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams//6e16c26536564c2f9dbc5f725a820cff/index.mpd', - drm: { - 'com.widevine.alpha': { - 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/?specConform=true', - 'httpRequestHeaders': { - 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' - } - }, - 'com.microsoft.playready': { - 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', - 'httpRequestHeaders': { - 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' - } - } - }, - testdata: { - periods: { - waitingTimeForPeriodSwitches: 70000, - minimumNumberOfPeriodSwitches: 1, - maximumNumberOfPeriodSwitches: 15, - } - }, - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], - }, - { - name: 'Multiperiod DASH-IF livesim2', - type: 'live', - url: 'https://livesim2.dashif.org/livesim2/periods_60/continuous_1/testpic_2s/Manifest.mpd', - testdata: { - periods: { - waitingTimeForPeriodSwitches: 60000, - minimumNumberOfPeriodSwitches: 1, - maximumNumberOfPeriodSwitches: 2, - } - }, - testcases: [Constants.TESTCASES.GENERIC.ALL], - excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], - } -] - diff --git a/test/functional-karma/config/drm.js b/test/functional-karma/config/drm.js new file mode 100644 index 0000000000..17a211bb67 --- /dev/null +++ b/test/functional-karma/config/drm.js @@ -0,0 +1,52 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: '1080p with PlayReady and Widevine DRM, single key', + type: 'vod', + url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd', + drm: { + 'com.widevine.alpha': { + 'serverURL': 'https://drm-widevine-licensing.axtest.net/AcquireLicense', + 'httpRequestHeaders': { + 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' + }, + 'httpTimeout': 5000 + }, + 'com.microsoft.playready': { + 'serverURL': 'https://drm-playready-licensing.axtest.net/AcquireLicense', + 'httpRequestHeaders': { + 'X-AxDRM-Message': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA' + }, + 'httpTimeout': 5000 + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: '1080p with W3C Clear Key, single key', + type: 'vod', + url: 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd', + drm: { + 'org.w3.clearkey': { + 'clearkeys': { + 'nrQFDeRLSAKTLifXUIPiZg': 'FmY0xnWCPCNaSpRG-tUuTQ' + } + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Shaka Demo Assets: Angel-One Widevine', + type: 'vod', + url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + drm: { + 'com.widevine.alpha': { + serverURL: 'https://cwip-shaka-proxy.appspot.com/no_auth' + } + } + }, +] + + diff --git a/test/functional-karma/config/emsg.js b/test/functional-karma/config/emsg.js new file mode 100644 index 0000000000..286197c047 --- /dev/null +++ b/test/functional-karma/config/emsg.js @@ -0,0 +1,19 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'livesim2 SCTE35', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/scte35_2/testpic_2s/Manifest.mpd', + testcases: [Constants.TESTCASES.SIMPLE.EMSG_TRIGGERED], + testdata: { + emsg: { + minimumNumberOfEvents: 2, + runtime: 65000, + schemeIdUri: 'urn:scte:scte35:2013:bin' + } + } + }, +] + + diff --git a/test/functional-karma/config/ept-delta.js b/test/functional-karma/config/ept-delta.js new file mode 100644 index 0000000000..09f8d6a583 --- /dev/null +++ b/test/functional-karma/config/ept-delta.js @@ -0,0 +1,34 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Segment Timeline with negative video EPT Delta', + type: 'vod', + url: '/base/content/gap/video_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with negative audio EPT Delta', + type: 'vod', + url: '/base/content/gap/audio_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with positive video EPT Delta', + type: 'vod', + url: '/base/content/gap/video_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + }, + { + name: 'Segment Timeline with positive audio EPT Delta', + type: 'vod', + url: '/base/content/gap/audio_negative_ept_delta.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.SIMPLE.SEEK, Constants.TESTCASES.SIMPLE.ATTACH_AT_NON_ZERO], + } +] + + diff --git a/test/functional-karma/config/gaps.js b/test/functional-karma/config/gaps.js new file mode 100644 index 0000000000..dc7202f548 --- /dev/null +++ b/test/functional-karma/config/gaps.js @@ -0,0 +1,32 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Segment Timeline with missing audio segment in MPD for time 0', + type: 'vod', + url: '/base/content/gap/audio_gap_at_start_timeline.mpd', + testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], + testdata: { + gaps: [ + { + start: 0, + end: 5.97 + }] + } + }, + { + name: 'Segment Timeline with missing video segment in MPD for time 0', + type: 'vod', + url: '/base/content/gap/video_gap_at_start_timeline.mpd', + testcases: [Constants.TESTCASES.ADVANCED.SEEK_IN_GAPS], + testdata: { + gaps: [ + { + start: 0, + end: 6 + }] + } + }, +] + + diff --git a/test/functional-karma/config/live-basic.js b/test/functional-karma/config/live-basic.js new file mode 100644 index 0000000000..7b53cc91d0 --- /dev/null +++ b/test/functional-karma/config/live-basic.js @@ -0,0 +1,36 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'DASH-IF Live Sim - Segment Template without manifest updates', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Timeline with $time$', + url: 'https://livesim2.dashif.org/livesim2/segtimeline_1/testpic_2s/Manifest.mpd', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Timeline with $number$', + url: 'https://livesim2.dashif.org/livesim2/segtimelinenr_1/testpic_2s/Manifest.mpd', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'AWS Single Period $number$', + url: 'https://d10gktn8v7end7.cloudfront.net/out/v1/6ee19df3afa24fe190a8ae16c2c88560/index.mpd', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Audio only live', + url: 'https://livesim.dashif.org/livesim/testpic_2s/audio.mpd', + type: 'live', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, +] + + diff --git a/test/functional-karma/config/low-latency.js b/test/functional-karma/config/low-latency.js new file mode 100644 index 0000000000..dd9edf9e59 --- /dev/null +++ b/test/functional-karma/config/low-latency.js @@ -0,0 +1,12 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Akamai Low Latency - Multi Rate', + type: 'live', + url: 'https://cmafref.akamaized.net/cmaf/live-ull/2006350/akambr/out.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, +] + + diff --git a/test/functional-karma/config/mss.js b/test/functional-karma/config/mss.js new file mode 100644 index 0000000000..e2fdcf4bf6 --- /dev/null +++ b/test/functional-karma/config/mss.js @@ -0,0 +1,10 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'MSS', + type: 'vod', + url: 'http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest', + testcases: [Constants.TESTCASES.GENERIC.ALL] + } +] diff --git a/test/functional-karma/config/multi-audio.js b/test/functional-karma/config/multi-audio.js new file mode 100644 index 0000000000..b2c6a30ab2 --- /dev/null +++ b/test/functional-karma/config/multi-audio.js @@ -0,0 +1,23 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Multi audio', + type: 'vod', + url: 'http://refapp.hbbtv.org/videos/02_gran_dillama_1080p_ma_25f75g6sv5/manifest.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Shaka Demo Assets: Angel-One Widevine', + type: 'vod', + url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd', + testcases: [Constants.TESTCASES.SIMPLE.SWITCH_AUDIO], + drm: { + 'com.widevine.alpha': { + serverURL: 'https://cwip-shaka-proxy.appspot.com/no_auth' + } + } + }, +] + + diff --git a/test/functional-karma/config/multiperiod.js b/test/functional-karma/config/multiperiod.js new file mode 100644 index 0000000000..948ce9c23c --- /dev/null +++ b/test/functional-karma/config/multiperiod.js @@ -0,0 +1,63 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'AWS Multiperiod unencrypted', + type: 'live', + url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams/4577dca5f8a44756875ab5cc913cd1f1/index.mpd', + testdata: { + periods: { + waitingTimeForPeriodSwitches: 70000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 15, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK] + }, + + { + name: 'Multiperiod - Number + Timeline - Compact manifest - Thumbnails (1 track) - Encryption (2 keys : audio + video) - No key rotation', + type: 'live', + url: 'https://d24rwxnt7vw9qb.cloudfront.net/v1/dash/e6d234965645b411ad572802b6c9d5a10799c9c1/All_Reference_Streams//6e16c26536564c2f9dbc5f725a820cff/index.mpd', + drm: { + 'com.widevine.alpha': { + 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-widevine/cenc/?specConform=true', + 'httpRequestHeaders': { + 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' + } + }, + 'com.microsoft.playready': { + 'serverURL': 'https://lic.staging.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx', + 'httpRequestHeaders': { + 'x-dt-custom-data': 'ewogICAgInVzZXJJZCI6ICJhd3MtZWxlbWVudGFsOjpzcGVrZS10ZXN0aW5nIiwKICAgICJzZXNzaW9uSWQiOiAiZWxlbWVudGFsLXJlZnN0cmVhbSIsCiAgICAibWVyY2hhbnQiOiAiYXdzLWVsZW1lbnRhbCIKfQo=' + } + } + }, + testdata: { + periods: { + waitingTimeForPeriodSwitches: 70000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 15, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], + }, + { + name: 'Multiperiod DASH-IF livesim2', + type: 'live', + url: 'https://livesim2.dashif.org/livesim2/periods_60/continuous_1/testpic_2s/Manifest.mpd', + testdata: { + periods: { + waitingTimeForPeriodSwitches: 60000, + minimumNumberOfPeriodSwitches: 1, + maximumNumberOfPeriodSwitches: 2, + } + }, + testcases: [Constants.TESTCASES.GENERIC.ALL], + excludedTestcases: [Constants.TESTCASES.ADVANCED.NO_RELOAD_AFTER_SEEK], + } +] + + diff --git a/test/functional-karma/config/single-vector.js b/test/functional-karma/config/single-vector.js index 7edf5be0b5..286197c047 100644 --- a/test/functional-karma/config/single-vector.js +++ b/test/functional-karma/config/single-vector.js @@ -2,18 +2,17 @@ import Constants from '../helper/Constants'; export default [ { - name: 'Livesim SCTE35', + name: 'livesim2 SCTE35', type: 'live', url: 'https://livesim2.dashif.org/livesim2/scte35_2/testpic_2s/Manifest.mpd', - testcases: [Constants.TESTCASES.GENERIC.SIMPLE_ALL], + testcases: [Constants.TESTCASES.SIMPLE.EMSG_TRIGGERED], testdata: { emsg: { minimumNumberOfEvents: 2, runtime: 65000, - schemeIdUri: 'urn:scte:scte35:2013:xml' + schemeIdUri: 'urn:scte:scte35:2013:bin' } - }, - excludedTestcases: [] + } }, ] diff --git a/test/functional-karma/config/subtitle.js b/test/functional-karma/config/subtitle.js index 3b652943eb..23dad6860f 100644 --- a/test/functional-karma/config/subtitle.js +++ b/test/functional-karma/config/subtitle.js @@ -98,7 +98,7 @@ export default [ }, { 'url': 'https://storage.googleapis.com/shaka-demo-assets/sintel-many-subs/dash.mpd', - 'name': 'Shaka 44 differenr subtitles', + 'name': 'Shaka 44 different subtitles', type: 'vod', testcases: [Constants.TESTCASES.SIMPLE.SWITCH_TEXT], } diff --git a/test/functional-karma/config/vod-basic.js b/test/functional-karma/config/vod-basic.js new file mode 100644 index 0000000000..36364b088f --- /dev/null +++ b/test/functional-karma/config/vod-basic.js @@ -0,0 +1,24 @@ +import Constants from '../helper/Constants'; + +export default [ + { + name: 'Segment Base', + type: 'vod', + url: 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Template, number based', + type: 'vod', + url: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, + { + name: 'Segment Timeline, time based', + type: 'vod', + url: 'https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd', + testcases: [Constants.TESTCASES.GENERIC.ALL] + }, +] + + diff --git a/test/functional-karma/helper/Utils.js b/test/functional-karma/helper/Utils.js index 5161b559f2..6c193e0246 100644 --- a/test/functional-karma/helper/Utils.js +++ b/test/functional-karma/helper/Utils.js @@ -1,6 +1,18 @@ -import Constants from '../helper/Constants'; -import content from '../config/content'; -import singleVector from '../config/single-vector'; +import Constants from '../helper/Constants.js'; +import drm from '../config/drm.js' +import emsg from '../config/emsg.js' +import eptDelta from '../config/ept-delta.js' +import gaps from '../config/gaps.js' +import liveBasic from '../config/live-basic.js' +import lowLatency from '../config/low-latency.js' +import mss from '../config/mss.js' +import multiAudio from '../config/multi-audio.js' +import multiperiod from '../config/multiperiod.js' +import subtitle from '../config/subtitle.js' +import vendor from '../config/vendor.js' +import vodBasic from '../config/vod-basic.js' + +const content = [].concat(drm, emsg, eptDelta, gaps, liveBasic, lowLatency, mss, multiAudio, multiperiod, subtitle, vendor, vodBasic); class Utils { diff --git a/test/functional-karma/index.md b/test/functional-karma/index.md index 76824280ce..00eb4badb5 100644 --- a/test/functional-karma/index.md +++ b/test/functional-karma/index.md @@ -18,7 +18,6 @@ The source files are placed in multiple folders: * `view`: Customized view for the test execution including a video element. # Configuration - The main configuration for the test execution is defined in `karma.functional.conf.js`. To adjust the list of testvectors or the testcases the existing `config/content.js` can be adjusted. As an alternative, include a different configuration file in `helper/Utils.js`. Future additions to the test framework should allow definition of the testfile