From be289f6375e02f1e459f12b755a30e6ed3df8a7d Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Tue, 16 Apr 2024 08:44:02 -0700 Subject: [PATCH 1/9] 3.12.1 --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c835046c..812b7529e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + +## [3.12.1](https://github.com/videojs/http-streaming/compare/v3.12.0...v3.12.1) (2024-04-16) + +### Bug Fixes + +* enableFunction not passing playlist to fastQualityChange ([#1502](https://github.com/videojs/http-streaming/issues/1502)) ([e50ecb1](https://github.com/videojs/http-streaming/commit/e50ecb1)) +* llHLS does not need forcedTimestampOffset ([#1501](https://github.com/videojs/http-streaming/issues/1501)) ([f5d1209](https://github.com/videojs/http-streaming/commit/f5d1209)) + +### Documentation + +* removing duplicated step ([#1476](https://github.com/videojs/http-streaming/issues/1476)) ([e4acc57](https://github.com/videojs/http-streaming/commit/e4acc57)) + # [3.12.0](https://github.com/videojs/http-streaming/compare/v3.11.3...v3.12.0) (2024-03-12) diff --git a/package-lock.json b/package-lock.json index 08588d78e..617b1627b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.0", + "version": "3.12.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6bb72f378..2d2836134 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.0", + "version": "3.12.1", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "main": "dist/videojs-http-streaming.cjs.js", "module": "dist/videojs-http-streaming.es.js", From bdfe0e02c08673627837d3ea575d23b8c666f50f Mon Sep 17 00:00:00 2001 From: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:24:32 -0400 Subject: [PATCH 2/9] fix: use paren media sequence sync for audio and vtt, since they are opt-in features and can be enabled after main init (#1505) Co-authored-by: Dzianis Dashkevich --- src/sync-controller.js | 12 ++++++------ src/util/media-sequence-sync.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/sync-controller.js b/src/sync-controller.js index 46bce919a..fdab91c4e 100644 --- a/src/sync-controller.js +++ b/src/sync-controller.js @@ -5,7 +5,7 @@ import {sumDurations, getPartsAndSegments} from './playlist'; import videojs from 'video.js'; import logger from './util/logger'; -import MediaSequenceSync from './util/media-sequence-sync'; +import {MediaSequenceSync, DependantMediaSequenceSync} from './util/media-sequence-sync'; // The maximum gap allowed between two media sequence tags when trying to // synchronize expired playlist segments. @@ -233,11 +233,11 @@ export default class SyncController extends videojs.EventTarget { // For some reason this map helps with syncing between quality switch for MPEG-DASH as well. // Moreover if we disable this map for MPEG-DASH - quality switch will be broken. // MPEG-DASH should have its own separate sync strategy - this.mediaSequenceStorage_ = { - main: new MediaSequenceSync(), - audio: new MediaSequenceSync(), - vtt: new MediaSequenceSync() - }; + const main = new MediaSequenceSync(); + const audio = new DependantMediaSequenceSync(main); + const vtt = new DependantMediaSequenceSync(main); + + this.mediaSequenceStorage_ = {main, audio, vtt}; this.logger_ = logger('SyncController'); } diff --git a/src/util/media-sequence-sync.js b/src/util/media-sequence-sync.js index 41797cea6..21aeb6eb9 100644 --- a/src/util/media-sequence-sync.js +++ b/src/util/media-sequence-sync.js @@ -79,11 +79,11 @@ class SyncInfoData { } } -export default class MediaSequenceSync { +export class MediaSequenceSync { constructor() { /** * @type {Map} - * @private + * @protected */ this.storage_ = new Map(); this.diagnostics_ = ''; @@ -160,6 +160,10 @@ export default class MediaSequenceSync { return null; } + getSyncInfoForMediaSequence(mediaSequence) { + return this.storage_.get(mediaSequence); + } + updateStorage_(segments, startingMediaSequence, startingTime) { const newStorage = new Map(); let newDiagnostics = '\n'; @@ -244,3 +248,25 @@ export default class MediaSequenceSync { return mediaSequence !== undefined && mediaSequence !== null && Array.isArray(segments) && segments.length; } } + +export class DependantMediaSequenceSync extends MediaSequenceSync { + constructor(parent) { + super(); + + this.parent_ = parent; + } + + calculateBaseTime_(mediaSequence, fallback) { + if (!this.storage_.size) { + const info = this.parent_.getSyncInfoForMediaSequence(mediaSequence); + + if (info) { + return info.segmentSyncInfo.start; + } + + return 0; + } + + return super.calculateBaseTime_(mediaSequence, fallback); + } +} From b6ff608eeed6a2e7ef99ecfb00fe4d1bef54f254 Mon Sep 17 00:00:00 2001 From: Dzianis Dashkevich Date: Mon, 22 Apr 2024 15:25:51 -0400 Subject: [PATCH 3/9] 3.12.2 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812b7529e..0ab3610b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ + +## [3.12.2](https://github.com/videojs/http-streaming/compare/v3.12.1...v3.12.2) (2024-04-22) + +### Bug Fixes + +* use paren media sequence sync for audio and vtt, since they are opt-in features and can be enabled after main init ([#1505](https://github.com/videojs/http-streaming/issues/1505)) ([bdfe0e0](https://github.com/videojs/http-streaming/commit/bdfe0e0)) + ## [3.12.1](https://github.com/videojs/http-streaming/compare/v3.12.0...v3.12.1) (2024-04-16) diff --git a/package-lock.json b/package-lock.json index 617b1627b..c201b689d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.1", + "version": "3.12.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2d2836134..399b83499 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.1", + "version": "3.12.2", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "main": "dist/videojs-http-streaming.cjs.js", "module": "dist/videojs-http-streaming.es.js", From c94a2308443f8c7b165f19abe1f6583b4c5b3922 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Thu, 16 May 2024 11:40:55 -0700 Subject: [PATCH 4/9] feat: streaming events and errors (#1508) --- package-lock.json | 576 ++++++++++++++-------------- package.json | 4 +- src/content-steering-controller.js | 30 +- src/dash-playlist-loader.js | 86 ++++- src/error-codes.js | 30 ++ src/media-segment-request.js | 154 ++++++-- src/playback-watcher.js | 24 +- src/playlist-controller.js | 83 ++++ src/playlist-loader.js | 95 ++++- src/rendition-mixin.js | 13 +- src/segment-loader.js | 117 +++++- src/segment-transmuxer.js | 19 +- src/source-updater.js | 14 +- src/timeline-change-controller.js | 9 +- src/util/container-request.js | 4 +- src/videojs-http-streaming.js | 36 +- src/vtt-segment-loader.js | 8 +- src/xhr.js | 1 + test/dash-playlist-loader.test.js | 12 +- test/media-segment-request.test.js | 70 ++-- test/playback.test.js | 533 +++++++++++++++++++++++++ test/playlist-controller.test.js | 55 ++- test/segment-loader.test.js | 7 +- test/segment-transmuxer.test.js | 15 +- test/videojs-http-streaming.test.js | 5 +- 25 files changed, 1550 insertions(+), 450 deletions(-) diff --git a/package-lock.json b/package-lock.json index c201b689d..175dcace6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1384,9 +1384,9 @@ }, "dependencies": { "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -1779,9 +1779,9 @@ } }, "@videojs/http-streaming": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.10.0.tgz", - "integrity": "sha512-Lf1rmhTalV4Gw0bJqHmH4lfk/FlepUDs9smuMtorblAYnqDlE2tbUOb7sBXVYoXGdbWbdTW8jH2cnS+6HWYJ4Q==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.12.2.tgz", + "integrity": "sha512-P7l3qZdxW216b6KWPBBr+7Sj95exL25AWyD+hJsHA/Ghwrh8FsKplMleCE6JBumVT+5on1efMAPAFBlarv9c2w==", "requires": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "4.0.0", @@ -1789,30 +1789,8 @@ "global": "^4.4.0", "m3u8-parser": "^7.1.0", "mpd-parser": "^1.3.0", - "mux.js": "7.0.2", + "mux.js": "7.0.3", "video.js": "^7 || ^8" - }, - "dependencies": { - "mpd-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.0.tgz", - "integrity": "sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.0.0", - "@xmldom/xmldom": "^0.8.3", - "global": "^4.4.0" - } - }, - "mux.js": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.0.2.tgz", - "integrity": "sha512-CM6+QuyDbc0qW1OfEjkd2+jVKzTXF+z5VOKH0eZxtZtnrG/ilkW/U7l7IXGtBNLASF9sKZMcK1u669cq50Qq0A==", - "requires": { - "@babel/runtime": "^7.11.2", - "global": "^4.4.0" - } - } } }, "@videojs/update-changelog": { @@ -1874,7 +1852,7 @@ "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", "dev": true }, "accepts": { @@ -1902,7 +1880,7 @@ "add-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, "aes-decrypter": { @@ -1962,7 +1940,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true, "optional": true }, @@ -1986,7 +1964,7 @@ "ansi": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", - "integrity": "sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A==", + "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=", "dev": true }, "ansi-colors": { @@ -2091,19 +2069,19 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "astral-regex": { @@ -2115,7 +2093,7 @@ "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, "axios": { @@ -2216,7 +2194,7 @@ "binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", "dev": true, "requires": { "buffers": "~0.1.1", @@ -2395,7 +2373,7 @@ "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", "dev": true }, "builtin-modules": { @@ -2484,7 +2462,7 @@ "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", "dev": true, "requires": { "traverse": ">=0.3.0 <0.4" @@ -2493,7 +2471,7 @@ "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", "dev": true } } @@ -2584,7 +2562,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "string-width": { @@ -2600,7 +2578,7 @@ "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -2638,7 +2616,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "color-convert": { @@ -2653,7 +2631,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colorette": { @@ -2683,7 +2661,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "compare-func": { @@ -2782,7 +2760,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } @@ -3099,7 +3077,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { "array-find-index": "^1.0.1" @@ -3108,19 +3086,19 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", "dev": true }, "d3": { "version": "3.5.17", "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==", + "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=", "dev": true }, "dargs": { @@ -3153,7 +3131,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decamelize-keys": { @@ -3204,7 +3182,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, "depd": { @@ -3222,7 +3200,7 @@ "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, "diff": { @@ -3399,7 +3377,7 @@ "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { "custom-event": "~1.0.0", @@ -3475,7 +3453,7 @@ "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { "readable-stream": "^2.0.2" @@ -3516,7 +3494,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, "electron-to-chromium": { @@ -3534,7 +3512,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, "engine.io": { @@ -3581,7 +3559,7 @@ "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, "entities": { @@ -3658,7 +3636,7 @@ "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { "es6-promise": "^4.0.3" @@ -3673,19 +3651,19 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "escodegen": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { "esprima": "^2.7.1", @@ -3698,7 +3676,7 @@ "source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", "dev": true, "optional": true, "requires": { @@ -3805,9 +3783,9 @@ "dev": true }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -3830,9 +3808,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -3840,7 +3818,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "prelude-ls": { @@ -3872,13 +3850,19 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true } } }, "eslint-config-videojs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-videojs/-/eslint-config-videojs-6.0.0.tgz", - "integrity": "sha512-kD/A8QGdmHH7SOkEidJt1ICN0B5d3yvAxMUXCWWWtJVf1jgGAwcWL43VSeYYwp31Iby77QgOOWtnvzuNvVhHpQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-videojs/-/eslint-config-videojs-6.1.0.tgz", + "integrity": "sha512-zEhT16DlA6Hz9NYhawEpqS0hmWbWjnK6egmgYpK76Q5F+ng9XS6M0TquYmLYuYoNrrFJu6l+cAjhW0ztt9RqkA==", "dev": true }, "eslint-plugin-jsdoc": { @@ -3908,9 +3892,9 @@ } }, "eslint-plugin-markdown": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.1.tgz", - "integrity": "sha512-FgWp4iyYvTFxPwfbxofTvXxgzPsDuSKHQy2S+a8Ve6savbujey+lgrFFbXQA0HPygISpRYWYBjooPzhYSF81iA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-3.0.1.tgz", + "integrity": "sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==", "dev": true, "requires": { "mdast-util-from-markdown": "^0.8.5" @@ -3985,13 +3969,13 @@ "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -4025,7 +4009,7 @@ "estraverse": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", "dev": true }, "estree-walker": { @@ -4043,7 +4027,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, "eventemitter3": { @@ -4084,7 +4068,7 @@ "external-editor": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha512-0XYlP43jzxMgJjugDJ85Z0UDPnowkUbfFztNvsSGC9sJVIk97MZbGEb9WAhIVH0UgNxoLj/9ZQgB4CHJyz2GGQ==", + "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", "dev": true, "requires": { "extend": "^3.0.0", @@ -4095,7 +4079,7 @@ "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha512-89PTqMWGDva+GqClOqBV9s3SMh7MA3Mq0pJUdAoHuF65YoE7O0LermaZkVfT5/Ngfo18H4eYiyG7zKOtnEbxsw==", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", "dev": true, "requires": { "os-tmpdir": "~1.0.1" @@ -4106,7 +4090,7 @@ "eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", "dev": true }, "fast-deep-equal": { @@ -4124,7 +4108,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "fault": { @@ -4139,7 +4123,7 @@ "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { "escape-string-regexp": "^1.0.5", @@ -4191,7 +4175,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } @@ -4222,19 +4206,20 @@ } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "dependencies": { "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true } } @@ -4254,13 +4239,13 @@ "format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=", "dev": true }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, "fs-extra": { @@ -4283,7 +4268,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "fsevents": { @@ -4340,7 +4325,7 @@ "gauge": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", - "integrity": "sha512-fVbU2wRE91yDvKUnrIaQlHKAWKY5e08PmztCrwuH5YVQ+Z/p3d0ny2T48o6uvAAXHIUnfaQdHkmxYbQft1eHVA==", + "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", "dev": true, "requires": { "ansi": "^0.3.0", @@ -4436,7 +4421,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true }, "get-stream": { @@ -4471,7 +4456,7 @@ "git-remote-origin-url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", "dev": true, "requires": { "gitconfiglocal": "^1.0.0", @@ -4499,7 +4484,7 @@ "gitconfiglocal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", "dev": true, "requires": { "ini": "^1.3.2" @@ -4508,7 +4493,7 @@ "github-url-from-git": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", - "integrity": "sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==", + "integrity": "sha1-+YX+3MCpqledyI16/waNVcxiUaA=", "dev": true }, "glob": { @@ -4604,7 +4589,7 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -4613,7 +4598,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true } } @@ -4633,7 +4618,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { @@ -4654,7 +4639,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true }, "hosted-git-info": { @@ -4839,7 +4824,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -4861,7 +4846,7 @@ "inquirer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha512-diSnpgfv/Ozq6QKuV2mUcwZ+D24b03J3W6EVxzvtkCWJTPrH2gKLsqgSW0vzRMZZFhFdhnvzka0RUJxIm7AOxQ==", + "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", "dev": true, "requires": { "ansi-escapes": "^1.1.0", @@ -4883,25 +4868,25 @@ "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "^2.2.1", @@ -4914,7 +4899,7 @@ "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { "restore-cursor": "^1.0.1" @@ -4923,13 +4908,13 @@ "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -4938,13 +4923,13 @@ "onetime": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true }, "restore-cursor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { "exit-hook": "^1.0.0", @@ -4954,7 +4939,7 @@ "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -4965,7 +4950,7 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -4974,7 +4959,7 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } } @@ -5015,7 +5000,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-bigint": { @@ -5103,7 +5088,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-finite": { @@ -5141,7 +5126,7 @@ "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, "is-negative-zero": { @@ -5174,7 +5159,7 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, "is-reference": { @@ -5199,7 +5184,7 @@ "is-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, "is-shared-array-buffer": { @@ -5235,7 +5220,7 @@ "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "dev": true, "requires": { "text-extensions": "^1.0.0" @@ -5244,7 +5229,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, "is-weakref": { @@ -5268,7 +5253,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isbinaryfile": { @@ -5280,19 +5265,19 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "istanbul": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg==", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { "abbrev": "1.0.x", @@ -5314,7 +5299,7 @@ "glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { "inflight": "^1.0.4", @@ -5327,7 +5312,7 @@ "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, "mkdirp": { @@ -5342,13 +5327,13 @@ "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { "has-flag": "^1.0.0" @@ -5511,6 +5496,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -5538,7 +5529,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "json5": { @@ -5550,7 +5541,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -5569,7 +5560,7 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, "just-extend": { @@ -5668,13 +5659,13 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", "dev": true }, "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { "camelcase": "^2.0.0", @@ -5684,7 +5675,7 @@ "dateformat": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg==", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { "get-stdin": "^4.0.1", @@ -5694,7 +5685,7 @@ "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { "path-exists": "^2.0.0", @@ -5710,7 +5701,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { "repeating": "^2.0.0" @@ -5719,7 +5710,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5732,13 +5723,13 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { "camelcase-keys": "^2.0.0", @@ -5768,7 +5759,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { "error-ex": "^1.2.0" @@ -5777,7 +5768,7 @@ "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { "pinkie-promise": "^2.0.0" @@ -5786,7 +5777,7 @@ "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5797,7 +5788,7 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { "load-json-file": "^1.0.0", @@ -5808,7 +5799,7 @@ "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { "find-up": "^1.0.0", @@ -5818,7 +5809,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { "indent-string": "^2.1.0", @@ -5834,13 +5825,13 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { "is-utf8": "^0.2.0" @@ -5849,7 +5840,7 @@ "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { "get-stdin": "^4.0.1" @@ -5858,7 +5849,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true } } @@ -5896,7 +5887,7 @@ "karma-ie-launcher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", - "integrity": "sha512-ts71ke8pHvw6qdRtq0+7VY3ANLoZuUNNkA8abRaWV13QRPNm7TtSOqyszjHUtuwOWKcsSz4tbUtrNICrQC+SXQ==", + "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", "dev": true, "requires": { "lodash": "^4.6.1" @@ -5937,6 +5928,15 @@ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", "integrity": "sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==" }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5955,7 +5955,7 @@ "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -6019,7 +6019,7 @@ "listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", "dev": true }, "listr2": { @@ -6058,7 +6058,7 @@ "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -6070,7 +6070,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -6080,7 +6080,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -6103,43 +6103,37 @@ "lodash-compat": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/lodash-compat/-/lodash-compat-3.10.2.tgz", - "integrity": "sha512-k8SE/OwvWfYZqx3MA/Ry1SHBDWre8Z8tCs0Ba0bF5OqVNvymxgFZ/4VDtbTxzTvcoG11JpTMFsaeZp/yGYvFnA==", + "integrity": "sha1-xpQBKKnTD46QLNLPmf0Muk7PwYM=", "dev": true }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, "lodash.merge": { @@ -6151,19 +6145,19 @@ "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", - "integrity": "sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg==", + "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=", "dev": true }, "lodash.padend": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=", "dev": true }, "lodash.padstart": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw==", + "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=", "dev": true }, "lodash.truncate": { @@ -6175,7 +6169,7 @@ "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, "log-update": { @@ -6280,7 +6274,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { "currently-unhandled": "^0.4.1", @@ -6598,19 +6592,19 @@ "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, "meow": { @@ -6852,7 +6846,7 @@ "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", "requires": { "dom-walk": "^0.1.0" } @@ -6921,7 +6915,7 @@ "mute-stream": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha512-m0kBTDLF/0lgzCsPVmJSKM5xkLNX7ZAB0Q+n2DP37JMIRPVC2R4c3BdO6x++bXFKftbhvSfKgwxAexME+BRDRw==", + "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", "dev": true }, "mux.js": { @@ -7015,12 +7009,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", "integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==", "dev": true - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==", - "dev": true } } }, @@ -7036,7 +7024,7 @@ "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { "abbrev": "1" @@ -7119,7 +7107,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "semver": { @@ -7131,7 +7119,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -7140,7 +7128,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "which": { @@ -7166,7 +7154,7 @@ "npmlog": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", - "integrity": "sha512-DaL6RTb8Qh4tMe2ttPT1qWccETy2Vi5/8p+htMpLBeXJTr2CAqnF5WQtSP2eFpvaNbhLZ5uilDb98mRm4Q+lZQ==", + "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", "dev": true, "requires": { "ansi": "~0.3.1", @@ -7177,13 +7165,13 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-inspect": { @@ -7222,7 +7210,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -7277,19 +7265,19 @@ "os-shim": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", "dev": true }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-limit": { @@ -7361,7 +7349,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { @@ -7388,7 +7376,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true } } @@ -7405,7 +7393,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -7437,13 +7425,13 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { "pinkie": "^2.0.0" @@ -7517,7 +7505,7 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, "prettyjson": { @@ -7533,7 +7521,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", @@ -7548,15 +7536,15 @@ "dev": true }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, "qjobs": { @@ -7668,7 +7656,7 @@ "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -7705,7 +7693,7 @@ "read-pkg-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { "find-up": "^2.0.0", @@ -7715,7 +7703,7 @@ "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -7724,7 +7712,7 @@ "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -7743,7 +7731,7 @@ "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -7752,13 +7740,13 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true } } @@ -7786,7 +7774,7 @@ "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { "resolve": "^1.1.6" @@ -7903,13 +7891,13 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { "is-finite": "^1.0.0" @@ -7918,7 +7906,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-from-string": { @@ -7930,7 +7918,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "requizzle": { @@ -8111,19 +8099,19 @@ "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -8132,7 +8120,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "semver": { @@ -8144,7 +8132,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -8153,7 +8141,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "which": { @@ -8184,7 +8172,7 @@ "rx": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", "dev": true }, "rxjs": { @@ -8228,7 +8216,7 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, "semver-regex": { @@ -8313,7 +8301,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "dev": true }, "setprototypeof": { @@ -8512,7 +8500,7 @@ "spawn-sync": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", "dev": true, "requires": { "concat-stream": "^1.4.7", @@ -8572,13 +8560,13 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, "statuses": { @@ -8680,7 +8668,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true } } @@ -8697,13 +8685,13 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "strip-final-newline": { @@ -8737,13 +8725,12 @@ } }, "table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "requires": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", @@ -8751,15 +8738,15 @@ }, "dependencies": { "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" } }, "ansi-styles": { @@ -8808,7 +8795,7 @@ "tabtab": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz", - "integrity": "sha512-xEwHn571JmOrNGJB1Ehu/Dc2/5pu4aIvCnlKmxrJzzhAmZEy8+RL5cjxq/J66GE0Qf8FRvFg9V3jFos8oz0IQA==", + "integrity": "sha1-egR/FDsBC0y9MfhX6ClhUSy/ThQ=", "dev": true, "requires": { "debug": "^2.2.0", @@ -8842,7 +8829,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } @@ -8850,7 +8837,7 @@ "taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", "dev": true }, "temp-dir": { @@ -8910,7 +8897,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, "through2": { @@ -8944,7 +8931,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "to-regex-range": { @@ -8995,7 +8982,7 @@ "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { "prelude-ls": "~1.1.2" @@ -9026,7 +9013,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, "ua-parser-js": { @@ -9060,6 +9047,12 @@ "which-boxed-primitive": "^1.0.2" } }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -9097,7 +9090,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, "unzipper": { @@ -9120,7 +9113,7 @@ "bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", "dev": true }, "readable-stream": { @@ -9168,7 +9161,7 @@ "update-section": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/update-section/-/update-section-0.3.3.tgz", - "integrity": "sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==", + "integrity": "sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=", "dev": true }, "uri-js": { @@ -9188,13 +9181,13 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, "uuid": { @@ -9204,9 +9197,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, "validate-npm-package-license": { @@ -9226,12 +9219,12 @@ "dev": true }, "video.js": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.11.0.tgz", - "integrity": "sha512-wlcCwZztiWlNxhC1PzKTxWQv+OK+z+Cqyc/SI/kNnLkLQ3gr/Zh2rRikLMSMrw0QJD02aQWmQFXdlB2aiLzSzQ==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.14.0.tgz", + "integrity": "sha512-XoKvHAENYpmLI66GYkcGaGuAhHwYMcxWNO1XRRfVl9v6U62jC1PGVd7Fd/AnsOCTiPjYqzJIAOD4tYJTJtb0cA==", "requires": { "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "3.10.0", + "@videojs/http-streaming": "3.12.2", "@videojs/vhs-utils": "^4.0.0", "@videojs/xhr": "2.6.0", "aes-decrypter": "^4.0.1", @@ -9241,22 +9234,17 @@ "mpd-parser": "^1.2.2", "mux.js": "^7.0.1", "safe-json-parse": "4.0.0", - "videojs-contrib-quality-levels": "4.0.0", + "videojs-contrib-quality-levels": "4.1.0", "videojs-font": "4.1.0", "videojs-vtt.js": "0.15.5" }, "dependencies": { - "videojs-font": { + "videojs-contrib-quality-levels": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz", - "integrity": "sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==" - }, - "videojs-vtt.js": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", - "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", + "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", "requires": { - "global": "^4.3.1" + "global": "^4.4.0" } } } @@ -9275,10 +9263,16 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.0.0.tgz", "integrity": "sha512-u5rmd8BjLwANp7XwuQ0Q/me34bMe6zg9PQdHfTS7aXgiVRbNTb4djcmfG7aeSrkpZjg+XCLezFNenlJaCjBHKw==", + "dev": true, "requires": { "global": "^4.4.0" } }, + "videojs-font": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz", + "integrity": "sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==" + }, "videojs-generate-karma-config": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/videojs-generate-karma-config/-/videojs-generate-karma-config-8.0.1.tgz", @@ -9346,17 +9340,17 @@ } }, "videojs-standard": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/videojs-standard/-/videojs-standard-9.0.1.tgz", - "integrity": "sha512-klpkgUD8qgMNTP3nELWdJ41iNSBVSyN0lllambdcsZkEBnCIUoucv7DtL/um6rpSgaw2j+nQYQusTa0OoY/IEQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/videojs-standard/-/videojs-standard-9.1.0.tgz", + "integrity": "sha512-PcGGksY8Z2QkdiHVvWrOFDqDvt2ppO3ZWRaFrpDTICkl9sy2qRXOxYd9iQ2eqFQxhpWy2z408nyzMn4xghMeCw==", "dev": true, "requires": { "commander": "^7.2.0", "eslint": "^7.28.0", - "eslint-config-videojs": "^6.0.0", + "eslint-config-videojs": "^6.1.0", "eslint-plugin-jsdoc": "^35.3.0", "eslint-plugin-json-light": "^1.0.3", - "eslint-plugin-markdown": "^2.2.0", + "eslint-plugin-markdown": "^3.0.0", "find-root": "^1.1.0", "tsmlb": "^1.0.0" }, @@ -9369,10 +9363,18 @@ } } }, + "videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "requires": { + "global": "^4.3.1" + } + }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, "water-plant-uml": { @@ -9452,7 +9454,7 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, "wrap-ansi": { @@ -9495,7 +9497,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "ws": { diff --git a/package.json b/package.json index 399b83499..31240b086 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "video.js": "^7 || ^8" }, "peerDependencies": { - "video.js": "^8.11.0" + "video.js": "^8.14.0" }, "devDependencies": { "@babel/cli": "^7.21.0", @@ -93,7 +93,7 @@ "videojs-generate-karma-config": "^8.0.1", "videojs-generate-rollup-config": "^7.0.0", "videojs-generator-verify": "~3.0.1", - "videojs-standard": "^9.0.0", + "videojs-standard": "^9.1.0", "water-plant-uml": "^2.0.2" }, "generator-videojs-plugin": { diff --git a/src/content-steering-controller.js b/src/content-steering-controller.js index fab61160b..d3ee8e75c 100644 --- a/src/content-steering-controller.js +++ b/src/content-steering-controller.js @@ -169,7 +169,13 @@ export default class ContentSteeringController extends videojs.EventTarget { this.dispose(); return; } + const metadata = { + contentSteeringInfo: { + uri + } + }; + this.trigger({ type: 'contentsteeringloadstart', metadata }); this.request_ = this.xhr_({ uri, requestType: 'content-steering-manifest' @@ -205,9 +211,31 @@ export default class ContentSteeringController extends videojs.EventTarget { this.startTTLTimeout_(); return; } - const steeringManifestJson = JSON.parse(this.request_.responseText); + this.trigger({ type: 'contentsteeringloadcomplete', metadata }); + let steeringManifestJson; + + try { + steeringManifestJson = JSON.parse(this.request_.responseText); + } catch (parseError) { + const errorMetadata = { + errorType: videojs.Error.StreamingContentSteeringParserError, + error: parseError + }; + + this.trigger({ type: 'error', metadata: errorMetadata }); + } this.assignSteeringProperties_(steeringManifestJson); + const parsedMetadata = { + contentSteeringInfo: metadata.contentSteeringInfo, + contentSteeringManifest: { + version: this.steeringManifest.version, + reloadUri: this.steeringManifest.reloadUri, + priority: this.steeringManifest.priority + } + }; + + this.trigger({ type: 'contentsteeringparsed', metadata: parsedMetadata }); this.startTTLTimeout_(); }); } diff --git a/src/dash-playlist-loader.js b/src/dash-playlist-loader.js index 398230c2c..6d988690a 100644 --- a/src/dash-playlist-loader.js +++ b/src/dash-playlist-loader.js @@ -22,6 +22,7 @@ import containerRequest from './util/container-request.js'; import {toUint8} from '@videojs/vhs-utils/es/byte-helpers'; import logger from './util/logger'; import {merge} from './util/vjs-compat'; +import { getStreamingNetworkErrorMetadata } from './error-codes.js'; const { EventTarget } = videojs; @@ -391,20 +392,18 @@ export default class DashPlaylistLoader extends EventTarget { const uri = resolveManifestRedirect(playlist.sidx.resolvedUri); const fin = (err, request) => { - // TODO: add error metdata here once we create an error type in video.js if (this.requestErrored_(err, request, startingState)) { return; } const sidxMapping = this.mainPlaylistLoader_.sidxMapping_; + const { requestType } = request; let sidx; try { sidx = parseSidx(toUint8(request.response).subarray(8)); } catch (e) { - e.metadata = { - errorType: videojs.Error.DashManifestSidxParsingError - }; + e.metadata = getStreamingNetworkErrorMetadata({requestType, request, parseFailure: true }); // sidx parsing failed. this.requestErrored_(e, request, startingState); @@ -420,6 +419,7 @@ export default class DashPlaylistLoader extends EventTarget { return cb(true); }; + const REQUEST_TYPE = 'dash-sidx'; this.request = containerRequest(uri, this.vhs_.xhr, (err, request, container, bytes) => { if (err) { @@ -439,11 +439,7 @@ export default class DashPlaylistLoader extends EventTarget { internal: true, playlistExclusionDuration: Infinity, // MEDIA_ERR_NETWORK - code: 2, - metadata: { - errorType: videojs.Error.UnsupportedSidxContainer, - sidxContainer - } + code: 2 }, request); } @@ -462,9 +458,10 @@ export default class DashPlaylistLoader extends EventTarget { this.request = this.vhs_.xhr({ uri, responseType: 'arraybuffer', + requestType: 'dash-sidx', headers: segmentXhrHeaders({byterange: playlist.sidx.byterange}) }, fin); - }); + }, REQUEST_TYPE); } dispose() { @@ -646,17 +643,31 @@ export default class DashPlaylistLoader extends EventTarget { } requestMain_(cb) { + const metadata = { + manifestInfo: { + uri: this.mainPlaylistLoader_.srcUrl + } + }; + + this.trigger({type: 'manifestrequeststart', metadata}); this.request = this.vhs_.xhr({ uri: this.mainPlaylistLoader_.srcUrl, withCredentials: this.withCredentials, requestType: 'dash-manifest' }, (error, req) => { + if (error) { + const { requestType } = req; + + error.metadata = getStreamingNetworkErrorMetadata({ requestType, request: req, error }); + } + if (this.requestErrored_(error, req)) { if (this.state === 'HAVE_NOTHING') { this.started = false; } return; } + this.trigger({type: 'manifestrequestcomplete', metadata}); const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_; @@ -717,6 +728,9 @@ export default class DashPlaylistLoader extends EventTarget { } if (error) { + const { requestType } = req; + + this.error.metadata = getStreamingNetworkErrorMetadata({ requestType, request: req, error }); // sync request failed, fall back to using date header from mpd // TODO: log warning this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now(); @@ -762,14 +776,31 @@ export default class DashPlaylistLoader extends EventTarget { this.mediaRequest_ = null; const oldMain = this.mainPlaylistLoader_.main; + const metadata = { + manifestInfo: { + uri: this.mainPlaylistLoader_.srcUrl + } + }; - let newMain = parseMainXml({ - mainXml: this.mainPlaylistLoader_.mainXml_, - srcUrl: this.mainPlaylistLoader_.srcUrl, - clientOffset: this.mainPlaylistLoader_.clientOffset_, - sidxMapping: this.mainPlaylistLoader_.sidxMapping_, - previousManifest: oldMain - }); + this.trigger({type: 'manifestparsestart', metadata}); + let newMain; + + try { + newMain = parseMainXml({ + mainXml: this.mainPlaylistLoader_.mainXml_, + srcUrl: this.mainPlaylistLoader_.srcUrl, + clientOffset: this.mainPlaylistLoader_.clientOffset_, + sidxMapping: this.mainPlaylistLoader_.sidxMapping_, + previousManifest: oldMain + }); + } catch (error) { + this.error = error; + this.error.metadata = { + errorType: videojs.Error.StreamingDashManifestParserError, + error + }; + this.trigger('error'); + } // if we have an old main to compare the new main against if (oldMain) { @@ -789,6 +820,27 @@ export default class DashPlaylistLoader extends EventTarget { } this.addEventStreamToMetadataTrack_(newMain); + if (newMain) { + const { duration, endList } = newMain; + const renditions = []; + + newMain.playlists.forEach((playlist) => { + renditions.push({ + id: playlist.id, + bandwidth: playlist.attributes.BANDWIDTH, + resolution: playlist.attributes.RESOLUTION, + codecs: playlist.attributes.CODECS + }); + }); + const parsedManifest = { + duration, + isLive: !endList, + renditions + }; + + metadata.parsedManifest = parsedManifest; + this.trigger({type: 'manifestparsecomplete', metadata}); + } return Boolean(newMain); } diff --git a/src/error-codes.js b/src/error-codes.js index 241c0c3e7..2a3ae6beb 100644 --- a/src/error-codes.js +++ b/src/error-codes.js @@ -1,2 +1,32 @@ +import videojs from 'video.js'; + // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror export const QUOTA_EXCEEDED_ERR = 22; + +export const getStreamingNetworkErrorMetadata = ({ requestType, request, error, parseFailure }) => { + const isBadStatus = request.status < 200 || request.status > 299; + const isFailure = request.status >= 400 && request.status <= 499; + const errorMetadata = { + uri: request.uri, + requestType + }; + const isBadStatusOrParseFailure = (isBadStatus && !isFailure) || parseFailure; + + if (error && isFailure) { + // copy original error and add to the metadata. + errorMetadata.error = {...error}; + errorMetadata.errorType = videojs.Error.NetworkRequestFailed; + } else if (request.aborted) { + errorMetadata.errorType = videojs.Error.NetworkRequestAborted; + } else if (request.timedout) { + errorMetadata.erroType = videojs.Error.NetworkRequestTimeout; + } else if (isBadStatusOrParseFailure) { + const errorType = parseFailure ? videojs.Error.NetworkBodyParserFailed : videojs.Error.NetworkBadStatus; + + errorMetadata.errorType = errorType; + errorMetadata.status = request.status; + errorMetadata.headers = request.headers; + } + + return errorMetadata; +}; diff --git a/src/media-segment-request.js b/src/media-segment-request.js index 5ec7131e9..934d7ea1a 100644 --- a/src/media-segment-request.js +++ b/src/media-segment-request.js @@ -9,6 +9,8 @@ import { isLikelyFmp4MediaSegment } from '@videojs/vhs-utils/es/containers'; import {merge} from './util/vjs-compat'; +import { getStreamingNetworkErrorMetadata } from './error-codes.js'; +import { segmentInfoPayload } from './segment-loader.js'; export const REQUEST_ERRORS = { FAILURE: 2, @@ -72,12 +74,16 @@ const getProgressStats = (progressEvent) => { * @param {Object} request - the XHR request that possibly generated the error */ const handleErrors = (error, request) => { + const { requestType } = request; + const metadata = getStreamingNetworkErrorMetadata({ requestType, request, error }); + if (request.timedout) { return { status: request.status, message: 'HLS request timed-out at URL: ' + request.uri, code: REQUEST_ERRORS.TIMEOUT, - xhr: request + xhr: request, + metadata }; } @@ -86,7 +92,8 @@ const handleErrors = (error, request) => { status: request.status, message: 'HLS request aborted at URL: ' + request.uri, code: REQUEST_ERRORS.ABORTED, - xhr: request + xhr: request, + metadata }; } @@ -95,7 +102,8 @@ const handleErrors = (error, request) => { status: request.status, message: 'HLS request errored at URL: ' + request.uri, code: REQUEST_ERRORS.FAILURE, - xhr: request + xhr: request, + metadata }; } @@ -104,7 +112,8 @@ const handleErrors = (error, request) => { status: request.status, message: 'Empty HLS response at URL: ' + request.uri, code: REQUEST_ERRORS.FAILURE, - xhr: request + xhr: request, + metadata }; } @@ -121,7 +130,7 @@ const handleErrors = (error, request) => { * @param {Function} finishProcessingFn - a callback to execute to continue processing * this request */ -const handleKeyResponse = (segment, objects, finishProcessingFn) => (error, request) => { +const handleKeyResponse = (segment, objects, finishProcessingFn, triggerSegmentEventFn) => (error, request) => { const response = request.response; const errorObj = handleErrors(error, request); @@ -149,7 +158,9 @@ const handleKeyResponse = (segment, objects, finishProcessingFn) => (error, requ for (let i = 0; i < objects.length; i++) { objects[i].bytes = bytes; } + const keyInfo = { uri: request.uri }; + triggerSegmentEventFn({ type: 'segmentkeyloadcomplete', segment, keyInfo }); return finishProcessingFn(null, segment); }; @@ -167,7 +178,6 @@ const parseInitSegment = (segment, callback) => { message: `Found unsupported ${mediaType} container for initialization segment at URL: ${uri}`, code: REQUEST_ERRORS.FAILURE, metadata: { - errorType: videojs.Error.UnsupportedMediaInitialization, mediaType } }); @@ -212,7 +222,7 @@ const parseInitSegment = (segment, callback) => { * this request */ const handleInitSegmentResponse = -({segment, finishProcessingFn}) => (error, request) => { +({segment, finishProcessingFn, triggerSegmentEventFn}) => (error, request) => { const errorObj = handleErrors(error, request); if (errorObj) { @@ -220,6 +230,7 @@ const handleInitSegmentResponse = } const bytes = new Uint8Array(request.response); + triggerSegmentEventFn({ type: 'segmentloaded', segment }); // init segment is encypted, we will have to wait // until the key request is done to decrypt. if (segment.map.key) { @@ -254,14 +265,15 @@ const handleInitSegmentResponse = const handleSegmentResponse = ({ segment, finishProcessingFn, - responseType + responseType, + triggerSegmentEventFn }) => (error, request) => { const errorObj = handleErrors(error, request); if (errorObj) { return finishProcessingFn(errorObj, segment); } - + triggerSegmentEventFn({ type: 'segmentloaded', segment }); const newBytes = // although responseText "should" exist, this guard serves to prevent an error being // thrown for two primary cases: @@ -296,7 +308,8 @@ const transmuxAndNotify = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }) => { const fmp4Tracks = segment.map && segment.map.tracks || {}; const isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); @@ -350,9 +363,33 @@ const transmuxAndNotify = ({ } }, onVideoSegmentTimingInfo: (videoSegmentTimingInfo) => { + const timingInfo = { + pts: { + start: videoSegmentTimingInfo.start.presentation, + end: videoSegmentTimingInfo.end.presentation + }, + dts: { + start: videoSegmentTimingInfo.start.decode, + end: videoSegmentTimingInfo.end.decode + } + }; + + triggerSegmentEventFn({ type: 'segmenttransmuxingtiminginfoavailable', segment, timingInfo }); videoSegmentTimingInfoFn(videoSegmentTimingInfo); }, onAudioSegmentTimingInfo: (audioSegmentTimingInfo) => { + const timingInfo = { + pts: { + start: audioSegmentTimingInfo.start.pts, + end: audioSegmentTimingInfo.end.pts + }, + dts: { + start: audioSegmentTimingInfo.start.dts, + end: audioSegmentTimingInfo.end.dts + } + }; + + triggerSegmentEventFn({ type: 'segmenttransmuxingtiminginfoavailable', segment, timingInfo }); audioSegmentTimingInfoFn(audioSegmentTimingInfo); }, onId3: (id3Frames, dispatchType) => { @@ -366,13 +403,16 @@ const transmuxAndNotify = ({ endedTimelineFn(); }, onTransmuxerLog, - onDone: (result) => { + onDone: (result, error) => { if (!doneFn) { return; } result.type = result.type === 'combined' ? 'video' : result.type; - doneFn(null, segment, result); - } + triggerSegmentEventFn({ type: 'segmenttransmuxingcomplete', segment }); + doneFn(error, segment, result); + }, + segment, + triggerSegmentEventFn }); // In the transmuxer, we don't yet have the ability to extract a "proper" start time. @@ -415,7 +455,8 @@ const handleSegmentBytes = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }) => { let bytesAsUint8Array = new Uint8Array(bytes); @@ -565,11 +606,12 @@ const handleSegmentBytes = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }); }; -const decrypt = function({id, key, encryptedBytes, decryptionWorker}, callback) { +const decrypt = function({id, key, encryptedBytes, decryptionWorker, segment, doneFn}, callback) { const decryptionHandler = (event) => { if (event.data.source === id) { decryptionWorker.removeEventListener('message', decryptionHandler); @@ -583,8 +625,25 @@ const decrypt = function({id, key, encryptedBytes, decryptionWorker}, callback) } }; - decryptionWorker.addEventListener('message', decryptionHandler); + decryptionWorker.onerror = () => { + const message = 'An error occurred in the decryption worker'; + const segmentInfo = segmentInfoPayload({segment}); + const decryptError = { + message, + metadata: { + error: new Error(message), + errorType: videojs.Error.StreamingFailedToDecryptSegment, + segmentInfo, + keyInfo: { + uri: segment.key.resolvedUri || segment.map.key.resolvedUri + } + } + }; + doneFn(decryptError, segment); + }; + + decryptionWorker.addEventListener('message', decryptionHandler); let keyBytes; if (key.bytes.slice) { @@ -642,15 +701,20 @@ const decryptSegment = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }) => { + triggerSegmentEventFn({ type: 'segmentdecryptionstart' }); decrypt({ id: segment.requestId, key: segment.key, encryptedBytes: segment.encryptedBytes, - decryptionWorker + decryptionWorker, + segment, + doneFn }, (decryptedBytes) => { segment.bytes = decryptedBytes; + triggerSegmentEventFn({ type: 'segmentdecryptioncomplete', segment }); handleSegmentBytes({ segment, @@ -665,7 +729,8 @@ const decryptSegment = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }); }); }; @@ -712,7 +777,8 @@ const waitForCompletion = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }) => { let count = 0; let didError = false; @@ -759,7 +825,8 @@ const waitForCompletion = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }); } // Otherwise, everything is ready just continue @@ -776,24 +843,30 @@ const waitForCompletion = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }); }; // Keep track of when *all* of the requests have completed segment.endOfAllRequests = Date.now(); if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) { - return decrypt({ - decryptionWorker, + triggerSegmentEventFn({ type: 'segmentdecryptionstart', segment }); + // add -init to the "id" to differentiate between segment + // and init segment decryption, just in case they happen + // at the same time at some point in the future. + segment.requestId += '-init'; + return decrypt({ decryptionWorker, // add -init to the "id" to differentiate between segment // and init segment decryption, just in case they happen // at the same time at some point in the future. id: segment.requestId + '-init', encryptedBytes: segment.map.encryptedBytes, - key: segment.map.key - }, (decryptedBytes) => { + key: segment.map.key, + segment, + doneFn }, (decryptedBytes) => { segment.map.bytes = decryptedBytes; - + triggerSegmentEventFn({ type: 'segmentdecryptioncomplete', segment }); parseInitSegment(segment, (parseError) => { if (parseError) { abortAll(activeXhrs); @@ -966,7 +1039,8 @@ export const mediaSegmentRequest = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }) => { const activeXhrs = []; const finishProcessingFn = waitForCompletion({ @@ -982,7 +1056,8 @@ export const mediaSegmentRequest = ({ endedTimelineFn, dataFn, doneFn, - onTransmuxerLog + onTransmuxerLog, + triggerSegmentEventFn }); // optionally, request the decryption key @@ -997,7 +1072,10 @@ export const mediaSegmentRequest = ({ responseType: 'arraybuffer', requestType: 'segment-key' }); - const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn); + const keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn, triggerSegmentEventFn); + const keyInfo = { uri: segment.key.resolvedUri }; + + triggerSegmentEventFn({ type: 'segmentkeyloadstart', segment, keyInfo }); const keyXhr = xhr(keyRequestOptions, keyRequestCallback); activeXhrs.push(keyXhr); @@ -1013,7 +1091,10 @@ export const mediaSegmentRequest = ({ responseType: 'arraybuffer', requestType: 'segment-key' }); - const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn); + const mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn, triggerSegmentEventFn); + const keyInfo = { uri: segment.map.key.resolvedUri }; + + triggerSegmentEventFn({ type: 'segmentkeyloadstart', segment, keyInfo }); const mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback); activeXhrs.push(mapKeyXhr); @@ -1024,7 +1105,9 @@ export const mediaSegmentRequest = ({ headers: segmentXhrHeaders(segment.map), requestType: 'segment-media-initialization' }); - const initSegmentRequestCallback = handleInitSegmentResponse({segment, finishProcessingFn}); + const initSegmentRequestCallback = handleInitSegmentResponse({segment, finishProcessingFn, triggerSegmentEventFn}); + + triggerSegmentEventFn({ type: 'segmentloadstart', segment }); const initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); activeXhrs.push(initSegmentXhr); @@ -1040,8 +1123,11 @@ export const mediaSegmentRequest = ({ const segmentRequestCallback = handleSegmentResponse({ segment, finishProcessingFn, - responseType: segmentRequestOptions.responseType + responseType: segmentRequestOptions.responseType, + triggerSegmentEventFn }); + + triggerSegmentEventFn({ type: 'segmentloadstart', segment }); const segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); segmentXhr.addEventListener( diff --git a/src/playback-watcher.js b/src/playback-watcher.js index 509959efe..9db9c95cd 100644 --- a/src/playback-watcher.js +++ b/src/playback-watcher.js @@ -11,6 +11,8 @@ import window from 'global/window'; import * as Ranges from './ranges'; import logger from './util/logger'; +import { createTimeRanges } from './util/vjs-compat'; +import videojs from 'video.js'; // Set of events that reset the playback-watcher time check logic and clear the timeout const timerCancelEvents = [ @@ -24,7 +26,7 @@ const timerCancelEvents = [ /** * @class PlaybackWatcher */ -export default class PlaybackWatcher { +export default class PlaybackWatcher extends videojs.EventTarget { /** * Represents an PlaybackWatcher object. * @@ -32,12 +34,14 @@ export default class PlaybackWatcher { * @param {Object} options an object that includes the tech and settings */ constructor(options) { + super(); this.playlistController_ = options.playlistController; this.tech_ = options.tech; this.seekable = options.seekable; this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta; this.media = options.media; + this.playedRanges_ = []; this.consecutiveUpdates = 0; this.lastRecordedTime = null; @@ -205,6 +209,11 @@ export default class PlaybackWatcher { // the buffered value for this loader changed // appends are working if (isBufferedDifferent) { + const metadata = { + bufferedRanges: buffered + }; + + pc.trigger({ type: 'bufferedrangeschanged', metadata }); this.resetSegmentDownloads_(type); return; } @@ -271,6 +280,12 @@ export default class PlaybackWatcher { } else if (currentTime === this.lastRecordedTime) { this.consecutiveUpdates++; } else { + this.playedRanges_.push(createTimeRanges([this.lastRecordedTime, currentTime])); + const metadata = { + playedRanges: this.playedRanges_ + }; + + this.playlistController_.trigger({ type: 'playedrangeschanged', metadata }); this.consecutiveUpdates = 0; this.lastRecordedTime = currentTime; } @@ -589,7 +604,14 @@ export default class PlaybackWatcher { // only seek if we still have not played this.tech_.setCurrentTime(nextRange.start(0) + Ranges.TIME_FUDGE_FACTOR); + const metadata = { + gapInfo: { + from: currentTime, + to: nextRange.start(0) + } + }; + this.playlistController_.trigger({type: 'gapjumped', metadata}); this.tech_.trigger({type: 'usage', name: 'vhs-gap-skip'}); } diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 4af9996e0..1a32bb9b3 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -185,6 +185,7 @@ export class PlaylistController extends videojs.EventTarget { this.withCredentials = withCredentials; this.tech_ = tech; this.vhs_ = tech.vhs; + this.player_ = options.player_; this.sourceType_ = sourceType; this.useCueTags_ = useCueTags; this.playlistExclusionDuration = playlistExclusionDuration; @@ -409,6 +410,17 @@ export class PlaylistController extends videojs.EventTarget { if (oldId && oldId !== newId) { this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`); + const metadata = { + renditionInfo: { + id: newId, + bandwidth: playlist.attributes.BANDWIDTH, + resolution: playlist.attributes.RESOLUTION, + codecs: playlist.attributes.CODECS + }, + cause + }; + + this.trigger({type: 'renditionselected', metadata}); this.tech_.trigger({type: 'usage', name: `vhs-rendition-change-${cause}`}); } this.mainPlaylistLoader_.media(playlist, delay); @@ -732,6 +744,26 @@ export class PlaylistController extends videojs.EventTarget { this.mainPlaylistLoader_.on('renditionenabled', () => { this.tech_.trigger({type: 'usage', name: 'vhs-rendition-enabled'}); }); + + const playlistLoaderEvents = [ + 'manifestrequeststart', + 'manifestrequestcomplete', + 'manifestparsestart', + 'manifestparsecomplete', + 'playlistrequeststart', + 'playlistrequestcomplete', + 'playlistparsestart', + 'playlistparsecomplete', + 'renditiondisabled', + 'renditionenabled' + ]; + + playlistLoaderEvents.forEach((eventName) => { + this.mainPlaylistLoader_.on(eventName, (metadata) => { + // trigger directly on the player to ensure early events are fired. + this.player_.trigger({...metadata}); + }); + }); } /** @@ -950,6 +982,40 @@ export class PlaylistController extends videojs.EventTarget { this.logger_('audioSegmentLoader ended'); this.onEndOfStream(); }); + + const segmentLoaderEvents = [ + 'segmentselected', + 'segmentloadstart', + 'segmentloaded', + 'segmentkeyloadstart', + 'segmentkeyloadcomplete', + 'segmentdecryptionstart', + 'segmentdecryptioncomplete', + 'segmenttransmuxingstart', + 'segmenttransmuxingcomplete', + 'segmenttransmuxingtrackinfoavailable', + 'segmenttransmuxingtiminginfoavailable', + 'segmentappendstart', + 'appendsdone', + 'bandwidthupdated', + 'timelinechange', + 'codecschange' + ]; + + segmentLoaderEvents.forEach((eventName) => { + this.mainSegmentLoader_.on(eventName, (metadata) => { + this.player_.trigger({...metadata}); + }); + + this.audioSegmentLoader_.on(eventName, (metadata) => { + this.player_.trigger({...metadata}); + }); + + this.subtitleSegmentLoader_.on(eventName, (metadata) => { + this.player_.trigger({...metadata}); + }); + }); + } mediaSecondsLoaded_() { @@ -1633,7 +1699,11 @@ export class PlaylistController extends videojs.EventTarget { } this.logger_(`seekable updated [${Ranges.printableRange(this.seekable_)}]`); + const metadata = { + seekableRanges: this.seekable_ + }; + this.trigger({type: 'seekablerangeschanged', metadata}); this.tech_.trigger('seekablechanged'); } @@ -1781,6 +1851,7 @@ export class PlaylistController extends videojs.EventTarget { return true; } + // find from and to for codec switch event getCodecsOrExclude_() { const media = { main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {}, @@ -2173,6 +2244,18 @@ export class PlaylistController extends videojs.EventTarget { */ attachContentSteeringListeners_() { this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this)); + const contentSteeringEvents = [ + 'contentsteeringloadstart', + 'contentsteeringloadcomplete', + 'contentsteeringparsed' + ]; + + contentSteeringEvents.forEach((eventName) => { + this.contentSteeringController_.on(eventName, (metadata) => { + this.trigger({...metadata}); + }); + }); + if (this.sourceType_ === 'dash') { this.mainPlaylistLoader_.on('loadedplaylist', () => { const main = this.main(); diff --git a/src/playlist-loader.js b/src/playlist-loader.js index 31094ae0c..febdeb0f2 100644 --- a/src/playlist-loader.js +++ b/src/playlist-loader.js @@ -21,6 +21,7 @@ import { import {getKnownPartCount} from './playlist.js'; import {merge} from './util/vjs-compat'; import DateRangesStorage from './util/date-ranges'; +import { getStreamingNetworkErrorMetadata } from './error-codes.js'; const { EventTarget } = videojs; @@ -371,6 +372,34 @@ export const refreshDelay = (media, update) => { return (media.partTargetDuration || media.targetDuration || 10) * 500; }; +const playlistMetadataPayload = (playlists, type, isLive) => { + if (!playlists) { + return; + } + const renditions = []; + + playlists.forEach((playlist) => { + // we need attributes to populate rendition data. + if (!playlist.attributes) { + return; + } + const { BANDWIDTH, RESOLUTION, CODECS } = playlist.attributes; + + renditions.push({ + id: playlist.id, + bandwidth: BANDWIDTH, + resolution: RESOLUTION, + codecs: CODECS + }); + }); + + return { + type, + isLive, + renditions + }; +}; + /** * Load a playlist from a remote location * @@ -486,23 +515,29 @@ export default class PlaylistLoader extends EventTarget { message: `HLS playlist request error at URL: ${uri}.`, responseText: xhr.responseText, code: (xhr.status >= 500) ? 4 : 2, - metadata: { - errorType: videojs.Error.HlsPlaylistRequestError - } + metadata: getStreamingNetworkErrorMetadata({ requestType: xhr.requestType, request: xhr, error: xhr.error }) }; this.trigger('error'); } parseManifest_({url, manifestString}) { - return parseManifest({ - onwarn: ({message}) => this.logger_(`m3u8-parser warn for ${url}: ${message}`), - oninfo: ({message}) => this.logger_(`m3u8-parser info for ${url}: ${message}`), - manifestString, - customTagParsers: this.customTagParsers, - customTagMappers: this.customTagMappers, - llhls: this.llhls - }); + try { + return parseManifest({ + onwarn: ({message}) => this.logger_(`m3u8-parser warn for ${url}: ${message}`), + oninfo: ({message}) => this.logger_(`m3u8-parser info for ${url}: ${message}`), + manifestString, + customTagParsers: this.customTagParsers, + customTagMappers: this.customTagMappers, + llhls: this.llhls + }); + } catch (error) { + this.error = error; + this.error.metadata = { + errorType: videojs.Error.StreamingHlsPlaylistParserError, + error + }; + } } /** @@ -522,6 +557,14 @@ export default class PlaylistLoader extends EventTarget { this.request = null; this.state = 'HAVE_METADATA'; + const metadata = { + playlistInfo: { + type: 'media', + uri: url + } + }; + + this.trigger({type: 'playlistparsestart', metadata }); const playlist = playlistObject || this.parseManifest_({ url, manifestString: playlistString @@ -550,7 +593,8 @@ export default class PlaylistLoader extends EventTarget { } this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update)); - + metadata.parsedPlaylist = playlistMetadataPayload(this.main.playlists, metadata.playlistInfo.type, !this.media_.endList); + this.trigger({ type: 'playlistparsecomplete', metadata }); this.trigger('loadedplaylist'); } @@ -690,6 +734,14 @@ export default class PlaylistLoader extends EventTarget { } this.pendingMedia_ = playlist; + const metadata = { + playlistInfo: { + type: 'media', + uri: playlist.uri + } + }; + + this.trigger({ type: 'playlistrequeststart', metadata }); this.request = this.vhs_.xhr({ uri: playlist.resolvedUri, @@ -709,6 +761,8 @@ export default class PlaylistLoader extends EventTarget { return this.playlistRequestError(this.request, playlist, startingState); } + this.trigger({ type: 'playlistrequestcomplete', metadata }); + this.haveMetadata({ playlistString: req.responseText, url: playlist.uri, @@ -836,7 +890,14 @@ export default class PlaylistLoader extends EventTarget { }, 0); return; } + const metadata = { + playlistInfo: { + type: 'multivariant', + uri: this.src + } + }; + this.trigger({ type: 'playlistrequeststart', metadata }); // request the specified URL this.request = this.vhs_.xhr({ uri: this.src, @@ -858,23 +919,27 @@ export default class PlaylistLoader extends EventTarget { responseText: req.responseText, // MEDIA_ERR_NETWORK code: 2, - metadata: { - errorType: videojs.Error.HlsPlaylistRequestError - } + metadata: getStreamingNetworkErrorMetadata({ requestType: req.requestType, request: req, error }) }; if (this.state === 'HAVE_NOTHING') { this.started = false; } return this.trigger('error'); } + this.trigger({ type: 'playlistrequestcomplete', metadata }); this.src = resolveManifestRedirect(this.src, req); + this.trigger({ type: 'playlistparsestart', metadata }); const manifest = this.parseManifest_({ manifestString: req.responseText, url: this.src }); + // we haven't loaded any variant playlists here so we default to false for isLive. + metadata.parsedPlaylist = playlistMetadataPayload(manifest.playlists, metadata.playlistInfo.type, false); + this.trigger({ type: 'playlistparsecomplete', metadata }); + this.setupInitialPlaylist(manifest); }); } diff --git a/src/rendition-mixin.js b/src/rendition-mixin.js index a889c9c65..8a2e35a60 100644 --- a/src/rendition-mixin.js +++ b/src/rendition-mixin.js @@ -27,14 +27,23 @@ const enableFunction = (loader, playlistID, changePlaylistFn) => (enable) => { } else { playlist.disabled = true; } + const metadata = { + renditionInfo: { + id: playlistID, + bandwidth: playlist.attributes.BANDWIDTH, + resolution: playlist.attributes.RESOLUTION, + codecs: playlist.attributes.CODECS + }, + cause: 'fast-quality' + }; if (enable !== currentlyEnabled && !incompatible) { // Ensure the outside world knows about our changes changePlaylistFn(playlist); if (enable) { - loader.trigger('renditionenabled'); + loader.trigger({ type: 'renditionenabled', metadata}); } else { - loader.trigger('renditiondisabled'); + loader.trigger({ type: 'renditiondisabled', metadata}); } } return enable; diff --git a/src/segment-loader.js b/src/segment-loader.js index c761d157e..fe1b201ca 100644 --- a/src/segment-loader.js +++ b/src/segment-loader.js @@ -510,6 +510,29 @@ export const getTroublesomeSegmentDurationMessage = (segmentInfo, sourceType) => return null; }; +/** + * + * @param {Object} options type of segment loader and segment either segmentInfo or simple segment + * @return a segmentInfo payload for events or errors. + */ +export const segmentInfoPayload = ({type, segment}) => { + if (!segment) { + return; + } + const isEncrypted = Boolean(segment.key || segment.map && segment.map.ke); + const isMediaInitialization = Boolean(segment.map && !segment.map.bytes); + const start = segment.startOfSegment === undefined ? segment.start : segment.startOfSegment; + + return { + type: type || segment.type, + uri: segment.resolvedUri || segment.uri, + start, + duration: segment.duration, + isEncrypted, + isMediaInitialization + }; +}; + /** * An object that manages segment loading and appending. * @@ -652,6 +675,9 @@ export default class SegmentLoader extends videojs.EventTarget { } }); + this.sourceUpdater_.on('codecschange', (metadata) => { + this.trigger({type: 'codecschange', ...metadata}); + }); // Only the main loader needs to listen for pending timeline changes, as the main // loader should wait for audio to be ready to change its timeline so that both main // and audio timelines change together. For more details, see the @@ -667,7 +693,8 @@ export default class SegmentLoader extends videojs.EventTarget { // since its loads follow main, needs to listen on timeline changes. For more details, // see the shouldWaitForTimelineChange function. if (this.loaderType_ === 'audio') { - this.timelineChangeController_.on('timelinechange', () => { + this.timelineChangeController_.on('timelinechange', (metadata) => { + this.trigger({type: 'timelinechange', ...metadata }); if (this.hasEnoughInfoToLoad_()) { this.processLoadQueue_(); } @@ -1385,6 +1412,12 @@ bufferedEnd: ${lastBufferedEnd(this.buffered_())} return; } + const metadata = { + segmentInfo: segmentInfoPayload({type: this.loaderType_, segment: segmentInfo}) + }; + + this.trigger({ type: 'segmentselected', metadata }); + if (typeof segmentInfo.timestampOffset === 'number') { this.isPendingTimestampOffset_ = false; this.timelineChangeController_.pendingTimelineChange({ @@ -1496,6 +1529,15 @@ Fetch At Buffer: ${this.fetchAtBuffer_} const syncInfo = this.getSyncInfoFromMediaSequenceSync_(targetTime); if (!syncInfo) { + const message = 'No sync info found while using media sequence sync'; + + this.error({ + message, + metadata: { + errorType: videojs.Error.StreamingFailedToSelectNextSegment, + error: new Error(message) + } + }); this.logger_('chooseNextRequest_ - no sync info found using media sequence sync'); // no match return null; @@ -1843,6 +1885,16 @@ Fetch At Buffer: ${this.fetchAtBuffer_} } handleTrackInfo_(simpleSegment, trackInfo) { + const { hasAudio, hasVideo } = trackInfo; + const metadata = { + segmentInfo: segmentInfoPayload({type: this.loaderType_, segment: simpleSegment}), + trackInfo: { + hasAudio, + hasVideo + } + }; + + this.trigger({type: 'segmenttransmuxingtrackinfoavailable', metadata}); this.earlyAbortWhenNeeded_(simpleSegment.stats); if (this.checkForAbort_(simpleSegment.requestId)) { @@ -2370,10 +2422,7 @@ Fetch At Buffer: ${this.fetchAtBuffer_} `video buffer: ${timeRangesToArray(videoBuffered).join(', ')}, `); this.error({ message: 'Quota exceeded error with append of a single segment of content', - excludeUntil: Infinity, - metadata: { - errorType: videojs.Error.SegmentExceedsSourceBufferQuota - } + excludeUntil: Infinity }); this.trigger('error'); return; @@ -2438,7 +2487,7 @@ Fetch At Buffer: ${this.fetchAtBuffer_} message: `${type} append of ${bytes.length}b failed for segment ` + `#${segmentInfo.mediaIndex} in playlist ${segmentInfo.playlist.id}`, metadata: { - errorType: videojs.Error.SegmentAppendError + errorType: videojs.Error.StreamingFailedToAppendSegment } }); this.trigger('appenderror'); @@ -2464,7 +2513,11 @@ Fetch At Buffer: ${this.fetchAtBuffer_} segments }); } + const metadata = { + segmentInfo: segmentInfoPayload({type: this.loaderType_, segment: segmentInfo}) + }; + this.trigger({ type: 'segmentappendstart', metadata }); this.sourceUpdater_.appendBuffer( {segmentInfo, type, bytes}, this.handleAppendError_.bind(this, {segmentInfo, type, bytes}) @@ -2628,11 +2681,27 @@ ${segmentInfoString(segmentInfo)}`); this.logger_('received endedtimeline callback'); }, id3Fn: this.handleId3_.bind(this), - dataFn: this.handleData_.bind(this), doneFn: this.segmentRequestFinished_.bind(this), onTransmuxerLog: ({message, level, stream}) => { this.logger_(`${segmentInfoString(segmentInfo)} logged from transmuxer stream ${stream} as a ${level}: ${message}`); + }, + triggerSegmentEventFn: ({ type, segment, keyInfo, trackInfo, timingInfo }) => { + const segInfo = segmentInfoPayload({segment}); + const metadata = { segmentInfo: segInfo }; + // add other properties if necessary. + + if (keyInfo) { + metadata.keyInfo = keyInfo; + } + if (trackInfo) { + metadata.trackInfo = trackInfo; + } + if (timingInfo) { + metadata.timingInfo = timingInfo; + } + + this.trigger({ type, metadata }); } }); } @@ -2675,6 +2744,8 @@ ${segmentInfoString(segmentInfo)}`); createSimplifiedSegmentObj_(segmentInfo) { const segment = segmentInfo.segment; const part = segmentInfo.part; + const isEncrypted = segmentInfo.segment.key || segmentInfo.segment.map && segmentInfo.segment.map.key; + const isMediaInitialization = segmentInfo.segment.map && !segmentInfo.segment.map.bytes; const simpleSegment = { resolvedUri: part ? part.resolvedUri : segment.resolvedUri, @@ -2683,7 +2754,12 @@ ${segmentInfoString(segmentInfo)}`); transmuxer: segmentInfo.transmuxer, audioAppendStart: segmentInfo.audioAppendStart, gopsToAlignWith: segmentInfo.gopsToAlignWith, - part: segmentInfo.part + part: segmentInfo.part, + type: this.loaderType_, + start: segmentInfo.startOfSegment, + duration: segmentInfo.duration, + isEncrypted, + isMediaInitialization }; const previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1]; @@ -2746,6 +2822,15 @@ ${segmentInfoString(segmentInfo)}`); ` is less than the min to record ${MIN_SEGMENT_DURATION_TO_SAVE_STATS}`); return; } + const metadata = { + bandwidthInfo: { + from: this.bandwidth, + to: stats.bandwidth + } + }; + + // player event with payload + this.trigger({type: 'bandwidthupdated', metadata}); this.bandwidth = stats.bandwidth; this.roundTrip = stats.roundTripTime; @@ -2923,10 +3008,7 @@ ${segmentInfoString(segmentInfo)}`); if (!trackInfo) { this.error({ message: 'No starting media returned, likely due to an unsupported media format.', - playlistExclusionDuration: Infinity, - metadata: { - errorType: videojs.Error.SegmentUnsupportedMediaFormat - } + playlistExclusionDuration: Infinity }); this.trigger('error'); return; @@ -3007,10 +3089,7 @@ ${segmentInfoString(segmentInfo)}`); if (illegalMediaSwitchError) { this.error({ message: illegalMediaSwitchError, - playlistExclusionDuration: Infinity, - metadata: { - errorType: videojs.Error.SegmentSwitchError - } + playlistExclusionDuration: Infinity }); this.trigger('error'); return true; @@ -3108,7 +3187,11 @@ ${segmentInfoString(segmentInfo)}`); handleAppendsDone_() { // appendsdone can cause an abort if (this.pendingSegment_) { - this.trigger('appendsdone'); + const metadata = { + segmentInfo: segmentInfoPayload({type: this.loaderType_, segment: this.pendingSegment_}) + }; + + this.trigger({ type: 'appendsdone', metadata}); } if (!this.pendingSegment_) { diff --git a/src/segment-transmuxer.js b/src/segment-transmuxer.js index 6d1bb789e..9db17e16f 100644 --- a/src/segment-transmuxer.js +++ b/src/segment-transmuxer.js @@ -1,4 +1,6 @@ import TransmuxWorker from 'worker!./transmuxer-worker.js'; +import videojs from 'video.js'; +import { segmentInfoPayload } from './segment-loader'; export const handleData_ = (event, transmuxedData, callback) => { const { @@ -82,7 +84,9 @@ export const processTransmux = (options) => { onDone, onEndedTimeline, onTransmuxerLog, - isEndOfTimeline + isEndOfTimeline, + segment, + triggerSegmentEventFn } = options; const transmuxedData = { buffer: [] @@ -153,8 +157,20 @@ export const processTransmux = (options) => { dequeue(transmuxer); /* eslint-enable */ }; + const handleError = () => { + const error = { + message: 'Received an error message from the transmuxer worker', + metadata: { + errorType: videojs.Error.StreamingFailedToTransmuxSegment, + segmentInfo: segmentInfoPayload({segment}) + } + }; + + onDone(null, error); + }; transmuxer.onmessage = handleMessage; + transmuxer.onerror = handleError; if (audioAppendStart) { transmuxer.postMessage({ @@ -182,6 +198,7 @@ export const processTransmux = (options) => { const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer; const byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset; + triggerSegmentEventFn({ type: 'segmenttransmuxingstart', segment }); transmuxer.postMessage( { action: 'push', diff --git a/src/source-updater.js b/src/source-updater.js index 48c1d889c..853510701 100644 --- a/src/source-updater.js +++ b/src/source-updater.js @@ -284,14 +284,26 @@ const actions = { if (oldCodecBase === newCodecBase) { return; } + const metadata = { + codecsChangeInfo: { + from: oldCodec, + to: codec + } + }; - sourceUpdater.logger_(`changing ${type}Buffer codec from ${sourceUpdater.codecs[type]} to ${codec}`); + sourceUpdater.trigger({ type: 'codecschange', metadata }); + sourceUpdater.logger_(`changing ${type}Buffer codec from ${oldCodec} to ${codec}`); // check if change to the provided type is supported try { sourceBuffer.changeType(mime); sourceUpdater.codecs[type] = codec; } catch (e) { + metadata.errorType = videojs.Error.StreamingCodecsChangeError; + metadata.error = e; + e.metadata = metadata; + sourceUpdater.error_ = e; + sourceUpdater.trigger('error'); videojs.log.warn(`Failed to changeType on ${type}Buffer`, e); } } diff --git a/src/timeline-change-controller.js b/src/timeline-change-controller.js index 90cc794cd..109c88b19 100644 --- a/src/timeline-change-controller.js +++ b/src/timeline-change-controller.js @@ -34,7 +34,14 @@ export default class TimelineChangeController extends videojs.EventTarget { if (typeof from === 'number' && typeof to === 'number') { this.lastTimelineChanges_[type] = { type, from, to }; delete this.pendingTimelineChanges_[type]; - this.trigger('timelinechange'); + const metadata = { + timelineChangeInfo: { + from, + to + } + }; + + this.trigger({ type: 'timelinechange', metadata }); } return this.lastTimelineChanges_[type]; } diff --git a/src/util/container-request.js b/src/util/container-request.js index 8bd571936..affd31b5e 100644 --- a/src/util/container-request.js +++ b/src/util/container-request.js @@ -2,6 +2,7 @@ import {getId3Offset} from '@videojs/vhs-utils/es/id3-helpers'; import {detectContainerForBytes} from '@videojs/vhs-utils/es/containers'; import {stringToBytes, concatTypedArrays} from '@videojs/vhs-utils/es/byte-helpers'; import {callbackWrapper} from '../xhr'; +import { getStreamingNetworkErrorMetadata } from '../error-codes'; // calls back if the request is readyState DONE // which will only happen if the request is complete. @@ -12,7 +13,7 @@ const callbackOnCompleted = (request, cb) => { return; }; -const containerRequest = (uri, xhr, cb) => { +const containerRequest = (uri, xhr, cb, requestType) => { let bytes = []; let id3Offset; let finished = false; @@ -28,6 +29,7 @@ const containerRequest = (uri, xhr, cb) => { return; } if (error) { + error.metadata = getStreamingNetworkErrorMetadata({ requestType, request, error }); return endRequestAndCallback(error, request, '', bytes); } diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index df14c4ea2..2ad41e134 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -795,6 +795,8 @@ class VhsHandler extends Component { this.options_.seekTo = (time) => { this.tech_.setCurrentTime(time); }; + // pass player to allow for player level eventing on construction. + this.options_.player_ = this.player_; this.playlistController_ = new PlaylistController(this.options_); @@ -812,6 +814,7 @@ class VhsHandler extends Component { this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions); + this.attachStreamingEventListeners_(); this.playlistController_.on('error', () => { const player = videojs.players[this.tech_.options_.playerId]; let error = this.playlistController_.error; @@ -1096,10 +1099,7 @@ class VhsHandler extends Component { this.logger_('error while creating EME key session', err); this.player_.error({ message: 'Failed to initialize media keys for EME', - code: 3, - metadata: { - errorType: videojs.Error.EMEKeySessionCreationError - } + code: 3 }); }); } @@ -1326,6 +1326,34 @@ class VhsHandler extends Component { // This allows hooks to be set before the source is set to vhs when handleSource is called. this.player_.trigger('xhr-hooks-ready'); } + + attachStreamingEventListeners_() { + const playlistControllerEvents = [ + 'seekablerangeschanged', + 'bufferedrangeschanged', + 'contentsteeringloadstart', + 'contentsteeringloadcomplete', + 'contentsteeringparsed' + ]; + + const playbackWatcher = [ + 'gapjumped', + 'playedrangeschanged' + ]; + + // re-emit streaming events and payloads on the player. + playlistControllerEvents.forEach((eventName) => { + this.playlistController_.on(eventName, (metadata) => { + this.player_.trigger({...metadata}); + }); + }); + + playbackWatcher.forEach((eventName) => { + this.playbackWatcher_.on(eventName, (metadata) => { + this.player_.trigger({...metadata}); + }); + }); + } } /** diff --git a/src/vtt-segment-loader.js b/src/vtt-segment-loader.js index 2af32fe30..c727088a0 100644 --- a/src/vtt-segment-loader.js +++ b/src/vtt-segment-loader.js @@ -313,10 +313,7 @@ export default class VTTSegmentLoader extends SegmentLoader { .then( () => this.segmentRequestFinished_(error, simpleSegment, result), () => this.stopForError({ - message: 'Error loading vtt.js', - metadata: { - errorType: videojs.Error.VttLoadError - } + message: 'Error loading vtt.js' }) ); return; @@ -330,7 +327,8 @@ export default class VTTSegmentLoader extends SegmentLoader { this.stopForError({ message: e.message, metadata: { - errorType: videojs.Error.VttCueParsingError + errorType: videojs.Error.StreamingVttParserError, + error: e } }); return; diff --git a/src/xhr.js b/src/xhr.js index aebe6fdad..0b8d300e3 100644 --- a/src/xhr.js +++ b/src/xhr.js @@ -132,6 +132,7 @@ const xhrFactory = function() { return originalAbort.apply(request, arguments); }; request.uri = options.uri; + request.requestType = options.requestType; request.requestTime = Date.now(); return request; }; diff --git a/test/dash-playlist-loader.test.js b/test/dash-playlist-loader.test.js index aa32d2755..0b91545a9 100644 --- a/test/dash-playlist-loader.test.js +++ b/test/dash-playlist-loader.test.js @@ -723,10 +723,6 @@ QUnit.test('addSidxSegments_: adds/triggers error on invalid container', functio code: 2, internal: true, message: 'Unsupported unknown container type for sidx segment at URL: sidx.mp4', - metadata: { - errorType: 'unsupported-sidx-container-error', - sidxContainer: 'unknown' - }, playlist, response: '', status: 200 @@ -2026,7 +2022,13 @@ QUnit.test('addSidxSegments_: errors if request for sidx fails', function(assert { status: 500, message: 'DASH request error at URL: sidx.mp4', - metadata: undefined, + metadata: { + errorType: 'networkbadstatus', + headers: {}, + requestType: 'dash-sidx', + status: 500, + uri: 'sidx.mp4' + }, response: '', code: 2 }, diff --git a/test/media-segment-request.test.js b/test/media-segment-request.test.js index bfb89843e..6943d3e35 100644 --- a/test/media-segment-request.test.js +++ b/test/media-segment-request.test.js @@ -97,7 +97,8 @@ QUnit.module('Media Segment Request - make it to transmuxer', { xhrOptions: this.xhrOptions, decryptionWorker: this.mockDecrypter, segment: {}, - onTransmuxerLog: () => {} + onTransmuxerLog: () => {}, + triggerSegmentEventFn: () => {} }; [ @@ -303,7 +304,8 @@ QUnit.test('cancels outstanding segment request on abort', function(assert) { segment: { resolvedUri: '0-test.ts' }, abortFn: () => aborts++, progressFn: this.noop, - doneFn: this.noop + doneFn: this.noop, + triggerSegmentEventFn: this.noop }); // Simulate Firefox's handling of aborted segments - @@ -335,7 +337,8 @@ QUnit.test('cancels outstanding key requests on abort', function(assert) { }, abortFn: () => aborts++, progressFn: this.noop, - doneFn: this.noop + doneFn: this.noop, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -378,7 +381,8 @@ QUnit.test('cancels outstanding key requests on failure', function(assert) { assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'segment request failed'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -414,7 +418,8 @@ QUnit.test('cancels outstanding key requests on timeout', function(assert) { assert.equal(error.code, REQUEST_ERRORS.TIMEOUT, 'key request failed'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -453,7 +458,8 @@ QUnit.test( assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'request failed'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -474,6 +480,7 @@ QUnit.test( } ); +// TODO: come back to this QUnit.test('the key response is converted to the correct format', function(assert) { const done = assert.async(); const postMessage = this.mockDecrypter.postMessage; @@ -516,7 +523,8 @@ QUnit.test('the key response is converted to the correct format', function(asser // verify stats assert.equal(segmentData.stats.bytesReceived, 10, '10 bytes'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -563,7 +571,8 @@ QUnit.test('segment with key has bytes decrypted', function(assert) { // verify stats assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -613,7 +622,8 @@ QUnit.test('segment with key bytes does not request key again', function(assert) // verify stats assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes'); done(); - }}); + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 1, 'there is one request'); const segmentReq = this.requests.shift(); @@ -654,7 +664,8 @@ QUnit.test('key 404 calls back with error', function(assert) { assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'error code set to FAILURE'); assert.notOk(segmentData.bytes, 'no bytes in segment'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -697,7 +708,8 @@ QUnit.test('key 500 calls back with error', function(assert) { assert.equal(error.code, REQUEST_ERRORS.FAILURE, 'error code set to FAILURE'); assert.notOk(segmentData.bytes, 'no bytes in segment'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -775,7 +787,8 @@ QUnit.test('init segment with key has bytes decrypted', function(assert) { assert.ok(trackInfo, 'got track info'); assert.ok(Object.keys(timingInfo).length, 'got timing info'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 3, 'there are three requests'); @@ -881,7 +894,8 @@ QUnit.test('segment/init segment share a key and get decrypted', function(assert assert.ok(trackInfo, 'got track info'); assert.ok(Object.keys(timingInfo).length, 'got timing info'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 3, 'there are three requests'); @@ -987,7 +1001,8 @@ QUnit.test('segment/init segment different key and get decrypted', function(asse assert.ok(trackInfo, 'got track info'); assert.ok(Object.keys(timingInfo).length, 'got timing info'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 4, 'there are four requests'); @@ -1067,7 +1082,8 @@ QUnit.test('encrypted init segment parse error', function(assert) { // decrypted webm init segment caused this error. assert.ok(error, 'error for invalid init segment'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 4, 'there are four requests'); @@ -1133,7 +1149,8 @@ QUnit.test('encrypted init segment request failure', function(assert) { }); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 4, 'there are four requests'); @@ -1235,7 +1252,8 @@ QUnit.test('encrypted init segment with decrypted bytes not re-requested', funct assert.ok(trackInfo, 'got track info'); assert.ok(Object.keys(timingInfo).length, 'got timing info'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -1289,7 +1307,8 @@ QUnit.test( // verify stats assert.equal(segmentData.stats.bytesReceived, 8, '8 bytes'); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 3, 'there are three requests'); @@ -1404,7 +1423,8 @@ QUnit.test('non-TS segment will get parsed for captions', function(assert) { assert.ok(gotData, 'received data event'); transmuxer.off(); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -1451,7 +1471,8 @@ QUnit.test('webm segment calls back with error', function(assert) { 'receieved error message' ); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -1561,7 +1582,8 @@ QUnit.test('non-TS segment will get parsed for captions on next segment request assert.equal(gotData, 1, 'received data event'); transmuxer.off(); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -1692,7 +1714,8 @@ QUnit.test('can get emsg ID3 frames from fmp4 video segment', function(assert) { assert.equal(gotData, 1, 'received data event'); transmuxer.off(); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); @@ -1822,7 +1845,8 @@ QUnit.test('can get emsg ID3 frames from fmp4 audio segment', function(assert) { assert.equal(gotData, 1, 'received data event'); transmuxer.off(); done(); - } + }, + triggerSegmentEventFn: this.noop }); assert.equal(this.requests.length, 2, 'there are two requests'); diff --git a/test/playback.test.js b/test/playback.test.js index ef2835549..c91029f58 100644 --- a/test/playback.test.js +++ b/test/playback.test.js @@ -526,3 +526,536 @@ QUnit.test('hls manifest object', function(assert) { done(); }); }); + +// playlist event tests (hls specific) +QUnit.test('Advanced Bip Bop playlist events', function(assert) { + const done = assert.async(); + + this.player.defaultPlaybackRate(1); + + assert.expect(10); + const player = this.player; + let playlistrequeststartCallCount = 0; + let playlistparsestartCallCount = 0; + let playlistrequestcompleteCallCount = 0; + let playlistparsecompleteCallCount = 0; + + player.on('playlistrequeststart', (event) => { + const expectedMetadata = { + playlistInfo: { + type: 'multivariant', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8' + } + }; + + if (playlistrequeststartCallCount === 1) { + expectedMetadata.playlistInfo.type = 'media'; + expectedMetadata.playlistInfo.uri = 'gear1/prog_index.m3u8'; + } + assert.deepEqual(event.metadata, expectedMetadata, 'playlistrequeststart got expected metadata'); + playlistrequeststartCallCount++; + }); + + player.on('playlistrequestcomplete', (event) => { + const expectedMetadata = { + playlistInfo: { + type: 'multivariant', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8' + } + }; + + if (playlistrequestcompleteCallCount === 1) { + expectedMetadata.playlistInfo.type = 'media'; + expectedMetadata.playlistInfo.uri = 'gear1/prog_index.m3u8'; + } + assert.deepEqual(event.metadata, expectedMetadata, 'playlistrequestcomplete got expected metadata'); + playlistrequestcompleteCallCount++; + }); + + player.on('playlistparsestart', (event) => { + const expectedMetadata = { + playlistInfo: { + type: 'multivariant', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8' + } + }; + + if (playlistparsestartCallCount === 1) { + expectedMetadata.playlistInfo.type = 'media'; + expectedMetadata.playlistInfo.uri = 'gear1/prog_index.m3u8'; + } + assert.deepEqual(event.metadata, expectedMetadata, 'playlistparsestart got expected metadata'); + playlistparsestartCallCount++; + }); + + player.on('playlistparsecomplete', (event) => { + const expectedMetadata = { + playlistInfo: { + type: 'multivariant', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8' + }, + parsedPlaylist: { + isLive: false, + renditions: [ + { + bandwidth: 263851, + codecs: 'mp4a.40.2, avc1.4d400d', + id: undefined, + resolution: { + height: 234, + width: 416 + } + }, + { + bandwidth: 577610, + codecs: 'mp4a.40.2, avc1.4d401e', + id: undefined, + resolution: { + height: 360, + width: 640 + } + }, + { + bandwidth: 915905, + codecs: 'mp4a.40.2, avc1.4d401f', + id: undefined, + resolution: { + height: 540, + width: 960 + } + }, + { + bandwidth: 1030138, + codecs: 'mp4a.40.2, avc1.4d401f', + id: undefined, + resolution: { + height: 720, + width: 1280 + } + }, + { + bandwidth: 1924009, + codecs: 'mp4a.40.2, avc1.4d401f', + id: undefined, + resolution: { + height: 1080, + width: 1920 + } + }, + { + bandwidth: 41457, + codecs: 'mp4a.40.2', + id: undefined, + resolution: undefined + } + ], + type: 'multivariant' + } + }; + + if (playlistparsecompleteCallCount === 1) { + expectedMetadata.playlistInfo.type = 'media'; + expectedMetadata.playlistInfo.uri = 'gear1/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.type = 'media'; + expectedMetadata.parsedPlaylist.renditions[0].id = '0-gear1/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.renditions[1].id = '1-gear2/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.renditions[2].id = '2-gear3/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.renditions[3].id = '3-gear4/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.renditions[4].id = '4-gear5/prog_index.m3u8'; + expectedMetadata.parsedPlaylist.renditions[5].id = '5-gear0/prog_index.m3u8'; + } + assert.deepEqual(event.metadata, expectedMetadata, 'playlistparsecomplete got expected metadata'); + playlistparsecompleteCallCount++; + }); + + playFor(player, 0.1, function() { + assert.ok(true, 'played for at least two seconds'); + assert.equal(player.error(), null, 'has no player errors'); + + done(); + }); + + player.src({ + src: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8', + type: 'application/x-mpegURL' + }); +}); + +// manifest event tests (dash specific) +QUnit.test('Big Buck Bunny manifest events', function(assert) { + const done = assert.async(); + + this.player.defaultPlaybackRate(1); + + assert.expect(6); + const player = this.player; + + player.one('manifestrequeststart', (event) => { + const expectedMetadata = { + manifestInfo: { + uri: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'manifestrequeststart got expected metadata'); + }); + + player.one('manifestrequestcomplete', (event) => { + const expectedMetadata = { + manifestInfo: { + uri: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'manifestrequestcomplete got expected metadata'); + }); + + player.one('manifestparsestart', (event) => { + const expectedMetadata = { + manifestInfo: { + uri: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'manifestparsestart got expected metadata'); + }); + + player.one('manifestparsecomplete', (event) => { + const expectedMetadata = { + manifestInfo: { + uri: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd' + }, + parsedManifest: { + duration: 634.566, + isLive: false, + renditions: [ + { + bandwidth: 3134488, + codecs: 'avc1.64001f', + id: '0-placeholder-uri-0', + resolution: { + height: 576, + width: 1024 + } + }, + { + bandwidth: 4952892, + codecs: 'avc1.64001f', + id: '1-placeholder-uri-1', + resolution: { + height: 720, + width: 1280 + } + }, + { + bandwidth: 9914554, + codecs: 'avc1.640028', + id: '2-placeholder-uri-2', + resolution: { + height: 1080, + width: 1920 + } + }, + { + bandwidth: 254320, + codecs: 'avc1.64000d', + id: '3-placeholder-uri-3', + resolution: { + height: 180, + width: 320 + } + }, + { + bandwidth: 507246, + codecs: 'avc1.64000d', + id: '4-placeholder-uri-4', + resolution: { + height: 180, + width: 320 + } + }, + { + bandwidth: 759798, + codecs: 'avc1.640015', + id: '5-placeholder-uri-5', + resolution: { + height: 270, + width: 480 + } + }, + { + bandwidth: 1254758, + codecs: 'avc1.64001e', + id: '6-placeholder-uri-6', + resolution: { + height: 360, + width: 640 + } + }, + { + bandwidth: 1013310, + codecs: 'avc1.64001e', + id: '7-placeholder-uri-7', + resolution: { + height: 360, + width: 640 + } + }, + { + bandwidth: 1883700, + codecs: 'avc1.64001e', + id: '8-placeholder-uri-8', + resolution: { + height: 432, + width: 768 + } + }, + { + bandwidth: 14931538, + codecs: 'avc1.640033', + id: '9-placeholder-uri-9', + resolution: { + height: 2160, + width: 3840 + } + } + ] + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'manifestparsestart got expected metadata'); + }); + + playFor(player, 0.1, function() { + assert.ok(true, 'played for at least two seconds'); + assert.equal(player.error(), null, 'has no player errors'); + + done(); + }); + + player.src({ + src: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + type: 'application/dash+xml' + }); +}); + +// segment event tests +QUnit.test('Advanced Bip Bop segment events', function(assert) { + const done = assert.async(); + + this.player.defaultPlaybackRate(1); + + assert.expect(11); + const player = this.player; + + player.one('segmentselected', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmentselected got expected metadata'); + }); + + player.one('segmentloadstart', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmentloadstart got expected metadata'); + }); + + player.one('segmentloaded', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmentloaded got expected metadata'); + }); + + // ts specific + player.one('segmenttransmuxingstart', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmenttransmuxingstart got expected metadata'); + }); + + player.one('segmenttransmuxingcomplete', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmenttransmuxingcomplete got expected metadata'); + }); + + player.one('segmenttransmuxingtrackinfoavailable', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + }, + trackInfo: { + hasVideo: true, + hasAudio: true + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmenttransmuxingtrackinfoavailable got expected metadata'); + }); + + player.one('segmenttransmuxingtiminginfoavailable', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + }, + timingInfo: { + dts: { + end: 19.976644444444446, + start: 10 + }, + pts: { + end: 19.976644444444446, + start: 10 + } + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmenttransmuxingtiminginfoavailable got expected metadata'); + }); + + player.one('segmentappendstart', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'segmentappendstart got expected metadata'); + }); + + player.one('appendsdone', (event) => { + const expectedMetadata = { + segmentInfo: { + duration: 9.9766, + isEncrypted: false, + isMediaInitialization: false, + start: 0, + type: 'main', + uri: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/gear1/main.ts' + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'appendsdone got expected metadata'); + }); + + playFor(player, 0.1, function() { + assert.ok(true, 'played for at least two seconds'); + assert.equal(player.error(), null, 'has no player errors'); + + done(); + }); + + player.src({ + src: 'https://s3.amazonaws.com/_bc_dml/example-content/bipbop-advanced/bipbop_16x9_variant.m3u8', + type: 'application/x-mpegURL' + }); +}); + +QUnit.test('Big Buck Bunny streaming events', function(assert) { + const done = assert.async(); + + this.player.defaultPlaybackRate(1); + + assert.expect(7); + const player = this.player; + + player.one('bandwidthupdated', (event) => { + assert.notOk(isNaN(event.metadata.bandwidthInfo.from), 'manifestrequeststart got expected metadata'); + assert.notOk(isNaN(event.metadata.bandwidthInfo.to), 'manifestrequeststart got expected metadata'); + }); + + player.one('timelinechange', (event) => { + const expectedMetadata = { + timelineChangeInfo: { + from: -1, + to: 0 + } + }; + + assert.deepEqual(event.metadata, expectedMetadata, 'timelinechange got expected metadata'); + }); + + player.one('seekablerangeschanged', (event) => { + assert.ok(event.metadata.seekableRanges, 'manifestparsestart got expected metadata'); + }); + + player.one('bufferedrangeschanged', (event) => { + assert.ok(event.metadata.bufferedRanges, 'manifestparsestart got expected metadata'); + }); + + player.one('playedrangeschanged', (event) => { + assert.ok(event.metadata.playedRanges, 'manifestparsestart got expected metadata'); + }); + + playFor(player, 0.1, function() { + assert.ok(true, 'played for at least two seconds'); + assert.equal(player.error(), null, 'has no player errors'); + + done(); + }); + + player.src({ + src: 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd', + type: 'application/dash+xml' + }); +}); diff --git a/test/playlist-controller.test.js b/test/playlist-controller.test.js index 85a45edb1..ebded8451 100644 --- a/test/playlist-controller.test.js +++ b/test/playlist-controller.test.js @@ -271,7 +271,8 @@ QUnit.test('getAudioTrackPlaylists_ invalid audio groups', function(assert) { QUnit.test('throws error when given an empty URL', function(assert) { const options = { src: 'test', - tech: this.player.tech_ + tech: this.player.tech_, + player_: this.player }; const controller = new PlaylistController(options); @@ -323,7 +324,8 @@ QUnit.test('obeys auto preload option', function(assert) { QUnit.test('passes options to PlaylistLoader', function(assert) { const options = { src: 'test', - tech: this.player.tech_ + tech: this.player.tech_, + player_: this.player }; let controller = new PlaylistController(options); @@ -344,7 +346,8 @@ QUnit.test('addMetadataToTextTrack adds expected metadata to the metadataTrack', const options = { src: 'test.mpd', tech: this.player.tech_, - sourceType: 'dash' + sourceType: 'dash', + player_: this.player }; // Test messageData property manifest @@ -461,7 +464,8 @@ QUnit.test('addDateRangesToTextTrack adds expected metadata to the metadataTrack const options = { src: 'manifest/daterange.m3u8', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const controller = new PlaylistController(options); const dateRanges = [{ @@ -522,7 +526,8 @@ QUnit.test('creates appropriate PlaylistLoader for sourceType', function(assert) const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; let pc = new PlaylistController(options); @@ -555,7 +560,8 @@ QUnit.test('creates appropriate PlaylistLoader for sourceType', function(assert) QUnit.test('passes options to SegmentLoader', function(assert) { const options = { src: 'test', - tech: this.player.tech_ + tech: this.player.tech_, + player_: this.player }; let controller = new PlaylistController(options); @@ -755,7 +761,7 @@ QUnit.test('resets everything for a fast quality change', function(assert) { QUnit.test('loadVttJs should be passed to the vttSegmentLoader and resolved on vttjsloaded', function(assert) { const stub = sinon.stub(this.player.tech_, 'addWebVttScript_').callsFake(() => this.player.tech_.trigger('vttjsloaded')); - const controller = new PlaylistController({ src: 'test', tech: this.player.tech_}); + const controller = new PlaylistController({ src: 'test', tech: this.player.tech_, player_: this.player }); controller.subtitleSegmentLoader_.loadVttJs().then(() => { assert.equal(stub.callCount, 1, 'tech addWebVttScript called once'); @@ -764,7 +770,7 @@ QUnit.test('loadVttJs should be passed to the vttSegmentLoader and resolved on v QUnit.test('loadVttJs should be passed to the vttSegmentLoader and rejected on vttjserror', function(assert) { const stub = sinon.stub(this.player.tech_, 'addWebVttScript_').callsFake(() => this.player.tech_.trigger('vttjserror')); - const controller = new PlaylistController({ src: 'test', tech: this.player.tech_}); + const controller = new PlaylistController({ src: 'test', tech: this.player.tech_, player_: this.player }); controller.subtitleSegmentLoader_.loadVttJs().catch(() => { assert.equal(stub.callCount, 1, 'tech addWebVttScript called once'); @@ -4966,7 +4972,8 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ excludes non usable DASH playlists const options = { src: 'test', tech: this.player.tech_, - sourceType: 'dash' + sourceType: 'dash', + player_: this.player }; const pc = new PlaylistController(options); @@ -5011,7 +5018,8 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re includes non usable DASH playli const options = { src: 'test', tech: this.player.tech_, - sourceType: 'dash' + sourceType: 'dash', + player_: this.player }; const pc = new PlaylistController(options); @@ -5065,7 +5073,8 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all const options = { src: 'test', tech: this.player.tech_, - sourceType: 'dash' + sourceType: 'dash', + player_: this.player }; const pc = new PlaylistController(options); const origWarn = videojs.log.warn; @@ -6663,7 +6672,8 @@ QUnit.module('PlaylistController contentSteering', { this.controllerOptions = { src: 'test', tech: this.player.tech_, - sourceType: 'dash' + sourceType: 'dash', + player_: this.player }; this.csMainPlaylist = { @@ -6713,7 +6723,8 @@ QUnit.test('initContentSteeringController_ for HLS', function(assert) { const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7225,7 +7236,8 @@ QUnit.test('Pathway cloning - add a new pathway when the clone has not existed', const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7286,7 +7298,8 @@ QUnit.test('Pathway cloning - update the pathway when the BASE-ID does not match const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7360,7 +7373,8 @@ QUnit.test('Pathway cloning - update the pathway when there is a new param', fun const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7436,7 +7450,8 @@ QUnit.test('Pathway cloning - update the pathway when a param is missing', funct const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7509,7 +7524,8 @@ QUnit.test('Pathway cloning - delete the pathway when it is no longer in the ste const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); @@ -7570,7 +7586,8 @@ QUnit.test('Pathway cloning - do nothing when next and past clones are the same' const options = { src: 'test', tech: this.player.tech_, - sourceType: 'hls' + sourceType: 'hls', + player_: this.player }; const pc = new PlaylistController(options); diff --git a/test/segment-loader.test.js b/test/segment-loader.test.js index e424032cf..2dba67148 100644 --- a/test/segment-loader.test.js +++ b/test/segment-loader.test.js @@ -3147,7 +3147,7 @@ QUnit.module('SegmentLoader', function(hooks) { { message: 'video append of 2960b failed for segment #0 in playlist playlist.m3u8', metadata: { - errorType: 'segment-append-error' + errorType: 'streamingfailedtoappendsegment' } }, 'loader triggered and saved the appenderror' @@ -4818,10 +4818,7 @@ QUnit.module('SegmentLoader', function(hooks) { loader.error_, { message: 'Quota exceeded error with append of a single segment of content', - excludeUntil: Infinity, - metadata: { - errorType: 'segment-exceeds-source-buffer-quota-error' - } + excludeUntil: Infinity }, 'loader triggered and saved the error' ); diff --git a/test/segment-transmuxer.test.js b/test/segment-transmuxer.test.js index 9175aae5c..edfc4cdb4 100644 --- a/test/segment-transmuxer.test.js +++ b/test/segment-transmuxer.test.js @@ -84,7 +84,8 @@ QUnit.test('transmux returns data for full appends', function(assert) { assert.ok(videoSegmentTimingInfoFn.callCount, 'got videoSegmentTimingInfo events'); assert.ok(audioSegmentTimingInfoFn.callCount, 'got audioSegmentTimingInfo events'); done(); - } + }, + triggerSegmentEventFn: () => {} }); }); @@ -111,7 +112,8 @@ QUnit.test('transmux returns captions for full appends', function(assert) { assert.ok(dataFn.callCount, 'got data events'); assert.ok(captionsFn.callCount, 'got captions'); done(); - } + }, + triggerSegmentEventFn: () => {} }); }); @@ -221,7 +223,8 @@ QUnit.test('processTransmux posts all actions', function(assert) { onVideoTimingInfo: noop, onId3: noop, onCaptions: noop, - onDone: noop + onDone: noop, + triggerSegmentEventFn: () => {} }); assert.deepEqual( @@ -405,7 +408,8 @@ QUnit.test('transmux waits for endTimeline if isEndOfTimeline', function(assert) assert.ok(audioSegmentTimingInfoFn.callCount, 'got audioSegmentTimingInfo events'); assert.ok(onEndedTimelineFn.callCount, 'got onEndedTimeline event'); done(); - } + }, + triggerSegmentEventFn: () => {} }); }); @@ -445,6 +449,7 @@ QUnit.test('transmux does not wait for endTimeline if not isEndOfTimeline', func assert.ok(audioSegmentTimingInfoFn.callCount, 'got audioSegmentTimingInfo events'); assert.notOk(onEndedTimelineFn.callCount, 'did not get onEndedTimeline event'); done(); - } + }, + triggerSegmentEventFn: () => {} }); }); diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js index f898bb006..929dc00b6 100644 --- a/test/videojs-http-streaming.test.js +++ b/test/videojs-http-streaming.test.js @@ -5149,10 +5149,7 @@ QUnit.test('player error when key session creation rejects promise', function(as errorObject, { code: 3, - message: 'Failed to initialize media keys for EME', - metadata: { - errorType: 'eme-key-session-creation-error' - } + message: 'Failed to initialize media keys for EME' }, 'called player error with correct error' ); From e46ba74ac73edbc0e14adcb255a52e7eb8672468 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Fri, 17 May 2024 08:38:42 -0700 Subject: [PATCH 5/9] chore: update contrib-eme to v5.3.1 (#1512) --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 175dcace6..315281801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9250,13 +9250,12 @@ } }, "videojs-contrib-eme": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/videojs-contrib-eme/-/videojs-contrib-eme-5.0.1.tgz", - "integrity": "sha512-mkmGVfXgkMLf5BYO/WA35Nc1yObFb5oF8kNimD2/wc6U/Kk0VHBsdVU4QAfPvpZ4EFbnblsqxT6nQ4q2pl9zTg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/videojs-contrib-eme/-/videojs-contrib-eme-5.3.1.tgz", + "integrity": "sha512-grcg8y6EvBOu4sprCKR8nHfjqt7hjnvshX3DnO46VizNqM6JBt99dePL54dO3iMIBElYMRCNC08GFOMqXqpYPA==", "dev": true, "requires": { - "global": "^4.3.2", - "video.js": "^6 || ^7 || ^8" + "global": "^4.3.2" } }, "videojs-contrib-quality-levels": { diff --git a/package.json b/package.json index 31240b086..02cf71c56 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "shelljs": "^0.8.5", "sinon": "^8.1.1", "url-toolkit": "^2.2.1", - "videojs-contrib-eme": "^5.0.1", + "videojs-contrib-eme": "^5.3.1", "videojs-contrib-quality-levels": "^4.0.0", "videojs-generate-karma-config": "^8.0.1", "videojs-generate-rollup-config": "^7.0.0", From 5f2af5f41ca4d135bbcce644de74920f6c8683a1 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Tue, 21 May 2024 11:23:29 -0700 Subject: [PATCH 6/9] 3.13.0 --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab3610b6..45822448e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +# [3.13.0](https://github.com/videojs/http-streaming/compare/v3.12.2...v3.13.0) (2024-05-21) + +### Features + +* streaming events and errors ([#1508](https://github.com/videojs/http-streaming/issues/1508)) ([c94a230](https://github.com/videojs/http-streaming/commit/c94a230)) + +### Chores + +* update contrib-eme to v5.3.1 ([#1512](https://github.com/videojs/http-streaming/issues/1512)) ([e46ba74](https://github.com/videojs/http-streaming/commit/e46ba74)) + ## [3.12.2](https://github.com/videojs/http-streaming/compare/v3.12.1...v3.12.2) (2024-04-22) diff --git a/package-lock.json b/package-lock.json index 315281801..864503d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.2", + "version": "3.13.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 02cf71c56..d1c9b1698 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.12.2", + "version": "3.13.0", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "main": "dist/videojs-http-streaming.cjs.js", "module": "dist/videojs-http-streaming.es.js", From a542ec8815623ab4be6433be6e62037615b7e3dc Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Tue, 11 Jun 2024 17:11:19 -0700 Subject: [PATCH 7/9] fix: requestId init tag (#1518) --- src/media-segment-request.js | 4 ---- test/media-segment-request.test.js | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/media-segment-request.js b/src/media-segment-request.js index 934d7ea1a..1f34e8205 100644 --- a/src/media-segment-request.js +++ b/src/media-segment-request.js @@ -852,10 +852,6 @@ const waitForCompletion = ({ segment.endOfAllRequests = Date.now(); if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) { triggerSegmentEventFn({ type: 'segmentdecryptionstart', segment }); - // add -init to the "id" to differentiate between segment - // and init segment decryption, just in case they happen - // at the same time at some point in the future. - segment.requestId += '-init'; return decrypt({ decryptionWorker, // add -init to the "id" to differentiate between segment // and init segment decryption, just in case they happen diff --git a/test/media-segment-request.test.js b/test/media-segment-request.test.js index 6943d3e35..a3b670288 100644 --- a/test/media-segment-request.test.js +++ b/test/media-segment-request.test.js @@ -757,7 +757,8 @@ QUnit.test('init segment with key has bytes decrypted', function(assert) { bytes: new Uint32Array([0, 0, 0, 1]) } } - } + }, + requestId: 'foo.bar.id' }, trackInfoFn(segment, _trackInfo) { trackInfo = _trackInfo; @@ -779,6 +780,7 @@ QUnit.test('init segment with key has bytes decrypted', function(assert) { 16, 'key bytes are readable' ); + assert.equal(segmentData.requestId, 'foo.bar.id', 'requestId is expected value'); // verify stats assert.equal(segmentData.stats.bytesReceived, 6198, '6198 bytes'); From 0a7a3627d8cfb7fe0f6116ad9cb934e33a24d09e Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Tue, 11 Jun 2024 19:24:07 -0700 Subject: [PATCH 8/9] 3.13.1 --- CHANGELOG.md | 7 +++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45822448e..2bbb4390c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ + +## [3.13.1](https://github.com/videojs/http-streaming/compare/v3.13.0...v3.13.1) (2024-06-12) + +### Bug Fixes + +* requestId init tag ([#1518](https://github.com/videojs/http-streaming/issues/1518)) ([a542ec8](https://github.com/videojs/http-streaming/commit/a542ec8)) + # [3.13.0](https://github.com/videojs/http-streaming/compare/v3.12.2...v3.13.0) (2024-05-21) diff --git a/package-lock.json b/package-lock.json index 864503d95..6fd14ef86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.13.0", + "version": "3.13.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d1c9b1698..d61660e99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.13.0", + "version": "3.13.1", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "main": "dist/videojs-http-streaming.cjs.js", "module": "dist/videojs-http-streaming.es.js", From d6851ccee1cc45173f7bbbfb6b7944293d2cd19a Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Fri, 14 Jun 2024 09:25:32 -0700 Subject: [PATCH 9/9] fix: videoTimestampOffset in sourceUpdater (#1519) --- src/source-updater.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source-updater.js b/src/source-updater.js index 853510701..09414a589 100644 --- a/src/source-updater.js +++ b/src/source-updater.js @@ -818,7 +818,7 @@ export default class SourceUpdater extends videojs.EventTarget { if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same - this.videoTimestampOffset !== offset) { + this.videoTimestampOffset_ !== offset) { pushQueue({ type: 'video', sourceUpdater: this,