From c0a174e26a79a8e907cb7409cb8b6840be2bcdc3 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 29 Jun 2022 09:17:02 -0700 Subject: [PATCH] Initial public release. --- .gitignore | 2 + LICENSE | 201 + NOTICE | 13 + README.md | 89 + cert/.gitignore | 2 + cert/fingerprint | 12 + cert/generate | 19 + client/.gitignore | 3 + client/package-lock.json | 4235 ++++++++++++++ client/package.json | 13 + client/src/index.html | 82 + client/src/init.ts | 59 + client/src/message.ts | 14 + client/src/mp4.ts | 85 + client/src/mp4box.all.js | 8247 ++++++++++++++++++++++++++++ client/src/player.css | 79 + client/src/player.ts | 334 ++ client/src/segment.ts | 146 + client/src/source.ts | 81 + client/src/stream.ts | 254 + client/src/track.ts | 124 + client/src/types/webtransport.d.ts | 84 + client/src/util.ts | 4 + client/tsconfig.json | 12 + client/yarn.lock | 1354 +++++ media/.gitignore | 3 + server/.gitignore | 1 + server/go.mod | 38 + server/go.sum | 325 ++ server/internal/warp/media.go | 358 ++ server/internal/warp/message.go | 22 + server/internal/warp/server.go | 99 + server/internal/warp/session.go | 276 + server/internal/warp/socket.go | 264 + server/internal/warp/stream.go | 145 + server/warp-server/main.go | 70 + 36 files changed, 17149 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 cert/.gitignore create mode 100755 cert/fingerprint create mode 100755 cert/generate create mode 100644 client/.gitignore create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/src/index.html create mode 100644 client/src/init.ts create mode 100644 client/src/message.ts create mode 100644 client/src/mp4.ts create mode 100644 client/src/mp4box.all.js create mode 100644 client/src/player.css create mode 100644 client/src/player.ts create mode 100644 client/src/segment.ts create mode 100644 client/src/source.ts create mode 100644 client/src/stream.ts create mode 100644 client/src/track.ts create mode 100644 client/src/types/webtransport.d.ts create mode 100644 client/src/util.ts create mode 100644 client/tsconfig.json create mode 100644 client/yarn.lock create mode 100644 media/.gitignore create mode 100644 server/.gitignore create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/internal/warp/media.go create mode 100644 server/internal/warp/message.go create mode 100644 server/internal/warp/server.go create mode 100644 server/internal/warp/session.go create mode 100644 server/internal/warp/socket.go create mode 100644 server/internal/warp/stream.go create mode 100644 server/warp-server/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0eb56e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.mp4 +logs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..7152ee0 --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +Copyright Amazon.com Inc. or its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a01e3ba --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# warp +Live media delivery protocol utilizing QUIC streams. + +## How +Warp works by delivering each audio and video segment as a separate QUIC stream. These streams are assigned a priority such that old video will arrive last and can be dropped. This avoids buffering in many cases, offering the viewer a potentially better experience. + +## Browser Support +This demo currently only works on Chrome for two reasons: + +1. WebTransport support. +2. [https://github.com/whatwg/html/issues/6359](Media underflow behavior). + +### Specification +See the [https://datatracker.ietf.org/doc/draft-lcurley-warp/](Warp draft). This demo includes a few custom messages. + +## Setup +### Software +* Go +* ffmpeg +* openssl +* Chrome Canary + +### Media +This demo simulates a live stream by reading a file from disk and sleeping based on media timestamps. Obviously you should hook this up to a real live stream to do anything useful + +Download your favorite media file: +``` +wget http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4 -O media/combined.mp4 +``` + +Use ffmpeg to create a LL-DASH playlist. This creates a segment every 2s and MP4 fragment every 50ms. +``` +ffmpeg -i media/combined.mp4 -f dash -use_timeline 0 -r:v 24 -g:v 48 -keyint_min:v 48 -sc_threshold:v 0 -tune zerolatency -streaming 1 -ldash 1 -seg_duration 2 -frag_duration 0.01 -frag_type duration media/fragmented.mpd +``` + +You can increase the `frag_duration` (microseconds) to slightly reduce the file size in exchange for higher latency. + +### TLS +Unfortunately, QUIC mandates TLS and makes local development difficult. + +#### Existing +If you have a valid certificate you can use it instead of self-signing. The go binaries take a `-cert` and `-key` argument. + +Skip the remaining steps in this section and use your hostname instead of `localhost.warp.demo`. + +#### Self-Signed +Generate a self-signed certificate for local testing: +``` +./cert/generate +``` + +This creates `cert/localhost.warp.demo.crt` and `cert/localhost.warp.demo.key`. + +#### CORS +To have the browser accept our self-signed certificate, you'll need to add an entry to `/etc/hosts`. + +``` +echo '127.0.0.1 localhost.warp.demo' | sudo tee -a /etc/hosts +``` + +#### Chrome +Now we need to make Chrome accept these certificates, which normally would involve trusting a root CA but this was not working with WebTransport when I last tried. + +Instead, we need to run a *fresh instance* of Chrome, instructing it to allow our self-signed certificate. This command will not work if Chrome is already running, so it's easier to use Chrome Canary instead. This command also needs to be executed in the project root because it invokes `./cert/fingerprint`. + +Launch a new instance of Chrome Canary: +``` +/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --origin-to-force-quic-on="localhost.warp.demo:4443" --ignore-certificate-errors-spki-list="`./cert/fingerprint`" https://localhost.warp.demo:4444 +``` + +Note that this will open our web server on `localhost.warp.demo:4444`, which is started in the next section. + +### Warp Server +The Warp server defaults to listening on UDP 4443. It supports HTTP/3 and WebTransport, pushing media over WebTransport streams once a connection has been established. A more refined implementation would load content based on the WebTransport URL or some other messaging scheme. + +``` +cd server +go run ./warp-server +``` + +### Web Server +The web assets need to be hosted with a HTTPS server. If you're using a self-signed certificate, you will need to ignore the security warning in Chrome (Advanced -> proceed to localhost.warp.demo). + +``` +cd client +yarn serve +``` + +These can be accessed on `https://localhost.warp.demo:4444` by default. diff --git a/cert/.gitignore b/cert/.gitignore new file mode 100644 index 0000000..be870b4 --- /dev/null +++ b/cert/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.key diff --git a/cert/fingerprint b/cert/fingerprint new file mode 100755 index 0000000..63dc5eb --- /dev/null +++ b/cert/fingerprint @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail + +HOST="localhost.warp.demo" + +cd "$(dirname "${BASH_SOURCE[0]}")" + +# Outputs the certificate fingerprint in the format Chrome expects +openssl x509 -pubkey -noout -in "${HOST}.crt" | + openssl rsa -pubin -outform der 2>/dev/null | + openssl dgst -sha256 -binary | + base64 diff --git a/cert/generate b/cert/generate new file mode 100755 index 0000000..be3222b --- /dev/null +++ b/cert/generate @@ -0,0 +1,19 @@ +#!/bin/bash +set -euxo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" + +# Generate a new RSA key/cert for local development +HOST="localhost.warp.demo" + +openssl req \ + -x509 \ + -out "${HOST}.crt" \ + -keyout "${HOST}.key" \ + -newkey rsa:2048 \ + -nodes \ + -sha256 \ + -subj "/CN=${HOST}" \ + -extensions EXT \ + -config <( \ + printf "[dn]\nCN=${HOST}\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:${HOST}\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..6d34cdf --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,3 @@ +node_modules +.parcel-cache +dist diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..a53106f --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,4235 @@ +{ + "name": "client", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "datastream-js": "^1.0.7" + }, + "devDependencies": { + "@parcel/validator-typescript": "^2.6.0", + "parcel": "^2.6.0", + "typescript": ">=3.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@lezer/common": { + "version": "0.15.12", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz", + "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lezer/lr": { + "version": "0.15.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz", + "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^0.15.0" + } + }, + "node_modules/@mischnic/json-sourcemap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz", + "integrity": "sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lezer/common": "^0.15.7", + "@lezer/lr": "^0.15.4", + "json5": "^2.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.0.2.tgz", + "integrity": "sha512-DznYtF3lHuZDSRaIOYeif4JgO0NtO2Xf8DsngAugMx/bUdTFbg86jDTmkVJBNmV+cxszz6OjGvinnS8AbJ342g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@parcel/bundler-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.6.0.tgz", + "integrity": "sha512-AplEdGm/odV7yGmoeOnglxnY31WlNB5EqGLFGxkgs7uwDaTWoTX/9SWPG6xfvirhjDpms8sLSiVuBdFRCCLtNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/cache": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.6.0.tgz", + "integrity": "sha512-4vbD5uSuf+kRnrFesKhpn0AKnOw8u2UlvCyrplYmp1g9bNAkIooC/nDGdmkb/9SviPEbni9PEanQEHDU2+slpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/utils": "2.6.0", + "lmdb": "2.3.10" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/codeframe": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.6.0.tgz", + "integrity": "sha512-yXXxrO9yyedHKpTwC+Af0+vPmQm+A9xeEhkt4f0yVg1n4t4yUIxYlTedzbM8ygZEEBtkXU9jJ+PkgXbfMf0dqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/compressor-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.6.0.tgz", + "integrity": "sha512-rtMU2mGl88bic6Xbq1u5L49bMK4s5185b0k7h3JRdS6/0rR+Xp4k/o9Wog+hHjK/s82z1eF9WmET779ZpIDIQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/config-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.6.0.tgz", + "integrity": "sha512-DXovFPhZITmTvFaSEdC8RRqROs9FLIJ4u8yFSU6FUyq2wpvtYVRXXoDrvXgClh2csXmK7JTJTp5JF7r0rd2UaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/bundler-default": "2.6.0", + "@parcel/compressor-raw": "2.6.0", + "@parcel/namer-default": "2.6.0", + "@parcel/optimizer-css": "2.6.0", + "@parcel/optimizer-htmlnano": "2.6.0", + "@parcel/optimizer-image": "2.6.0", + "@parcel/optimizer-svgo": "2.6.0", + "@parcel/optimizer-terser": "2.6.0", + "@parcel/packager-css": "2.6.0", + "@parcel/packager-html": "2.6.0", + "@parcel/packager-js": "2.6.0", + "@parcel/packager-raw": "2.6.0", + "@parcel/packager-svg": "2.6.0", + "@parcel/reporter-dev-server": "2.6.0", + "@parcel/resolver-default": "2.6.0", + "@parcel/runtime-browser-hmr": "2.6.0", + "@parcel/runtime-js": "2.6.0", + "@parcel/runtime-react-refresh": "2.6.0", + "@parcel/runtime-service-worker": "2.6.0", + "@parcel/transformer-babel": "2.6.0", + "@parcel/transformer-css": "2.6.0", + "@parcel/transformer-html": "2.6.0", + "@parcel/transformer-image": "2.6.0", + "@parcel/transformer-js": "2.6.0", + "@parcel/transformer-json": "2.6.0", + "@parcel/transformer-postcss": "2.6.0", + "@parcel/transformer-posthtml": "2.6.0", + "@parcel/transformer-raw": "2.6.0", + "@parcel/transformer-react-refresh-wrap": "2.6.0", + "@parcel/transformer-svg": "2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.6.0.tgz", + "integrity": "sha512-8OOWbPuxpFydpwNyKoz6d3e3O4DmxNYmMw4DXwrPSj/jyg7oa+SDtMT0/VXEhujE0HYkQPCHt4npRajkSuf99A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/cache": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/graph": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "abortcontroller-polyfill": "^1.1.9", + "base-x": "^3.0.8", + "browserslist": "^4.6.6", + "clone": "^2.1.1", + "dotenv": "^7.0.0", + "dotenv-expand": "^5.1.0", + "json5": "^2.2.0", + "msgpackr": "^1.5.4", + "nullthrows": "^1.1.1", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/css": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@parcel/css/-/css-1.9.0.tgz", + "integrity": "sha512-egCetUQ1H6pgYxOIxVQ8X/YT5e8G0R8eq6aVaUHrqnZ7A8cc6FYgknl9XRmoy2Xxo9h1htrbzdaEShQ5gROwvw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/css-darwin-arm64": "1.9.0", + "@parcel/css-darwin-x64": "1.9.0", + "@parcel/css-linux-arm-gnueabihf": "1.9.0", + "@parcel/css-linux-arm64-gnu": "1.9.0", + "@parcel/css-linux-arm64-musl": "1.9.0", + "@parcel/css-linux-x64-gnu": "1.9.0", + "@parcel/css-linux-x64-musl": "1.9.0", + "@parcel/css-win32-x64-msvc": "1.9.0" + } + }, + "node_modules/@parcel/css-darwin-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@parcel/css-darwin-x64/-/css-darwin-x64-1.9.0.tgz", + "integrity": "sha512-4SpuwiM/4ayOgKflqSLd87XT7YwyC3wd2QuzOOkasjbe38UU+tot/87l2lQYEB538YinLdfwFQuFLDY0x9MxgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/diagnostic": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.6.0.tgz", + "integrity": "sha512-+p8gC2FKxSI2veD7SoaNlP572v4kw+nafCQEPDtJuzYYRqywYUGncch25dkpgNApB4W4cXVkZu3ZbtIpCAmjQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/events": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.6.0.tgz", + "integrity": "sha512-2WaKtBs4iYwS88j4zRdyTJTgh8iuY4E32FMmjzzbheqETs6I05gWuPReGukJYxk8vc0Ir7tbzp12oAfpgo0Y+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/fs": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.6.0.tgz", + "integrity": "sha512-6vxtx5Zy6MvDvH1EPx9JxjKGF03bR7VE1dUf4HLeX2D8YmpL5hkHJnlRCFdcH08rzOVwaJLzg1QNtblWJXQ9CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/fs-search": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/watcher": "^2.0.0", + "@parcel/workers": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/fs-search": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.6.0.tgz", + "integrity": "sha512-1nXzM3H/cA4kzLKvDBvwmNisKCdRqlgkLXh+OR1Zu28Kn4W34KuJMcHWW8cC+WIuuKqDh5oo2WPsC5y65GXBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/graph": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-2.6.0.tgz", + "integrity": "sha512-rxrAzWm6rwbCRPbu0Z+zwMscpG8omffODniVWPlX2G0jgQGpjKsutBQ6RMfFIcfaQ4MzL3pIQOTf8bkjQOPsbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/hash": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/hash/-/hash-2.6.0.tgz", + "integrity": "sha512-YugWqhLxqK80Lo++3B3Kr5UPCHOdS8iI2zJ1jkzUeH9v6WUzbwWOnmPf6lN2S5m1BrIFFJd8Jc+CbEXWi8zoJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "xxhash-wasm": "^0.4.2" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/logger": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.6.0.tgz", + "integrity": "sha512-J1/7kPfSGBvMKSZdi0WCNuN0fIeiWxifnDGn7W/K8KhD422YwFJA8N046ps8nkDOPIXf1osnIECNp4GIR9oSYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/markdown-ansi": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.6.0.tgz", + "integrity": "sha512-fyjkrJQQSfKTUFTTasdZ6WrAkDoQ2+DYDjj+3p+RncYyrIa9zArKx4IiRiipsvNdtMvP0/hTdK8F3BOJ3KSU/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/namer-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.6.0.tgz", + "integrity": "sha512-r8O12r7ozJBctnFxVdXbf/fK97GIdNj3hiiUNWlXEmED9sw6ZPcChaLcfot0/443g8i87JDmSTKJ8js2tuz5XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/node-resolver-core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-2.6.0.tgz", + "integrity": "sha512-AJDj5DZbB58plv0li8bdVSD+zpnkHE36Om3TYyNn1jgXXwgBM64Er/9p8yQn356jBqTQMh7zlJqvbdIyOiMeMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.6.0.tgz", + "integrity": "sha512-VMJknUwfKCw6Woov0lnPGdsGZewcI4ghW8WKmNZzC5uKCetk1XetV55QHBc1RNjGfsjfSTZiSa3guATj2zFJkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/css": "^1.9.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-htmlnano": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.6.0.tgz", + "integrity": "sha512-HmvcUoYpfdx8ZfID4WOj/SE8N78NEBmzAffZ8f827mYMr4ZrbKzAgg6OG3tBbfF0zxH0bIjZcwqwZYk4SdbG7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "htmlnano": "^2.0.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "svgo": "^2.4.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.6.0.tgz", + "integrity": "sha512-FDNr3LJ8SWR9rrtdCrZOlYF1hE9G5pxUWawGxUasbvqwcY5lEQwr2KRmfGZeg+KwOnzlImlY6dP2LGox1NFddQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-svgo": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.6.0.tgz", + "integrity": "sha512-LMTDVMd7T/IfLG59yLWl8Uw2HYGbj2C3jIwkMqH9MBUT5KILK66T3t0yV86SoZJnxZ6xBIJ+kCcCRssCzhvanw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "svgo": "^2.4.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/optimizer-terser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-terser/-/optimizer-terser-2.6.0.tgz", + "integrity": "sha512-oezRt6Lz/QqcVDXyMfFjzQc7n0ThJowLJ4Lyhu8rMh0ZJYzc4UCFCw/19d4nRnzE+Qg0vj3mQCpdkA9/64E44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "terser": "^5.2.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/package-manager": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.6.0.tgz", + "integrity": "sha512-AqFfdkbOw51q/3ia2mIsFTmrpYEyUb3k+2uYC5GsLMz3go6OGn7/Crz0lZLSclv5EtwpRg3TWr9yL7RekVN/Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/packager-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.6.0.tgz", + "integrity": "sha512-iXUttSe+wtnIM2PKCyFC2I4+Szv+8qHpC3wXeJlXlzd8wljm42y+6Fs4FZ0zihTccRxI2UUhFnKu90ag+5AmjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-html": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.6.0.tgz", + "integrity": "sha512-HsiXMkU9AJr3LLjsP2Kteho2jCVpabTwcU/fauwbwirhg0xNlRsKxYZRCllRhPkb0FWAnkjzwjOj01MHD6NJCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.6.0.tgz", + "integrity": "sha512-Uz3pqIFchFfKszWnNGDgIwM1uwHHJp7Dts6VzS9lf/2RbRgZT0fmce+NPgnVO5MMKBHzdvm32ShT6gFAABF5Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "globals": "^13.2.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.6.0.tgz", + "integrity": "sha512-ktT6Qc/GgCq8H1+6y+AXufVzQj1s6KRoKf83qswCD0iY3MwCbJoEfc3IsB4K64FpHIL5Eu0z54IId+INvGbOYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/packager-svg": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.6.0.tgz", + "integrity": "sha512-OF2RShyspXu7H4Dn2PmchfMMYPx+kWjOXiYVQ6OkOI0MZmOydx7p8nrcG5+y7vCJTPlta828BSwva0GdKfn46A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "posthtml": "^0.16.4" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/plugin": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.6.0.tgz", + "integrity": "sha512-LzOaiK8R6eFEoov1cb3/W+o0XvXdI/VbDhMDl0L0II+/56M0UeayYtFP5QGTDn/fZqVlYfzPCtt3EMwdG7/dow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/types": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-cli": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.6.0.tgz", + "integrity": "sha512-QFG957NXx3L0D8Zw0+B2j7IHy8f/UzOVu6VvKE3rMkhq/iR2qLrPohQ+uvxlee+CLC0cG2qRSgJ7Ve/rjQPoJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "chalk": "^4.1.0", + "term-size": "^2.2.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/reporter-dev-server": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.6.0.tgz", + "integrity": "sha512-VvygsCA+uzWyijIV8zqU1gFyhAWknuaY4KIWhV4kCT8afRJwsLSwt/tpdaKDPuPU45h3tTsUdXH1wjaIk+dGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/resolver-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.6.0.tgz", + "integrity": "sha512-ATk9wXvy5GOHAqyHbnCnU11fUPTtf8dLjpgVqL5XylwugZnyBXbynoTWX4w8h6mffkVtdfmzTJx/o4Lresz9sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/node-resolver-core": "2.6.0", + "@parcel/plugin": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-browser-hmr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.6.0.tgz", + "integrity": "sha512-90xvv/10cFML5dAhClBEJZ/ExiBQVPqQsZcvRmVZmc5mpZVJMKattWCQrd7pAf7FDYl4JAcvsK3DTwvRT/oLNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.6.0.tgz", + "integrity": "sha512-R4tJAIT/SX7VBQ+f7WmeekREQzzLsmgP1j486uKhQNyYrpvsN0HnRbg5aqvZjEjkEmSeJR0mOlWtMK5/m+0yTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-react-refresh": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.6.0.tgz", + "integrity": "sha512-2sRd13gc2EbMV/O5n2NPVGGhKBasb1fDTXGEY8y7qi9xDKc+ewok/D83T+w243FhCPS9Pf3ur5GkbPlrJGcenQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "react-error-overlay": "6.0.9", + "react-refresh": "^0.9.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/runtime-service-worker": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.6.0.tgz", + "integrity": "sha512-nVlknGw5J5Bkd1Wr1TbyWHhUd9CmVVebaRg/lpfVKYhAuE/2r+3N0+J8qbEIgtTRcHaSV7wTNpg4weSWq46VeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/source-map": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.0.5.tgz", + "integrity": "sha512-DRVlCFKLpqBSIbMxUoVlHgfiv12HTW/U7nnhzw52YgzDVXUX9OA41dXS1PU0pJ1si+D1k8msATUC+AoldN43mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": "^12.18.3 || >=14" + } + }, + "node_modules/@parcel/transformer-babel": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.6.0.tgz", + "integrity": "sha512-qTDzhLoaTpRJoppCNqaAlcUYxcDEvJffem1h3SAQiwvCLUBQowLyeaBy8sUxu54AU6eHFJyBld5ZocENyHTBCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "json5": "^2.2.0", + "nullthrows": "^1.1.1", + "semver": "^5.7.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.6.0.tgz", + "integrity": "sha512-Ei9NPE5Rl9V+MGd8qddfZD0Fsqbvky8J62RwYsqLkptFl9FkhgwOu8Cmokz7IIc4GJ2qzfnG5y54K/Bi7Moq4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/css": "^1.9.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-html": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.6.0.tgz", + "integrity": "sha512-YQh5WzNFjPhgV09P+zVS++albTCTvbPYAJXp5zUJ4HavzcpV2IB3HAPRk9x+iXUeRBQYYiO5SMMRkdy9a4CzQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.6.0.tgz", + "integrity": "sha512-Zkh1i6nWNOTOReKlZD+bLJCHA16dPLO6Or7ETAHtSF3iRzMNFcVFp+851Awj3l4zeJ6CoCWlyxsR4CEdioRgiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/workers": "2.6.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/transformer-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.6.0.tgz", + "integrity": "sha512-4v2r3EVdMKowBziVBW9HZqvAv88HaeiezkWyMX4wAfplo9jBtWEp99KEQINzSEdbXROR81M9oJjlGF5+yoVr/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "@swc/helpers": "^0.3.15", + "browserslist": "^4.6.6", + "detect-libc": "^1.0.3", + "nullthrows": "^1.1.1", + "regenerator-runtime": "^0.13.7", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@parcel/transformer-json": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.6.0.tgz", + "integrity": "sha512-zb+TQAdHWdXijKcFhLe+5KN1O0IzXwW1gJhPr8DJEA3qhPaCsncsw5RCVjQlP3a7NXr1mMm1eMtO6bhIMqbXeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "json5": "^2.2.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-postcss": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.6.0.tgz", + "integrity": "sha512-czmh2mOPJLwYbtnPTFlxKYcaQHH6huIlpfNX1XgdsaEYS+yFs8ZXpzqjxI1wu6rMW0R0q5aon72yB3PJewvqNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "clone": "^2.1.1", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-posthtml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.6.0.tgz", + "integrity": "sha512-R1FmPMZ0pgrbPZkDppa2pE+6KDK3Wxof6uQo7juHLB2ELGOTaYofsG3nrRdk+chyAHaVv4qWLqXbfZK6pGepEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.6.0.tgz", + "integrity": "sha512-QDirlWCS/qy0DQ3WvDIAnFP52n1TJW/uWH+4PGMNnX4/M3/2UchY8xp9CN0tx4NQ4g09S8o3gLlHvNxQqZxFrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-react-refresh-wrap": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.6.0.tgz", + "integrity": "sha512-G34orfvLDUTumuerqNmA8T8NUHk+R0jwUjbVPO7gpB6VCVQ5ocTABdE9vN9Uu/cUsHij40TUFwqK4R9TFEBIEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "react-refresh": "^0.9.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/transformer-svg": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.6.0.tgz", + "integrity": "sha512-e7yrb7775A7tEGRsAHQSMhXe+u4yisH5W0PuIzAQQy/a2IwBjaSxNnvyelN7tNX0FYq0BK6An5wRbhK4YmM+xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/ts-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/ts-utils/-/ts-utils-2.6.0.tgz", + "integrity": "sha512-U2Spr/vdOnxLzztXP6WpMO7JZTsaYO1G6F/cUTG5fReTQ0imM952FAc/WswpZWAPZqXqWCnvC/Z91JIkMDuYrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "typescript": ">=3.0.0" + } + }, + "node_modules/@parcel/types": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.6.0.tgz", + "integrity": "sha512-lAMYvOBfNEJMsPJ+plbB50305o0TwNrY1xX5RRIWBqwOa6bYmbW1ZljUk1tQvnkpIE4eAHQwnPR5Z2XWg18wGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/cache": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/workers": "2.6.0", + "utility-types": "^3.10.0" + } + }, + "node_modules/@parcel/utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.6.0.tgz", + "integrity": "sha512-ElXz+QHtT1JQIucbQJBk7SzAGoOlBp4yodEQVvTKS7GA+hEGrSP/cmibl6qm29Rjtd0zgQsdd+2XmP3xvP2gQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/codeframe": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/markdown-ansi": "2.6.0", + "@parcel/source-map": "^2.0.0", + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/validator-typescript": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/validator-typescript/-/validator-typescript-2.6.0.tgz", + "integrity": "sha512-NMroc+QPoTo436COHsqEQsn+Qd+7HE1s1X6he1Bqb+RMB4rZsvOZI22MgFj1eU5MpfYuM4zTID0Uz221hiS59w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/ts-utils": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0" + }, + "engines": { + "node": ">= 12.0.0", + "parcel": "^2.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "typescript": ">=3.0.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.5.tgz", + "integrity": "sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^3.2.1", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/workers": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.6.0.tgz", + "integrity": "sha512-3tcI2LF5fd/WZtSnSjyWdDE+G+FitdNrRgSObzSp+axHKMAM23sO0z7KY8s2SYCF40msdYbFUW8eI6JlYNJoWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/diagnostic": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "chrome-trace-event": "^1.0.2", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "peerDependencies": { + "@parcel/core": "^2.6.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true, + "license": "MIT" + }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001352", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", + "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/datastream-js": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/datastream-js/-/datastream-js-1.0.7.tgz", + "integrity": "sha512-rW7N3QkEx01reZ6/BF1s3sGnh1JdFpektwSqgUz8bmmvfmD+NNGTPhbTePZjs0B3VSizFX26Kr8qNtNJDz0NAQ==", + "dependencies": { + "text-encoding": "^0.6.4" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=6" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.150", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.150.tgz", + "integrity": "sha512-MP3oBer0X7ZeS9GJ0H6lmkn561UxiwOIY9TTkdxVY7lI9G6GVCKfgJaHaDcakwdKxBXA4T3ybeswH/WBIN/KTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/htmlnano": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.2.tgz", + "integrity": "sha512-+ZrQFS4Ub+zd+/fWwfvoYCEGNEa0/zrpys6CyXxvZDwtL7Pl+pOtRkiujyvBQ7Lmfp7/iEPxtOFgxWA16Gkj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.1", + "posthtml": "^0.16.5", + "timsort": "^0.3.0" + }, + "peerDependencies": { + "cssnano": "^5.0.11", + "postcss": "^8.3.11", + "purgecss": "^4.0.3", + "relateurl": "^0.2.7", + "srcset": "^5.0.0", + "svgo": "^2.8.0", + "terser": "^5.10.0", + "uncss": "^0.17.3" + }, + "peerDependenciesMeta": { + "cssnano": { + "optional": true + }, + "postcss": { + "optional": true + }, + "purgecss": { + "optional": true + }, + "relateurl": { + "optional": true + }, + "srcset": { + "optional": true + }, + "svgo": { + "optional": true + }, + "terser": { + "optional": true + }, + "uncss": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lmdb": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.3.10.tgz", + "integrity": "sha512-GtH+nStn9V59CfYeQ5ddx6YTfuFCmu86UJojIjJAweG+/Fm0PDknuk3ovgYDtY/foMeMdZa8/P7oSljW/d5UPw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "msgpackr": "^1.5.4", + "nan": "^2.14.2", + "node-addon-api": "^4.3.0", + "node-gyp-build-optional-packages": "^4.3.2", + "ordered-binary": "^1.2.4", + "weak-lru-cache": "^1.2.2" + }, + "optionalDependencies": { + "lmdb-darwin-arm64": "2.3.10", + "lmdb-darwin-x64": "2.3.10", + "lmdb-linux-arm": "2.3.10", + "lmdb-linux-arm64": "2.3.10", + "lmdb-linux-x64": "2.3.10", + "lmdb-win32-x64": "2.3.10" + } + }, + "node_modules/lmdb-darwin-x64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/lmdb-darwin-x64/-/lmdb-darwin-x64-2.3.10.tgz", + "integrity": "sha512-gAc/1b/FZOb9yVOT+o0huA+hdW82oxLo5r22dFTLoRUFG1JMzxdTjmnW6ONVOHdqC9a5bt3vBCEY3jmXNqV26A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lmdb/node_modules/node-gyp-build-optional-packages": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-4.3.5.tgz", + "integrity": "sha512-5ke7D8SiQsTQL7CkHpfR1tLwfqtKc0KYEmlnkwd40jHCASskZeS98qoZ1qDUns2aUQWikcjidRUs6PM/3iyN/w==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/msgpackr": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.6.1.tgz", + "integrity": "sha512-Je+xBEfdjtvA4bKaOv8iRhjC8qX2oJwpYH4f7JrG4uMVJVmnmkAT4pjKdbztKprGj3iwjcxPzb5umVZ02Qq3tA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^2.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-2.0.2.tgz", + "integrity": "sha512-coskCeJG2KDny23zWeu+6tNy7BLnAiOGgiwzlgdm4oeSsTpqEJJPguHIuKZcCdB7tzhZbXNYSg6jZAXkZErkJA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.2" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "2.0.2" + } + }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz", + "integrity": "sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build-optional": "optional.js", + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ordered-binary": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.2.5.tgz", + "integrity": "sha512-djRmZoEpOGvIRW7ufsCDHtvcUa18UC9TxnPbHhSVFZHsoyg0dtut1bWtBZ/fmxdPN62oWXrV6adM7NoWU+CneA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parcel": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.6.0.tgz", + "integrity": "sha512-pSTJ7wC6uTl16PKLXQV7RfL9FGoIDA1iVpNvaav47n6UkUdKqfx0spcVPpw35kWdRcHJF61YAvkPjP2hTwHQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/config-default": "2.6.0", + "@parcel/core": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/reporter-cli": "2.6.0", + "@parcel/reporter-dev-server": "2.6.0", + "@parcel/utils": "2.6.0", + "chalk": "^4.1.0", + "commander": "^7.0.0", + "get-port": "^4.2.0", + "v8-compile-cache": "^2.0.0" + }, + "bin": { + "parcel": "lib/bin.js" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/posthtml": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", + "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml/node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", + "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz", + "integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "deprecated": "no longer maintained" + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.20.2", + "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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xxhash-wasm": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz", + "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "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==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@lezer/common": { + "version": "0.15.12", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz", + "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==", + "dev": true + }, + "@lezer/lr": { + "version": "0.15.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz", + "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==", + "dev": true, + "requires": { + "@lezer/common": "^0.15.0" + } + }, + "@mischnic/json-sourcemap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz", + "integrity": "sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==", + "dev": true, + "requires": { + "@lezer/common": "^0.15.7", + "@lezer/lr": "^0.15.4", + "json5": "^2.2.1" + } + }, + "@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.0.2.tgz", + "integrity": "sha512-DznYtF3lHuZDSRaIOYeif4JgO0NtO2Xf8DsngAugMx/bUdTFbg86jDTmkVJBNmV+cxszz6OjGvinnS8AbJ342g==", + "dev": true, + "optional": true + }, + "@parcel/bundler-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.6.0.tgz", + "integrity": "sha512-AplEdGm/odV7yGmoeOnglxnY31WlNB5EqGLFGxkgs7uwDaTWoTX/9SWPG6xfvirhjDpms8sLSiVuBdFRCCLtNA==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/cache": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.6.0.tgz", + "integrity": "sha512-4vbD5uSuf+kRnrFesKhpn0AKnOw8u2UlvCyrplYmp1g9bNAkIooC/nDGdmkb/9SviPEbni9PEanQEHDU2+slpA==", + "dev": true, + "requires": { + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/utils": "2.6.0", + "lmdb": "2.3.10" + } + }, + "@parcel/codeframe": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.6.0.tgz", + "integrity": "sha512-yXXxrO9yyedHKpTwC+Af0+vPmQm+A9xeEhkt4f0yVg1n4t4yUIxYlTedzbM8ygZEEBtkXU9jJ+PkgXbfMf0dqw==", + "dev": true, + "requires": { + "chalk": "^4.1.0" + } + }, + "@parcel/compressor-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.6.0.tgz", + "integrity": "sha512-rtMU2mGl88bic6Xbq1u5L49bMK4s5185b0k7h3JRdS6/0rR+Xp4k/o9Wog+hHjK/s82z1eF9WmET779ZpIDIQQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0" + } + }, + "@parcel/config-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.6.0.tgz", + "integrity": "sha512-DXovFPhZITmTvFaSEdC8RRqROs9FLIJ4u8yFSU6FUyq2wpvtYVRXXoDrvXgClh2csXmK7JTJTp5JF7r0rd2UaA==", + "dev": true, + "requires": { + "@parcel/bundler-default": "2.6.0", + "@parcel/compressor-raw": "2.6.0", + "@parcel/namer-default": "2.6.0", + "@parcel/optimizer-css": "2.6.0", + "@parcel/optimizer-htmlnano": "2.6.0", + "@parcel/optimizer-image": "2.6.0", + "@parcel/optimizer-svgo": "2.6.0", + "@parcel/optimizer-terser": "2.6.0", + "@parcel/packager-css": "2.6.0", + "@parcel/packager-html": "2.6.0", + "@parcel/packager-js": "2.6.0", + "@parcel/packager-raw": "2.6.0", + "@parcel/packager-svg": "2.6.0", + "@parcel/reporter-dev-server": "2.6.0", + "@parcel/resolver-default": "2.6.0", + "@parcel/runtime-browser-hmr": "2.6.0", + "@parcel/runtime-js": "2.6.0", + "@parcel/runtime-react-refresh": "2.6.0", + "@parcel/runtime-service-worker": "2.6.0", + "@parcel/transformer-babel": "2.6.0", + "@parcel/transformer-css": "2.6.0", + "@parcel/transformer-html": "2.6.0", + "@parcel/transformer-image": "2.6.0", + "@parcel/transformer-js": "2.6.0", + "@parcel/transformer-json": "2.6.0", + "@parcel/transformer-postcss": "2.6.0", + "@parcel/transformer-posthtml": "2.6.0", + "@parcel/transformer-raw": "2.6.0", + "@parcel/transformer-react-refresh-wrap": "2.6.0", + "@parcel/transformer-svg": "2.6.0" + } + }, + "@parcel/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.6.0.tgz", + "integrity": "sha512-8OOWbPuxpFydpwNyKoz6d3e3O4DmxNYmMw4DXwrPSj/jyg7oa+SDtMT0/VXEhujE0HYkQPCHt4npRajkSuf99A==", + "dev": true, + "requires": { + "@mischnic/json-sourcemap": "^0.1.0", + "@parcel/cache": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/graph": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "abortcontroller-polyfill": "^1.1.9", + "base-x": "^3.0.8", + "browserslist": "^4.6.6", + "clone": "^2.1.1", + "dotenv": "^7.0.0", + "dotenv-expand": "^5.1.0", + "json5": "^2.2.0", + "msgpackr": "^1.5.4", + "nullthrows": "^1.1.1", + "semver": "^5.7.1" + } + }, + "@parcel/css": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@parcel/css/-/css-1.9.0.tgz", + "integrity": "sha512-egCetUQ1H6pgYxOIxVQ8X/YT5e8G0R8eq6aVaUHrqnZ7A8cc6FYgknl9XRmoy2Xxo9h1htrbzdaEShQ5gROwvw==", + "dev": true, + "requires": { + "@parcel/css-darwin-arm64": "1.9.0", + "@parcel/css-darwin-x64": "1.9.0", + "@parcel/css-linux-arm-gnueabihf": "1.9.0", + "@parcel/css-linux-arm64-gnu": "1.9.0", + "@parcel/css-linux-arm64-musl": "1.9.0", + "@parcel/css-linux-x64-gnu": "1.9.0", + "@parcel/css-linux-x64-musl": "1.9.0", + "@parcel/css-win32-x64-msvc": "1.9.0", + "detect-libc": "^1.0.3" + } + }, + "@parcel/css-darwin-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@parcel/css-darwin-x64/-/css-darwin-x64-1.9.0.tgz", + "integrity": "sha512-4SpuwiM/4ayOgKflqSLd87XT7YwyC3wd2QuzOOkasjbe38UU+tot/87l2lQYEB538YinLdfwFQuFLDY0x9MxgA==", + "dev": true, + "optional": true + }, + "@parcel/diagnostic": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.6.0.tgz", + "integrity": "sha512-+p8gC2FKxSI2veD7SoaNlP572v4kw+nafCQEPDtJuzYYRqywYUGncch25dkpgNApB4W4cXVkZu3ZbtIpCAmjQQ==", + "dev": true, + "requires": { + "@mischnic/json-sourcemap": "^0.1.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/events": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.6.0.tgz", + "integrity": "sha512-2WaKtBs4iYwS88j4zRdyTJTgh8iuY4E32FMmjzzbheqETs6I05gWuPReGukJYxk8vc0Ir7tbzp12oAfpgo0Y+g==", + "dev": true + }, + "@parcel/fs": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.6.0.tgz", + "integrity": "sha512-6vxtx5Zy6MvDvH1EPx9JxjKGF03bR7VE1dUf4HLeX2D8YmpL5hkHJnlRCFdcH08rzOVwaJLzg1QNtblWJXQ9CA==", + "dev": true, + "requires": { + "@parcel/fs-search": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/watcher": "^2.0.0", + "@parcel/workers": "2.6.0" + } + }, + "@parcel/fs-search": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.6.0.tgz", + "integrity": "sha512-1nXzM3H/cA4kzLKvDBvwmNisKCdRqlgkLXh+OR1Zu28Kn4W34KuJMcHWW8cC+WIuuKqDh5oo2WPsC5y65GXBKQ==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3" + } + }, + "@parcel/graph": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-2.6.0.tgz", + "integrity": "sha512-rxrAzWm6rwbCRPbu0Z+zwMscpG8omffODniVWPlX2G0jgQGpjKsutBQ6RMfFIcfaQ4MzL3pIQOTf8bkjQOPsbg==", + "dev": true, + "requires": { + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/hash": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/hash/-/hash-2.6.0.tgz", + "integrity": "sha512-YugWqhLxqK80Lo++3B3Kr5UPCHOdS8iI2zJ1jkzUeH9v6WUzbwWOnmPf6lN2S5m1BrIFFJd8Jc+CbEXWi8zoJA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3", + "xxhash-wasm": "^0.4.2" + } + }, + "@parcel/logger": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.6.0.tgz", + "integrity": "sha512-J1/7kPfSGBvMKSZdi0WCNuN0fIeiWxifnDGn7W/K8KhD422YwFJA8N046ps8nkDOPIXf1osnIECNp4GIR9oSYw==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0" + } + }, + "@parcel/markdown-ansi": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.6.0.tgz", + "integrity": "sha512-fyjkrJQQSfKTUFTTasdZ6WrAkDoQ2+DYDjj+3p+RncYyrIa9zArKx4IiRiipsvNdtMvP0/hTdK8F3BOJ3KSU/g==", + "dev": true, + "requires": { + "chalk": "^4.1.0" + } + }, + "@parcel/namer-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.6.0.tgz", + "integrity": "sha512-r8O12r7ozJBctnFxVdXbf/fK97GIdNj3hiiUNWlXEmED9sw6ZPcChaLcfot0/443g8i87JDmSTKJ8js2tuz5XA==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/node-resolver-core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-2.6.0.tgz", + "integrity": "sha512-AJDj5DZbB58plv0li8bdVSD+zpnkHE36Om3TYyNn1jgXXwgBM64Er/9p8yQn356jBqTQMh7zlJqvbdIyOiMeMg==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/optimizer-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.6.0.tgz", + "integrity": "sha512-VMJknUwfKCw6Woov0lnPGdsGZewcI4ghW8WKmNZzC5uKCetk1XetV55QHBc1RNjGfsjfSTZiSa3guATj2zFJkQ==", + "dev": true, + "requires": { + "@parcel/css": "^1.9.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "nullthrows": "^1.1.1" + } + }, + "@parcel/optimizer-htmlnano": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.6.0.tgz", + "integrity": "sha512-HmvcUoYpfdx8ZfID4WOj/SE8N78NEBmzAffZ8f827mYMr4ZrbKzAgg6OG3tBbfF0zxH0bIjZcwqwZYk4SdbG7g==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "htmlnano": "^2.0.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "svgo": "^2.4.0" + } + }, + "@parcel/optimizer-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.6.0.tgz", + "integrity": "sha512-FDNr3LJ8SWR9rrtdCrZOlYF1hE9G5pxUWawGxUasbvqwcY5lEQwr2KRmfGZeg+KwOnzlImlY6dP2LGox1NFddQ==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "detect-libc": "^1.0.3" + } + }, + "@parcel/optimizer-svgo": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.6.0.tgz", + "integrity": "sha512-LMTDVMd7T/IfLG59yLWl8Uw2HYGbj2C3jIwkMqH9MBUT5KILK66T3t0yV86SoZJnxZ6xBIJ+kCcCRssCzhvanw==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "svgo": "^2.4.0" + } + }, + "@parcel/optimizer-terser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-terser/-/optimizer-terser-2.6.0.tgz", + "integrity": "sha512-oezRt6Lz/QqcVDXyMfFjzQc7n0ThJowLJ4Lyhu8rMh0ZJYzc4UCFCw/19d4nRnzE+Qg0vj3mQCpdkA9/64E44g==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "terser": "^5.2.0" + } + }, + "@parcel/package-manager": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.6.0.tgz", + "integrity": "sha512-AqFfdkbOw51q/3ia2mIsFTmrpYEyUb3k+2uYC5GsLMz3go6OGn7/Crz0lZLSclv5EtwpRg3TWr9yL7RekVN/Uw==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "semver": "^5.7.1" + } + }, + "@parcel/packager-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.6.0.tgz", + "integrity": "sha512-iXUttSe+wtnIM2PKCyFC2I4+Szv+8qHpC3wXeJlXlzd8wljm42y+6Fs4FZ0zihTccRxI2UUhFnKu90ag+5AmjA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/packager-html": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.6.0.tgz", + "integrity": "sha512-HsiXMkU9AJr3LLjsP2Kteho2jCVpabTwcU/fauwbwirhg0xNlRsKxYZRCllRhPkb0FWAnkjzwjOj01MHD6NJCg==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5" + } + }, + "@parcel/packager-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.6.0.tgz", + "integrity": "sha512-Uz3pqIFchFfKszWnNGDgIwM1uwHHJp7Dts6VzS9lf/2RbRgZT0fmce+NPgnVO5MMKBHzdvm32ShT6gFAABF5Vw==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "globals": "^13.2.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/packager-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.6.0.tgz", + "integrity": "sha512-ktT6Qc/GgCq8H1+6y+AXufVzQj1s6KRoKf83qswCD0iY3MwCbJoEfc3IsB4K64FpHIL5Eu0z54IId+INvGbOYA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0" + } + }, + "@parcel/packager-svg": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.6.0.tgz", + "integrity": "sha512-OF2RShyspXu7H4Dn2PmchfMMYPx+kWjOXiYVQ6OkOI0MZmOydx7p8nrcG5+y7vCJTPlta828BSwva0GdKfn46A==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "posthtml": "^0.16.4" + } + }, + "@parcel/plugin": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.6.0.tgz", + "integrity": "sha512-LzOaiK8R6eFEoov1cb3/W+o0XvXdI/VbDhMDl0L0II+/56M0UeayYtFP5QGTDn/fZqVlYfzPCtt3EMwdG7/dow==", + "dev": true, + "requires": { + "@parcel/types": "2.6.0" + } + }, + "@parcel/reporter-cli": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.6.0.tgz", + "integrity": "sha512-QFG957NXx3L0D8Zw0+B2j7IHy8f/UzOVu6VvKE3rMkhq/iR2qLrPohQ+uvxlee+CLC0cG2qRSgJ7Ve/rjQPoJg==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "chalk": "^4.1.0", + "term-size": "^2.2.1" + } + }, + "@parcel/reporter-dev-server": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.6.0.tgz", + "integrity": "sha512-VvygsCA+uzWyijIV8zqU1gFyhAWknuaY4KIWhV4kCT8afRJwsLSwt/tpdaKDPuPU45h3tTsUdXH1wjaIk+dGeQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0" + } + }, + "@parcel/resolver-default": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.6.0.tgz", + "integrity": "sha512-ATk9wXvy5GOHAqyHbnCnU11fUPTtf8dLjpgVqL5XylwugZnyBXbynoTWX4w8h6mffkVtdfmzTJx/o4Lresz9sA==", + "dev": true, + "requires": { + "@parcel/node-resolver-core": "2.6.0", + "@parcel/plugin": "2.6.0" + } + }, + "@parcel/runtime-browser-hmr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.6.0.tgz", + "integrity": "sha512-90xvv/10cFML5dAhClBEJZ/ExiBQVPqQsZcvRmVZmc5mpZVJMKattWCQrd7pAf7FDYl4JAcvsK3DTwvRT/oLNA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0" + } + }, + "@parcel/runtime-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.6.0.tgz", + "integrity": "sha512-R4tJAIT/SX7VBQ+f7WmeekREQzzLsmgP1j486uKhQNyYrpvsN0HnRbg5aqvZjEjkEmSeJR0mOlWtMK5/m+0yTA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/runtime-react-refresh": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.6.0.tgz", + "integrity": "sha512-2sRd13gc2EbMV/O5n2NPVGGhKBasb1fDTXGEY8y7qi9xDKc+ewok/D83T+w243FhCPS9Pf3ur5GkbPlrJGcenQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "react-error-overlay": "6.0.9", + "react-refresh": "^0.9.0" + } + }, + "@parcel/runtime-service-worker": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.6.0.tgz", + "integrity": "sha512-nVlknGw5J5Bkd1Wr1TbyWHhUd9CmVVebaRg/lpfVKYhAuE/2r+3N0+J8qbEIgtTRcHaSV7wTNpg4weSWq46VeA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/source-map": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.0.5.tgz", + "integrity": "sha512-DRVlCFKLpqBSIbMxUoVlHgfiv12HTW/U7nnhzw52YgzDVXUX9OA41dXS1PU0pJ1si+D1k8msATUC+AoldN43mg==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3" + } + }, + "@parcel/transformer-babel": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.6.0.tgz", + "integrity": "sha512-qTDzhLoaTpRJoppCNqaAlcUYxcDEvJffem1h3SAQiwvCLUBQowLyeaBy8sUxu54AU6eHFJyBld5ZocENyHTBCA==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "json5": "^2.2.0", + "nullthrows": "^1.1.1", + "semver": "^5.7.0" + } + }, + "@parcel/transformer-css": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.6.0.tgz", + "integrity": "sha512-Ei9NPE5Rl9V+MGd8qddfZD0Fsqbvky8J62RwYsqLkptFl9FkhgwOu8Cmokz7IIc4GJ2qzfnG5y54K/Bi7Moq4Q==", + "dev": true, + "requires": { + "@parcel/css": "^1.9.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "browserslist": "^4.6.6", + "nullthrows": "^1.1.1" + } + }, + "@parcel/transformer-html": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.6.0.tgz", + "integrity": "sha512-YQh5WzNFjPhgV09P+zVS++albTCTvbPYAJXp5zUJ4HavzcpV2IB3HAPRk9x+iXUeRBQYYiO5SMMRkdy9a4CzQQ==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + } + }, + "@parcel/transformer-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.6.0.tgz", + "integrity": "sha512-Zkh1i6nWNOTOReKlZD+bLJCHA16dPLO6Or7ETAHtSF3iRzMNFcVFp+851Awj3l4zeJ6CoCWlyxsR4CEdioRgiQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/workers": "2.6.0", + "nullthrows": "^1.1.1" + } + }, + "@parcel/transformer-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.6.0.tgz", + "integrity": "sha512-4v2r3EVdMKowBziVBW9HZqvAv88HaeiezkWyMX4wAfplo9jBtWEp99KEQINzSEdbXROR81M9oJjlGF5+yoVr/w==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/utils": "2.6.0", + "@parcel/workers": "2.6.0", + "@swc/helpers": "^0.3.15", + "browserslist": "^4.6.6", + "detect-libc": "^1.0.3", + "nullthrows": "^1.1.1", + "regenerator-runtime": "^0.13.7", + "semver": "^5.7.1" + } + }, + "@parcel/transformer-json": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.6.0.tgz", + "integrity": "sha512-zb+TQAdHWdXijKcFhLe+5KN1O0IzXwW1gJhPr8DJEA3qhPaCsncsw5RCVjQlP3a7NXr1mMm1eMtO6bhIMqbXeA==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "json5": "^2.2.0" + } + }, + "@parcel/transformer-postcss": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.6.0.tgz", + "integrity": "sha512-czmh2mOPJLwYbtnPTFlxKYcaQHH6huIlpfNX1XgdsaEYS+yFs8ZXpzqjxI1wu6rMW0R0q5aon72yB3PJewvqNQ==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "clone": "^2.1.1", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "semver": "^5.7.1" + } + }, + "@parcel/transformer-posthtml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.6.0.tgz", + "integrity": "sha512-R1FmPMZ0pgrbPZkDppa2pE+6KDK3Wxof6uQo7juHLB2ELGOTaYofsG3nrRdk+chyAHaVv4qWLqXbfZK6pGepEg==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + } + }, + "@parcel/transformer-raw": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.6.0.tgz", + "integrity": "sha512-QDirlWCS/qy0DQ3WvDIAnFP52n1TJW/uWH+4PGMNnX4/M3/2UchY8xp9CN0tx4NQ4g09S8o3gLlHvNxQqZxFrQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0" + } + }, + "@parcel/transformer-react-refresh-wrap": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.6.0.tgz", + "integrity": "sha512-G34orfvLDUTumuerqNmA8T8NUHk+R0jwUjbVPO7gpB6VCVQ5ocTABdE9vN9Uu/cUsHij40TUFwqK4R9TFEBIEQ==", + "dev": true, + "requires": { + "@parcel/plugin": "2.6.0", + "@parcel/utils": "2.6.0", + "react-refresh": "^0.9.0" + } + }, + "@parcel/transformer-svg": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.6.0.tgz", + "integrity": "sha512-e7yrb7775A7tEGRsAHQSMhXe+u4yisH5W0PuIzAQQy/a2IwBjaSxNnvyelN7tNX0FYq0BK6An5wRbhK4YmM+xw==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/plugin": "2.6.0", + "nullthrows": "^1.1.1", + "posthtml": "^0.16.5", + "posthtml-parser": "^0.10.1", + "posthtml-render": "^3.0.0", + "semver": "^5.7.1" + } + }, + "@parcel/ts-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/ts-utils/-/ts-utils-2.6.0.tgz", + "integrity": "sha512-U2Spr/vdOnxLzztXP6WpMO7JZTsaYO1G6F/cUTG5fReTQ0imM952FAc/WswpZWAPZqXqWCnvC/Z91JIkMDuYrA==", + "dev": true, + "requires": { + "nullthrows": "^1.1.1" + } + }, + "@parcel/types": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.6.0.tgz", + "integrity": "sha512-lAMYvOBfNEJMsPJ+plbB50305o0TwNrY1xX5RRIWBqwOa6bYmbW1ZljUk1tQvnkpIE4eAHQwnPR5Z2XWg18wGQ==", + "dev": true, + "requires": { + "@parcel/cache": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/source-map": "^2.0.0", + "@parcel/workers": "2.6.0", + "utility-types": "^3.10.0" + } + }, + "@parcel/utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.6.0.tgz", + "integrity": "sha512-ElXz+QHtT1JQIucbQJBk7SzAGoOlBp4yodEQVvTKS7GA+hEGrSP/cmibl6qm29Rjtd0zgQsdd+2XmP3xvP2gQQ==", + "dev": true, + "requires": { + "@parcel/codeframe": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/hash": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/markdown-ansi": "2.6.0", + "@parcel/source-map": "^2.0.0", + "chalk": "^4.1.0" + } + }, + "@parcel/validator-typescript": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/validator-typescript/-/validator-typescript-2.6.0.tgz", + "integrity": "sha512-NMroc+QPoTo436COHsqEQsn+Qd+7HE1s1X6he1Bqb+RMB4rZsvOZI22MgFj1eU5MpfYuM4zTID0Uz221hiS59w==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/plugin": "2.6.0", + "@parcel/ts-utils": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0" + } + }, + "@parcel/watcher": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.5.tgz", + "integrity": "sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw==", + "dev": true, + "requires": { + "node-addon-api": "^3.2.1", + "node-gyp-build": "^4.3.0" + } + }, + "@parcel/workers": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.6.0.tgz", + "integrity": "sha512-3tcI2LF5fd/WZtSnSjyWdDE+G+FitdNrRgSObzSp+axHKMAM23sO0z7KY8s2SYCF40msdYbFUW8eI6JlYNJoWQ==", + "dev": true, + "requires": { + "@parcel/diagnostic": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/types": "2.6.0", + "@parcel/utils": "2.6.0", + "chrome-trace-event": "^1.0.2", + "nullthrows": "^1.1.1" + } + }, + "@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==", + "dev": true + }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "browserslist": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz", + "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001349", + "electron-to-chromium": "^1.4.147", + "escalade": "^3.1.1", + "node-releases": "^2.0.5", + "picocolors": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001352", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", + "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, + "datastream-js": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/datastream-js/-/datastream-js-1.0.7.tgz", + "integrity": "sha512-rW7N3QkEx01reZ6/BF1s3sGnh1JdFpektwSqgUz8bmmvfmD+NNGTPhbTePZjs0B3VSizFX26Kr8qNtNJDz0NAQ==", + "requires": { + "text-encoding": "^0.6.4" + } + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "dependencies": { + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.150", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.150.tgz", + "integrity": "sha512-MP3oBer0X7ZeS9GJ0H6lmkn561UxiwOIY9TTkdxVY7lI9G6GVCKfgJaHaDcakwdKxBXA4T3ybeswH/WBIN/KTA==", + "dev": true + }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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==", + "dev": true + }, + "get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true + }, + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "htmlnano": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.2.tgz", + "integrity": "sha512-+ZrQFS4Ub+zd+/fWwfvoYCEGNEa0/zrpys6CyXxvZDwtL7Pl+pOtRkiujyvBQ7Lmfp7/iEPxtOFgxWA16Gkj3w==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.1", + "posthtml": "^0.16.5", + "timsort": "^0.3.0" + } + }, + "htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "lmdb": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.3.10.tgz", + "integrity": "sha512-GtH+nStn9V59CfYeQ5ddx6YTfuFCmu86UJojIjJAweG+/Fm0PDknuk3ovgYDtY/foMeMdZa8/P7oSljW/d5UPw==", + "dev": true, + "requires": { + "lmdb-darwin-arm64": "2.3.10", + "lmdb-darwin-x64": "2.3.10", + "lmdb-linux-arm": "2.3.10", + "lmdb-linux-arm64": "2.3.10", + "lmdb-linux-x64": "2.3.10", + "lmdb-win32-x64": "2.3.10", + "msgpackr": "^1.5.4", + "nan": "^2.14.2", + "node-addon-api": "^4.3.0", + "node-gyp-build-optional-packages": "^4.3.2", + "ordered-binary": "^1.2.4", + "weak-lru-cache": "^1.2.2" + }, + "dependencies": { + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, + "node-gyp-build-optional-packages": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-4.3.5.tgz", + "integrity": "sha512-5ke7D8SiQsTQL7CkHpfR1tLwfqtKc0KYEmlnkwd40jHCASskZeS98qoZ1qDUns2aUQWikcjidRUs6PM/3iyN/w==", + "dev": true + } + } + }, + "lmdb-darwin-x64": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/lmdb-darwin-x64/-/lmdb-darwin-x64-2.3.10.tgz", + "integrity": "sha512-gAc/1b/FZOb9yVOT+o0huA+hdW82oxLo5r22dFTLoRUFG1JMzxdTjmnW6ONVOHdqC9a5bt3vBCEY3jmXNqV26A==", + "dev": true, + "optional": true + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "msgpackr": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.6.1.tgz", + "integrity": "sha512-Je+xBEfdjtvA4bKaOv8iRhjC8qX2oJwpYH4f7JrG4uMVJVmnmkAT4pjKdbztKprGj3iwjcxPzb5umVZ02Qq3tA==", + "dev": true, + "requires": { + "msgpackr-extract": "^2.0.2" + } + }, + "msgpackr-extract": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-2.0.2.tgz", + "integrity": "sha512-coskCeJG2KDny23zWeu+6tNy7BLnAiOGgiwzlgdm4oeSsTpqEJJPguHIuKZcCdB7tzhZbXNYSg6jZAXkZErkJA==", + "dev": true, + "optional": true, + "requires": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "2.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "2.0.2", + "node-gyp-build-optional-packages": "5.0.2" + } + }, + "nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "dev": true + }, + "node-gyp-build-optional-packages": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz", + "integrity": "sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g==", + "dev": true, + "optional": true + }, + "node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true + }, + "ordered-binary": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.2.5.tgz", + "integrity": "sha512-djRmZoEpOGvIRW7ufsCDHtvcUa18UC9TxnPbHhSVFZHsoyg0dtut1bWtBZ/fmxdPN62oWXrV6adM7NoWU+CneA==", + "dev": true + }, + "parcel": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.6.0.tgz", + "integrity": "sha512-pSTJ7wC6uTl16PKLXQV7RfL9FGoIDA1iVpNvaav47n6UkUdKqfx0spcVPpw35kWdRcHJF61YAvkPjP2hTwHQ+Q==", + "dev": true, + "requires": { + "@parcel/config-default": "2.6.0", + "@parcel/core": "2.6.0", + "@parcel/diagnostic": "2.6.0", + "@parcel/events": "2.6.0", + "@parcel/fs": "2.6.0", + "@parcel/logger": "2.6.0", + "@parcel/package-manager": "2.6.0", + "@parcel/reporter-cli": "2.6.0", + "@parcel/reporter-dev-server": "2.6.0", + "@parcel/utils": "2.6.0", + "chalk": "^4.1.0", + "commander": "^7.0.0", + "get-port": "^4.2.0", + "v8-compile-cache": "^2.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "posthtml": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dev": true, + "requires": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "dependencies": { + "posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "requires": { + "htmlparser2": "^7.1.1" + } + } + } + }, + "posthtml-parser": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", + "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", + "dev": true, + "requires": { + "htmlparser2": "^7.1.1" + } + }, + "posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "requires": { + "is-json": "^2.0.1" + } + }, + "react-error-overlay": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", + "dev": true + }, + "react-refresh": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", + "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + } + }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true + }, + "terser": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz", + "integrity": "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "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 + }, + "typescript": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", + "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "dev": true + }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "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==", + "dev": true + }, + "weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "xxhash-wasm": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz", + "integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..59e63dd --- /dev/null +++ b/client/package.json @@ -0,0 +1,13 @@ +{ + "source": "src/index.html", + "scripts": { + "serve": "parcel serve --https --host localhost.warp.demo --port 4444", + "build": "parcel build", + "check": "tsc --noEmit" + }, + "devDependencies": { + "@parcel/validator-typescript": "^2.6.0", + "parcel": "^2.6.0", + "typescript": ">=3.0.0" + } +} diff --git a/client/src/index.html b/client/src/index.html new file mode 100644 index 0000000..f4f2aa6 --- /dev/null +++ b/client/src/index.html @@ -0,0 +1,82 @@ + + + + + + WARP + + + + + +
+
+
click to play
+ +
+ +
+ + +
+ +
+ +
+ + +
+
+
+ + + + diff --git a/client/src/init.ts b/client/src/init.ts new file mode 100644 index 0000000..6d258a7 --- /dev/null +++ b/client/src/init.ts @@ -0,0 +1,59 @@ +import { MP4New, MP4File, MP4ArrayBuffer, MP4Info } from "./mp4" + +export class InitParser { + mp4box: MP4File; + offset: number; + + raw: MP4ArrayBuffer[]; + ready: Promise; + + constructor() { + this.mp4box = MP4New() + + this.raw = [] + this.offset = 0 + + // Create a promise that gets resolved once the init segment has been parsed. + this.ready = new Promise((resolve, reject) => { + this.mp4box.onError = reject + + // https://github.com/gpac/mp4box.js#onreadyinfo + this.mp4box.onReady = (info: MP4Info) => { + if (!info.isFragmented) { + reject("expected a fragmented mp4") + } + + if (info.tracks.length != 1) { + reject("expected a single track") + } + + resolve({ + info: info, + raw: this.raw, + }) + } + }) + } + + push(data: Uint8Array) { + // Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately + let box = new Uint8Array(data.byteLength); + box.set(data) + + // and for some reason we need to modify the underlying ArrayBuffer with fileStart + let buffer = box.buffer as MP4ArrayBuffer + buffer.fileStart = this.offset + + // Parse the data + this.offset = this.mp4box.appendBuffer(buffer) + this.mp4box.flush() + + // Add the box to our queue of chunks + this.raw.push(buffer) + } +} + +export interface Init { + raw: MP4ArrayBuffer[]; + info: MP4Info; +} diff --git a/client/src/message.ts b/client/src/message.ts new file mode 100644 index 0000000..4173619 --- /dev/null +++ b/client/src/message.ts @@ -0,0 +1,14 @@ +export interface Message { + init?: MessageInit + segment?: MessageSegment +} + +export interface MessageInit { + id: number // integer id +} + +export interface MessageSegment { + init: number // integer id of the init segment + timestamp: number // presentation timestamp in milliseconds of the first sample + // TODO track would be nice +} diff --git a/client/src/mp4.ts b/client/src/mp4.ts new file mode 100644 index 0000000..c150364 --- /dev/null +++ b/client/src/mp4.ts @@ -0,0 +1,85 @@ +// Wrapper around MP4Box to play nicely with MP4Box. +// I tried getting a mp4box.all.d.ts file to work but just couldn't figure it out +import { createFile, ISOFile, DataStream, BoxParser } from "./mp4box.all" + +// Rename some stuff so it's on brand. +export { createFile as MP4New, ISOFile as MP4File, DataStream as MP4Stream, BoxParser as MP4Parser } + +export type MP4ArrayBuffer = ArrayBuffer & {fileStart: number}; + +export interface MP4MediaTrack { + id: number; + created: Date; + modified: Date; + movie_duration: number; + layer: number; + alternate_group: number; + volume: number; + track_width: number; + track_height: number; + timescale: number; + duration: number; + bitrate: number; + codec: string; + language: string; + nb_samples: number; +} + +export interface MP4VideoData { + width: number; + height: number; +} + +export interface MP4VideoTrack extends MP4MediaTrack { + video: MP4VideoData; +} + +export interface MP4AudioData { + sample_rate: number; + channel_count: number; + sample_size: number; +} + +export interface MP4AudioTrack extends MP4MediaTrack { + audio: MP4AudioData; +} + +export type MP4Track = MP4VideoTrack | MP4AudioTrack; + +export interface MP4Info { + duration: number; + timescale: number; + fragment_duration: number; + isFragmented: boolean; + isProgressive: boolean; + hasIOD: boolean; + brands: string[]; + created: Date; + modified: Date; + tracks: MP4Track[]; + mime: string; + videoTracks: MP4Track[]; + audioTracks: MP4Track[]; +} + +export interface MP4Sample { + number: number; + track_id: number; + timescale: number; + description_index: number; + description: any; + data: ArrayBuffer; + size: number; + alreadyRead: number; + duration: number; + cts: number; + dts: number; + is_sync: boolean; + is_leading: number; + depends_on: number; + is_depended_on: number; + has_redundancy: number; + degration_priority: number; + offset: number; + subsamples: any; +} diff --git a/client/src/mp4box.all.js b/client/src/mp4box.all.js new file mode 100644 index 0000000..4528833 --- /dev/null +++ b/client/src/mp4box.all.js @@ -0,0 +1,8247 @@ +// file:src/log.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var Log = (function (){ + var start = new Date(); + var LOG_LEVEL_ERROR = 4; + var LOG_LEVEL_WARNING = 3; + var LOG_LEVEL_INFO = 2; + var LOG_LEVEL_DEBUG = 1; + var log_level = LOG_LEVEL_ERROR; + var logObject = { + setLogLevel : function(level) { + if (level == this.debug) log_level = LOG_LEVEL_DEBUG; + else if (level == this.info) log_level = LOG_LEVEL_INFO; + else if (level == this.warn) log_level = LOG_LEVEL_WARNING; + else if (level == this.error) log_level = LOG_LEVEL_ERROR; + else log_level = LOG_LEVEL_ERROR; + }, + debug : function(module, msg) { + if (console.debug === undefined) { + console.debug = console.log; + } + if (LOG_LEVEL_DEBUG >= log_level) { + console.debug("["+Log.getDurationString(new Date()-start,1000)+"]","["+module+"]",msg); + } + }, + log : function(module, msg) { + this.debug(module.msg) + }, + info : function(module, msg) { + if (LOG_LEVEL_INFO >= log_level) { + console.info("["+Log.getDurationString(new Date()-start,1000)+"]","["+module+"]",msg); + } + }, + warn : function(module, msg) { + if (LOG_LEVEL_WARNING >= log_level) { + console.warn("["+Log.getDurationString(new Date()-start,1000)+"]","["+module+"]",msg); + } + }, + error : function(module, msg) { + if (LOG_LEVEL_ERROR >= log_level) { + console.error("["+Log.getDurationString(new Date()-start,1000)+"]","["+module+"]",msg); + } + } + }; + return logObject; + })(); + +/* Helper function to print a duration value in the form H:MM:SS.MS */ +Log.getDurationString = function(duration, _timescale) { + var neg; + /* Helper function to print a number on a fixed number of digits */ + function pad(number, length) { + var str = '' + number; + var a = str.split('.'); + while (a[0].length < length) { + a[0] = '0' + a[0]; + } + return a.join('.'); + } + if (duration < 0) { + neg = true; + duration = -duration; + } else { + neg = false; + } + var timescale = _timescale || 1; + var duration_sec = duration/timescale; + var hours = Math.floor(duration_sec/3600); + duration_sec -= hours * 3600; + var minutes = Math.floor(duration_sec/60); + duration_sec -= minutes * 60; + var msec = duration_sec*1000; + duration_sec = Math.floor(duration_sec); + msec -= duration_sec*1000; + msec = Math.floor(msec); + return (neg ? "-": "")+hours+":"+pad(minutes,2)+":"+pad(duration_sec,2)+"."+pad(msec,3); +} + +/* Helper function to stringify HTML5 TimeRanges objects */ +Log.printRanges = function(ranges) { + var length = ranges.length; + if (length > 0) { + var str = ""; + for (var i = 0; i < length; i++) { + if (i > 0) str += ","; + str += "["+Log.getDurationString(ranges.start(i))+ ","+Log.getDurationString(ranges.end(i))+"]"; + } + return str; + } else { + return "(empty)"; + } +} + +if (typeof exports !== 'undefined') { + exports.Log = Log; +} +// file:src/stream.js +var MP4BoxStream = function(arrayBuffer) { + if (arrayBuffer instanceof ArrayBuffer) { + this.buffer = arrayBuffer; + this.dataview = new DataView(arrayBuffer); + } else { + throw ("Needs an array buffer"); + } + this.position = 0; +}; + +/************************************************************************* + Common API between MultiBufferStream and SimpleStream + *************************************************************************/ +MP4BoxStream.prototype.getPosition = function() { + return this.position; +} + +MP4BoxStream.prototype.getEndPosition = function() { + return this.buffer.byteLength; +} + +MP4BoxStream.prototype.getLength = function() { + return this.buffer.byteLength; +} + +MP4BoxStream.prototype.seek = function (pos) { + var npos = Math.max(0, Math.min(this.buffer.byteLength, pos)); + this.position = (isNaN(npos) || !isFinite(npos)) ? 0 : npos; + return true; +} + +MP4BoxStream.prototype.isEos = function () { + return this.getPosition() >= this.getEndPosition(); +} + +/************************************************************************* + Read methods, simimar to DataStream but simpler + *************************************************************************/ +MP4BoxStream.prototype.readAnyInt = function(size, signed) { + var res = 0; + if (this.position + size <= this.buffer.byteLength) { + switch (size) { + case 1: + if (signed) { + res = this.dataview.getInt8(this.position); + } else { + res = this.dataview.getUint8(this.position); + } + break; + case 2: + if (signed) { + res = this.dataview.getInt16(this.position); + } else { + res = this.dataview.getUint16(this.position); + } + break; + case 3: + if (signed) { + throw ("No method for reading signed 24 bits values"); + } else { + res = this.dataview.getUint8(this.position) << 16; + res |= this.dataview.getUint8(this.position+1) << 8; + res |= this.dataview.getUint8(this.position+2); + } + break; + case 4: + if (signed) { + res = this.dataview.getInt32(this.position); + } else { + res = this.dataview.getUint32(this.position); + } + break; + case 8: + if (signed) { + throw ("No method for reading signed 64 bits values"); + } else { + res = this.dataview.getUint32(this.position) << 32; + res |= this.dataview.getUint32(this.position+4); + } + break; + default: + throw ("readInt method not implemented for size: "+size); + } + this.position+= size; + return res; + } else { + throw ("Not enough bytes in buffer"); + } +} + +MP4BoxStream.prototype.readUint8 = function() { + return this.readAnyInt(1, false); +} + +MP4BoxStream.prototype.readUint16 = function() { + return this.readAnyInt(2, false); +} + +MP4BoxStream.prototype.readUint24 = function() { + return this.readAnyInt(3, false); +} + +MP4BoxStream.prototype.readUint32 = function() { + return this.readAnyInt(4, false); +} + +MP4BoxStream.prototype.readUint64 = function() { + return this.readAnyInt(8, false); +} + +MP4BoxStream.prototype.readString = function(length) { + if (this.position + length <= this.buffer.byteLength) { + var s = ""; + for (var i = 0; i < length; i++) { + s += String.fromCharCode(this.readUint8()); + } + return s; + } else { + throw ("Not enough bytes in buffer"); + } +} + +MP4BoxStream.prototype.readCString = function() { + var arr = []; + while(true) { + var b = this.readUint8(); + if (b !== 0) { + arr.push(b); + } else { + break; + } + } + return String.fromCharCode.apply(null, arr); +} + +MP4BoxStream.prototype.readInt8 = function() { + return this.readAnyInt(1, true); +} + +MP4BoxStream.prototype.readInt16 = function() { + return this.readAnyInt(2, true); +} + +MP4BoxStream.prototype.readInt32 = function() { + return this.readAnyInt(4, true); +} + +MP4BoxStream.prototype.readInt64 = function() { + return this.readAnyInt(8, false); +} + +MP4BoxStream.prototype.readUint8Array = function(length) { + var arr = new Uint8Array(length); + for (var i = 0; i < length; i++) { + arr[i] = this.readUint8(); + } + return arr; +} + +MP4BoxStream.prototype.readInt16Array = function(length) { + var arr = new Int16Array(length); + for (var i = 0; i < length; i++) { + arr[i] = this.readInt16(); + } + return arr; +} + +MP4BoxStream.prototype.readUint16Array = function(length) { + var arr = new Int16Array(length); + for (var i = 0; i < length; i++) { + arr[i] = this.readUint16(); + } + return arr; +} + +MP4BoxStream.prototype.readUint32Array = function(length) { + var arr = new Uint32Array(length); + for (var i = 0; i < length; i++) { + arr[i] = this.readUint32(); + } + return arr; +} + +MP4BoxStream.prototype.readInt32Array = function(length) { + var arr = new Int32Array(length); + for (var i = 0; i < length; i++) { + arr[i] = this.readInt32(); + } + return arr; +} + +if (typeof exports !== 'undefined') { + exports.MP4BoxStream = MP4BoxStream; +}// file:src/DataStream.js +/** + DataStream reads scalars, arrays and structs of data from an ArrayBuffer. + It's like a file-like DataView on steroids. + + @param {ArrayBuffer} arrayBuffer ArrayBuffer to read from. + @param {?Number} byteOffset Offset from arrayBuffer beginning for the DataStream. + @param {?Boolean} endianness DataStream.BIG_ENDIAN or DataStream.LITTLE_ENDIAN (the default). + */ +var DataStream = function(arrayBuffer, byteOffset, endianness) { + this._byteOffset = byteOffset || 0; + if (arrayBuffer instanceof ArrayBuffer) { + this.buffer = arrayBuffer; + } else if (typeof arrayBuffer == "object") { + this.dataView = arrayBuffer; + if (byteOffset) { + this._byteOffset += byteOffset; + } + } else { + this.buffer = new ArrayBuffer(arrayBuffer || 0); + } + this.position = 0; + this.endianness = endianness == null ? DataStream.LITTLE_ENDIAN : endianness; +}; +DataStream.prototype = {}; + +DataStream.prototype.getPosition = function() { + return this.position; +} + +/** + Internal function to resize the DataStream buffer when required. + @param {number} extra Number of bytes to add to the buffer allocation. + @return {null} + */ +DataStream.prototype._realloc = function(extra) { + if (!this._dynamicSize) { + return; + } + var req = this._byteOffset + this.position + extra; + var blen = this._buffer.byteLength; + if (req <= blen) { + if (req > this._byteLength) { + this._byteLength = req; + } + return; + } + if (blen < 1) { + blen = 1; + } + while (req > blen) { + blen *= 2; + } + var buf = new ArrayBuffer(blen); + var src = new Uint8Array(this._buffer); + var dst = new Uint8Array(buf, 0, src.length); + dst.set(src); + this.buffer = buf; + this._byteLength = req; +}; + +/** + Internal function to trim the DataStream buffer when required. + Used for stripping out the extra bytes from the backing buffer when + the virtual byteLength is smaller than the buffer byteLength (happens after + growing the buffer with writes and not filling the extra space completely). + + @return {null} + */ +DataStream.prototype._trimAlloc = function() { + if (this._byteLength == this._buffer.byteLength) { + return; + } + var buf = new ArrayBuffer(this._byteLength); + var dst = new Uint8Array(buf); + var src = new Uint8Array(this._buffer, 0, dst.length); + dst.set(src); + this.buffer = buf; +}; + + +/** + Big-endian const to use as default endianness. + @type {boolean} + */ +DataStream.BIG_ENDIAN = false; + +/** + Little-endian const to use as default endianness. + @type {boolean} + */ +DataStream.LITTLE_ENDIAN = true; + +/** + Virtual byte length of the DataStream backing buffer. + Updated to be max of original buffer size and last written size. + If dynamicSize is false is set to buffer size. + @type {number} + */ +DataStream.prototype._byteLength = 0; + +/** + Returns the byte length of the DataStream object. + @type {number} + */ +Object.defineProperty(DataStream.prototype, 'byteLength', + { get: function() { + return this._byteLength - this._byteOffset; + }}); + +/** + Set/get the backing ArrayBuffer of the DataStream object. + The setter updates the DataView to point to the new buffer. + @type {Object} + */ +Object.defineProperty(DataStream.prototype, 'buffer', + { get: function() { + this._trimAlloc(); + return this._buffer; + }, + set: function(v) { + this._buffer = v; + this._dataView = new DataView(this._buffer, this._byteOffset); + this._byteLength = this._buffer.byteLength; + } }); + +/** + Set/get the byteOffset of the DataStream object. + The setter updates the DataView to point to the new byteOffset. + @type {number} + */ +Object.defineProperty(DataStream.prototype, 'byteOffset', + { get: function() { + return this._byteOffset; + }, + set: function(v) { + this._byteOffset = v; + this._dataView = new DataView(this._buffer, this._byteOffset); + this._byteLength = this._buffer.byteLength; + } }); + +/** + Set/get the backing DataView of the DataStream object. + The setter updates the buffer and byteOffset to point to the DataView values. + @type {Object} + */ +Object.defineProperty(DataStream.prototype, 'dataView', + { get: function() { + return this._dataView; + }, + set: function(v) { + this._byteOffset = v.byteOffset; + this._buffer = v.buffer; + this._dataView = new DataView(this._buffer, this._byteOffset); + this._byteLength = this._byteOffset + v.byteLength; + } }); + +/** + Sets the DataStream read/write position to given position. + Clamps between 0 and DataStream length. + + @param {number} pos Position to seek to. + @return {null} + */ +DataStream.prototype.seek = function(pos) { + var npos = Math.max(0, Math.min(this.byteLength, pos)); + this.position = (isNaN(npos) || !isFinite(npos)) ? 0 : npos; +}; + +/** + Returns true if the DataStream seek pointer is at the end of buffer and + there's no more data to read. + + @return {boolean} True if the seek pointer is at the end of the buffer. + */ +DataStream.prototype.isEof = function() { + return (this.position >= this._byteLength); +}; + + +/** + Maps a Uint8Array into the DataStream buffer. + + Nice for quickly reading in data. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Uint8Array to the DataStream backing buffer. + */ +DataStream.prototype.mapUint8Array = function(length) { + this._realloc(length * 1); + var arr = new Uint8Array(this._buffer, this.byteOffset+this.position, length); + this.position += length * 1; + return arr; +}; + + +/** + Reads an Int32Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Int32Array. + */ +DataStream.prototype.readInt32Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 4) : length; + var arr = new Int32Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads an Int16Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Int16Array. + */ +DataStream.prototype.readInt16Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 2) : length; + var arr = new Int16Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads an Int8Array of desired length from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Int8Array. + */ +DataStream.prototype.readInt8Array = function(length) { + length = length == null ? (this.byteLength-this.position) : length; + var arr = new Int8Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads a Uint32Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Uint32Array. + */ +DataStream.prototype.readUint32Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 4) : length; + var arr = new Uint32Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads a Uint16Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Uint16Array. + */ +DataStream.prototype.readUint16Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 2) : length; + var arr = new Uint16Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads a Uint8Array of desired length from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Uint8Array. + */ +DataStream.prototype.readUint8Array = function(length) { + length = length == null ? (this.byteLength-this.position) : length; + var arr = new Uint8Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads a Float64Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Float64Array. + */ +DataStream.prototype.readFloat64Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 8) : length; + var arr = new Float64Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + +/** + Reads a Float32Array of desired length and endianness from the DataStream. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} The read Float32Array. + */ +DataStream.prototype.readFloat32Array = function(length, e) { + length = length == null ? (this.byteLength-this.position / 4) : length; + var arr = new Float32Array(length); + DataStream.memcpy(arr.buffer, 0, + this.buffer, this.byteOffset+this.position, + length*arr.BYTES_PER_ELEMENT); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += arr.byteLength; + return arr; +}; + + +/** + Reads a 32-bit int from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readInt32 = function(e) { + var v = this._dataView.getInt32(this.position, e == null ? this.endianness : e); + this.position += 4; + return v; +}; + +/** + Reads a 16-bit int from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readInt16 = function(e) { + var v = this._dataView.getInt16(this.position, e == null ? this.endianness : e); + this.position += 2; + return v; +}; + +/** + Reads an 8-bit int from the DataStream. + + @return {number} The read number. + */ +DataStream.prototype.readInt8 = function() { + var v = this._dataView.getInt8(this.position); + this.position += 1; + return v; +}; + +/** + Reads a 32-bit unsigned int from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readUint32 = function(e) { + var v = this._dataView.getUint32(this.position, e == null ? this.endianness : e); + this.position += 4; + return v; +}; + +/** + Reads a 16-bit unsigned int from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readUint16 = function(e) { + var v = this._dataView.getUint16(this.position, e == null ? this.endianness : e); + this.position += 2; + return v; +}; + +/** + Reads an 8-bit unsigned int from the DataStream. + + @return {number} The read number. + */ +DataStream.prototype.readUint8 = function() { + var v = this._dataView.getUint8(this.position); + this.position += 1; + return v; +}; + +/** + Reads a 32-bit float from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readFloat32 = function(e) { + var v = this._dataView.getFloat32(this.position, e == null ? this.endianness : e); + this.position += 4; + return v; +}; + +/** + Reads a 64-bit float from the DataStream with the desired endianness. + + @param {?boolean} e Endianness of the number. + @return {number} The read number. + */ +DataStream.prototype.readFloat64 = function(e) { + var v = this._dataView.getFloat64(this.position, e == null ? this.endianness : e); + this.position += 8; + return v; +}; + +/** + Native endianness. Either DataStream.BIG_ENDIAN or DataStream.LITTLE_ENDIAN + depending on the platform endianness. + + @type {boolean} + */ +DataStream.endianness = new Int8Array(new Int16Array([1]).buffer)[0] > 0; + +/** + Copies byteLength bytes from the src buffer at srcOffset to the + dst buffer at dstOffset. + + @param {Object} dst Destination ArrayBuffer to write to. + @param {number} dstOffset Offset to the destination ArrayBuffer. + @param {Object} src Source ArrayBuffer to read from. + @param {number} srcOffset Offset to the source ArrayBuffer. + @param {number} byteLength Number of bytes to copy. + */ +DataStream.memcpy = function(dst, dstOffset, src, srcOffset, byteLength) { + var dstU8 = new Uint8Array(dst, dstOffset, byteLength); + var srcU8 = new Uint8Array(src, srcOffset, byteLength); + dstU8.set(srcU8); +}; + +/** + Converts array to native endianness in-place. + + @param {Object} array Typed array to convert. + @param {boolean} arrayIsLittleEndian True if the data in the array is + little-endian. Set false for big-endian. + @return {Object} The converted typed array. + */ +DataStream.arrayToNative = function(array, arrayIsLittleEndian) { + if (arrayIsLittleEndian == this.endianness) { + return array; + } else { + return this.flipArrayEndianness(array); + } +}; + +/** + Converts native endianness array to desired endianness in-place. + + @param {Object} array Typed array to convert. + @param {boolean} littleEndian True if the converted array should be + little-endian. Set false for big-endian. + @return {Object} The converted typed array. + */ +DataStream.nativeToEndian = function(array, littleEndian) { + if (this.endianness == littleEndian) { + return array; + } else { + return this.flipArrayEndianness(array); + } +}; + +/** + Flips typed array endianness in-place. + + @param {Object} array Typed array to flip. + @return {Object} The converted typed array. + */ +DataStream.flipArrayEndianness = function(array) { + var u8 = new Uint8Array(array.buffer, array.byteOffset, array.byteLength); + for (var i=0; ik; j--, k++) { + var tmp = u8[k]; + u8[k] = u8[j]; + u8[j] = tmp; + } + } + return array; +}; + +/** + Seek position where DataStream#readStruct ran into a problem. + Useful for debugging struct parsing. + + @type {number} + */ +DataStream.prototype.failurePosition = 0; + +String.fromCharCodeUint8 = function(uint8arr) { + var arr = []; + for (var i = 0; i < uint8arr.length; i++) { + arr[i] = uint8arr[i]; + } + return String.fromCharCode.apply(null, arr); +} +/** + Read a string of desired length and encoding from the DataStream. + + @param {number} length The length of the string to read in bytes. + @param {?string} encoding The encoding of the string data in the DataStream. + Defaults to ASCII. + @return {string} The read string. + */ +DataStream.prototype.readString = function(length, encoding) { + if (encoding == null || encoding == "ASCII") { + return String.fromCharCodeUint8.apply(null, [this.mapUint8Array(length == null ? this.byteLength-this.position : length)]); + } else { + return (new TextDecoder(encoding)).decode(this.mapUint8Array(length)); + } +}; + +/** + Read null-terminated string of desired length from the DataStream. Truncates + the returned string so that the null byte is not a part of it. + + @param {?number} length The length of the string to read. + @return {string} The read string. + */ +DataStream.prototype.readCString = function(length) { + var blen = this.byteLength-this.position; + var u8 = new Uint8Array(this._buffer, this._byteOffset + this.position); + var len = blen; + if (length != null) { + len = Math.min(length, blen); + } + for (var i = 0; i < len && u8[i] !== 0; i++); // find first zero byte + var s = String.fromCharCodeUint8.apply(null, [this.mapUint8Array(i)]); + if (length != null) { + this.position += len-i; + } else if (i != blen) { + this.position += 1; // trailing zero if not at end of buffer + } + return s; +}; + +/* + TODO: fix endianness for 24/64-bit fields + TODO: check range/support for 64-bits numbers in JavaScript +*/ +var MAX_SIZE = Math.pow(2, 32); + +DataStream.prototype.readInt64 = function () { + return (this.readInt32()*MAX_SIZE)+this.readUint32(); +} +DataStream.prototype.readUint64 = function () { + return (this.readUint32()*MAX_SIZE)+this.readUint32(); +} + +DataStream.prototype.readInt64 = function () { + return (this.readUint32()*MAX_SIZE)+this.readUint32(); +} + +DataStream.prototype.readUint24 = function () { + return (this.readUint8()<<16)+(this.readUint8()<<8)+this.readUint8(); +} + +if (typeof exports !== 'undefined') { + exports.DataStream = DataStream; +} +// file:src/DataStream-write.js +/** + Saves the DataStream contents to the given filename. + Uses Chrome's anchor download property to initiate download. + + @param {string} filename Filename to save as. + @return {null} + */ +DataStream.prototype.save = function(filename) { + var blob = new Blob([this.buffer]); + if (window.URL && URL.createObjectURL) { + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + // Required in Firefox: + document.body.appendChild(a); + a.setAttribute('href', url); + a.setAttribute('download', filename); + // Required in Firefox: + a.setAttribute('target', '_self'); + a.click(); + window.URL.revokeObjectURL(url); + } else { + throw("DataStream.save: Can't create object URL."); + } +}; + +/** + Whether to extend DataStream buffer when trying to write beyond its size. + If set, the buffer is reallocated to twice its current size until the + requested write fits the buffer. + @type {boolean} + */ +DataStream.prototype._dynamicSize = true; +Object.defineProperty(DataStream.prototype, 'dynamicSize', + { get: function() { + return this._dynamicSize; + }, + set: function(v) { + if (!v) { + this._trimAlloc(); + } + this._dynamicSize = v; + } }); + +/** + Internal function to trim the DataStream buffer when required. + Used for stripping out the first bytes when not needed anymore. + + @return {null} + */ +DataStream.prototype.shift = function(offset) { + var buf = new ArrayBuffer(this._byteLength-offset); + var dst = new Uint8Array(buf); + var src = new Uint8Array(this._buffer, offset, dst.length); + dst.set(src); + this.buffer = buf; + this.position -= offset; +}; + +/** + Writes an Int32Array of specified endianness to the DataStream. + + @param {Object} arr The array to write. + @param {?boolean} e Endianness of the data to write. + */ +DataStream.prototype.writeInt32Array = function(arr, e) { + this._realloc(arr.length * 4); + if (arr instanceof Int32Array && + this.byteOffset+this.position % arr.BYTES_PER_ELEMENT === 0) { + DataStream.memcpy(this._buffer, this.byteOffset+this.position, + arr.buffer, 0, + arr.byteLength); + this.mapInt32Array(arr.length, e); + } else { + for (var i=0; i>16); + this.writeUint8((v & 0x0000FF00)>>8); + this.writeUint8((v & 0x000000FF)); +} + +DataStream.prototype.adjustUint32 = function(position, value) { + var pos = this.position; + this.seek(position); + this.writeUint32(value); + this.seek(pos); +} +// file:src/DataStream-map.js +/** + Maps an Int32Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Int32Array to the DataStream backing buffer. + */ +DataStream.prototype.mapInt32Array = function(length, e) { + this._realloc(length * 4); + var arr = new Int32Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 4; + return arr; +}; + +/** + Maps an Int16Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Int16Array to the DataStream backing buffer. + */ +DataStream.prototype.mapInt16Array = function(length, e) { + this._realloc(length * 2); + var arr = new Int16Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 2; + return arr; +}; + +/** + Maps an Int8Array into the DataStream buffer. + + Nice for quickly reading in data. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Int8Array to the DataStream backing buffer. + */ +DataStream.prototype.mapInt8Array = function(length) { + this._realloc(length * 1); + var arr = new Int8Array(this._buffer, this.byteOffset+this.position, length); + this.position += length * 1; + return arr; +}; + +/** + Maps a Uint32Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Uint32Array to the DataStream backing buffer. + */ +DataStream.prototype.mapUint32Array = function(length, e) { + this._realloc(length * 4); + var arr = new Uint32Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 4; + return arr; +}; + +/** + Maps a Uint16Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Uint16Array to the DataStream backing buffer. + */ +DataStream.prototype.mapUint16Array = function(length, e) { + this._realloc(length * 2); + var arr = new Uint16Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 2; + return arr; +}; + +/** + Maps a Float64Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Float64Array to the DataStream backing buffer. + */ +DataStream.prototype.mapFloat64Array = function(length, e) { + this._realloc(length * 8); + var arr = new Float64Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 8; + return arr; +}; + +/** + Maps a Float32Array into the DataStream buffer, swizzling it to native + endianness in-place. The current offset from the start of the buffer needs to + be a multiple of element size, just like with typed array views. + + Nice for quickly reading in data. Warning: potentially modifies the buffer + contents. + + @param {number} length Number of elements to map. + @param {?boolean} e Endianness of the data to read. + @return {Object} Float32Array to the DataStream backing buffer. + */ +DataStream.prototype.mapFloat32Array = function(length, e) { + this._realloc(length * 4); + var arr = new Float32Array(this._buffer, this.byteOffset+this.position, length); + DataStream.arrayToNative(arr, e == null ? this.endianness : e); + this.position += length * 4; + return arr; +}; +// file:src/buffer.js +/** + * MultiBufferStream is a class that acts as a SimpleStream for parsing + * It holds several, possibly non-contiguous ArrayBuffer objects, each with a fileStart property + * containing the offset for the buffer data in an original/virtual file + * + * It inherits also from DataStream for all read/write/alloc operations + */ + +/** + * Constructor + */ +var MultiBufferStream = function(buffer) { + /* List of ArrayBuffers, with a fileStart property, sorted in fileStart order and non overlapping */ + this.buffers = []; + this.bufferIndex = -1; + if (buffer) { + this.insertBuffer(buffer); + this.bufferIndex = 0; + } +} +MultiBufferStream.prototype = new DataStream(new ArrayBuffer(), 0, DataStream.BIG_ENDIAN); + +/************************************************************************************ + Methods for the managnement of the buffers (insertion, removal, concatenation, ...) + ***********************************************************************************/ + +MultiBufferStream.prototype.initialized = function() { + var firstBuffer; + if (this.bufferIndex > -1) { + return true; + } else if (this.buffers.length > 0) { + firstBuffer = this.buffers[0]; + if (firstBuffer.fileStart === 0) { + this.buffer = firstBuffer; + this.bufferIndex = 0; + Log.debug("MultiBufferStream", "Stream ready for parsing"); + return true; + } else { + Log.warn("MultiBufferStream", "The first buffer should have a fileStart of 0"); + this.logBufferLevel(); + return false; + } + } else { + Log.warn("MultiBufferStream", "No buffer to start parsing from"); + this.logBufferLevel(); + return false; + } +} + +/** + * helper functions to concatenate two ArrayBuffer objects + * @param {ArrayBuffer} buffer1 + * @param {ArrayBuffer} buffer2 + * @return {ArrayBuffer} the concatenation of buffer1 and buffer2 in that order + */ +ArrayBuffer.concat = function(buffer1, buffer2) { + Log.debug("ArrayBuffer", "Trying to create a new buffer of size: "+(buffer1.byteLength + buffer2.byteLength)); + var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); + tmp.set(new Uint8Array(buffer1), 0); + tmp.set(new Uint8Array(buffer2), buffer1.byteLength); + return tmp.buffer; +}; + +/** + * Reduces the size of a given buffer, but taking the part between offset and offset+newlength + * @param {ArrayBuffer} buffer + * @param {Number} offset the start of new buffer + * @param {Number} newLength the length of the new buffer + * @return {ArrayBuffer} the new buffer + */ +MultiBufferStream.prototype.reduceBuffer = function(buffer, offset, newLength) { + var smallB; + smallB = new Uint8Array(newLength); + smallB.set(new Uint8Array(buffer, offset, newLength)); + smallB.buffer.fileStart = buffer.fileStart+offset; + smallB.buffer.usedBytes = 0; + return smallB.buffer; +} + +/** + * Inserts the new buffer in the sorted list of buffers, + * making sure, it is not overlapping with existing ones (possibly reducing its size). + * if the new buffer overrides/replaces the 0-th buffer (for instance because it is bigger), + * updates the DataStream buffer for parsing +*/ +MultiBufferStream.prototype.insertBuffer = function(ab) { + var to_add = true; + /* TODO: improve insertion if many buffers */ + for (var i = 0; i < this.buffers.length; i++) { + var b = this.buffers[i]; + if (ab.fileStart <= b.fileStart) { + /* the insertion position is found */ + if (ab.fileStart === b.fileStart) { + /* The new buffer overlaps with an existing buffer */ + if (ab.byteLength > b.byteLength) { + /* the new buffer is bigger than the existing one + remove the existing buffer and try again to insert + the new buffer to check overlap with the next ones */ + this.buffers.splice(i, 1); + i--; + continue; + } else { + /* the new buffer is smaller than the existing one, just drop it */ + Log.warn("MultiBufferStream", "Buffer (fileStart: "+ab.fileStart+" - Length: "+ab.byteLength+") already appended, ignoring"); + } + } else { + /* The beginning of the new buffer is not overlapping with an existing buffer + let's check the end of it */ + if (ab.fileStart + ab.byteLength <= b.fileStart) { + /* no overlap, we can add it as is */ + } else { + /* There is some overlap, cut the new buffer short, and add it*/ + ab = this.reduceBuffer(ab, 0, b.fileStart - ab.fileStart); + } + Log.debug("MultiBufferStream", "Appending new buffer (fileStart: "+ab.fileStart+" - Length: "+ab.byteLength+")"); + this.buffers.splice(i, 0, ab); + /* if this new buffer is inserted in the first place in the list of the buffer, + and the DataStream is initialized, make it the buffer used for parsing */ + if (i === 0) { + this.buffer = ab; + } + } + to_add = false; + break; + } else if (ab.fileStart < b.fileStart + b.byteLength) { + /* the new buffer overlaps its beginning with the end of the current buffer */ + var offset = b.fileStart + b.byteLength - ab.fileStart; + var newLength = ab.byteLength - offset; + if (newLength > 0) { + /* the new buffer is bigger than the current overlap, drop the overlapping part and try again inserting the remaining buffer */ + ab = this.reduceBuffer(ab, offset, newLength); + } else { + /* the content of the new buffer is entirely contained in the existing buffer, drop it entirely */ + to_add = false; + break; + } + } + } + /* if the buffer has not been added, we can add it at the end */ + if (to_add) { + Log.debug("MultiBufferStream", "Appending new buffer (fileStart: "+ab.fileStart+" - Length: "+ab.byteLength+")"); + this.buffers.push(ab); + /* if this new buffer is inserted in the first place in the list of the buffer, + and the DataStream is initialized, make it the buffer used for parsing */ + if (i === 0) { + this.buffer = ab; + } + } +} + +/** + * Displays the status of the buffers (number and used bytes) + * @param {Object} info callback method for display + */ +MultiBufferStream.prototype.logBufferLevel = function(info) { + var i; + var buffer; + var used, total; + var ranges = []; + var range; + var bufferedString = ""; + used = 0; + total = 0; + for (i = 0; i < this.buffers.length; i++) { + buffer = this.buffers[i]; + if (i === 0) { + range = {}; + ranges.push(range); + range.start = buffer.fileStart; + range.end = buffer.fileStart+buffer.byteLength; + bufferedString += "["+range.start+"-"; + } else if (range.end === buffer.fileStart) { + range.end = buffer.fileStart+buffer.byteLength; + } else { + range = {}; + range.start = buffer.fileStart; + bufferedString += (ranges[ranges.length-1].end-1)+"], ["+range.start+"-"; + range.end = buffer.fileStart+buffer.byteLength; + ranges.push(range); + } + used += buffer.usedBytes; + total += buffer.byteLength; + } + if (ranges.length > 0) { + bufferedString += (range.end-1)+"]"; + } + var log = (info ? Log.info : Log.debug) + if (this.buffers.length === 0) { + log("MultiBufferStream", "No more buffer in memory"); + } else { + log("MultiBufferStream", ""+this.buffers.length+" stored buffer(s) ("+used+"/"+total+" bytes), continuous ranges: "+bufferedString); + } +} + +MultiBufferStream.prototype.cleanBuffers = function () { + var i; + var buffer; + for (i = 0; i < this.buffers.length; i++) { + buffer = this.buffers[i]; + if (buffer.usedBytes === buffer.byteLength) { + Log.debug("MultiBufferStream", "Removing buffer #"+i); + this.buffers.splice(i, 1); + i--; + } + } +} + +MultiBufferStream.prototype.mergeNextBuffer = function() { + var next_buffer; + if (this.bufferIndex+1 < this.buffers.length) { + next_buffer = this.buffers[this.bufferIndex+1]; + if (next_buffer.fileStart === this.buffer.fileStart + this.buffer.byteLength) { + var oldLength = this.buffer.byteLength; + var oldUsedBytes = this.buffer.usedBytes; + var oldFileStart = this.buffer.fileStart; + this.buffers[this.bufferIndex] = ArrayBuffer.concat(this.buffer, next_buffer); + this.buffer = this.buffers[this.bufferIndex]; + this.buffers.splice(this.bufferIndex+1, 1); + this.buffer.usedBytes = oldUsedBytes; /* TODO: should it be += ? */ + this.buffer.fileStart = oldFileStart; + Log.debug("ISOFile", "Concatenating buffer for box parsing (length: "+oldLength+"->"+this.buffer.byteLength+")"); + return true; + } else { + return false; + } + } else { + return false; + } +} + + +/************************************************************************* + Seek-related functions + *************************************************************************/ + +/** + * Finds the buffer that holds the given file position + * @param {Boolean} fromStart indicates if the search should start from the current buffer (false) + * or from the first buffer (true) + * @param {Number} filePosition position in the file to seek to + * @param {Boolean} markAsUsed indicates if the bytes in between the current position and the seek position + * should be marked as used for garbage collection + * @return {Number} the index of the buffer holding the seeked file position, -1 if not found. + */ +MultiBufferStream.prototype.findPosition = function(fromStart, filePosition, markAsUsed) { + var i; + var abuffer = null; + var index = -1; + + /* find the buffer with the largest position smaller than the given position */ + if (fromStart === true) { + /* the reposition can be in the past, we need to check from the beginning of the list of buffers */ + i = 0; + } else { + i = this.bufferIndex; + } + + while (i < this.buffers.length) { + abuffer = this.buffers[i]; + if (abuffer.fileStart <= filePosition) { + index = i; + if (markAsUsed) { + if (abuffer.fileStart + abuffer.byteLength <= filePosition) { + abuffer.usedBytes = abuffer.byteLength; + } else { + abuffer.usedBytes = filePosition - abuffer.fileStart; + } + this.logBufferLevel(); + } + } else { + break; + } + i++; + } + + if (index !== -1) { + abuffer = this.buffers[index]; + if (abuffer.fileStart + abuffer.byteLength >= filePosition) { + Log.debug("MultiBufferStream", "Found position in existing buffer #"+index); + return index; + } else { + return -1; + } + } else { + return -1; + } +} + +/** + * Finds the largest file position contained in a buffer or in the next buffers if they are contiguous (no gap) + * starting from the given buffer index or from the current buffer if the index is not given + * + * @param {Number} inputindex Index of the buffer to start from + * @return {Number} The largest file position found in the buffers + */ +MultiBufferStream.prototype.findEndContiguousBuf = function(inputindex) { + var i; + var currentBuf; + var nextBuf; + var index = (inputindex !== undefined ? inputindex : this.bufferIndex); + currentBuf = this.buffers[index]; + /* find the end of the contiguous range of data */ + if (this.buffers.length > index+1) { + for (i = index+1; i < this.buffers.length; i++) { + nextBuf = this.buffers[i]; + if (nextBuf.fileStart === currentBuf.fileStart + currentBuf.byteLength) { + currentBuf = nextBuf; + } else { + break; + } + } + } + /* return the position of last byte in the file that we have */ + return currentBuf.fileStart + currentBuf.byteLength; +} + +/** + * Returns the largest file position contained in the buffers, larger than the given position + * @param {Number} pos the file position to start from + * @return {Number} the largest position in the current buffer or in the buffer and the next contiguous + * buffer that holds the given position + */ +MultiBufferStream.prototype.getEndFilePositionAfter = function(pos) { + var index = this.findPosition(true, pos, false); + if (index !== -1) { + return this.findEndContiguousBuf(index); + } else { + return pos; + } +} + +/************************************************************************* + Garbage collection related functions + *************************************************************************/ + +/** + * Marks a given number of bytes as used in the current buffer for garbage collection + * @param {Number} nbBytes + */ +MultiBufferStream.prototype.addUsedBytes = function(nbBytes) { + this.buffer.usedBytes += nbBytes; + this.logBufferLevel(); +} + +/** + * Marks the entire current buffer as used, ready for garbage collection + */ +MultiBufferStream.prototype.setAllUsedBytes = function() { + this.buffer.usedBytes = this.buffer.byteLength; + this.logBufferLevel(); +} + +/************************************************************************* + Common API between MultiBufferStream and SimpleStream + *************************************************************************/ + +/** + * Tries to seek to a given file position + * if possible, repositions the parsing from there and returns true + * if not possible, does not change anything and returns false + * @param {Number} filePosition position in the file to seek to + * @param {Boolean} fromStart indicates if the search should start from the current buffer (false) + * or from the first buffer (true) + * @param {Boolean} markAsUsed indicates if the bytes in between the current position and the seek position + * should be marked as used for garbage collection + * @return {Boolean} true if the seek succeeded, false otherwise + */ +MultiBufferStream.prototype.seek = function(filePosition, fromStart, markAsUsed) { + var index; + index = this.findPosition(fromStart, filePosition, markAsUsed); + if (index !== -1) { + this.buffer = this.buffers[index]; + this.bufferIndex = index; + this.position = filePosition - this.buffer.fileStart; + Log.debug("MultiBufferStream", "Repositioning parser at buffer position: "+this.position); + return true; + } else { + Log.debug("MultiBufferStream", "Position "+filePosition+" not found in buffered data"); + return false; + } +} + +/** + * Returns the current position in the file + * @return {Number} the position in the file + */ +MultiBufferStream.prototype.getPosition = function() { + if (this.bufferIndex === -1 || this.buffers[this.bufferIndex] === null) { + throw "Error accessing position in the MultiBufferStream"; + } + return this.buffers[this.bufferIndex].fileStart+this.position; +} + +/** + * Returns the length of the current buffer + * @return {Number} the length of the current buffer + */ +MultiBufferStream.prototype.getLength = function() { + return this.byteLength; +} + +MultiBufferStream.prototype.getEndPosition = function() { + if (this.bufferIndex === -1 || this.buffers[this.bufferIndex] === null) { + throw "Error accessing position in the MultiBufferStream"; + } + return this.buffers[this.bufferIndex].fileStart+this.byteLength; +} + +if (typeof exports !== 'undefined') { + exports.MultiBufferStream = MultiBufferStream; +}// file:src/descriptor.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var MPEG4DescriptorParser = function () { + var ES_DescrTag = 0x03; + var DecoderConfigDescrTag = 0x04; + var DecSpecificInfoTag = 0x05; + var SLConfigDescrTag = 0x06; + + var descTagToName = []; + descTagToName[ES_DescrTag] = "ES_Descriptor"; + descTagToName[DecoderConfigDescrTag] = "DecoderConfigDescriptor"; + descTagToName[DecSpecificInfoTag] = "DecoderSpecificInfo"; + descTagToName[SLConfigDescrTag] = "SLConfigDescriptor"; + + this.getDescriptorName = function(tag) { + return descTagToName[tag]; + } + + var that = this; + var classes = {}; + + this.parseOneDescriptor = function (stream) { + var hdrSize = 0; + var size = 0; + var tag; + var desc; + var byteRead; + tag = stream.readUint8(); + hdrSize++; + byteRead = stream.readUint8(); + hdrSize++; + while (byteRead & 0x80) { + size = (byteRead & 0x7F)<<7; + byteRead = stream.readUint8(); + hdrSize++; + } + size += byteRead & 0x7F; + Log.debug("MPEG4DescriptorParser", "Found "+(descTagToName[tag] || "Descriptor "+tag)+", size "+size+" at position "+stream.getPosition()); + if (descTagToName[tag]) { + desc = new classes[descTagToName[tag]](size); + } else { + desc = new classes.Descriptor(size); + } + desc.parse(stream); + return desc; + } + + classes.Descriptor = function(_tag, _size) { + this.tag = _tag; + this.size = _size; + this.descs = []; + } + + classes.Descriptor.prototype.parse = function (stream) { + this.data = stream.readUint8Array(this.size); + } + + classes.Descriptor.prototype.findDescriptor = function (tag) { + for (var i = 0; i < this.descs.length; i++) { + if (this.descs[i].tag == tag) { + return this.descs[i]; + } + } + return null; + } + + classes.Descriptor.prototype.parseRemainingDescriptors = function (stream) { + var start = stream.position; + while (stream.position < start+this.size) { + var desc = that.parseOneDescriptor(stream); + this.descs.push(desc); + } + } + + classes.ES_Descriptor = function (size) { + classes.Descriptor.call(this, ES_DescrTag, size); + } + + classes.ES_Descriptor.prototype = new classes.Descriptor(); + + classes.ES_Descriptor.prototype.parse = function(stream) { + this.ES_ID = stream.readUint16(); + this.flags = stream.readUint8(); + this.size -= 3; + if (this.flags & 0x80) { + this.dependsOn_ES_ID = stream.readUint16(); + this.size -= 2; + } else { + this.dependsOn_ES_ID = 0; + } + if (this.flags & 0x40) { + var l = stream.readUint8(); + this.URL = stream.readString(l); + this.size -= l+1; + } else { + this.URL = ""; + } + if (this.flags & 0x20) { + this.OCR_ES_ID = stream.readUint16(); + this.size -= 2; + } else { + this.OCR_ES_ID = 0; + } + this.parseRemainingDescriptors(stream); + } + + classes.ES_Descriptor.prototype.getOTI = function(stream) { + var dcd = this.findDescriptor(DecoderConfigDescrTag); + if (dcd) { + return dcd.oti; + } else { + return 0; + } + } + + classes.ES_Descriptor.prototype.getAudioConfig = function(stream) { + var dcd = this.findDescriptor(DecoderConfigDescrTag); + if (!dcd) return null; + var dsi = dcd.findDescriptor(DecSpecificInfoTag); + if (dsi && dsi.data) { + var audioObjectType = (dsi.data[0]& 0xF8) >> 3; + if (audioObjectType === 31 && dsi.data.length >= 2) { + audioObjectType = 32 + ((dsi.data[0] & 0x7) << 3) + ((dsi.data[1] & 0xE0) >> 5); + } + return audioObjectType; + } else { + return null; + } + } + + classes.DecoderConfigDescriptor = function (size) { + classes.Descriptor.call(this, DecoderConfigDescrTag, size); + } + classes.DecoderConfigDescriptor.prototype = new classes.Descriptor(); + + classes.DecoderConfigDescriptor.prototype.parse = function(stream) { + this.oti = stream.readUint8(); + this.streamType = stream.readUint8(); + this.bufferSize = stream.readUint24(); + this.maxBitrate = stream.readUint32(); + this.avgBitrate = stream.readUint32(); + this.size -= 13; + this.parseRemainingDescriptors(stream); + } + + classes.DecoderSpecificInfo = function (size) { + classes.Descriptor.call(this, DecSpecificInfoTag, size); + } + classes.DecoderSpecificInfo.prototype = new classes.Descriptor(); + + classes.SLConfigDescriptor = function (size) { + classes.Descriptor.call(this, SLConfigDescrTag, size); + } + classes.SLConfigDescriptor.prototype = new classes.Descriptor(); + + return this; +} + +if (typeof exports !== 'undefined') { + exports.MPEG4DescriptorParser = MPEG4DescriptorParser; +}// file:src/box.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var BoxParser = { + ERR_INVALID_DATA : -1, + ERR_NOT_ENOUGH_DATA : 0, + OK : 1, + + // Boxes to be created with default parsing + BASIC_BOXES: [ "mdat", "idat", "free", "skip", "meco", "strk" ], + FULL_BOXES: [ "hmhd", "nmhd", "iods", "xml ", "bxml", "ipro", "mere" ], + CONTAINER_BOXES: [ + [ "moov", [ "trak", "pssh" ] ], + [ "trak" ], + [ "edts" ], + [ "mdia" ], + [ "minf" ], + [ "dinf" ], + [ "stbl", [ "sgpd", "sbgp" ] ], + [ "mvex", [ "trex" ] ], + [ "moof", [ "traf" ] ], + [ "traf", [ "trun", "sgpd", "sbgp" ] ], + [ "vttc" ], + [ "tref" ], + [ "iref" ], + [ "mfra", [ "tfra" ] ], + [ "meco" ], + [ "hnti" ], + [ "hinf" ], + [ "strk" ], + [ "strd" ], + [ "sinf" ], + [ "rinf" ], + [ "schi" ], + [ "trgr" ], + [ "udta", ["kind"] ], + [ "iprp", ["ipma"] ], + [ "ipco"] + ], + // Boxes effectively created + boxCodes : [], + fullBoxCodes : [], + containerBoxCodes : [], + sampleEntryCodes : {}, + sampleGroupEntryCodes: [], + trackGroupTypes: [], + UUIDBoxes: {}, + UUIDs: [], + initialize: function() { + BoxParser.FullBox.prototype = new BoxParser.Box(); + BoxParser.ContainerBox.prototype = new BoxParser.Box(); + BoxParser.SampleEntry.prototype = new BoxParser.Box(); + BoxParser.TrackGroupTypeBox.prototype = new BoxParser.FullBox(); + + /* creating constructors for simple boxes */ + BoxParser.BASIC_BOXES.forEach(function(type) { + BoxParser.createBoxCtor(type) + }); + BoxParser.FULL_BOXES.forEach(function(type) { + BoxParser.createFullBoxCtor(type); + }); + BoxParser.CONTAINER_BOXES.forEach(function(types) { + BoxParser.createContainerBoxCtor(types[0], null, types[1]); + }); + }, + Box: function(_type, _size, _uuid) { + this.type = _type; + this.size = _size; + this.uuid = _uuid; + }, + FullBox: function(type, size, uuid) { + BoxParser.Box.call(this, type, size, uuid); + this.flags = 0; + this.version = 0; + }, + ContainerBox: function(type, size, uuid) { + BoxParser.Box.call(this, type, size, uuid); + this.boxes = []; + }, + SampleEntry: function(type, size, hdr_size, start) { + BoxParser.ContainerBox.call(this, type, size); + this.hdr_size = hdr_size; + this.start = start; + }, + SampleGroupEntry: function(type) { + this.grouping_type = type; + }, + TrackGroupTypeBox: function(type, size) { + BoxParser.FullBox.call(this, type, size); + }, + createBoxCtor: function(type, parseMethod){ + BoxParser.boxCodes.push(type); + BoxParser[type+"Box"] = function(size) { + BoxParser.Box.call(this, type, size); + } + BoxParser[type+"Box"].prototype = new BoxParser.Box(); + if (parseMethod) BoxParser[type+"Box"].prototype.parse = parseMethod; + }, + createFullBoxCtor: function(type, parseMethod) { + //BoxParser.fullBoxCodes.push(type); + BoxParser[type+"Box"] = function(size) { + BoxParser.FullBox.call(this, type, size); + } + BoxParser[type+"Box"].prototype = new BoxParser.FullBox(); + BoxParser[type+"Box"].prototype.parse = function(stream) { + this.parseFullHeader(stream); + if (parseMethod) { + parseMethod.call(this, stream); + } + }; + }, + addSubBoxArrays: function(subBoxNames) { + if (subBoxNames) { + this.subBoxNames = subBoxNames; + var nbSubBoxes = subBoxNames.length; + for (var k = 0; k parentSize) { + Log.error("BoxParser", "Box of type '"+type+"' has a size "+size+" greater than its container size "+parentSize); + return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size, start: start }; + } + if (size !== 0 && start + size > stream.getEndPosition()) { + stream.seek(start); + Log.info("BoxParser", "Not enough data in stream to parse the entire '"+type+"' box"); + return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size, start: start }; + } + if (headerOnly) { + return { code: BoxParser.OK, type: type, size: size, hdr_size: hdr_size, start: start }; + } else { + if (BoxParser[type+"Box"]) { + box = new BoxParser[type+"Box"](size); + } else { + if (type !== "uuid") { + Log.warn("BoxParser", "Unknown box type: '"+type+"'"); + box = new BoxParser.Box(type, size); + box.has_unparsed_data = true; + } else { + if (BoxParser.UUIDBoxes[uuid]) { + box = new BoxParser.UUIDBoxes[uuid](size); + } else { + Log.warn("BoxParser", "Unknown uuid type: '"+uuid+"'"); + box = new BoxParser.Box(type, size); + box.uuid = uuid; + box.has_unparsed_data = true; + } + } + } + } + box.hdr_size = hdr_size; + /* recording the position of the box in the input stream */ + box.start = start; + if (box.write === BoxParser.Box.prototype.write && box.type !== "mdat") { + Log.info("BoxParser", "'"+box_type+"' box writing not yet implemented, keeping unparsed data in memory for later write"); + box.parseDataAndRewind(stream); + } + box.parse(stream); + diff = stream.getPosition() - (box.start+box.size); + if (diff < 0) { + Log.warn("BoxParser", "Parsing of box '"+box_type+"' did not read the entire indicated box data size (missing "+(-diff)+" bytes), seeking forward"); + stream.seek(box.start+box.size); + } else if (diff > 0) { + Log.error("BoxParser", "Parsing of box '"+box_type+"' read "+diff+" more bytes than the indicated box data size, seeking backwards"); + if (box.size !== 0) stream.seek(box.start+box.size); + } + return { code: BoxParser.OK, box: box, size: box.size }; +} + +BoxParser.Box.prototype.parse = function(stream) { + if (this.type != "mdat") { + this.data = stream.readUint8Array(this.size-this.hdr_size); + } else { + if (this.size === 0) { + stream.seek(stream.getEndPosition()); + } else { + stream.seek(this.start+this.size); + } + } +} + +/* Used to parse a box without consuming its data, to allow detailled parsing + Useful for boxes for which a write method is not yet implemented */ +BoxParser.Box.prototype.parseDataAndRewind = function(stream) { + this.data = stream.readUint8Array(this.size-this.hdr_size); + // rewinding + stream.position -= this.size-this.hdr_size; +} + +BoxParser.FullBox.prototype.parseDataAndRewind = function(stream) { + this.parseFullHeader(stream); + this.data = stream.readUint8Array(this.size-this.hdr_size); + // restore the header size as if the full header had not been parsed + this.hdr_size -= 4; + // rewinding + stream.position -= this.size-this.hdr_size; +} + +BoxParser.FullBox.prototype.parseFullHeader = function (stream) { + this.version = stream.readUint8(); + this.flags = stream.readUint24(); + this.hdr_size += 4; +} + +BoxParser.FullBox.prototype.parse = function (stream) { + this.parseFullHeader(stream); + this.data = stream.readUint8Array(this.size-this.hdr_size); +} + +BoxParser.ContainerBox.prototype.parse = function(stream) { + var ret; + var box; + while (stream.getPosition() < this.start+this.size) { + ret = BoxParser.parseOneBox(stream, false, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + box = ret.box; + /* store the box in the 'boxes' array to preserve box order (for offset) but also store box in a property for more direct access */ + this.boxes.push(box); + if (this.subBoxNames && this.subBoxNames.indexOf(box.type) != -1) { + this[this.subBoxNames[this.subBoxNames.indexOf(box.type)]+"s"].push(box); + } else { + var box_type = box.type !== "uuid" ? box.type : box.uuid; + if (this[box_type]) { + Log.warn("Box of type "+box_type+" already stored in field of this type"); + } else { + this[box_type] = box; + } + } + } else { + return; + } + } +} + +BoxParser.Box.prototype.parseLanguage = function(stream) { + this.language = stream.readUint16(); + var chars = []; + chars[0] = (this.language>>10)&0x1F; + chars[1] = (this.language>>5)&0x1F; + chars[2] = (this.language)&0x1F; + this.languageString = String.fromCharCode(chars[0]+0x60, chars[1]+0x60, chars[2]+0x60); +} + +// file:src/parsing/sampleentries/sampleentry.js +BoxParser.SAMPLE_ENTRY_TYPE_VISUAL = "Visual"; +BoxParser.SAMPLE_ENTRY_TYPE_AUDIO = "Audio"; +BoxParser.SAMPLE_ENTRY_TYPE_HINT = "Hint"; +BoxParser.SAMPLE_ENTRY_TYPE_METADATA = "Metadata"; +BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE = "Subtitle"; +BoxParser.SAMPLE_ENTRY_TYPE_SYSTEM = "System"; +BoxParser.SAMPLE_ENTRY_TYPE_TEXT = "Text"; + +BoxParser.SampleEntry.prototype.parseHeader = function(stream) { + stream.readUint8Array(6); + this.data_reference_index = stream.readUint16(); + this.hdr_size += 8; +} + +BoxParser.SampleEntry.prototype.parse = function(stream) { + this.parseHeader(stream); + this.data = stream.readUint8Array(this.size - this.hdr_size); +} + +BoxParser.SampleEntry.prototype.parseDataAndRewind = function(stream) { + this.parseHeader(stream); + this.data = stream.readUint8Array(this.size - this.hdr_size); + // restore the header size as if the sample entry header had not been parsed + this.hdr_size -= 8; + // rewinding + stream.position -= this.size-this.hdr_size; +} + +BoxParser.SampleEntry.prototype.parseFooter = function(stream) { + BoxParser.ContainerBox.prototype.parse.call(this, stream); +} + +// Base SampleEntry types with default parsing +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_HINT); +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_METADATA); +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE); +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SYSTEM); +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_TEXT); + +//Base SampleEntry types for Audio and Video with specific parsing +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, function(stream) { + var compressorname_length; + this.parseHeader(stream); + stream.readUint16(); + stream.readUint16(); + stream.readUint32Array(3); + this.width = stream.readUint16(); + this.height = stream.readUint16(); + this.horizresolution = stream.readUint32(); + this.vertresolution = stream.readUint32(); + stream.readUint32(); + this.frame_count = stream.readUint16(); + compressorname_length = Math.min(31, stream.readUint8()); + this.compressorname = stream.readString(compressorname_length); + if (compressorname_length < 31) { + stream.readString(31 - compressorname_length); + } + this.depth = stream.readUint16(); + stream.readUint16(); + this.parseFooter(stream); +}); + +BoxParser.createMediaSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, function(stream) { + this.parseHeader(stream); + stream.readUint32Array(2); + this.channel_count = stream.readUint16(); + this.samplesize = stream.readUint16(); + stream.readUint16(); + stream.readUint16(); + this.samplerate = (stream.readUint32()/(1<<16)); + this.parseFooter(stream); +}); + +// Sample entries inheriting from Audio and Video +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "avc1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "avc2"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "avc3"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "avc4"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "av01"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "hvc1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "hev1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vvc1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vvi1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vvs1"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vvcN"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vp08"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "vp09"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, "mp4a"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, "ac-3"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, "ec-3"); +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, "Opus"); + +// Encrypted sample entries +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_VISUAL, "encv"); +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_AUDIO, "enca"); +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE, "encu"); +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SYSTEM, "encs"); +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_TEXT, "enct"); +BoxParser.createEncryptedSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_METADATA, "encm"); +// file:src/parsing/a1lx.js +BoxParser.createBoxCtor("a1lx", function(stream) { + var large_size = stream.readUint8() & 1; + var FieldLength = ((large_size & 1) + 1) * 16; + this.layer_size = []; + for (var i = 0; i < 3; i++) { + if (FieldLength == 16) { + this.layer_size[i] = stream.readUint16(); + } else { + this.layer_size[i] = stream.readUint32(); + } + } +});// file:src/parsing/a1op.js +BoxParser.createBoxCtor("a1op", function(stream) { + this.op_index = stream.readUint8(); +});// file:src/parsing/auxC.js +BoxParser.createFullBoxCtor("auxC", function(stream) { + this.aux_type = stream.readCString(); + var aux_subtype_length = this.size - this.hdr_size - (this.aux_type.length + 1); + this.aux_subtype = stream.readUint8Array(aux_subtype_length); +});// file:src/parsing/av1C.js +BoxParser.createBoxCtor("av1C", function(stream) { + var i; + var toparse; + var tmp = stream.readUint8(); + if ((tmp >> 7) & 0x1 !== 1) { + Log.error("av1C marker problem"); + return; + } + this.version = tmp & 0x7F; + if (this.version !== 1) { + Log.error("av1C version "+this.version+" not supported"); + return; + } + tmp = stream.readUint8(); + this.seq_profile = (tmp >> 5) & 0x7; + this.seq_level_idx_0 = tmp & 0x1F; + tmp = stream.readUint8(); + this.seq_tier_0 = (tmp >> 7) & 0x1; + this.high_bitdepth = (tmp >> 6) & 0x1; + this.twelve_bit = (tmp >> 5) & 0x1; + this.monochrome = (tmp >> 4) & 0x1; + this.chroma_subsampling_x = (tmp >> 3) & 0x1; + this.chroma_subsampling_y = (tmp >> 2) & 0x1; + this.chroma_sample_position = (tmp & 0x3); + tmp = stream.readUint8(); + this.reserved_1 = (tmp >> 5) & 0x7; + if (this.reserved_1 !== 0) { + Log.error("av1C reserved_1 parsing problem"); + return; + } + this.initial_presentation_delay_present = (tmp >> 4) & 0x1; + if (this.initial_presentation_delay_present === 1) { + this.initial_presentation_delay_minus_one = (tmp & 0xF); + } else { + this.reserved_2 = (tmp & 0xF); + if (this.reserved_2 !== 0) { + Log.error("av1C reserved_2 parsing problem"); + return; + } + } + + var configOBUs_length = this.size - this.hdr_size - 4; + this.configOBUs = stream.readUint8Array(configOBUs_length); +}); + +// file:src/parsing/avcC.js +BoxParser.createBoxCtor("avcC", function(stream) { + var i; + var toparse; + this.configurationVersion = stream.readUint8(); + this.AVCProfileIndication = stream.readUint8(); + this.profile_compatibility = stream.readUint8(); + this.AVCLevelIndication = stream.readUint8(); + this.lengthSizeMinusOne = (stream.readUint8() & 0x3); + this.nb_SPS_nalus = (stream.readUint8() & 0x1F); + toparse = this.size - this.hdr_size - 6; + this.SPS = []; + for (i = 0; i < this.nb_SPS_nalus; i++) { + this.SPS[i] = {}; + this.SPS[i].length = stream.readUint16(); + this.SPS[i].nalu = stream.readUint8Array(this.SPS[i].length); + toparse -= 2+this.SPS[i].length; + } + this.nb_PPS_nalus = stream.readUint8(); + toparse--; + this.PPS = []; + for (i = 0; i < this.nb_PPS_nalus; i++) { + this.PPS[i] = {}; + this.PPS[i].length = stream.readUint16(); + this.PPS[i].nalu = stream.readUint8Array(this.PPS[i].length); + toparse -= 2+this.PPS[i].length; + } + if (toparse>0) { + this.ext = stream.readUint8Array(toparse); + } +}); + +// file:src/parsing/btrt.js +BoxParser.createBoxCtor("btrt", function(stream) { + this.bufferSizeDB = stream.readUint32(); + this.maxBitrate = stream.readUint32(); + this.avgBitrate = stream.readUint32(); +}); + +// file:src/parsing/clap.js +BoxParser.createBoxCtor("clap", function(stream) { + this.cleanApertureWidthN = stream.readUint32(); + this.cleanApertureWidthD = stream.readUint32(); + this.cleanApertureHeightN = stream.readUint32(); + this.cleanApertureHeightD = stream.readUint32(); + this.horizOffN = stream.readUint32(); + this.horizOffD = stream.readUint32(); + this.vertOffN = stream.readUint32(); + this.vertOffD = stream.readUint32(); +});// file:src/parsing/clli.js +BoxParser.createBoxCtor("clli", function(stream) { + this.max_content_light_level = stream.readUint16(); + this.max_pic_average_light_level = stream.readUint16(); +}); + +// file:src/parsing/co64.js +BoxParser.createFullBoxCtor("co64", function(stream) { + var entry_count; + var i; + entry_count = stream.readUint32(); + this.chunk_offsets = []; + if (this.version === 0) { + for(i=0; i> 7; + } else if (this.colour_type === 'rICC') { + this.ICC_profile = stream.readUint8Array(this.size - 4); + } else if (this.colour_type === 'prof') { + this.ICC_profile = stream.readUint8Array(this.size - 4); + } +});// file:src/parsing/cprt.js +BoxParser.createFullBoxCtor("cprt", function (stream) { + this.parseLanguage(stream); + this.notice = stream.readCString(); +}); + +// file:src/parsing/cslg.js +BoxParser.createFullBoxCtor("cslg", function(stream) { + var entry_count; + if (this.version === 0) { + this.compositionToDTSShift = stream.readInt32(); /* signed */ + this.leastDecodeToDisplayDelta = stream.readInt32(); /* signed */ + this.greatestDecodeToDisplayDelta = stream.readInt32(); /* signed */ + this.compositionStartTime = stream.readInt32(); /* signed */ + this.compositionEndTime = stream.readInt32(); /* signed */ + } +}); + +// file:src/parsing/ctts.js +BoxParser.createFullBoxCtor("ctts", function(stream) { + var entry_count; + var i; + entry_count = stream.readUint32(); + this.sample_counts = []; + this.sample_offsets = []; + if (this.version === 0) { + for(i=0; i> 6; + this.bsid = ((tmp_byte1 >> 1) & 0x1F); + this.bsmod = ((tmp_byte1 & 0x1) << 2) | ((tmp_byte2 >> 6) & 0x3); + this.acmod = ((tmp_byte2 >> 3) & 0x7); + this.lfeon = ((tmp_byte2 >> 2) & 0x1); + this.bit_rate_code = (tmp_byte2 & 0x3) | ((tmp_byte3 >> 5) & 0x7); +}); + +// file:src/parsing/dec3.js +BoxParser.createBoxCtor("dec3", function(stream) { + var tmp_16 = stream.readUint16(); + this.data_rate = tmp_16 >> 3; + this.num_ind_sub = tmp_16 & 0x7; + this.ind_subs = []; + for (var i = 0; i < this.num_ind_sub+1; i++) { + var ind_sub = {}; + this.ind_subs.push(ind_sub); + var tmp_byte1 = stream.readUint8(); + var tmp_byte2 = stream.readUint8(); + var tmp_byte3 = stream.readUint8(); + ind_sub.fscod = tmp_byte1 >> 6; + ind_sub.bsid = ((tmp_byte1 >> 1) & 0x1F); + ind_sub.bsmod = ((tmp_byte1 & 0x1) << 4) | ((tmp_byte2 >> 4) & 0xF); + ind_sub.acmod = ((tmp_byte2 >> 1) & 0x7); + ind_sub.lfeon = (tmp_byte2 & 0x1); + ind_sub.num_dep_sub = ((tmp_byte3 >> 1) & 0xF); + if (ind_sub.num_dep_sub > 0) { + ind_sub.chan_loc = ((tmp_byte3 & 0x1) << 8) | stream.readUint8(); + } + } +}); + +// file:src/parsing/dfLa.js +BoxParser.createFullBoxCtor("dfLa", function(stream) { + var BLOCKTYPE_MASK = 0x7F; + var LASTMETADATABLOCKFLAG_MASK = 0x80; + + var boxesFound = []; + var knownBlockTypes = [ + "STREAMINFO", + "PADDING", + "APPLICATION", + "SEEKTABLE", + "VORBIS_COMMENT", + "CUESHEET", + "PICTURE", + "RESERVED" + ]; + + // dfLa is a FullBox + this.parseFullHeader(stream); + + // for (i=0; ; i++) { // to end of box + do { + var flagAndType = stream.readUint8(); + + var type = Math.min( + (flagAndType & BLOCKTYPE_MASK), + (knownBlockTypes.length - 1) + ); + + // if this is a STREAMINFO block, read the true samplerate since this + // can be different to the AudioSampleEntry samplerate. + if (!(type)) { + // read past all the other stuff + stream.readUint8Array(13); + + // extract samplerate + this.samplerate = (stream.readUint32() >> 12); + + // read to end of STREAMINFO + stream.readUint8Array(20); + } else { + // not interested in other block types so just discard length bytes + stream.readUint8Array(stream.readUint24()); + } + + boxesFound.push(knownBlockTypes[type]); + + if (!!(flagAndType & LASTMETADATABLOCKFLAG_MASK)) { + break; + } + } while (true); + + this.numMetadataBlocks = + boxesFound.length + " (" + boxesFound.join(", ") + ")"; +}); +// file:src/parsing/dimm.js +BoxParser.createBoxCtor("dimm", function(stream) { + this.bytessent = stream.readUint64(); +}); + +// file:src/parsing/dmax.js +BoxParser.createBoxCtor("dmax", function(stream) { + this.time = stream.readUint32(); +}); + +// file:src/parsing/dmed.js +BoxParser.createBoxCtor("dmed", function(stream) { + this.bytessent = stream.readUint64(); +}); + +// file:src/parsing/dOps.js +BoxParser.createBoxCtor("dOps", function(stream) { + this.Version = stream.readUint8(); + this.OutputChannelCount = stream.readUint8(); + this.PreSkip = stream.readUint16(); + this.InputSampleRate = stream.readUint32(); + this.OutputGain = stream.readInt16(); + this.ChannelMappingFamily = stream.readUint8(); + if (this.ChannelMappingFamily !== 0) { + this.StreamCount = stream.readUint8(); + this.CoupledCount = stream.readUint8(); + this.ChannelMapping = []; + for (var i = 0; i < this.OutputChannelCount; i++) { + this.ChannelMapping[i] = stream.readUint8(); + } + } +}); + +// file:src/parsing/dref.js +BoxParser.createFullBoxCtor("dref", function(stream) { + var ret; + var box; + this.entries = []; + var entry_count = stream.readUint32(); + for (var i = 0; i < entry_count; i++) { + ret = BoxParser.parseOneBox(stream, false, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + box = ret.box; + this.entries.push(box); + } else { + return; + } + } +}); + +// file:src/parsing/drep.js +BoxParser.createBoxCtor("drep", function(stream) { + this.bytessent = stream.readUint64(); +}); + +// file:src/parsing/elng.js +BoxParser.createFullBoxCtor("elng", function(stream) { + this.extended_language = stream.readString(this.size-this.hdr_size); +}); + +// file:src/parsing/elst.js +BoxParser.createFullBoxCtor("elst", function(stream) { + this.entries = []; + var entry_count = stream.readUint32(); + for (var i = 0; i < entry_count; i++) { + var entry = {}; + this.entries.push(entry); + if (this.version === 1) { + entry.segment_duration = stream.readUint64(); + entry.media_time = stream.readInt64(); + } else { + entry.segment_duration = stream.readUint32(); + entry.media_time = stream.readInt32(); + } + entry.media_rate_integer = stream.readInt16(); + entry.media_rate_fraction = stream.readInt16(); + } +}); + +// file:src/parsing/emsg.js +BoxParser.createFullBoxCtor("emsg", function(stream) { + if (this.version == 1) { + this.timescale = stream.readUint32(); + this.presentation_time = stream.readUint64(); + this.event_duration = stream.readUint32(); + this.id = stream.readUint32(); + this.scheme_id_uri = stream.readCString(); + this.value = stream.readCString(); + } else { + this.scheme_id_uri = stream.readCString(); + this.value = stream.readCString(); + this.timescale = stream.readUint32(); + this.presentation_time_delta = stream.readUint32(); + this.event_duration = stream.readUint32(); + this.id = stream.readUint32(); + } + var message_size = this.size - this.hdr_size - (4*4 + (this.scheme_id_uri.length+1) + (this.value.length+1)); + if (this.version == 1) { + message_size -= 4; + } + this.message_data = stream.readUint8Array(message_size); +}); + +// file:src/parsing/esds.js +BoxParser.createFullBoxCtor("esds", function(stream) { + var esd_data = stream.readUint8Array(this.size-this.hdr_size); + if (typeof MPEG4DescriptorParser !== "undefined") { + var esd_parser = new MPEG4DescriptorParser(); + this.esd = esd_parser.parseOneDescriptor(new DataStream(esd_data.buffer, 0, DataStream.BIG_ENDIAN)); + } +}); + +// file:src/parsing/fiel.js +BoxParser.createBoxCtor("fiel", function(stream) { + this.fieldCount = stream.readUint8(); + this.fieldOrdering = stream.readUint8(); +}); + +// file:src/parsing/frma.js +BoxParser.createBoxCtor("frma", function(stream) { + this.data_format = stream.readString(4); +}); + +// file:src/parsing/ftyp.js +BoxParser.createBoxCtor("ftyp", function(stream) { + var toparse = this.size - this.hdr_size; + this.major_brand = stream.readString(4); + this.minor_version = stream.readUint32(); + toparse -= 8; + this.compatible_brands = []; + var i = 0; + while (toparse>=4) { + this.compatible_brands[i] = stream.readString(4); + toparse -= 4; + i++; + } +}); + +// file:src/parsing/hdlr.js +BoxParser.createFullBoxCtor("hdlr", function(stream) { + if (this.version === 0) { + stream.readUint32(); + this.handler = stream.readString(4); + stream.readUint32Array(3); + this.name = stream.readString(this.size-this.hdr_size-20); + if (this.name[this.name.length-1]==='\0') { + this.name = this.name.slice(0,-1); + } + } +}); + +// file:src/parsing/hvcC.js +BoxParser.createBoxCtor("hvcC", function(stream) { + var i, j; + var nb_nalus; + var length; + var tmp_byte; + this.configurationVersion = stream.readUint8(); + tmp_byte = stream.readUint8(); + this.general_profile_space = tmp_byte >> 6; + this.general_tier_flag = (tmp_byte & 0x20) >> 5; + this.general_profile_idc = (tmp_byte & 0x1F); + this.general_profile_compatibility = stream.readUint32(); + this.general_constraint_indicator = stream.readUint8Array(6); + this.general_level_idc = stream.readUint8(); + this.min_spatial_segmentation_idc = stream.readUint16() & 0xFFF; + this.parallelismType = (stream.readUint8() & 0x3); + this.chroma_format_idc = (stream.readUint8() & 0x3); + this.bit_depth_luma_minus8 = (stream.readUint8() & 0x7); + this.bit_depth_chroma_minus8 = (stream.readUint8() & 0x7); + this.avgFrameRate = stream.readUint16(); + tmp_byte = stream.readUint8(); + this.constantFrameRate = (tmp_byte >> 6); + this.numTemporalLayers = (tmp_byte & 0XD) >> 3; + this.temporalIdNested = (tmp_byte & 0X4) >> 2; + this.lengthSizeMinusOne = (tmp_byte & 0X3); + + this.nalu_arrays = []; + var numOfArrays = stream.readUint8(); + for (i = 0; i < numOfArrays; i++) { + var nalu_array = []; + this.nalu_arrays.push(nalu_array); + tmp_byte = stream.readUint8() + nalu_array.completeness = (tmp_byte & 0x80) >> 7; + nalu_array.nalu_type = tmp_byte & 0x3F; + var numNalus = stream.readUint16(); + for (j = 0; j < numNalus; j++) { + var nalu = {} + nalu_array.push(nalu); + length = stream.readUint16(); + nalu.data = stream.readUint8Array(length); + } + } +}); + +// file:src/parsing/iinf.js +BoxParser.createFullBoxCtor("iinf", function(stream) { + var ret; + if (this.version === 0) { + this.entry_count = stream.readUint16(); + } else { + this.entry_count = stream.readUint32(); + } + this.item_infos = []; + for (var i = 0; i < this.entry_count; i++) { + ret = BoxParser.parseOneBox(stream, false, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + if (ret.box.type !== "infe") { + Log.error("BoxParser", "Expected 'infe' box, got "+ret.box.type); + } + this.item_infos[i] = ret.box; + } else { + return; + } + } +}); + +// file:src/parsing/iloc.js +BoxParser.createFullBoxCtor("iloc", function(stream) { + var byte; + byte = stream.readUint8(); + this.offset_size = (byte >> 4) & 0xF; + this.length_size = byte & 0xF; + byte = stream.readUint8(); + this.base_offset_size = (byte >> 4) & 0xF; + if (this.version === 1 || this.version === 2) { + this.index_size = byte & 0xF; + } else { + this.index_size = 0; + // reserved = byte & 0xF; + } + this.items = []; + var item_count = 0; + if (this.version < 2) { + item_count = stream.readUint16(); + } else if (this.version === 2) { + item_count = stream.readUint32(); + } else { + throw "version of iloc box not supported"; + } + for (var i = 0; i < item_count; i++) { + var item = {}; + this.items.push(item); + if (this.version < 2) { + item.item_ID = stream.readUint16(); + } else if (this.version === 2) { + item.item_ID = stream.readUint16(); + } else { + throw "version of iloc box not supported"; + } + if (this.version === 1 || this.version === 2) { + item.construction_method = (stream.readUint16() & 0xF); + } else { + item.construction_method = 0; + } + item.data_reference_index = stream.readUint16(); + switch(this.base_offset_size) { + case 0: + item.base_offset = 0; + break; + case 4: + item.base_offset = stream.readUint32(); + break; + case 8: + item.base_offset = stream.readUint64(); + break; + default: + throw "Error reading base offset size"; + } + var extent_count = stream.readUint16(); + item.extents = []; + for (var j=0; j < extent_count; j++) { + var extent = {}; + item.extents.push(extent); + if (this.version === 1 || this.version === 2) { + switch(this.index_size) { + case 0: + extent.extent_index = 0; + break; + case 4: + extent.extent_index = stream.readUint32(); + break; + case 8: + extent.extent_index = stream.readUint64(); + break; + default: + throw "Error reading extent index"; + } + } + switch(this.offset_size) { + case 0: + extent.extent_offset = 0; + break; + case 4: + extent.extent_offset = stream.readUint32(); + break; + case 8: + extent.extent_offset = stream.readUint64(); + break; + default: + throw "Error reading extent index"; + } + switch(this.length_size) { + case 0: + extent.extent_length = 0; + break; + case 4: + extent.extent_length = stream.readUint32(); + break; + case 8: + extent.extent_length = stream.readUint64(); + break; + default: + throw "Error reading extent index"; + } + } + } +}); + +// file:src/parsing/imir.js +BoxParser.createBoxCtor("imir", function(stream) { + var tmp = stream.readUint8(); + this.reserved = tmp >> 7; + this.axis = tmp & 1; +});// file:src/parsing/infe.js +BoxParser.createFullBoxCtor("infe", function(stream) { + if (this.version === 0 || this.version === 1) { + this.item_ID = stream.readUint16(); + this.item_protection_index = stream.readUint16(); + this.item_name = stream.readCString(); + this.content_type = stream.readCString(); + this.content_encoding = stream.readCString(); + } + if (this.version === 1) { + this.extension_type = stream.readString(4); + Log.warn("BoxParser", "Cannot parse extension type"); + stream.seek(this.start+this.size); + return; + } + if (this.version >= 2) { + if (this.version === 2) { + this.item_ID = stream.readUint16(); + } else if (this.version === 3) { + this.item_ID = stream.readUint32(); + } + this.item_protection_index = stream.readUint16(); + this.item_type = stream.readString(4); + this.item_name = stream.readCString(); + if (this.item_type === "mime") { + this.content_type = stream.readCString(); + this.content_encoding = stream.readCString(); + } else if (this.item_type === "uri ") { + this.item_uri_type = stream.readCString(); + } + } +}); +// file:src/parsing/ipma.js +BoxParser.createFullBoxCtor("ipma", function(stream) { + var i, j; + entry_count = stream.readUint32(); + this.associations = []; + for(i=0; i> 7) === 1; + if (this.flags & 0x1) { + p.property_index = (tmp & 0x7F) << 8 | stream.readUint8(); + } else { + p.property_index = (tmp & 0x7F); + } + } + } +}); + +// file:src/parsing/iref.js +BoxParser.createFullBoxCtor("iref", function(stream) { + var ret; + var entryCount; + var box; + this.references = []; + + while (stream.getPosition() < this.start+this.size) { + ret = BoxParser.parseOneBox(stream, true, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + if (this.version === 0) { + box = new BoxParser.SingleItemTypeReferenceBox(ret.type, ret.size, ret.hdr_size, ret.start); + } else { + box = new BoxParser.SingleItemTypeReferenceBoxLarge(ret.type, ret.size, ret.hdr_size, ret.start); + } + if (box.write === BoxParser.Box.prototype.write && box.type !== "mdat") { + Log.warn("BoxParser", box.type+" box writing not yet implemented, keeping unparsed data in memory for later write"); + box.parseDataAndRewind(stream); + } + box.parse(stream); + this.references.push(box); + } else { + return; + } + } +}); +// file:src/parsing/irot.js +BoxParser.createBoxCtor("irot", function(stream) { + this.angle = stream.readUint8() & 0x3; +}); + +// file:src/parsing/ispe.js +BoxParser.createFullBoxCtor("ispe", function(stream) { + this.image_width = stream.readUint32(); + this.image_height = stream.readUint32(); +});// file:src/parsing/kind.js +BoxParser.createFullBoxCtor("kind", function(stream) { + this.schemeURI = stream.readCString(); + this.value = stream.readCString(); +}); +// file:src/parsing/leva.js +BoxParser.createFullBoxCtor("leva", function(stream) { + var count = stream.readUint8(); + this.levels = []; + for (var i = 0; i < count; i++) { + var level = {}; + this.levels[i] = level; + level.track_ID = stream.readUint32(); + var tmp_byte = stream.readUint8(); + level.padding_flag = tmp_byte >> 7; + level.assignment_type = tmp_byte & 0x7F; + switch (level.assignment_type) { + case 0: + level.grouping_type = stream.readString(4); + break; + case 1: + level.grouping_type = stream.readString(4); + level.grouping_type_parameter = stream.readUint32(); + break; + case 2: + break; + case 3: + break; + case 4: + level.sub_track_id = stream.readUint32(); + break; + default: + Log.warn("BoxParser", "Unknown leva assignement type"); + } + } +}); + +// file:src/parsing/lsel.js +BoxParser.createBoxCtor("lsel", function(stream) { + this.layer_id = stream.readUint16(); +});// file:src/parsing/maxr.js +BoxParser.createBoxCtor("maxr", function(stream) { + this.period = stream.readUint32(); + this.bytes = stream.readUint32(); +}); + +// file:src/parsing/mdcv.js +BoxParser.createBoxCtor("mdcv", function(stream) { + this.display_primaries = []; + this.display_primaries[0] = {}; + this.display_primaries[0].x = stream.readUint16(); + this.display_primaries[0].y = stream.readUint16(); + this.display_primaries[1] = {}; + this.display_primaries[1].x = stream.readUint16(); + this.display_primaries[1].y = stream.readUint16(); + this.display_primaries[2] = {}; + this.display_primaries[2].x = stream.readUint16(); + this.display_primaries[2].y = stream.readUint16(); + this.white_point = {}; + this.white_point.x = stream.readUint16(); + this.white_point.y = stream.readUint16(); + this.max_display_mastering_luminance = stream.readUint32(); + this.min_display_mastering_luminance = stream.readUint32(); +}); + +// file:src/parsing/mdhd.js +BoxParser.createFullBoxCtor("mdhd", function(stream) { + if (this.version == 1) { + this.creation_time = stream.readUint64(); + this.modification_time = stream.readUint64(); + this.timescale = stream.readUint32(); + this.duration = stream.readUint64(); + } else { + this.creation_time = stream.readUint32(); + this.modification_time = stream.readUint32(); + this.timescale = stream.readUint32(); + this.duration = stream.readUint32(); + } + this.parseLanguage(stream); + stream.readUint16(); +}); + +// file:src/parsing/mehd.js +BoxParser.createFullBoxCtor("mehd", function(stream) { + if (this.flags & 0x1) { + Log.warn("BoxParser", "mehd box incorrectly uses flags set to 1, converting version to 1"); + this.version = 1; + } + if (this.version == 1) { + this.fragment_duration = stream.readUint64(); + } else { + this.fragment_duration = stream.readUint32(); + } +}); + +// file:src/parsing/meta.js +BoxParser.createFullBoxCtor("meta", function(stream) { + this.boxes = []; + BoxParser.ContainerBox.prototype.parse.call(this, stream); +}); +// file:src/parsing/mfhd.js +BoxParser.createFullBoxCtor("mfhd", function(stream) { + this.sequence_number = stream.readUint32(); +}); + +// file:src/parsing/mfro.js +BoxParser.createFullBoxCtor("mfro", function(stream) { + this._size = stream.readUint32(); +}); + +// file:src/parsing/mvhd.js +BoxParser.createFullBoxCtor("mvhd", function(stream) { + if (this.version == 1) { + this.creation_time = stream.readUint64(); + this.modification_time = stream.readUint64(); + this.timescale = stream.readUint32(); + this.duration = stream.readUint64(); + } else { + this.creation_time = stream.readUint32(); + this.modification_time = stream.readUint32(); + this.timescale = stream.readUint32(); + this.duration = stream.readUint32(); + } + this.rate = stream.readUint32(); + this.volume = stream.readUint16()>>8; + stream.readUint16(); + stream.readUint32Array(2); + this.matrix = stream.readUint32Array(9); + stream.readUint32Array(6); + this.next_track_id = stream.readUint32(); +}); +// file:src/parsing/npck.js +BoxParser.createBoxCtor("npck", function(stream) { + this.packetssent = stream.readUint32(); +}); + +// file:src/parsing/nump.js +BoxParser.createBoxCtor("nump", function(stream) { + this.packetssent = stream.readUint64(); +}); + +// file:src/parsing/padb.js +BoxParser.createFullBoxCtor("padb", function(stream) { + var sample_count = stream.readUint32(); + this.padbits = []; + for (var i = 0; i < Math.floor((sample_count+1)/2); i++) { + this.padbits = stream.readUint8(); + } +}); + +// file:src/parsing/pasp.js +BoxParser.createBoxCtor("pasp", function(stream) { + this.hSpacing = stream.readUint32(); + this.vSpacing = stream.readUint32(); +});// file:src/parsing/payl.js +BoxParser.createBoxCtor("payl", function(stream) { + this.text = stream.readString(this.size - this.hdr_size); +}); + +// file:src/parsing/payt.js +BoxParser.createBoxCtor("payt", function(stream) { + this.payloadID = stream.readUint32(); + var count = stream.readUint8(); + this.rtpmap_string = stream.readString(count); +}); + +// file:src/parsing/pdin.js +BoxParser.createFullBoxCtor("pdin", function(stream) { + var count = (this.size - this.hdr_size)/8; + this.rate = []; + this.initial_delay = []; + for (var i = 0; i < count; i++) { + this.rate[i] = stream.readUint32(); + this.initial_delay[i] = stream.readUint32(); + } +}); + +// file:src/parsing/pitm.js +BoxParser.createFullBoxCtor("pitm", function(stream) { + if (this.version === 0) { + this.item_id = stream.readUint16(); + } else { + this.item_id = stream.readUint32(); + } +}); + +// file:src/parsing/pixi.js +BoxParser.createFullBoxCtor("pixi", function(stream) { + var i; + this.num_channels = stream.readUint8(); + this.bits_per_channels = []; + for (i = 0; i < this.num_channels; i++) { + this.bits_per_channels[i] = stream.readUint8(); + } +}); + +// file:src/parsing/pmax.js +BoxParser.createBoxCtor("pmax", function(stream) { + this.bytes = stream.readUint32(); +}); + +// file:src/parsing/prft.js +BoxParser.createFullBoxCtor("prft", function(stream) { + this.ref_track_id = stream.readUint32(); + this.ntp_timestamp = stream.readUint64(); + if (this.version === 0) { + this.media_time = stream.readUint32(); + } else { + this.media_time = stream.readUint64(); + } +}); + +// file:src/parsing/pssh.js +BoxParser.createFullBoxCtor("pssh", function(stream) { + this.system_id = BoxParser.parseHex16(stream); + if (this.version > 0) { + var count = stream.readUint32(); + this.kid = []; + for (var i = 0; i < count; i++) { + this.kid[i] = BoxParser.parseHex16(stream); + } + } + var datasize = stream.readUint32(); + if (datasize > 0) { + this.data = stream.readUint8Array(datasize); + } +}); + +// file:src/parsing/qt/clef.js +BoxParser.createFullBoxCtor("clef", function(stream) { + this.width = stream.readUint32(); + this.height = stream.readUint32(); +});// file:src/parsing/qt/enof.js +BoxParser.createFullBoxCtor("enof", function(stream) { + this.width = stream.readUint32(); + this.height = stream.readUint32(); +});// file:src/parsing/qt/prof.js +BoxParser.createFullBoxCtor("prof", function(stream) { + this.width = stream.readUint32(); + this.height = stream.readUint32(); +});// file:src/parsing/qt/tapt.js +BoxParser.createContainerBoxCtor("tapt", null, [ "clef", "prof", "enof"]);// file:src/parsing/rtp.js +BoxParser.createBoxCtor("rtp ", function(stream) { + this.descriptionformat = stream.readString(4); + this.sdptext = stream.readString(this.size - this.hdr_size - 4); +}); + +// file:src/parsing/saio.js +BoxParser.createFullBoxCtor("saio", function(stream) { + if (this.flags & 0x1) { + this.aux_info_type = stream.readUint32(); + this.aux_info_type_parameter = stream.readUint32(); + } + var count = stream.readUint32(); + this.offset = []; + for (var i = 0; i < count; i++) { + if (this.version === 0) { + this.offset[i] = stream.readUint32(); + } else { + this.offset[i] = stream.readUint64(); + } + } +}); +// file:src/parsing/saiz.js +BoxParser.createFullBoxCtor("saiz", function(stream) { + if (this.flags & 0x1) { + this.aux_info_type = stream.readUint32(); + this.aux_info_type_parameter = stream.readUint32(); + } + this.default_sample_info_size = stream.readUint8(); + var count = stream.readUint32(); + this.sample_info_size = []; + if (this.default_sample_info_size === 0) { + for (var i = 0; i < count; i++) { + this.sample_info_size[i] = stream.readUint8(); + } + } +}); + +// file:src/parsing/sampleentries/mett.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_METADATA, "mett", function(stream) { + this.parseHeader(stream); + this.content_encoding = stream.readCString(); + this.mime_format = stream.readCString(); + this.parseFooter(stream); +}); + +// file:src/parsing/sampleentries/metx.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_METADATA, "metx", function(stream) { + this.parseHeader(stream); + this.content_encoding = stream.readCString(); + this.namespace = stream.readCString(); + this.schema_location = stream.readCString(); + this.parseFooter(stream); +}); + +// file:src/parsing/sampleentries/sbtt.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE, "sbtt", function(stream) { + this.parseHeader(stream); + this.content_encoding = stream.readCString(); + this.mime_format = stream.readCString(); + this.parseFooter(stream); +}); + +// file:src/parsing/sampleentries/stpp.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE, "stpp", function(stream) { + this.parseHeader(stream); + this.namespace = stream.readCString(); + this.schema_location = stream.readCString(); + this.auxiliary_mime_types = stream.readCString(); + this.parseFooter(stream); +}); + +// file:src/parsing/sampleentries/stxt.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE, "stxt", function(stream) { + this.parseHeader(stream); + this.content_encoding = stream.readCString(); + this.mime_format = stream.readCString(); + this.parseFooter(stream); +}); + +// file:src/parsing/sampleentries/tx3g.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_SUBTITLE, "tx3g", function(stream) { + this.parseHeader(stream); + this.displayFlags = stream.readUint32(); + this.horizontal_justification = stream.readInt8(); + this.vertical_justification = stream.readInt8(); + this.bg_color_rgba = stream.readUint8Array(4); + this.box_record = stream.readInt16Array(4); + this.style_record = stream.readUint8Array(12); + this.parseFooter(stream); +}); +// file:src/parsing/sampleentries/wvtt.js +BoxParser.createSampleEntryCtor(BoxParser.SAMPLE_ENTRY_TYPE_METADATA, "wvtt", function(stream) { + this.parseHeader(stream); + this.parseFooter(stream); +}); + +// file:src/parsing/samplegroups/alst.js +BoxParser.createSampleGroupCtor("alst", function(stream) { + var i; + var roll_count = stream.readUint16(); + this.first_output_sample = stream.readUint16(); + this.sample_offset = []; + for (i = 0; i < roll_count; i++) { + this.sample_offset[i] = stream.readUint32(); + } + var remaining = this.description_length - 4 - 4*roll_count; + this.num_output_samples = []; + this.num_total_samples = []; + for (i = 0; i < remaining/4; i++) { + this.num_output_samples[i] = stream.readUint16(); + this.num_total_samples[i] = stream.readUint16(); + } +}); + +// file:src/parsing/samplegroups/avll.js +BoxParser.createSampleGroupCtor("avll", function(stream) { + this.layerNumber = stream.readUint8(); + this.accurateStatisticsFlag = stream.readUint8(); + this.avgBitRate = stream.readUint16(); + this.avgFrameRate = stream.readUint16(); +}); + +// file:src/parsing/samplegroups/avss.js +BoxParser.createSampleGroupCtor("avss", function(stream) { + this.subSequenceIdentifier = stream.readUint16(); + this.layerNumber = stream.readUint8(); + var tmp_byte = stream.readUint8(); + this.durationFlag = tmp_byte >> 7; + this.avgRateFlag = (tmp_byte >> 6) & 0x1; + if (this.durationFlag) { + this.duration = stream.readUint32(); + } + if (this.avgRateFlag) { + this.accurateStatisticsFlag = stream.readUint8(); + this.avgBitRate = stream.readUint16(); + this.avgFrameRate = stream.readUint16(); + } + this.dependency = []; + var numReferences = stream.readUint8(); + for (var i = 0; i < numReferences; i++) { + var dependencyInfo = {}; + this.dependency.push(dependencyInfo); + dependencyInfo.subSeqDirectionFlag = stream.readUint8(); + dependencyInfo.layerNumber = stream.readUint8(); + dependencyInfo.subSequenceIdentifier = stream.readUint16(); + } +}); + +// file:src/parsing/samplegroups/dtrt.js +BoxParser.createSampleGroupCtor("dtrt", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/mvif.js +BoxParser.createSampleGroupCtor("mvif", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/prol.js +BoxParser.createSampleGroupCtor("prol", function(stream) { + this.roll_distance = stream.readInt16(); +}); + +// file:src/parsing/samplegroups/rap.js +BoxParser.createSampleGroupCtor("rap ", function(stream) { + var tmp_byte = stream.readUint8(); + this.num_leading_samples_known = tmp_byte >> 7; + this.num_leading_samples = tmp_byte & 0x7F; +}); + +// file:src/parsing/samplegroups/rash.js +BoxParser.createSampleGroupCtor("rash", function(stream) { + this.operation_point_count = stream.readUint16(); + if (this.description_length !== 2+(this.operation_point_count === 1?2:this.operation_point_count*6)+9) { + Log.warn("BoxParser", "Mismatch in "+this.grouping_type+" sample group length"); + this.data = stream.readUint8Array(this.description_length-2); + } else { + if (this.operation_point_count === 1) { + this.target_rate_share = stream.readUint16(); + } else { + this.target_rate_share = []; + this.available_bitrate = []; + for (var i = 0; i < this.operation_point_count; i++) { + this.available_bitrate[i] = stream.readUint32(); + this.target_rate_share[i] = stream.readUint16(); + } + } + this.maximum_bitrate = stream.readUint32(); + this.minimum_bitrate = stream.readUint32(); + this.discard_priority = stream.readUint8(); + } +}); + +// file:src/parsing/samplegroups/roll.js +BoxParser.createSampleGroupCtor("roll", function(stream) { + this.roll_distance = stream.readInt16(); +}); + +// file:src/parsing/samplegroups/samplegroup.js +BoxParser.SampleGroupEntry.prototype.parse = function(stream) { + Log.warn("BoxParser", "Unknown Sample Group type: "+this.grouping_type); + this.data = stream.readUint8Array(this.description_length); +} + +// file:src/parsing/samplegroups/scif.js +BoxParser.createSampleGroupCtor("scif", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/scnm.js +BoxParser.createSampleGroupCtor("scnm", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/seig.js +BoxParser.createSampleGroupCtor("seig", function(stream) { + this.reserved = stream.readUint8(); + var tmp = stream.readUint8(); + this.crypt_byte_block = tmp >> 4; + this.skip_byte_block = tmp & 0xF; + this.isProtected = stream.readUint8(); + this.Per_Sample_IV_Size = stream.readUint8(); + this.KID = BoxParser.parseHex16(stream); + this.constant_IV_size = 0; + this.constant_IV = 0; + if (this.isProtected === 1 && this.Per_Sample_IV_Size === 0) { + this.constant_IV_size = stream.readUint8(); + this.constant_IV = stream.readUint8Array(this.constant_IV_size); + } +}); + +// file:src/parsing/samplegroups/stsa.js +BoxParser.createSampleGroupCtor("stsa", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/sync.js +BoxParser.createSampleGroupCtor("sync", function(stream) { + var tmp_byte = stream.readUint8(); + this.NAL_unit_type = tmp_byte & 0x3F; +}); + +// file:src/parsing/samplegroups/tele.js +BoxParser.createSampleGroupCtor("tele", function(stream) { + var tmp_byte = stream.readUint8(); + this.level_independently_decodable = tmp_byte >> 7; +}); + +// file:src/parsing/samplegroups/tsas.js +BoxParser.createSampleGroupCtor("tsas", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/tscl.js +BoxParser.createSampleGroupCtor("tscl", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/samplegroups/vipr.js +BoxParser.createSampleGroupCtor("vipr", function(stream) { + Log.warn("BoxParser", "Sample Group type: "+this.grouping_type+" not fully parsed"); +}); + +// file:src/parsing/sbgp.js +BoxParser.createFullBoxCtor("sbgp", function(stream) { + this.grouping_type = stream.readString(4); + if (this.version === 1) { + this.grouping_type_parameter = stream.readUint32(); + } else { + this.grouping_type_parameter = 0; + } + this.entries = []; + var entry_count = stream.readUint32(); + for (var i = 0; i < entry_count; i++) { + var entry = {}; + this.entries.push(entry); + entry.sample_count = stream.readInt32(); + entry.group_description_index = stream.readInt32(); + } +}); + +// file:src/parsing/schm.js +BoxParser.createFullBoxCtor("schm", function(stream) { + this.scheme_type = stream.readString(4); + this.scheme_version = stream.readUint32(); + if (this.flags & 0x000001) { + this.scheme_uri = stream.readString(this.size - this.hdr_size - 8); + } +}); + +// file:src/parsing/sdp.js +BoxParser.createBoxCtor("sdp ", function(stream) { + this.sdptext = stream.readString(this.size - this.hdr_size); +}); + +// file:src/parsing/sdtp.js +BoxParser.createFullBoxCtor("sdtp", function(stream) { + var tmp_byte; + var count = (this.size - this.hdr_size); + this.is_leading = []; + this.sample_depends_on = []; + this.sample_is_depended_on = []; + this.sample_has_redundancy = []; + for (var i = 0; i < count; i++) { + tmp_byte = stream.readUint8(); + this.is_leading[i] = tmp_byte >> 6; + this.sample_depends_on[i] = (tmp_byte >> 4) & 0x3; + this.sample_is_depended_on[i] = (tmp_byte >> 2) & 0x3; + this.sample_has_redundancy[i] = tmp_byte & 0x3; + } +}); + +// file:src/parsing/senc.js +// Cannot be fully parsed because Per_Sample_IV_Size needs to be known +BoxParser.createFullBoxCtor("senc" /*, function(stream) { + this.parseFullHeader(stream); + var sample_count = stream.readUint32(); + this.samples = []; + for (var i = 0; i < sample_count; i++) { + var sample = {}; + // tenc.default_Per_Sample_IV_Size or seig.Per_Sample_IV_Size + sample.InitializationVector = this.readUint8Array(Per_Sample_IV_Size*8); + if (this.flags & 0x2) { + sample.subsamples = []; + subsample_count = stream.readUint16(); + for (var j = 0; j < subsample_count; j++) { + var subsample = {}; + subsample.BytesOfClearData = stream.readUint16(); + subsample.BytesOfProtectedData = stream.readUint32(); + sample.subsamples.push(subsample); + } + } + // TODO + this.samples.push(sample); + } +}*/); +// file:src/parsing/sgpd.js +BoxParser.createFullBoxCtor("sgpd", function(stream) { + this.grouping_type = stream.readString(4); + Log.debug("BoxParser", "Found Sample Groups of type "+this.grouping_type); + if (this.version === 1) { + this.default_length = stream.readUint32(); + } else { + this.default_length = 0; + } + if (this.version >= 2) { + this.default_group_description_index = stream.readUint32(); + } + this.entries = []; + var entry_count = stream.readUint32(); + for (var i = 0; i < entry_count; i++) { + var entry; + if (BoxParser[this.grouping_type+"SampleGroupEntry"]) { + entry = new BoxParser[this.grouping_type+"SampleGroupEntry"](this.grouping_type); + } else { + entry = new BoxParser.SampleGroupEntry(this.grouping_type); + } + this.entries.push(entry); + if (this.version === 1) { + if (this.default_length === 0) { + entry.description_length = stream.readUint32(); + } else { + entry.description_length = this.default_length; + } + } else { + entry.description_length = this.default_length; + } + if (entry.write === BoxParser.SampleGroupEntry.prototype.write) { + Log.info("BoxParser", "SampleGroup for type "+this.grouping_type+" writing not yet implemented, keeping unparsed data in memory for later write"); + // storing data + entry.data = stream.readUint8Array(entry.description_length); + // rewinding + stream.position -= entry.description_length; + } + entry.parse(stream); + } +}); + +// file:src/parsing/sidx.js +BoxParser.createFullBoxCtor("sidx", function(stream) { + this.reference_ID = stream.readUint32(); + this.timescale = stream.readUint32(); + if (this.version === 0) { + this.earliest_presentation_time = stream.readUint32(); + this.first_offset = stream.readUint32(); + } else { + this.earliest_presentation_time = stream.readUint64(); + this.first_offset = stream.readUint64(); + } + stream.readUint16(); + this.references = []; + var count = stream.readUint16(); + for (var i = 0; i < count; i++) { + var ref = {}; + this.references.push(ref); + var tmp_32 = stream.readUint32(); + ref.reference_type = (tmp_32 >> 31) & 0x1; + ref.referenced_size = tmp_32 & 0x7FFFFFFF; + ref.subsegment_duration = stream.readUint32(); + tmp_32 = stream.readUint32(); + ref.starts_with_SAP = (tmp_32 >> 31) & 0x1; + ref.SAP_type = (tmp_32 >> 28) & 0x7; + ref.SAP_delta_time = tmp_32 & 0xFFFFFFF; + } +}); + +// file:src/parsing/singleitemtypereference.js +BoxParser.SingleItemTypeReferenceBox = function(type, size, hdr_size, start) { + BoxParser.Box.call(this, type, size); + this.hdr_size = hdr_size; + this.start = start; +} +BoxParser.SingleItemTypeReferenceBox.prototype = new BoxParser.Box(); +BoxParser.SingleItemTypeReferenceBox.prototype.parse = function(stream) { + this.from_item_ID = stream.readUint16(); + var count = stream.readUint16(); + this.references = []; + for(var i = 0; i < count; i++) { + this.references[i] = stream.readUint16(); + } +} + +// file:src/parsing/singleitemtypereferencelarge.js +BoxParser.SingleItemTypeReferenceBoxLarge = function(type, size, hdr_size, start) { + BoxParser.Box.call(this, type, size); + this.hdr_size = hdr_size; + this.start = start; +} +BoxParser.SingleItemTypeReferenceBoxLarge.prototype = new BoxParser.Box(); +BoxParser.SingleItemTypeReferenceBoxLarge.prototype.parse = function(stream) { + this.from_item_ID = stream.readUint32(); + var count = stream.readUint16(); + this.references = []; + for(var i = 0; i < count; i++) { + this.references[i] = stream.readUint32(); + } +} + +// file:src/parsing/SmDm.js +BoxParser.createFullBoxCtor("SmDm", function(stream) { + this.primaryRChromaticity_x = stream.readUint16(); + this.primaryRChromaticity_y = stream.readUint16(); + this.primaryGChromaticity_x = stream.readUint16(); + this.primaryGChromaticity_y = stream.readUint16(); + this.primaryBChromaticity_x = stream.readUint16(); + this.primaryBChromaticity_y = stream.readUint16(); + this.whitePointChromaticity_x = stream.readUint16(); + this.whitePointChromaticity_y = stream.readUint16(); + this.luminanceMax = stream.readUint32(); + this.luminanceMin = stream.readUint32(); +}); + +// file:src/parsing/smhd.js +BoxParser.createFullBoxCtor("smhd", function(stream) { + this.balance = stream.readUint16(); + stream.readUint16(); +}); + +// file:src/parsing/ssix.js +BoxParser.createFullBoxCtor("ssix", function(stream) { + this.subsegments = []; + var subsegment_count = stream.readUint32(); + for (var i = 0; i < subsegment_count; i++) { + var subsegment = {}; + this.subsegments.push(subsegment); + subsegment.ranges = []; + var range_count = stream.readUint32(); + for (var j = 0; j < range_count; j++) { + var range = {}; + subsegment.ranges.push(range); + range.level = stream.readUint8(); + range.range_size = stream.readUint24(); + } + } +}); + +// file:src/parsing/stco.js +BoxParser.createFullBoxCtor("stco", function(stream) { + var entry_count; + entry_count = stream.readUint32(); + this.chunk_offsets = []; + if (this.version === 0) { + for (var i = 0; i < entry_count; i++) { + this.chunk_offsets.push(stream.readUint32()); + } + } +}); + +// file:src/parsing/stdp.js +BoxParser.createFullBoxCtor("stdp", function(stream) { + var count = (this.size - this.hdr_size)/2; + this.priority = []; + for (var i = 0; i < count; i++) { + this.priority[i] = stream.readUint16(); + } +}); + +// file:src/parsing/sthd.js +BoxParser.createFullBoxCtor("sthd"); + +// file:src/parsing/stri.js +BoxParser.createFullBoxCtor("stri", function(stream) { + this.switch_group = stream.readUint16(); + this.alternate_group = stream.readUint16(); + this.sub_track_id = stream.readUint32(); + var count = (this.size - this.hdr_size - 8)/4; + this.attribute_list = []; + for (var i = 0; i < count; i++) { + this.attribute_list[i] = stream.readUint32(); + } +}); + +// file:src/parsing/stsc.js +BoxParser.createFullBoxCtor("stsc", function(stream) { + var entry_count; + var i; + entry_count = stream.readUint32(); + this.first_chunk = []; + this.samples_per_chunk = []; + this.sample_description_index = []; + if (this.version === 0) { + for(i=0; i> 4) & 0xF; + this.sample_sizes[i+1] = tmp & 0xF; + } + } else if (this.field_size === 8) { + for (i = 0; i < sample_count; i++) { + this.sample_sizes[i] = stream.readUint8(); + } + } else if (this.field_size === 16) { + for (i = 0; i < sample_count; i++) { + this.sample_sizes[i] = stream.readUint16(); + } + } else { + Log.error("BoxParser", "Error in length field in stz2 box"); + } + } +}); + +// file:src/parsing/subs.js +BoxParser.createFullBoxCtor("subs", function(stream) { + var i,j; + var entry_count; + var subsample_count; + entry_count = stream.readUint32(); + this.entries = []; + for (i = 0; i < entry_count; i++) { + var sampleInfo = {}; + this.entries[i] = sampleInfo; + sampleInfo.sample_delta = stream.readUint32(); + sampleInfo.subsamples = []; + subsample_count = stream.readUint16(); + if (subsample_count>0) { + for (j = 0; j < subsample_count; j++) { + var subsample = {}; + sampleInfo.subsamples.push(subsample); + if (this.version == 1) { + subsample.size = stream.readUint32(); + } else { + subsample.size = stream.readUint16(); + } + subsample.priority = stream.readUint8(); + subsample.discardable = stream.readUint8(); + subsample.codec_specific_parameters = stream.readUint32(); + } + } + } +}); + +// file:src/parsing/tenc.js +BoxParser.createFullBoxCtor("tenc", function(stream) { + stream.readUint8(); // reserved + if (this.version === 0) { + stream.readUint8(); + } else { + var tmp = stream.readUint8(); + this.default_crypt_byte_block = (tmp >> 4) & 0xF; + this.default_skip_byte_block = tmp & 0xF; + } + this.default_isProtected = stream.readUint8(); + this.default_Per_Sample_IV_Size = stream.readUint8(); + this.default_KID = BoxParser.parseHex16(stream); + if (this.default_isProtected === 1 && this.default_Per_Sample_IV_Size === 0) { + this.default_constant_IV_size = stream.readUint8(); + this.default_constant_IV = stream.readUint8Array(this.default_constant_IV_size); + } +});// file:src/parsing/tfdt.js +BoxParser.createFullBoxCtor("tfdt", function(stream) { + if (this.version == 1) { + this.baseMediaDecodeTime = stream.readUint64(); + } else { + this.baseMediaDecodeTime = stream.readUint32(); + } +}); + +// file:src/parsing/tfhd.js +BoxParser.createFullBoxCtor("tfhd", function(stream) { + var readBytes = 0; + this.track_id = stream.readUint32(); + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET)) { + this.base_data_offset = stream.readUint64(); + readBytes += 8; + } else { + this.base_data_offset = 0; + } + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC)) { + this.default_sample_description_index = stream.readUint32(); + readBytes += 4; + } else { + this.default_sample_description_index = 0; + } + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR)) { + this.default_sample_duration = stream.readUint32(); + readBytes += 4; + } else { + this.default_sample_duration = 0; + } + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE)) { + this.default_sample_size = stream.readUint32(); + readBytes += 4; + } else { + this.default_sample_size = 0; + } + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS)) { + this.default_sample_flags = stream.readUint32(); + readBytes += 4; + } else { + this.default_sample_flags = 0; + } +}); + +// file:src/parsing/tfra.js +BoxParser.createFullBoxCtor("tfra", function(stream) { + this.track_ID = stream.readUint32(); + stream.readUint24(); + var tmp_byte = stream.readUint8(); + this.length_size_of_traf_num = (tmp_byte >> 4) & 0x3; + this.length_size_of_trun_num = (tmp_byte >> 2) & 0x3; + this.length_size_of_sample_num = (tmp_byte) & 0x3; + this.entries = []; + var number_of_entries = stream.readUint32(); + for (var i = 0; i < number_of_entries; i++) { + if (this.version === 1) { + this.time = stream.readUint64(); + this.moof_offset = stream.readUint64(); + } else { + this.time = stream.readUint32(); + this.moof_offset = stream.readUint32(); + } + this.traf_number = stream["readUint"+(8*(this.length_size_of_traf_num+1))](); + this.trun_number = stream["readUint"+(8*(this.length_size_of_trun_num+1))](); + this.sample_number = stream["readUint"+(8*(this.length_size_of_sample_num+1))](); + } +}); + +// file:src/parsing/tkhd.js +BoxParser.createFullBoxCtor("tkhd", function(stream) { + if (this.version == 1) { + this.creation_time = stream.readUint64(); + this.modification_time = stream.readUint64(); + this.track_id = stream.readUint32(); + stream.readUint32(); + this.duration = stream.readUint64(); + } else { + this.creation_time = stream.readUint32(); + this.modification_time = stream.readUint32(); + this.track_id = stream.readUint32(); + stream.readUint32(); + this.duration = stream.readUint32(); + } + stream.readUint32Array(2); + this.layer = stream.readInt16(); + this.alternate_group = stream.readInt16(); + this.volume = stream.readInt16()>>8; + stream.readUint16(); + this.matrix = stream.readInt32Array(9); + this.width = stream.readUint32(); + this.height = stream.readUint32(); +}); + +// file:src/parsing/tmax.js +BoxParser.createBoxCtor("tmax", function(stream) { + this.time = stream.readUint32(); +}); + +// file:src/parsing/tmin.js +BoxParser.createBoxCtor("tmin", function(stream) { + this.time = stream.readUint32(); +}); + +// file:src/parsing/totl.js +BoxParser.createBoxCtor("totl",function(stream) { + this.bytessent = stream.readUint32(); +}); + +// file:src/parsing/tpay.js +BoxParser.createBoxCtor("tpay", function(stream) { + this.bytessent = stream.readUint32(); +}); + +// file:src/parsing/tpyl.js +BoxParser.createBoxCtor("tpyl", function(stream) { + this.bytessent = stream.readUint64(); +}); + +// file:src/parsing/TrackGroup.js +BoxParser.TrackGroupTypeBox.prototype.parse = function(stream) { + this.parseFullHeader(stream); + this.track_group_id = stream.readUint32(); +} + +// file:src/parsing/trackgroups/msrc.js +BoxParser.createTrackGroupCtor("msrc");// file:src/parsing/TrakReference.js +BoxParser.TrackReferenceTypeBox = function(type, size, hdr_size, start) { + BoxParser.Box.call(this, type, size); + this.hdr_size = hdr_size; + this.start = start; +} +BoxParser.TrackReferenceTypeBox.prototype = new BoxParser.Box(); +BoxParser.TrackReferenceTypeBox.prototype.parse = function(stream) { + this.track_ids = stream.readUint32Array((this.size-this.hdr_size)/4); +} + +// file:src/parsing/tref.js +BoxParser.trefBox.prototype.parse = function(stream) { + var ret; + var box; + while (stream.getPosition() < this.start+this.size) { + ret = BoxParser.parseOneBox(stream, true, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + box = new BoxParser.TrackReferenceTypeBox(ret.type, ret.size, ret.hdr_size, ret.start); + if (box.write === BoxParser.Box.prototype.write && box.type !== "mdat") { + Log.info("BoxParser", "TrackReference "+box.type+" box writing not yet implemented, keeping unparsed data in memory for later write"); + box.parseDataAndRewind(stream); + } + box.parse(stream); + this.boxes.push(box); + } else { + return; + } + } +} + +// file:src/parsing/trep.js +BoxParser.createFullBoxCtor("trep", function(stream) { + this.track_ID = stream.readUint32(); + this.boxes = []; + while (stream.getPosition() < this.start+this.size) { + ret = BoxParser.parseOneBox(stream, false, this.size - (stream.getPosition() - this.start)); + if (ret.code === BoxParser.OK) { + box = ret.box; + this.boxes.push(box); + } else { + return; + } + } +}); + +// file:src/parsing/trex.js +BoxParser.createFullBoxCtor("trex", function(stream) { + this.track_id = stream.readUint32(); + this.default_sample_description_index = stream.readUint32(); + this.default_sample_duration = stream.readUint32(); + this.default_sample_size = stream.readUint32(); + this.default_sample_flags = stream.readUint32(); +}); + +// file:src/parsing/trpy.js +BoxParser.createBoxCtor("trpy", function(stream) { + this.bytessent = stream.readUint64(); +}); + +// file:src/parsing/trun.js +BoxParser.createFullBoxCtor("trun", function(stream) { + var readBytes = 0; + this.sample_count = stream.readUint32(); + readBytes+= 4; + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) ) { + this.data_offset = stream.readInt32(); //signed + readBytes += 4; + } else { + this.data_offset = 0; + } + if (this.size - this.hdr_size > readBytes && (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) ) { + this.first_sample_flags = stream.readUint32(); + readBytes += 4; + } else { + this.first_sample_flags = 0; + } + this.sample_duration = []; + this.sample_size = []; + this.sample_flags = []; + this.sample_composition_time_offset = []; + if (this.size - this.hdr_size > readBytes) { + for (var i = 0; i < this.sample_count; i++) { + if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { + this.sample_duration[i] = stream.readUint32(); + } + if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { + this.sample_size[i] = stream.readUint32(); + } + if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { + this.sample_flags[i] = stream.readUint32(); + } + if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { + if (this.version === 0) { + this.sample_composition_time_offset[i] = stream.readUint32(); + } else { + this.sample_composition_time_offset[i] = stream.readInt32(); //signed + } + } + } + } +}); + +// file:src/parsing/tsel.js +BoxParser.createFullBoxCtor("tsel", function(stream) { + this.switch_group = stream.readUint32(); + var count = (this.size - this.hdr_size - 4)/4; + this.attribute_list = []; + for (var i = 0; i < count; i++) { + this.attribute_list[i] = stream.readUint32(); + } +}); + +// file:src/parsing/txtC.js +BoxParser.createFullBoxCtor("txtC", function(stream) { + this.config = stream.readCString(); +}); + +// file:src/parsing/url.js +BoxParser.createFullBoxCtor("url ", function(stream) { + if (this.flags !== 0x000001) { + this.location = stream.readCString(); + } +}); + +// file:src/parsing/urn.js +BoxParser.createFullBoxCtor("urn ", function(stream) { + this.name = stream.readCString(); + if (this.size - this.hdr_size - this.name.length - 1 > 0) { + this.location = stream.readCString(); + } +}); + +// file:src/parsing/uuid/piff/piffLsm.js +BoxParser.createUUIDBox("a5d40b30e81411ddba2f0800200c9a66", true, false, function(stream) { + this.LiveServerManifest = stream.readString(this.size - this.hdr_size) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +});// file:src/parsing/uuid/piff/piffPssh.js +BoxParser.createUUIDBox("d08a4f1810f34a82b6c832d8aba183d3", true, false, function(stream) { + this.system_id = BoxParser.parseHex16(stream); + var datasize = stream.readUint32(); + if (datasize > 0) { + this.data = stream.readUint8Array(datasize); + } +}); + +// file:src/parsing/uuid/piff/piffSenc.js +BoxParser.createUUIDBox("a2394f525a9b4f14a2446c427c648df4", true, false /*, function(stream) { + if (this.flags & 0x1) { + this.AlgorithmID = stream.readUint24(); + this.IV_size = stream.readUint8(); + this.KID = BoxParser.parseHex16(stream); + } + var sample_count = stream.readUint32(); + this.samples = []; + for (var i = 0; i < sample_count; i++) { + var sample = {}; + sample.InitializationVector = this.readUint8Array(this.IV_size*8); + if (this.flags & 0x2) { + sample.subsamples = []; + sample.NumberOfEntries = stream.readUint16(); + for (var j = 0; j < sample.NumberOfEntries; j++) { + var subsample = {}; + subsample.BytesOfClearData = stream.readUint16(); + subsample.BytesOfProtectedData = stream.readUint32(); + sample.subsamples.push(subsample); + } + } + this.samples.push(sample); + } +}*/); +// file:src/parsing/uuid/piff/piffTenc.js +BoxParser.createUUIDBox("8974dbce7be74c5184f97148f9882554", true, false, function(stream) { + this.default_AlgorithmID = stream.readUint24(); + this.default_IV_size = stream.readUint8(); + this.default_KID = BoxParser.parseHex16(stream); +});// file:src/parsing/uuid/piff/piffTfrf.js +BoxParser.createUUIDBox("d4807ef2ca3946958e5426cb9e46a79f", true, false, function(stream) { + this.fragment_count = stream.readUint8(); + this.entries = []; + + for (var i = 0; i < this.fragment_count; i++) { + var entry = {}; + var absolute_time = 0; + var absolute_duration = 0; + + if (this.version === 1) { + absolute_time = stream.readUint64(); + absolute_duration = stream.readUint64(); + } else { + absolute_time = stream.readUint32(); + absolute_duration = stream.readUint32(); + } + + entry.absolute_time = absolute_time; + entry.absolute_duration = absolute_duration; + + this.entries.push(entry); + } +});// file:src/parsing/uuid/piff/piffTfxd.js +BoxParser.createUUIDBox("6d1d9b0542d544e680e2141daff757b2", true, false, function(stream) { + if (this.version === 1) { + this.absolute_time = stream.readUint64(); + this.duration = stream.readUint64(); + } else { + this.absolute_time = stream.readUint32(); + this.duration = stream.readUint32(); + } +});// file:src/parsing/vmhd.js +BoxParser.createFullBoxCtor("vmhd", function(stream) { + this.graphicsmode = stream.readUint16(); + this.opcolor = stream.readUint16Array(3); +}); + +// file:src/parsing/vpcC.js +BoxParser.createFullBoxCtor("vpcC", function (stream) { + var tmp; + if (this.version === 1) { + this.profile = stream.readUint8(); + this.level = stream.readUint8(); + tmp = stream.readUint8(); + this.bitDepth = tmp >> 4; + this.chromaSubsampling = (tmp >> 1) & 0x7; + this.videoFullRangeFlag = tmp & 0x1; + this.colourPrimaries = stream.readUint8(); + this.transferCharacteristics = stream.readUint8(); + this.matrixCoefficients = stream.readUint8(); + this.codecIntializationDataSize = stream.readUint16(); + this.codecIntializationData = stream.readUint8Array(this.codecIntializationDataSize); + } else { + this.profile = stream.readUint8(); + this.level = stream.readUint8(); + tmp = stream.readUint8(); + this.bitDepth = (tmp >> 4) & 0xF; + this.colorSpace = tmp & 0xF; + tmp = stream.readUint8(); + this.chromaSubsampling = (tmp >> 4) & 0xF; + this.transferFunction = (tmp >> 1) & 0x7; + this.videoFullRangeFlag = tmp & 0x1; + this.codecIntializationDataSize = stream.readUint16(); + this.codecIntializationData = stream.readUint8Array(this.codecIntializationDataSize); + } +});// file:src/parsing/vttC.js +BoxParser.createBoxCtor("vttC", function(stream) { + this.text = stream.readString(this.size - this.hdr_size); +}); + +// file:src/parsing/vvcC.js +BoxParser.createFullBoxCtor("vvcC", function (stream) { + var i, j; + + // helper object to simplify extracting individual bits + var bitReader = { + held_bits: undefined, + num_held_bits: 0, + + stream_read_1_bytes: function (strm) { + this.held_bits = strm.readUint8(); + this.num_held_bits = 1 * 8; + }, + stream_read_2_bytes: function (strm) { + this.held_bits = strm.readUint16(); + this.num_held_bits = 2 * 8; + }, + + extract_bits: function (num_bits) { + var ret = (this.held_bits >> (this.num_held_bits - num_bits)) & ((1 << num_bits) - 1); + this.num_held_bits -= num_bits; + return ret; + } + }; + + // VvcDecoderConfigurationRecord + bitReader.stream_read_1_bytes(stream); + bitReader.extract_bits(5); // reserved + this.lengthSizeMinusOne = bitReader.extract_bits(2); + this.ptl_present_flag = bitReader.extract_bits(1); + + if (this.ptl_present_flag) { + bitReader.stream_read_2_bytes(stream); + this.ols_idx = bitReader.extract_bits(9); + this.num_sublayers = bitReader.extract_bits(3); + this.constant_frame_rate = bitReader.extract_bits(2); + this.chroma_format_idc = bitReader.extract_bits(2); + + bitReader.stream_read_1_bytes(stream); + this.bit_depth_minus8 = bitReader.extract_bits(3); + bitReader.extract_bits(5); // reserved + + // VvcPTLRecord + { + bitReader.stream_read_2_bytes(stream); + bitReader.extract_bits(2); // reserved + this.num_bytes_constraint_info = bitReader.extract_bits(6); + this.general_profile_idc = bitReader.extract_bits(7); + this.general_tier_flag = bitReader.extract_bits(1); + + this.general_level_idc = stream.readUint8(); + + bitReader.stream_read_1_bytes(stream); + this.ptl_frame_only_constraint_flag = bitReader.extract_bits(1); + this.ptl_multilayer_enabled_flag = bitReader.extract_bits(1); + + this.general_constraint_info = new Uint8Array(this.num_bytes_constraint_info); + if (this.num_bytes_constraint_info) { + for (i = 0; i < this.num_bytes_constraint_info - 1; i++) { + var cnstr1 = bitReader.extract_bits(6); + bitReader.stream_read_1_bytes(stream); + var cnstr2 = bitReader.extract_bits(2); + + this.general_constraint_info[i] = ((cnstr1 << 2) | cnstr2); + } + this.general_constraint_info[this.num_bytes_constraint_info - 1] = bitReader.extract_bits(6); + } else { + //forbidden in spec! + bitReader.extract_bits(6); + } + + bitReader.stream_read_1_bytes(stream); + this.ptl_sublayer_present_mask = 0; + for (j = this.num_sublayers - 2; j >= 0; --j) { + var val = bitReader.extract_bits(1); + this.ptl_sublayer_present_mask |= val << j; + } + for (j = this.num_sublayers; j <= 8 && this.num_sublayers > 1; ++j) { + bitReader.extract_bits(1); // ptl_reserved_zero_bit + } + + for (j = this.num_sublayers - 2; j >= 0; --j) { + if (this.ptl_sublayer_present_mask & (1 << j)) { + this.sublayer_level_idc[j] = stream.readUint8(); + } + } + + this.ptl_num_sub_profiles = stream.readUint8(); + this.general_sub_profile_idc = []; + if (this.ptl_num_sub_profiles) { + for (i = 0; i < this.ptl_num_sub_profiles; i++) { + this.general_sub_profile_idc.push(stream.readUint32()); + } + } + } // end VvcPTLRecord + + this.max_picture_width = stream.readUint16(); + this.max_picture_height = stream.readUint16(); + this.avg_frame_rate = stream.readUint16(); + } + + var VVC_NALU_OPI = 12; + var VVC_NALU_DEC_PARAM = 13; + + this.nalu_arrays = []; + var num_of_arrays = stream.readUint8(); + for (i = 0; i < num_of_arrays; i++) { + var nalu_array = []; + this.nalu_arrays.push(nalu_array); + + bitReader.stream_read_1_bytes(stream); + nalu_array.completeness = bitReader.extract_bits(1); + bitReader.extract_bits(2); // reserved + nalu_array.nalu_type = bitReader.extract_bits(5); + + var numNalus = 1; + if (nalu_array.nalu_type != VVC_NALU_DEC_PARAM && nalu_array.nalu_type != VVC_NALU_OPI) { + numNalus = stream.readUint16(); + } + + for (j = 0; j < numNalus; j++) { + var len = stream.readUint16(); + nalu_array.push({ + data: stream.readUint8Array(len), + length: len + }); + } + } +}); +// file:src/parsing/vvnC.js +BoxParser.createFullBoxCtor("vvnC", function (stream) { + // VvcNALUConfigBox + var tmp = strm.readUint8(); + this.lengthSizeMinusOne = (tmp & 0x3); +}); +// file:src/box-codecs.js +BoxParser.SampleEntry.prototype.isVideo = function() { + return false; +} + +BoxParser.SampleEntry.prototype.isAudio = function() { + return false; +} + +BoxParser.SampleEntry.prototype.isSubtitle = function() { + return false; +} + +BoxParser.SampleEntry.prototype.isMetadata = function() { + return false; +} + +BoxParser.SampleEntry.prototype.isHint = function() { + return false; +} + +BoxParser.SampleEntry.prototype.getCodec = function() { + return this.type.replace('.',''); +} + +BoxParser.SampleEntry.prototype.getWidth = function() { + return ""; +} + +BoxParser.SampleEntry.prototype.getHeight = function() { + return ""; +} + +BoxParser.SampleEntry.prototype.getChannelCount = function() { + return ""; +} + +BoxParser.SampleEntry.prototype.getSampleRate = function() { + return ""; +} + +BoxParser.SampleEntry.prototype.getSampleSize = function() { + return ""; +} + +BoxParser.VisualSampleEntry.prototype.isVideo = function() { + return true; +} + +BoxParser.VisualSampleEntry.prototype.getWidth = function() { + return this.width; +} + +BoxParser.VisualSampleEntry.prototype.getHeight = function() { + return this.height; +} + +BoxParser.AudioSampleEntry.prototype.isAudio = function() { + return true; +} + +BoxParser.AudioSampleEntry.prototype.getChannelCount = function() { + return this.channel_count; +} + +BoxParser.AudioSampleEntry.prototype.getSampleRate = function() { + return this.samplerate; +} + +BoxParser.AudioSampleEntry.prototype.getSampleSize = function() { + return this.samplesize; +} + +BoxParser.SubtitleSampleEntry.prototype.isSubtitle = function() { + return true; +} + +BoxParser.MetadataSampleEntry.prototype.isMetadata = function() { + return true; +} + + +BoxParser.decimalToHex = function(d, padding) { + var hex = Number(d).toString(16); + padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding; + while (hex.length < padding) { + hex = "0" + hex; + } + return hex; +} + +BoxParser.avc1SampleEntry.prototype.getCodec = +BoxParser.avc2SampleEntry.prototype.getCodec = +BoxParser.avc3SampleEntry.prototype.getCodec = +BoxParser.avc4SampleEntry.prototype.getCodec = function() { + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + if (this.avcC) { + return baseCodec+"."+BoxParser.decimalToHex(this.avcC.AVCProfileIndication)+ + ""+BoxParser.decimalToHex(this.avcC.profile_compatibility)+ + ""+BoxParser.decimalToHex(this.avcC.AVCLevelIndication); + } else { + return baseCodec; + } +} + +BoxParser.hev1SampleEntry.prototype.getCodec = +BoxParser.hvc1SampleEntry.prototype.getCodec = function() { + var i; + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + if (this.hvcC) { + baseCodec += '.'; + switch (this.hvcC.general_profile_space) { + case 0: + baseCodec += ''; + break; + case 1: + baseCodec += 'A'; + break; + case 2: + baseCodec += 'B'; + break; + case 3: + baseCodec += 'C'; + break; + } + baseCodec += this.hvcC.general_profile_idc; + baseCodec += '.'; + var val = this.hvcC.general_profile_compatibility; + var reversed = 0; + for (i=0; i<32; i++) { + reversed |= val & 1; + if (i==31) break; + reversed <<= 1; + val >>=1; + } + baseCodec += BoxParser.decimalToHex(reversed, 0); + baseCodec += '.'; + if (this.hvcC.general_tier_flag === 0) { + baseCodec += 'L'; + } else { + baseCodec += 'H'; + } + baseCodec += this.hvcC.general_level_idc; + var hasByte = false; + var constraint_string = ""; + for (i = 5; i >= 0; i--) { + if (this.hvcC.general_constraint_indicator[i] || hasByte) { + constraint_string = "."+BoxParser.decimalToHex(this.hvcC.general_constraint_indicator[i], 0)+constraint_string; + hasByte = true; + } + } + baseCodec += constraint_string; + } + return baseCodec; +} + +BoxParser.vvc1SampleEntry.prototype.getCodec = +BoxParser.vvi1SampleEntry.prototype.getCodec = function () { + var i; + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + if (this.vvcC) { + baseCodec += '.' + this.vvcC.general_profile_idc; + if (this.vvcC.general_tier_flag) { + baseCodec += '.H'; + } else { + baseCodec += '.L'; + } + baseCodec += this.vvcC.general_level_idc; + + var constraint_string = ""; + if (this.vvcC.general_constraint_info) { + var bytes = []; + var byte = 0; + byte |= this.vvcC.ptl_frame_only_constraint << 7; + byte |= this.vvcC.ptl_multilayer_enabled << 6; + var last_nonzero; + for (i = 0; i < this.vvcC.general_constraint_info.length; ++i) { + byte |= (this.vvcC.general_constraint_info[i] >> 2) & 0x3f; + bytes.push(byte); + if (byte) { + last_nonzero = i; + } + + byte = (this.vvcC.general_constraint_info[i] >> 2) & 0x03; + } + + if (last_nonzero === undefined) { + constraint_string = ".CA"; + } + else { + constraint_string = ".C" + var base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + var held_bits = 0; + var num_held_bits = 0; + for (i = 0; i <= last_nonzero; ++i) { + held_bits = (held_bits << 8) | bytes[i]; + num_held_bits += 8; + + while (num_held_bits >= 5) { + var val = (held_bits >> (num_held_bits - 5)) & 0x1f; + constraint_string += base32_chars[val]; + + num_held_bits -= 5; + held_bits &= (1 << num_held_bits) - 1; + } + } + if (num_held_bits) { + held_bits <<= (5 - num_held_bits); // right-pad with zeros to 5 bits (is this correct?) + constraint_string += base32_chars[held_bits & 0x1f]; + } + } + } + baseCodec += constraint_string; + } + return baseCodec; +} + +BoxParser.mp4aSampleEntry.prototype.getCodec = function() { + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + if (this.esds && this.esds.esd) { + var oti = this.esds.esd.getOTI(); + var dsi = this.esds.esd.getAudioConfig(); + return baseCodec+"."+BoxParser.decimalToHex(oti)+(dsi ? "."+dsi: ""); + } else { + return baseCodec; + } +} + +BoxParser.stxtSampleEntry.prototype.getCodec = function() { + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + if(this.mime_format) { + return baseCodec + "." + this.mime_format; + } else { + return baseCodec + } +} + +BoxParser.vp08SampleEntry.prototype.getCodec = +BoxParser.vp09SampleEntry.prototype.getCodec = function() { + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + var level = this.vpcC.level; + if (level == 0) { + level = "00"; + } + var bitDepth = this.vpcC.bitDepth; + if (bitDepth == 8) { + bitDepth = "08"; + } + return baseCodec + ".0" + this.vpcC.profile + "." + level + "." + bitDepth; +} + +BoxParser.av01SampleEntry.prototype.getCodec = function() { + var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); + var level = this.av1C.seq_level_idx_0; + if (level < 10) { + level = "0" + level; + } + var bitdepth; + if (this.av1C.seq_profile === 2 && this.av1C.high_bitdepth === 1) { + bitdepth = (this.av1C.twelve_bit === 1) ? "12" : "10"; + } else if ( this.av1C.seq_profile <= 2 ) { + bitdepth = (this.av1C.high_bitdepth === 1) ? "10" : "08"; + } + // TODO need to parse the SH to find color config + return baseCodec+"."+this.av1C.seq_profile+"."+level+(this.av1C.seq_tier_0?"H":"M")+"."+bitdepth;//+"."+this.av1C.monochrome+"."+this.av1C.chroma_subsampling_x+""+this.av1C.chroma_subsampling_y+""+this.av1C.chroma_sample_position; +} +// file:src/box-write.js +/* + * Copyright (c) Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +BoxParser.Box.prototype.writeHeader = function(stream, msg) { + this.size += 8; + if (this.size > MAX_SIZE) { + this.size += 8; + } + if (this.type === "uuid") { + this.size += 16; + } + Log.debug("BoxWriter", "Writing box "+this.type+" of size: "+this.size+" at position "+stream.getPosition()+(msg || "")); + if (this.size > MAX_SIZE) { + stream.writeUint32(1); + } else { + this.sizePosition = stream.getPosition(); + stream.writeUint32(this.size); + } + stream.writeString(this.type, null, 4); + if (this.type === "uuid") { + stream.writeUint8Array(this.uuid); + } + if (this.size > MAX_SIZE) { + stream.writeUint64(this.size); + } +} + +BoxParser.FullBox.prototype.writeHeader = function(stream) { + this.size += 4; + BoxParser.Box.prototype.writeHeader.call(this, stream, " v="+this.version+" f="+this.flags); + stream.writeUint8(this.version); + stream.writeUint24(this.flags); +} + +BoxParser.Box.prototype.write = function(stream) { + if (this.type === "mdat") { + /* TODO: fix this */ + if (this.data) { + this.size = this.data.length; + this.writeHeader(stream); + stream.writeUint8Array(this.data); + } + } else { + this.size = (this.data ? this.data.length : 0); + this.writeHeader(stream); + if (this.data) { + stream.writeUint8Array(this.data); + } + } +} + +BoxParser.ContainerBox.prototype.write = function(stream) { + this.size = 0; + this.writeHeader(stream); + for (var i=0; i= 2) { + stream.writeUint32(this.default_sample_description_index); + } + stream.writeUint32(this.entries.length); + for (i = 0; i < this.entries.length; i++) { + entry = this.entries[i]; + if (this.version === 1) { + if (this.default_length === 0) { + stream.writeUint32(entry.description_length); + } + } + entry.write(stream); + } +} + + +// file:src/writing/sidx.js +BoxParser.sidxBox.prototype.write = function(stream) { + this.version = 0; + this.flags = 0; + this.size = 4*4+2+2+12*this.references.length; + this.writeHeader(stream); + stream.writeUint32(this.reference_ID); + stream.writeUint32(this.timescale); + stream.writeUint32(this.earliest_presentation_time); + stream.writeUint32(this.first_offset); + stream.writeUint16(0); + stream.writeUint16(this.references.length); + for (var i = 0; i < this.references.length; i++) { + var ref = this.references[i]; + stream.writeUint32(ref.reference_type << 31 | ref.referenced_size); + stream.writeUint32(ref.subsegment_duration); + stream.writeUint32(ref.starts_with_SAP << 31 | ref.SAP_type << 28 | ref.SAP_delta_time); + } +} + +// file:src/writing/smhd.js +BoxParser.smhdBox.prototype.write = function(stream) { + var i; + this.version = 0; + this.flags = 1; + this.size = 4; + this.writeHeader(stream); + stream.writeUint16(this.balance); + stream.writeUint16(0); +} +// file:src/writing/stco.js +BoxParser.stcoBox.prototype.write = function(stream) { + this.version = 0; + this.flags = 0; + this.size = 4+4*this.chunk_offsets.length; + this.writeHeader(stream); + stream.writeUint32(this.chunk_offsets.length); + stream.writeUint32Array(this.chunk_offsets); +} + +// file:src/writing/stsc.js +BoxParser.stscBox.prototype.write = function(stream) { + var i; + this.version = 0; + this.flags = 0; + this.size = 4+12*this.first_chunk.length; + this.writeHeader(stream); + stream.writeUint32(this.first_chunk.length); + for(i=0; i 0) { + i = 0; + while (i+1 < this.sample_sizes.length) { + if (this.sample_sizes[i+1] !== this.sample_sizes[0]) { + constant = false; + break; + } else { + i++; + } + } + } else { + constant = false; + } + this.size = 8; + if (!constant) { + this.size += 4*this.sample_sizes.length; + } + this.writeHeader(stream); + if (!constant) { + stream.writeUint32(0); + } else { + stream.writeUint32(this.sample_sizes[0]); + } + stream.writeUint32(this.sample_sizes.length); + if (!constant) { + stream.writeUint32Array(this.sample_sizes); + } +} + +// file:src/writing/stts.js +BoxParser.sttsBox.prototype.write = function(stream) { + var i; + this.version = 0; + this.flags = 0; + this.size = 4+8*this.sample_counts.length; + this.writeHeader(stream); + stream.writeUint32(this.sample_counts.length); + for(i=0; i UINT32_MAX ? 1 : 0; + this.flags = 0; + this.size = 4; + if (this.version === 1) { + this.size += 4; + } + this.writeHeader(stream); + if (this.version === 1) { + stream.writeUint64(this.baseMediaDecodeTime); + } else { + stream.writeUint32(this.baseMediaDecodeTime); + } +} + +// file:src/writing/tfhd.js +BoxParser.tfhdBox.prototype.write = function(stream) { + this.version = 0; + this.size = 4; + if (this.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET) { + this.size += 8; + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) { + this.size += 4; + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) { + this.size += 4; + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) { + this.size += 4; + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) { + this.size += 4; + } + this.writeHeader(stream); + stream.writeUint32(this.track_id); + if (this.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET) { + stream.writeUint64(this.base_data_offset); + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) { + stream.writeUint32(this.default_sample_description_index); + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) { + stream.writeUint32(this.default_sample_duration); + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) { + stream.writeUint32(this.default_sample_size); + } + if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) { + stream.writeUint32(this.default_sample_flags); + } +} + +// file:src/writing/tkhd.js +BoxParser.tkhdBox.prototype.write = function(stream) { + this.version = 0; + //this.flags = 0; + this.size = 4*18+2*4; + this.writeHeader(stream); + stream.writeUint32(this.creation_time); + stream.writeUint32(this.modification_time); + stream.writeUint32(this.track_id); + stream.writeUint32(0); + stream.writeUint32(this.duration); + stream.writeUint32(0); + stream.writeUint32(0); + stream.writeInt16(this.layer); + stream.writeInt16(this.alternate_group); + stream.writeInt16(this.volume<<8); + stream.writeUint16(0); + stream.writeInt32Array(this.matrix); + stream.writeUint32(this.width); + stream.writeUint32(this.height); +} + +// file:src/writing/trex.js +BoxParser.trexBox.prototype.write = function(stream) { + this.version = 0; + this.flags = 0; + this.size = 4*5; + this.writeHeader(stream); + stream.writeUint32(this.track_id); + stream.writeUint32(this.default_sample_description_index); + stream.writeUint32(this.default_sample_duration); + stream.writeUint32(this.default_sample_size); + stream.writeUint32(this.default_sample_flags); +} + +// file:src/writing/trun.js +BoxParser.trunBox.prototype.write = function(stream) { + this.version = 0; + this.size = 4; + if (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) { + this.size += 4; + } + if (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) { + this.size += 4; + } + if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { + this.size += 4*this.sample_duration.length; + } + if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { + this.size += 4*this.sample_size.length; + } + if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { + this.size += 4*this.sample_flags.length; + } + if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { + this.size += 4*this.sample_composition_time_offset.length; + } + this.writeHeader(stream); + stream.writeUint32(this.sample_count); + if (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) { + this.data_offset_position = stream.getPosition(); + stream.writeInt32(this.data_offset); //signed + } + if (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) { + stream.writeUint32(this.first_sample_flags); + } + for (var i = 0; i < this.sample_count; i++) { + if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { + stream.writeUint32(this.sample_duration[i]); + } + if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { + stream.writeUint32(this.sample_size[i]); + } + if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { + stream.writeUint32(this.sample_flags[i]); + } + if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { + if (this.version === 0) { + stream.writeUint32(this.sample_composition_time_offset[i]); + } else { + stream.writeInt32(this.sample_composition_time_offset[i]); //signed + } + } + } +} + +// file:src/writing/url.js +BoxParser["url Box"].prototype.write = function(stream) { + this.version = 0; + if (this.location) { + this.flags = 0; + this.size = this.location.length+1; + } else { + this.flags = 0x000001; + this.size = 0; + } + this.writeHeader(stream); + if (this.location) { + stream.writeCString(this.location); + } +} + +// file:src/writing/urn.js +BoxParser["urn Box"].prototype.write = function(stream) { + this.version = 0; + this.flags = 0; + this.size = this.name.length+1+(this.location ? this.location.length+1 : 0); + this.writeHeader(stream); + stream.writeCString(this.name); + if (this.location) { + stream.writeCString(this.location); + } +} + +// file:src/writing/vmhd.js +BoxParser.vmhdBox.prototype.write = function(stream) { + var i; + this.version = 0; + this.flags = 1; + this.size = 8; + this.writeHeader(stream); + stream.writeUint16(this.graphicsmode); + stream.writeUint16Array(this.opcolor); +} + +// file:src/box-unpack.js +/* + * Copyright (c) Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +BoxParser.cttsBox.prototype.unpack = function(samples) { + var i, j, k; + k = 0; + for (i = 0; i < this.sample_counts.length; i++) { + for (j = 0; j < this.sample_counts[i]; j++) { + samples[k].pts = samples[k].dts + this.sample_offsets[i]; + k++; + } + } +} + +BoxParser.sttsBox.prototype.unpack = function(samples) { + var i, j, k; + k = 0; + for (i = 0; i < this.sample_counts.length; i++) { + for (j = 0; j < this.sample_counts[i]; j++) { + if (k === 0) { + samples[k].dts = 0; + } else { + samples[k].dts = samples[k-1].dts + this.sample_deltas[i]; + } + k++; + } + } +} + +BoxParser.stcoBox.prototype.unpack = function(samples) { + var i; + for (i = 0; i < this.chunk_offsets.length; i++) { + samples[i].offset = this.chunk_offsets[i]; + } +} + +BoxParser.stscBox.prototype.unpack = function(samples) { + var i, j, k, l, m; + l = 0; + m = 0; + for (i = 0; i < this.first_chunk.length; i++) { + for (j = 0; j < (i+1 < this.first_chunk.length ? this.first_chunk[i+1] : Infinity); j++) { + m++; + for (k = 0; k < this.samples_per_chunk[i]; k++) { + if (samples[l]) { + samples[l].description_index = this.sample_description_index[i]; + samples[l].chunk_index = m; + } else { + return; + } + l++; + } + } + } +} + +BoxParser.stszBox.prototype.unpack = function(samples) { + var i; + for (i = 0; i < this.sample_sizes.length; i++) { + samples[i].size = this.sample_sizes[i]; + } +} +// file:src/box-diff.js + +BoxParser.DIFF_BOXES_PROP_NAMES = [ "boxes", "entries", "references", "subsamples", + "items", "item_infos", "extents", "associations", + "subsegments", "ranges", "seekLists", "seekPoints", + "esd", "levels"]; + +BoxParser.DIFF_PRIMITIVE_ARRAY_PROP_NAMES = [ "compatible_brands", "matrix", "opcolor", "sample_counts", "sample_counts", "sample_deltas", +"first_chunk", "samples_per_chunk", "sample_sizes", "chunk_offsets", "sample_offsets", "sample_description_index", "sample_duration" ]; + +BoxParser.boxEqualFields = function(box_a, box_b) { + if (box_a && !box_b) return false; + var prop; + for (prop in box_a) { + if (BoxParser.DIFF_BOXES_PROP_NAMES.indexOf(prop) > -1) { + continue; + // } else if (excluded_fields && excluded_fields.indexOf(prop) > -1) { + // continue; + } else if (box_a[prop] instanceof BoxParser.Box || box_b[prop] instanceof BoxParser.Box) { + continue; + } else if (typeof box_a[prop] === "undefined" || typeof box_b[prop] === "undefined") { + continue; + } else if (typeof box_a[prop] === "function" || typeof box_b[prop] === "function") { + continue; + } else if ( + (box_a.subBoxNames && box_a.subBoxNames.indexOf(prop.slice(0,4)) > -1) || + (box_b.subBoxNames && box_b.subBoxNames.indexOf(prop.slice(0,4)) > -1)) { + continue; + } else { + if (prop === "data" || prop === "start" || prop === "size" || prop === "creation_time" || prop === "modification_time") { + continue; + } else if (BoxParser.DIFF_PRIMITIVE_ARRAY_PROP_NAMES.indexOf(prop) > -1) { + continue; + } else { + if (box_a[prop] !== box_b[prop]) { + return false; + } + } + } + } + return true; +} + +BoxParser.boxEqual = function(box_a, box_b) { + if (!BoxParser.boxEqualFields(box_a, box_b)) { + return false; + } + for (var j = 0; j < BoxParser.DIFF_BOXES_PROP_NAMES.length; j++) { + var name = BoxParser.DIFF_BOXES_PROP_NAMES[j]; + if (box_a[name] && box_b[name]) { + if (!BoxParser.boxEqual(box_a[name], box_b[name])) { + return false; + } + } + } + return true; +}// file:src/text-mp4.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var VTTin4Parser = function() { +} + +VTTin4Parser.prototype.parseSample = function(data) { + var cues, cue; + var stream = new MP4BoxStream(data.buffer); + cues = []; + while (!stream.isEos()) { + cue = BoxParser.parseOneBox(stream, false); + if (cue.code === BoxParser.OK && cue.box.type === "vttc") { + cues.push(cue.box); + } + } + return cues; +} + +VTTin4Parser.prototype.getText = function (startTime, endTime, data) { + function pad(n, width, z) { + z = z || '0'; + n = n + ''; + return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; + } + function secToTimestamp(insec) { + var h = Math.floor(insec/3600); + var m = Math.floor((insec - h*3600)/60); + var s = Math.floor(insec - h*3600 - m*60); + var ms = Math.floor((insec - h*3600 - m*60 - s)*1000); + return ""+pad(h, 2)+":"+pad(m,2)+":"+pad(s, 2)+"."+pad(ms, 3); + } + var cues = this.parseSample(data); + var string = ""; + for (var i = 0; i < cues.length; i++) { + var cueIn4 = cues[i]; + string += secToTimestamp(startTime)+" --> "+secToTimestamp(endTime)+"\r\n"; + string += cueIn4.payl.text; + } + return string; +} + +var XMLSubtitlein4Parser = function() { +} + +XMLSubtitlein4Parser.prototype.parseSample = function(sample) { + var res = {}; + var i; + res.resources = []; + var stream = new MP4BoxStream(sample.data.buffer); + if (!sample.subsamples || sample.subsamples.length === 0) { + res.documentString = stream.readString(sample.data.length); + } else { + res.documentString = stream.readString(sample.subsamples[0].size); + if (sample.subsamples.length > 1) { + for (i = 1; i < sample.subsamples.length; i++) { + res.resources[i] = stream.readUint8Array(sample.subsamples[i].size); + } + } + } + if (typeof (DOMParser) !== "undefined") { + res.document = (new DOMParser()).parseFromString(res.documentString, "application/xml"); + } + return res; +} + +var Textin4Parser = function() { +} + +Textin4Parser.prototype.parseSample = function(sample) { + var textString; + var stream = new MP4BoxStream(sample.data.buffer); + textString = stream.readString(sample.data.length); + return textString; +} + +Textin4Parser.prototype.parseConfig = function(data) { + var textString; + var stream = new MP4BoxStream(data.buffer); + stream.readUint32(); // version & flags + textString = stream.readCString(); + return textString; +} + +if (typeof exports !== 'undefined') { + exports.XMLSubtitlein4Parser = XMLSubtitlein4Parser; + exports.Textin4Parser = Textin4Parser; +} +// file:src/isofile.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var ISOFile = function (stream) { + /* MutiBufferStream object used to parse boxes */ + this.stream = stream || new MultiBufferStream(); + /* Array of all boxes (in order) found in the file */ + this.boxes = []; + /* Array of all mdats */ + this.mdats = []; + /* Array of all moofs */ + this.moofs = []; + /* Boolean indicating if the file is compatible with progressive parsing (moov first) */ + this.isProgressive = false; + /* Boolean used to fire moov start event only once */ + this.moovStartFound = false; + /* Callback called when the moov parsing starts */ + this.onMoovStart = null; + /* Boolean keeping track of the call to onMoovStart, to avoid double calls */ + this.moovStartSent = false; + /* Callback called when the moov is entirely parsed */ + this.onReady = null; + /* Boolean keeping track of the call to onReady, to avoid double calls */ + this.readySent = false; + /* Callback to call when segments are ready */ + this.onSegment = null; + /* Callback to call when samples are ready */ + this.onSamples = null; + /* Callback to call when there is an error in the parsing or processing of samples */ + this.onError = null; + /* Boolean indicating if the moov box run-length encoded tables of sample information have been processed */ + this.sampleListBuilt = false; + /* Array of Track objects for which fragmentation of samples is requested */ + this.fragmentedTracks = []; + /* Array of Track objects for which extraction of samples is requested */ + this.extractedTracks = []; + /* Boolean indicating that fragmention is ready */ + this.isFragmentationInitialized = false; + /* Boolean indicating that fragmented has started */ + this.sampleProcessingStarted = false; + /* Number of the next 'moof' to generate when fragmenting */ + this.nextMoofNumber = 0; + /* Boolean indicating if the initial list of items has been produced */ + this.itemListBuilt = false; + /* Callback called when the sidx box is entirely parsed */ + this.onSidx = null; + /* Boolean keeping track of the call to onSidx, to avoid double calls */ + this.sidxSent = false; +} + +ISOFile.prototype.setSegmentOptions = function(id, user, options) { + var trak = this.getTrackById(id); + if (trak) { + var fragTrack = {}; + this.fragmentedTracks.push(fragTrack); + fragTrack.id = id; + fragTrack.user = user; + fragTrack.trak = trak; + trak.nextSample = 0; + fragTrack.segmentStream = null; + fragTrack.nb_samples = 1000; + fragTrack.rapAlignement = true; + if (options) { + if (options.nbSamples) fragTrack.nb_samples = options.nbSamples; + if (options.rapAlignement) fragTrack.rapAlignement = options.rapAlignement; + } + } +} + +ISOFile.prototype.unsetSegmentOptions = function(id) { + var index = -1; + for (var i = 0; i < this.fragmentedTracks.length; i++) { + var fragTrack = this.fragmentedTracks[i]; + if (fragTrack.id == id) { + index = i; + } + } + if (index > -1) { + this.fragmentedTracks.splice(index, 1); + } +} + +ISOFile.prototype.setExtractionOptions = function(id, user, options) { + var trak = this.getTrackById(id); + if (trak) { + var extractTrack = {}; + this.extractedTracks.push(extractTrack); + extractTrack.id = id; + extractTrack.user = user; + extractTrack.trak = trak; + trak.nextSample = 0; + extractTrack.nb_samples = 1000; + extractTrack.samples = []; + if (options) { + if (options.nbSamples) extractTrack.nb_samples = options.nbSamples; + } + } +} + +ISOFile.prototype.unsetExtractionOptions = function(id) { + var index = -1; + for (var i = 0; i < this.extractedTracks.length; i++) { + var extractTrack = this.extractedTracks[i]; + if (extractTrack.id == id) { + index = i; + } + } + if (index > -1) { + this.extractedTracks.splice(index, 1); + } +} + +ISOFile.prototype.parse = function() { + var found; + var ret; + var box; + var parseBoxHeadersOnly = false; + + if (this.restoreParsePosition) { + if (!this.restoreParsePosition()) { + return; + } + } + + while (true) { + + if (this.hasIncompleteMdat && this.hasIncompleteMdat()) { + if (this.processIncompleteMdat()) { + continue; + } else { + return; + } + } else { + if (this.saveParsePosition) { + this.saveParsePosition(); + } + ret = BoxParser.parseOneBox(this.stream, parseBoxHeadersOnly); + if (ret.code === BoxParser.ERR_NOT_ENOUGH_DATA) { + if (this.processIncompleteBox) { + if (this.processIncompleteBox(ret)) { + continue; + } else { + return; + } + } else { + return; + } + } else { + var box_type; + /* the box is entirely parsed */ + box = ret.box; + box_type = (box.type !== "uuid" ? box.type : box.uuid); + /* store the box in the 'boxes' array to preserve box order (for file rewrite if needed) */ + this.boxes.push(box); + /* but also store box in a property for more direct access */ + switch (box_type) { + case "mdat": + this.mdats.push(box); + break; + case "moof": + this.moofs.push(box); + break; + case "moov": + this.moovStartFound = true; + if (this.mdats.length === 0) { + this.isProgressive = true; + } + /* no break */ + /* falls through */ + default: + if (this[box_type] !== undefined) { + Log.warn("ISOFile", "Duplicate Box of type: "+box_type+", overriding previous occurrence"); + } + this[box_type] = box; + break; + } + if (this.updateUsedBytes) { + this.updateUsedBytes(box, ret); + } + } + } + } +} + +ISOFile.prototype.checkBuffer = function (ab) { + if (ab === null || ab === undefined) { + throw("Buffer must be defined and non empty"); + } + if (ab.fileStart === undefined) { + throw("Buffer must have a fileStart property"); + } + if (ab.byteLength === 0) { + Log.warn("ISOFile", "Ignoring empty buffer (fileStart: "+ab.fileStart+")"); + this.stream.logBufferLevel(); + return false; + } + Log.info("ISOFile", "Processing buffer (fileStart: "+ab.fileStart+")"); + + /* mark the bytes in the buffer as not being used yet */ + ab.usedBytes = 0; + this.stream.insertBuffer(ab); + this.stream.logBufferLevel(); + + if (!this.stream.initialized()) { + Log.warn("ISOFile", "Not ready to start parsing"); + return false; + } + return true; +} + +/* Processes a new ArrayBuffer (with a fileStart property) + Returns the next expected file position, or undefined if not ready to parse */ +ISOFile.prototype.appendBuffer = function(ab, last) { + var nextFileStart; + if (!this.checkBuffer(ab)) { + return; + } + + /* Parse whatever is in the existing buffers */ + this.parse(); + + /* Check if the moovStart callback needs to be called */ + if (this.moovStartFound && !this.moovStartSent) { + this.moovStartSent = true; + if (this.onMoovStart) this.onMoovStart(); + } + + if (this.moov) { + /* A moov box has been entirely parsed */ + + /* if this is the first call after the moov is found we initialize the list of samples (may be empty in fragmented files) */ + if (!this.sampleListBuilt) { + this.buildSampleLists(); + this.sampleListBuilt = true; + } + + /* We update the sample information if there are any new moof boxes */ + this.updateSampleLists(); + + /* If the application needs to be informed that the 'moov' has been found, + we create the information object and callback the application */ + if (this.onReady && !this.readySent) { + this.readySent = true; + this.onReady(this.getInfo()); + } + + /* See if any sample extraction or segment creation needs to be done with the available samples */ + this.processSamples(last); + + /* Inform about the best range to fetch next */ + if (this.nextSeekPosition) { + nextFileStart = this.nextSeekPosition; + this.nextSeekPosition = undefined; + } else { + nextFileStart = this.nextParsePosition; + } + if (this.stream.getEndFilePositionAfter) { + nextFileStart = this.stream.getEndFilePositionAfter(nextFileStart); + } + } else { + if (this.nextParsePosition) { + /* moov has not been parsed but the first buffer was received, + the next fetch should probably be the next box start */ + nextFileStart = this.nextParsePosition; + } else { + /* No valid buffer has been parsed yet, we cannot know what to parse next */ + nextFileStart = 0; + } + } + if (this.sidx) { + if (this.onSidx && !this.sidxSent) { + this.onSidx(this.sidx); + this.sidxSent = true; + } + } + if (this.meta) { + if (this.flattenItemInfo && !this.itemListBuilt) { + this.flattenItemInfo(); + this.itemListBuilt = true; + } + if (this.processItems) { + this.processItems(this.onItem); + } + } + + if (this.stream.cleanBuffers) { + Log.info("ISOFile", "Done processing buffer (fileStart: "+ab.fileStart+") - next buffer to fetch should have a fileStart position of "+nextFileStart); + this.stream.logBufferLevel(); + this.stream.cleanBuffers(); + this.stream.logBufferLevel(true); + Log.info("ISOFile", "Sample data size in memory: "+this.getAllocatedSampleDataSize()); + } + return nextFileStart; +} + +ISOFile.prototype.getInfo = function() { + var i, j; + var movie = {}; + var trak; + var track; + var ref; + var sample_desc; + var _1904 = (new Date('1904-01-01T00:00:00Z').getTime()); + + if (this.moov) { + movie.hasMoov = true; + movie.duration = this.moov.mvhd.duration; + movie.timescale = this.moov.mvhd.timescale; + movie.isFragmented = (this.moov.mvex != null); + if (movie.isFragmented && this.moov.mvex.mehd) { + movie.fragment_duration = this.moov.mvex.mehd.fragment_duration; + } + movie.isProgressive = this.isProgressive; + movie.hasIOD = (this.moov.iods != null); + movie.brands = []; + movie.brands.push(this.ftyp.major_brand); + movie.brands = movie.brands.concat(this.ftyp.compatible_brands); + movie.created = new Date(_1904+this.moov.mvhd.creation_time*1000); + movie.modified = new Date(_1904+this.moov.mvhd.modification_time*1000); + movie.tracks = []; + movie.audioTracks = []; + movie.videoTracks = []; + movie.subtitleTracks = []; + movie.metadataTracks = []; + movie.hintTracks = []; + movie.otherTracks = []; + for (i = 0; i < this.moov.traks.length; i++) { + trak = this.moov.traks[i]; + sample_desc = trak.mdia.minf.stbl.stsd.entries[0]; + track = {}; + movie.tracks.push(track); + track.id = trak.tkhd.track_id; + track.name = trak.mdia.hdlr.name; + track.references = []; + if (trak.tref) { + for (j = 0; j < trak.tref.boxes.length; j++) { + ref = {}; + track.references.push(ref); + ref.type = trak.tref.boxes[j].type; + ref.track_ids = trak.tref.boxes[j].track_ids; + } + } + if (trak.edts) { + track.edits = trak.edts.elst.entries; + } + track.created = new Date(_1904+trak.tkhd.creation_time*1000); + track.modified = new Date(_1904+trak.tkhd.modification_time*1000); + track.movie_duration = trak.tkhd.duration; + track.movie_timescale = movie.timescale; + track.layer = trak.tkhd.layer; + track.alternate_group = trak.tkhd.alternate_group; + track.volume = trak.tkhd.volume; + track.matrix = trak.tkhd.matrix; + track.track_width = trak.tkhd.width/(1<<16); + track.track_height = trak.tkhd.height/(1<<16); + track.timescale = trak.mdia.mdhd.timescale; + track.cts_shift = trak.mdia.minf.stbl.cslg; + track.duration = trak.mdia.mdhd.duration; + track.samples_duration = trak.samples_duration; + track.codec = sample_desc.getCodec(); + track.kind = (trak.udta && trak.udta.kinds.length ? trak.udta.kinds[0] : { schemeURI: "", value: ""}); + track.language = (trak.mdia.elng ? trak.mdia.elng.extended_language : trak.mdia.mdhd.languageString); + track.nb_samples = trak.samples.length; + track.size = trak.samples_size; + track.bitrate = (track.size*8*track.timescale)/track.samples_duration; + if (sample_desc.isAudio()) { + track.type = "audio"; + movie.audioTracks.push(track); + track.audio = {}; + track.audio.sample_rate = sample_desc.getSampleRate(); + track.audio.channel_count = sample_desc.getChannelCount(); + track.audio.sample_size = sample_desc.getSampleSize(); + } else if (sample_desc.isVideo()) { + track.type = "video"; + movie.videoTracks.push(track); + track.video = {}; + track.video.width = sample_desc.getWidth(); + track.video.height = sample_desc.getHeight(); + } else if (sample_desc.isSubtitle()) { + track.type = "subtitles"; + movie.subtitleTracks.push(track); + } else if (sample_desc.isHint()) { + track.type = "metadata"; + movie.hintTracks.push(track); + } else if (sample_desc.isMetadata()) { + track.type = "metadata"; + movie.metadataTracks.push(track); + } else { + track.type = "metadata"; + movie.otherTracks.push(track); + } + } + } else { + movie.hasMoov = false; + } + movie.mime = ""; + if (movie.hasMoov && movie.tracks) { + if (movie.videoTracks && movie.videoTracks.length > 0) { + movie.mime += 'video/mp4; codecs=\"'; + } else if (movie.audioTracks && movie.audioTracks.length > 0) { + movie.mime += 'audio/mp4; codecs=\"'; + } else { + movie.mime += 'application/mp4; codecs=\"'; + } + for (i = 0; i < movie.tracks.length; i++) { + if (i !== 0) movie.mime += ','; + movie.mime+= movie.tracks[i].codec; + } + movie.mime += '\"; profiles=\"'; + movie.mime += this.ftyp.compatible_brands.join(); + movie.mime += '\"'; + } + return movie; +} + +ISOFile.prototype.processSamples = function(last) { + var i; + var trak; + if (!this.sampleProcessingStarted) return; + + /* For each track marked for fragmentation, + check if the next sample is there (i.e. if the sample information is known (i.e. moof has arrived) and if it has been downloaded) + and create a fragment with it */ + if (this.isFragmentationInitialized && this.onSegment !== null) { + for (i = 0; i < this.fragmentedTracks.length; i++) { + var fragTrak = this.fragmentedTracks[i]; + trak = fragTrak.trak; + while (trak.nextSample < trak.samples.length && this.sampleProcessingStarted) { + /* The sample information is there (either because the file is not fragmented and this is not the last sample, + or because the file is fragmented and the moof for that sample has been received */ + Log.debug("ISOFile", "Creating media fragment on track #"+fragTrak.id +" for sample "+trak.nextSample); + var result = this.createFragment(fragTrak.id, trak.nextSample, fragTrak.segmentStream); + if (result) { + fragTrak.segmentStream = result; + trak.nextSample++; + } else { + /* The fragment could not be created because the media data is not there (not downloaded), wait for it */ + break; + } + /* A fragment is created by sample, but the segment is the accumulation in the buffer of these fragments. + It is flushed only as requested by the application (nb_samples) to avoid too many callbacks */ + if (trak.nextSample % fragTrak.nb_samples === 0 || (last || trak.nextSample >= trak.samples.length)) { + Log.info("ISOFile", "Sending fragmented data on track #"+fragTrak.id+" for samples ["+Math.max(0,trak.nextSample-fragTrak.nb_samples)+","+(trak.nextSample-1)+"]"); + Log.info("ISOFile", "Sample data size in memory: "+this.getAllocatedSampleDataSize()); + if (this.onSegment) { + this.onSegment(fragTrak.id, fragTrak.user, fragTrak.segmentStream.buffer, trak.nextSample, (last || trak.nextSample >= trak.samples.length)); + } + /* force the creation of a new buffer */ + fragTrak.segmentStream = null; + if (fragTrak !== this.fragmentedTracks[i]) { + /* make sure we can stop fragmentation if needed */ + break; + } + } + } + } + } + + if (this.onSamples !== null) { + /* For each track marked for data export, + check if the next sample is there (i.e. has been downloaded) and send it */ + for (i = 0; i < this.extractedTracks.length; i++) { + var extractTrak = this.extractedTracks[i]; + trak = extractTrak.trak; + while (trak.nextSample < trak.samples.length && this.sampleProcessingStarted) { + Log.debug("ISOFile", "Exporting on track #"+extractTrak.id +" sample #"+trak.nextSample); + var sample = this.getSample(trak, trak.nextSample); + if (sample) { + trak.nextSample++; + extractTrak.samples.push(sample); + } else { + break; + } + if (trak.nextSample % extractTrak.nb_samples === 0 || trak.nextSample >= trak.samples.length) { + Log.debug("ISOFile", "Sending samples on track #"+extractTrak.id+" for sample "+trak.nextSample); + if (this.onSamples) { + this.onSamples(extractTrak.id, extractTrak.user, extractTrak.samples); + } + extractTrak.samples = []; + if (extractTrak !== this.extractedTracks[i]) { + /* check if the extraction needs to be stopped */ + break; + } + } + } + } + } +} + +/* Find and return specific boxes using recursion and early return */ +ISOFile.prototype.getBox = function(type) { + var result = this.getBoxes(type, true); + return (result.length ? result[0] : null); +} + +ISOFile.prototype.getBoxes = function(type, returnEarly) { + var result = []; + ISOFile._sweep.call(this, type, result, returnEarly); + return result; +} + +ISOFile._sweep = function(type, result, returnEarly) { + if (this.type && this.type == type) result.push(this); + for (var box in this.boxes) { + if (result.length && returnEarly) return; + ISOFile._sweep.call(this.boxes[box], type, result, returnEarly); + } +} + +ISOFile.prototype.getTrackSamplesInfo = function(track_id) { + var track = this.getTrackById(track_id); + if (track) { + return track.samples; + } else { + return; + } +} + +ISOFile.prototype.getTrackSample = function(track_id, number) { + var track = this.getTrackById(track_id); + var sample = this.getSample(track, number); + return sample; +} + +/* Called by the application to release the resources associated to samples already forwarded to the application */ +ISOFile.prototype.releaseUsedSamples = function (id, sampleNum) { + var size = 0; + var trak = this.getTrackById(id); + if (!trak.lastValidSample) trak.lastValidSample = 0; + for (var i = trak.lastValidSample; i < sampleNum; i++) { + size+=this.releaseSample(trak, i); + } + Log.info("ISOFile", "Track #"+id+" released samples up to "+sampleNum+" (released size: "+size+", remaining: "+this.samplesDataSize+")"); + trak.lastValidSample = sampleNum; +} + +ISOFile.prototype.start = function() { + this.sampleProcessingStarted = true; + this.processSamples(false); +} + +ISOFile.prototype.stop = function() { + this.sampleProcessingStarted = false; +} + +/* Called by the application to flush the remaining samples (e.g. once the download is finished or when no more samples will be added) */ +ISOFile.prototype.flush = function() { + Log.info("ISOFile", "Flushing remaining samples"); + this.updateSampleLists(); + this.processSamples(true); + this.stream.cleanBuffers(); + this.stream.logBufferLevel(true); +} + +/* Finds the byte offset for a given time on a given track + also returns the time of the previous rap */ +ISOFile.prototype.seekTrack = function(time, useRap, trak) { + var j; + var sample; + var seek_offset = Infinity; + var rap_seek_sample_num = 0; + var seek_sample_num = 0; + var timescale; + + if (trak.samples.length === 0) { + Log.info("ISOFile", "No sample in track, cannot seek! Using time "+Log.getDurationString(0, 1) +" and offset: "+0); + return { offset: 0, time: 0 }; + } + + for (j = 0; j < trak.samples.length; j++) { + sample = trak.samples[j]; + if (j === 0) { + seek_sample_num = 0; + timescale = sample.timescale; + } else if (sample.cts > time * sample.timescale) { + seek_sample_num = j-1; + break; + } + if (useRap && sample.is_sync) { + rap_seek_sample_num = j; + } + } + if (useRap) { + seek_sample_num = rap_seek_sample_num; + } + time = trak.samples[seek_sample_num].cts; + trak.nextSample = seek_sample_num; + while (trak.samples[seek_sample_num].alreadyRead === trak.samples[seek_sample_num].size) { + // No remaining samples to look for, all are downloaded. + if (!trak.samples[seek_sample_num + 1]) { + break; + } + seek_sample_num++; + } + seek_offset = trak.samples[seek_sample_num].offset+trak.samples[seek_sample_num].alreadyRead; + Log.info("ISOFile", "Seeking to "+(useRap ? "RAP": "")+" sample #"+trak.nextSample+" on track "+trak.tkhd.track_id+", time "+Log.getDurationString(time, timescale) +" and offset: "+seek_offset); + return { offset: seek_offset, time: time/timescale }; +} + +/* Finds the byte offset in the file corresponding to the given time or to the time of the previous RAP */ +ISOFile.prototype.seek = function(time, useRap) { + var moov = this.moov; + var trak; + var trak_seek_info; + var i; + var seek_info = { offset: Infinity, time: Infinity }; + if (!this.moov) { + throw "Cannot seek: moov not received!"; + } else { + for (i = 0; i -1) { + media_type = mediaType; + break; + } + } + } + switch(media_type) { + case "Visual": + minf.add("vmhd").set("graphicsmode",0).set("opcolor", [ 0, 0, 0 ]); + sample_description_entry.set("width", options.width) + .set("height", options.height) + .set("horizresolution", 0x48<<16) + .set("vertresolution", 0x48<<16) + .set("frame_count", 1) + .set("compressorname", options.type+" Compressor") + .set("depth", 0x18); + if (options.avcDecoderConfigRecord) { + var avcC = new BoxParser.avcCBox(); + var stream = new MP4BoxStream(options.avcDecoderConfigRecord); + avcC.parse(stream); + sample_description_entry.addBox(avcC); + } + break; + case "Audio": + minf.add("smhd").set("balance", options.balance || 0); + sample_description_entry.set("channel_count", options.channel_count || 2) + .set("samplesize", options.samplesize || 16) + .set("samplerate", options.samplerate || 1<<16); + break; + case "Hint": + minf.add("hmhd"); // TODO: add properties + break; + case "Subtitle": + minf.add("sthd"); + switch (options.type) { + case "stpp": + sample_description_entry.set("namespace", options.namespace || "nonamespace") + .set("schema_location", options.schema_location || "") + .set("auxiliary_mime_types", options.auxiliary_mime_types || ""); + break; + } + break; + case "Metadata": + minf.add("nmhd"); + break; + case "System": + minf.add("nmhd"); + break; + default: + minf.add("nmhd"); + break; + } + if (options.description) { + sample_description_entry.addBox(options.description); + } + if (options.description_boxes) { + options.description_boxes.forEach(function (b) { + sample_description_entry.addBox(b); + }); + } + minf.add("dinf").add("dref").addEntry((new BoxParser["url Box"]()).set("flags", 0x1)); + var stbl = minf.add("stbl"); + stbl.add("stsd").addEntry(sample_description_entry); + stbl.add("stts").set("sample_counts", []) + .set("sample_deltas", []); + stbl.add("stsc").set("first_chunk", []) + .set("samples_per_chunk", []) + .set("sample_description_index", []); + stbl.add("stco").set("chunk_offsets", []); + stbl.add("stsz").set("sample_sizes", []); + + this.moov.mvex.add("trex").set("track_id", options.id) + .set("default_sample_description_index", options.default_sample_description_index || 1) + .set("default_sample_duration", options.default_sample_duration || 0) + .set("default_sample_size", options.default_sample_size || 0) + .set("default_sample_flags", options.default_sample_flags || 0); + this.buildTrakSampleLists(trak); + return options.id; +} + +BoxParser.Box.prototype.computeSize = function(stream_) { + var stream = stream_ || new DataStream(); + stream.endianness = DataStream.BIG_ENDIAN; + this.write(stream); +} + +ISOFile.prototype.addSample = function (track_id, data, _options) { + var options = _options || {}; + var sample = {}; + var trak = this.getTrackById(track_id); + if (trak === null) return; + sample.number = trak.samples.length; + sample.track_id = trak.tkhd.track_id; + sample.timescale = trak.mdia.mdhd.timescale; + sample.description_index = (options.sample_description_index ? options.sample_description_index - 1: 0); + sample.description = trak.mdia.minf.stbl.stsd.entries[sample.description_index]; + sample.data = data; + sample.size = data.byteLength; + sample.alreadyRead = sample.size; + sample.duration = options.duration || 1; + sample.cts = options.cts || 0; + sample.dts = options.dts || 0; + sample.is_sync = options.is_sync || false; + sample.is_leading = options.is_leading || 0; + sample.depends_on = options.depends_on || 0; + sample.is_depended_on = options.is_depended_on || 0; + sample.has_redundancy = options.has_redundancy || 0; + sample.degradation_priority = options.degradation_priority || 0; + sample.offset = 0; + sample.subsamples = options.subsamples; + trak.samples.push(sample); + trak.samples_size += sample.size; + trak.samples_duration += sample.duration; + if (!trak.first_dts) { + trak.first_dts = options.dts; + } + + this.processSamples(); + + var moof = this.createSingleSampleMoof(sample); + this.addBox(moof); + moof.computeSize(); + /* adjusting the data_offset now that the moof size is known*/ + moof.trafs[0].truns[0].data_offset = moof.size+8; //8 is mdat header + this.add("mdat").data = new Uint8Array(data); + return sample; +} + +ISOFile.prototype.createSingleSampleMoof = function(sample) { + var sample_flags = 0; + if (sample.is_sync) + sample_flags = (1 << 25); // sample_depends_on_none (I picture) + else + sample_flags = (1 << 16); // non-sync + + var moof = new BoxParser.moofBox(); + moof.add("mfhd").set("sequence_number", this.nextMoofNumber); + this.nextMoofNumber++; + var traf = moof.add("traf"); + var trak = this.getTrackById(sample.track_id); + traf.add("tfhd").set("track_id", sample.track_id) + .set("flags", BoxParser.TFHD_FLAG_DEFAULT_BASE_IS_MOOF); + traf.add("tfdt").set("baseMediaDecodeTime", (sample.dts - (trak.first_dts || 0))); + traf.add("trun").set("flags", BoxParser.TRUN_FLAGS_DATA_OFFSET | BoxParser.TRUN_FLAGS_DURATION | + BoxParser.TRUN_FLAGS_SIZE | BoxParser.TRUN_FLAGS_FLAGS | + BoxParser.TRUN_FLAGS_CTS_OFFSET) + .set("data_offset",0) + .set("first_sample_flags",0) + .set("sample_count",1) + .set("sample_duration",[sample.duration]) + .set("sample_size",[sample.size]) + .set("sample_flags",[sample_flags]) + .set("sample_composition_time_offset", [sample.cts - sample.dts]); + return moof; +} + +// file:src/isofile-sample-processing.js +/* Index of the last moof box received */ +ISOFile.prototype.lastMoofIndex = 0; + +/* size of the buffers allocated for samples */ +ISOFile.prototype.samplesDataSize = 0; + +/* Resets all sample tables */ +ISOFile.prototype.resetTables = function () { + var i; + var trak, stco, stsc, stsz, stts, ctts, stss; + this.initial_duration = this.moov.mvhd.duration; + this.moov.mvhd.duration = 0; + for (i = 0; i < this.moov.traks.length; i++) { + trak = this.moov.traks[i]; + trak.tkhd.duration = 0; + trak.mdia.mdhd.duration = 0; + stco = trak.mdia.minf.stbl.stco || trak.mdia.minf.stbl.co64; + stco.chunk_offsets = []; + stsc = trak.mdia.minf.stbl.stsc; + stsc.first_chunk = []; + stsc.samples_per_chunk = []; + stsc.sample_description_index = []; + stsz = trak.mdia.minf.stbl.stsz || trak.mdia.minf.stbl.stz2; + stsz.sample_sizes = []; + stts = trak.mdia.minf.stbl.stts; + stts.sample_counts = []; + stts.sample_deltas = []; + ctts = trak.mdia.minf.stbl.ctts; + if (ctts) { + ctts.sample_counts = []; + ctts.sample_offsets = []; + } + stss = trak.mdia.minf.stbl.stss; + var k = trak.mdia.minf.stbl.boxes.indexOf(stss); + if (k != -1) trak.mdia.minf.stbl.boxes[k] = null; + } +} + +ISOFile.initSampleGroups = function(trak, traf, sbgps, trak_sgpds, traf_sgpds) { + var l; + var k; + var sample_groups_info; + var sample_group_info; + var sample_group_key; + function SampleGroupInfo(_type, _parameter, _sbgp) { + this.grouping_type = _type; + this.grouping_type_parameter = _parameter; + this.sbgp = _sbgp; + this.last_sample_in_run = -1; + this.entry_index = -1; + } + if (traf) { + traf.sample_groups_info = []; + } + if (!trak.sample_groups_info) { + trak.sample_groups_info = []; + } + for (k = 0; k < sbgps.length; k++) { + sample_group_key = sbgps[k].grouping_type +"/"+ sbgps[k].grouping_type_parameter; + sample_group_info = new SampleGroupInfo(sbgps[k].grouping_type, sbgps[k].grouping_type_parameter, sbgps[k]); + if (traf) { + traf.sample_groups_info[sample_group_key] = sample_group_info; + } + if (!trak.sample_groups_info[sample_group_key]) { + trak.sample_groups_info[sample_group_key] = sample_group_info; + } + for (l=0; l = 2) { + sample_group_key = trak_sgpds[k].grouping_type +"/0"; + sample_group_info = new SampleGroupInfo(trak_sgpds[k].grouping_type, 0); + if (!trak.sample_groups_info[sample_group_key]) { + trak.sample_groups_info[sample_group_key] = sample_group_info; + } + } + } + } else { + if (traf_sgpds) { + for (k = 0; k < traf_sgpds.length; k++) { + if (!traf_sgpds[k].used && traf_sgpds[k].version >= 2) { + sample_group_key = traf_sgpds[k].grouping_type +"/0"; + sample_group_info = new SampleGroupInfo(traf_sgpds[k].grouping_type, 0); + sample_group_info.is_fragment = true; + if (!traf.sample_groups_info[sample_group_key]) { + traf.sample_groups_info[sample_group_key] = sample_group_info; + } + } + } + } + } +} + +ISOFile.setSampleGroupProperties = function(trak, sample, sample_number, sample_groups_info) { + var k; + var index; + sample.sample_groups = []; + for (k in sample_groups_info) { + sample.sample_groups[k] = {}; + sample.sample_groups[k].grouping_type = sample_groups_info[k].grouping_type; + sample.sample_groups[k].grouping_type_parameter = sample_groups_info[k].grouping_type_parameter; + if (sample_number >= sample_groups_info[k].last_sample_in_run) { + if (sample_groups_info[k].last_sample_in_run < 0) { + sample_groups_info[k].last_sample_in_run = 0; + } + sample_groups_info[k].entry_index++; + if (sample_groups_info[k].entry_index <= sample_groups_info[k].sbgp.entries.length - 1) { + sample_groups_info[k].last_sample_in_run += sample_groups_info[k].sbgp.entries[sample_groups_info[k].entry_index].sample_count; + } + } + if (sample_groups_info[k].entry_index <= sample_groups_info[k].sbgp.entries.length - 1) { + sample.sample_groups[k].group_description_index = sample_groups_info[k].sbgp.entries[sample_groups_info[k].entry_index].group_description_index; + } else { + sample.sample_groups[k].group_description_index = -1; // special value for not defined + } + if (sample.sample_groups[k].group_description_index !== 0) { + var description; + if (sample_groups_info[k].fragment_description) { + description = sample_groups_info[k].fragment_description; + } else { + description = sample_groups_info[k].description; + } + if (sample.sample_groups[k].group_description_index > 0) { + if (sample.sample_groups[k].group_description_index > 65535) { + index = (sample.sample_groups[k].group_description_index >> 16)-1; + } else { + index = sample.sample_groups[k].group_description_index-1; + } + if (description && index >= 0) { + sample.sample_groups[k].description = description.entries[index]; + } + } else { + if (description && description.version >= 2) { + if (description.default_group_description_index > 0) { + sample.sample_groups[k].description = description.entries[description.default_group_description_index-1]; + } + } + } + } + } +} + +ISOFile.process_sdtp = function (sdtp, sample, number) { + if (!sample) { + return; + } + if (sdtp) { + sample.is_leading = sdtp.is_leading[number]; + sample.depends_on = sdtp.sample_depends_on[number]; + sample.is_depended_on = sdtp.sample_is_depended_on[number]; + sample.has_redundancy = sdtp.sample_has_redundancy[number]; + } else { + sample.is_leading = 0; + sample.depends_on = 0; + sample.is_depended_on = 0 + sample.has_redundancy = 0; + } +} + +/* Build initial sample list from sample tables */ +ISOFile.prototype.buildSampleLists = function() { + var i; + var trak; + for (i = 0; i < this.moov.traks.length; i++) { + trak = this.moov.traks[i]; + this.buildTrakSampleLists(trak); + } +} + +ISOFile.prototype.buildTrakSampleLists = function(trak) { + var j, k; + var stco, stsc, stsz, stts, ctts, stss, stsd, subs, sbgps, sgpds, stdp; + var chunk_run_index, chunk_index, last_chunk_in_run, offset_in_chunk, last_sample_in_chunk; + var last_sample_in_stts_run, stts_run_index, last_sample_in_ctts_run, ctts_run_index, last_stss_index, last_subs_index, subs_entry_index, last_subs_sample_index; + + trak.samples = []; + trak.samples_duration = 0; + trak.samples_size = 0; + stco = trak.mdia.minf.stbl.stco || trak.mdia.minf.stbl.co64; + stsc = trak.mdia.minf.stbl.stsc; + stsz = trak.mdia.minf.stbl.stsz || trak.mdia.minf.stbl.stz2; + stts = trak.mdia.minf.stbl.stts; + ctts = trak.mdia.minf.stbl.ctts; + stss = trak.mdia.minf.stbl.stss; + stsd = trak.mdia.minf.stbl.stsd; + subs = trak.mdia.minf.stbl.subs; + stdp = trak.mdia.minf.stbl.stdp; + sbgps = trak.mdia.minf.stbl.sbgps; + sgpds = trak.mdia.minf.stbl.sgpds; + + last_sample_in_stts_run = -1; + stts_run_index = -1; + last_sample_in_ctts_run = -1; + ctts_run_index = -1; + last_stss_index = 0; + subs_entry_index = 0; + last_subs_sample_index = 0; + + ISOFile.initSampleGroups(trak, null, sbgps, sgpds); + + if (typeof stsz === "undefined") { + return; + } + + /* we build the samples one by one and compute their properties */ + for (j = 0; j < stsz.sample_sizes.length; j++) { + var sample = {}; + sample.number = j; + sample.track_id = trak.tkhd.track_id; + sample.timescale = trak.mdia.mdhd.timescale; + sample.alreadyRead = 0; + trak.samples[j] = sample; + /* size can be known directly */ + sample.size = stsz.sample_sizes[j]; + trak.samples_size += sample.size; + /* computing chunk-based properties (offset, sample description index)*/ + if (j === 0) { + chunk_index = 1; /* the first sample is in the first chunk (chunk indexes are 1-based) */ + chunk_run_index = 0; /* the first chunk is the first entry in the first_chunk table */ + sample.chunk_index = chunk_index; + sample.chunk_run_index = chunk_run_index; + last_sample_in_chunk = stsc.samples_per_chunk[chunk_run_index]; + offset_in_chunk = 0; + + /* Is there another entry in the first_chunk table ? */ + if (chunk_run_index + 1 < stsc.first_chunk.length) { + /* The last chunk in the run is the chunk before the next first chunk */ + last_chunk_in_run = stsc.first_chunk[chunk_run_index+1]-1; + } else { + /* There is only one entry in the table, it is valid for all future chunks*/ + last_chunk_in_run = Infinity; + } + } else { + if (j < last_sample_in_chunk) { + /* the sample is still in the current chunk */ + sample.chunk_index = chunk_index; + sample.chunk_run_index = chunk_run_index; + } else { + /* the sample is in the next chunk */ + chunk_index++; + sample.chunk_index = chunk_index; + /* reset the accumulated offset in the chunk */ + offset_in_chunk = 0; + if (chunk_index <= last_chunk_in_run) { + /* stay in the same entry of the first_chunk table */ + /* chunk_run_index unmodified */ + } else { + chunk_run_index++; + /* Is there another entry in the first_chunk table ? */ + if (chunk_run_index + 1 < stsc.first_chunk.length) { + /* The last chunk in the run is the chunk before the next first chunk */ + last_chunk_in_run = stsc.first_chunk[chunk_run_index+1]-1; + } else { + /* There is only one entry in the table, it is valid for all future chunks*/ + last_chunk_in_run = Infinity; + } + + } + sample.chunk_run_index = chunk_run_index; + last_sample_in_chunk += stsc.samples_per_chunk[chunk_run_index]; + } + } + + sample.description_index = stsc.sample_description_index[sample.chunk_run_index]-1; + sample.description = stsd.entries[sample.description_index]; + sample.offset = stco.chunk_offsets[sample.chunk_index-1] + offset_in_chunk; /* chunk indexes are 1-based */ + offset_in_chunk += sample.size; + + /* setting dts, cts, duration and rap flags */ + if (j > last_sample_in_stts_run) { + stts_run_index++; + if (last_sample_in_stts_run < 0) { + last_sample_in_stts_run = 0; + } + last_sample_in_stts_run += stts.sample_counts[stts_run_index]; + } + if (j > 0) { + trak.samples[j-1].duration = stts.sample_deltas[stts_run_index]; + trak.samples_duration += trak.samples[j-1].duration; + sample.dts = trak.samples[j-1].dts + trak.samples[j-1].duration; + } else { + sample.dts = 0; + } + if (ctts) { + if (j >= last_sample_in_ctts_run) { + ctts_run_index++; + if (last_sample_in_ctts_run < 0) { + last_sample_in_ctts_run = 0; + } + last_sample_in_ctts_run += ctts.sample_counts[ctts_run_index]; + } + sample.cts = trak.samples[j].dts + ctts.sample_offsets[ctts_run_index]; + } else { + sample.cts = sample.dts; + } + if (stss) { + if (j == stss.sample_numbers[last_stss_index] - 1) { // sample numbers are 1-based + sample.is_sync = true; + last_stss_index++; + } else { + sample.is_sync = false; + sample.degradation_priority = 0; + } + if (subs) { + if (subs.entries[subs_entry_index].sample_delta + last_subs_sample_index == j+1) { + sample.subsamples = subs.entries[subs_entry_index].subsamples; + last_subs_sample_index += subs.entries[subs_entry_index].sample_delta; + subs_entry_index++; + } + } + } else { + sample.is_sync = true; + } + ISOFile.process_sdtp(trak.mdia.minf.stbl.sdtp, sample, sample.number); + if (stdp) { + sample.degradation_priority = stdp.priority[j]; + } else { + sample.degradation_priority = 0; + } + if (subs) { + if (subs.entries[subs_entry_index].sample_delta + last_subs_sample_index == j) { + sample.subsamples = subs.entries[subs_entry_index].subsamples; + last_subs_sample_index += subs.entries[subs_entry_index].sample_delta; + } + } + if (sbgps.length > 0 || sgpds.length > 0) { + ISOFile.setSampleGroupProperties(trak, sample, j, trak.sample_groups_info); + } + } + if (j>0) { + trak.samples[j-1].duration = Math.max(trak.mdia.mdhd.duration - trak.samples[j-1].dts, 0); + trak.samples_duration += trak.samples[j-1].duration; + } +} + +/* Update sample list when new 'moof' boxes are received */ +ISOFile.prototype.updateSampleLists = function() { + var i, j, k; + var default_sample_description_index, default_sample_duration, default_sample_size, default_sample_flags; + var last_run_position; + var box, moof, traf, trak, trex; + var sample; + var sample_flags; + + if (this.moov === undefined) { + return; + } + /* if the input file is fragmented and fetched in multiple downloads, we need to update the list of samples */ + while (this.lastMoofIndex < this.moofs.length) { + box = this.moofs[this.lastMoofIndex]; + this.lastMoofIndex++; + if (box.type == "moof") { + moof = box; + for (i = 0; i < moof.trafs.length; i++) { + traf = moof.trafs[i]; + trak = this.getTrackById(traf.tfhd.track_id); + trex = this.getTrexById(traf.tfhd.track_id); + if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) { + default_sample_description_index = traf.tfhd.default_sample_description_index; + } else { + default_sample_description_index = (trex ? trex.default_sample_description_index: 1); + } + if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) { + default_sample_duration = traf.tfhd.default_sample_duration; + } else { + default_sample_duration = (trex ? trex.default_sample_duration : 0); + } + if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) { + default_sample_size = traf.tfhd.default_sample_size; + } else { + default_sample_size = (trex ? trex.default_sample_size : 0); + } + if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) { + default_sample_flags = traf.tfhd.default_sample_flags; + } else { + default_sample_flags = (trex ? trex.default_sample_flags : 0); + } + traf.sample_number = 0; + /* process sample groups */ + if (traf.sbgps.length > 0) { + ISOFile.initSampleGroups(trak, traf, traf.sbgps, trak.mdia.minf.stbl.sgpds, traf.sgpds); + } + for (j = 0; j < traf.truns.length; j++) { + var trun = traf.truns[j]; + for (k = 0; k < trun.sample_count; k++) { + sample = {}; + sample.moof_number = this.lastMoofIndex; + sample.number_in_traf = traf.sample_number; + traf.sample_number++; + sample.number = trak.samples.length; + traf.first_sample_index = trak.samples.length; + trak.samples.push(sample); + sample.track_id = trak.tkhd.track_id; + sample.timescale = trak.mdia.mdhd.timescale; + sample.description_index = default_sample_description_index-1; + sample.description = trak.mdia.minf.stbl.stsd.entries[sample.description_index]; + sample.size = default_sample_size; + if (trun.flags & BoxParser.TRUN_FLAGS_SIZE) { + sample.size = trun.sample_size[k]; + } + trak.samples_size += sample.size; + sample.duration = default_sample_duration; + if (trun.flags & BoxParser.TRUN_FLAGS_DURATION) { + sample.duration = trun.sample_duration[k]; + } + trak.samples_duration += sample.duration; + if (trak.first_traf_merged || k > 0) { + sample.dts = trak.samples[trak.samples.length-2].dts+trak.samples[trak.samples.length-2].duration; + } else { + if (traf.tfdt) { + sample.dts = traf.tfdt.baseMediaDecodeTime; + } else { + sample.dts = 0; + } + trak.first_traf_merged = true; + } + sample.cts = sample.dts; + if (trun.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { + sample.cts = sample.dts + trun.sample_composition_time_offset[k]; + } + sample_flags = default_sample_flags; + if (trun.flags & BoxParser.TRUN_FLAGS_FLAGS) { + sample_flags = trun.sample_flags[k]; + } else if (k === 0 && (trun.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG)) { + sample_flags = trun.first_sample_flags; + } + sample.is_sync = ((sample_flags >> 16 & 0x1) ? false : true); + sample.is_leading = (sample_flags >> 26 & 0x3); + sample.depends_on = (sample_flags >> 24 & 0x3); + sample.is_depended_on = (sample_flags >> 22 & 0x3); + sample.has_redundancy = (sample_flags >> 20 & 0x3); + sample.degradation_priority = (sample_flags & 0xFFFF); + //ISOFile.process_sdtp(traf.sdtp, sample, sample.number_in_traf); + var bdop = (traf.tfhd.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET) ? true : false; + var dbim = (traf.tfhd.flags & BoxParser.TFHD_FLAG_DEFAULT_BASE_IS_MOOF) ? true : false; + var dop = (trun.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) ? true : false; + var bdo = 0; + if (!bdop) { + if (!dbim) { + if (j === 0) { // the first track in the movie fragment + bdo = moof.start; // the position of the first byte of the enclosing Movie Fragment Box + } else { + bdo = last_run_position; // end of the data defined by the preceding *track* (irrespective of the track id) fragment in the moof + } + } else { + bdo = moof.start; + } + } else { + bdo = traf.tfhd.base_data_offset; + } + if (j === 0 && k === 0) { + if (dop) { + sample.offset = bdo + trun.data_offset; // If the data-offset is present, it is relative to the base-data-offset established in the track fragment header + } else { + sample.offset = bdo; // the data for this run starts the base-data-offset defined by the track fragment header + } + } else { + sample.offset = last_run_position; // this run starts immediately after the data of the previous run + } + last_run_position = sample.offset + sample.size; + if (traf.sbgps.length > 0 || traf.sgpds.length > 0 || + trak.mdia.minf.stbl.sbgps.length > 0 || trak.mdia.minf.stbl.sgpds.length > 0) { + ISOFile.setSampleGroupProperties(trak, sample, sample.number_in_traf, traf.sample_groups_info); + } + } + } + if (traf.subs) { + trak.has_fragment_subsamples = true; + var sample_index = traf.first_sample_index; + for (j = 0; j < traf.subs.entries.length; j++) { + sample_index += traf.subs.entries[j].sample_delta; + sample = trak.samples[sample_index-1]; + sample.subsamples = traf.subs.entries[j].subsamples; + } + } + } + } + } +} + +/* Try to get sample data for a given sample: + returns null if not found + returns the same sample if already requested + */ +ISOFile.prototype.getSample = function(trak, sampleNum) { + var buffer; + var sample = trak.samples[sampleNum]; + + if (!this.moov) { + return null; + } + + if (!sample.data) { + /* Not yet fetched */ + sample.data = new Uint8Array(sample.size); + sample.alreadyRead = 0; + this.samplesDataSize += sample.size; + Log.debug("ISOFile", "Allocating sample #"+sampleNum+" on track #"+trak.tkhd.track_id+" of size "+sample.size+" (total: "+this.samplesDataSize+")"); + } else if (sample.alreadyRead == sample.size) { + /* Already fetched entirely */ + return sample; + } + + /* The sample has only been partially fetched, we need to check in all buffers */ + while(true) { + var index = this.stream.findPosition(true, sample.offset + sample.alreadyRead, false); + if (index > -1) { + buffer = this.stream.buffers[index]; + var lengthAfterStart = buffer.byteLength - (sample.offset + sample.alreadyRead - buffer.fileStart); + if (sample.size - sample.alreadyRead <= lengthAfterStart) { + /* the (rest of the) sample is entirely contained in this buffer */ + + Log.debug("ISOFile","Getting sample #"+sampleNum+" data (alreadyRead: "+sample.alreadyRead+" offset: "+ + (sample.offset+sample.alreadyRead - buffer.fileStart)+" read size: "+(sample.size - sample.alreadyRead)+" full size: "+sample.size+")"); + + DataStream.memcpy(sample.data.buffer, sample.alreadyRead, + buffer, sample.offset+sample.alreadyRead - buffer.fileStart, sample.size - sample.alreadyRead); + + /* update the number of bytes used in this buffer and check if it needs to be removed */ + buffer.usedBytes += sample.size - sample.alreadyRead; + this.stream.logBufferLevel(); + + sample.alreadyRead = sample.size; + + return sample; + } else { + /* the sample does not end in this buffer */ + + if (lengthAfterStart === 0) return null; + + Log.debug("ISOFile","Getting sample #"+sampleNum+" partial data (alreadyRead: "+sample.alreadyRead+" offset: "+ + (sample.offset+sample.alreadyRead - buffer.fileStart)+" read size: "+lengthAfterStart+" full size: "+sample.size+")"); + + DataStream.memcpy(sample.data.buffer, sample.alreadyRead, + buffer, sample.offset+sample.alreadyRead - buffer.fileStart, lengthAfterStart); + sample.alreadyRead += lengthAfterStart; + + /* update the number of bytes used in this buffer and check if it needs to be removed */ + buffer.usedBytes += lengthAfterStart; + this.stream.logBufferLevel(); + + /* keep looking in the next buffer */ + } + } else { + return null; + } + } +} + +/* Release the memory used to store the data of the sample */ +ISOFile.prototype.releaseSample = function(trak, sampleNum) { + var sample = trak.samples[sampleNum]; + if (sample.data) { + this.samplesDataSize -= sample.size; + sample.data = null; + sample.alreadyRead = 0; + return sample.size; + } else { + return 0; + } +} + +ISOFile.prototype.getAllocatedSampleDataSize = function() { + return this.samplesDataSize; +} + +/* Builds the MIME Type 'codecs' sub-parameters for the whole file */ +ISOFile.prototype.getCodecs = function() { + var i; + var codecs = ""; + for (i = 0; i < this.moov.traks.length; i++) { + var trak = this.moov.traks[i]; + if (i>0) { + codecs+=","; + } + codecs += trak.mdia.minf.stbl.stsd.entries[0].getCodec(); + } + return codecs; +} + +/* Helper function */ +ISOFile.prototype.getTrexById = function(id) { + var i; + if (!this.moov || !this.moov.mvex) return null; + for (i = 0; i < this.moov.mvex.trexs.length; i++) { + var trex = this.moov.mvex.trexs[i]; + if (trex.track_id == id) return trex; + } + return null; +} + +/* Helper function */ +ISOFile.prototype.getTrackById = function(id) { + if (this.moov === undefined) { + return null; + } + for (var j = 0; j < this.moov.traks.length; j++) { + var trak = this.moov.traks[j]; + if (trak.tkhd.track_id == id) return trak; + } + return null; +} +// file:src/isofile-item-processing.js +ISOFile.prototype.items = []; +/* size of the buffers allocated for samples */ +ISOFile.prototype.itemsDataSize = 0; + +ISOFile.prototype.flattenItemInfo = function() { + var items = this.items; + var i, j; + var item; + var meta = this.meta; + if (meta === null || meta === undefined) return; + if (meta.hdlr === undefined) return; + if (meta.iinf === undefined) return; + for (i = 0; i < meta.iinf.item_infos.length; i++) { + item = {}; + item.id = meta.iinf.item_infos[i].item_ID; + items[item.id] = item; + item.ref_to = []; + item.name = meta.iinf.item_infos[i].item_name; + if (meta.iinf.item_infos[i].protection_index > 0) { + item.protection = meta.ipro.protections[meta.iinf.item_infos[i].protection_index-1]; + } + if (meta.iinf.item_infos[i].item_type) { + item.type = meta.iinf.item_infos[i].item_type; + } else { + item.type = "mime"; + } + item.content_type = meta.iinf.item_infos[i].content_type; + item.content_encoding = meta.iinf.item_infos[i].content_encoding; + } + if (meta.iloc) { + for(i = 0; i < meta.iloc.items.length; i++) { + var offset; + var itemloc = meta.iloc.items[i]; + item = items[itemloc.item_ID]; + if (itemloc.data_reference_index !== 0) { + Log.warn("Item storage with reference to other files: not supported"); + item.source = meta.dinf.boxes[itemloc.data_reference_index-1]; + } + switch(itemloc.construction_method) { + case 0: // offset into the file referenced by the data reference index + break; + case 1: // offset into the idat box of this meta box + Log.warn("Item storage with construction_method : not supported"); + break; + case 2: // offset into another item + Log.warn("Item storage with construction_method : not supported"); + break; + } + item.extents = []; + item.size = 0; + for (j = 0; j < itemloc.extents.length; j++) { + item.extents[j] = {}; + item.extents[j].offset = itemloc.extents[j].extent_offset + itemloc.base_offset; + item.extents[j].length = itemloc.extents[j].extent_length; + item.extents[j].alreadyRead = 0; + item.size += item.extents[j].length; + } + } + } + if (meta.pitm) { + items[meta.pitm.item_id].primary = true; + } + if (meta.iref) { + for (i=0; i 0 && propEntry.property_index-1 < meta.iprp.ipco.boxes.length) { + var propbox = meta.iprp.ipco.boxes[propEntry.property_index-1]; + item.properties[propbox.type] = propbox; + item.properties.boxes.push(propbox); + } + } + } + } + } +} + +ISOFile.prototype.getItem = function(item_id) { + var buffer; + var item; + + if (!this.meta) { + return null; + } + + item = this.items[item_id]; + if (!item.data && item.size) { + /* Not yet fetched */ + item.data = new Uint8Array(item.size); + item.alreadyRead = 0; + this.itemsDataSize += item.size; + Log.debug("ISOFile", "Allocating item #"+item_id+" of size "+item.size+" (total: "+this.itemsDataSize+")"); + } else if (item.alreadyRead === item.size) { + /* Already fetched entirely */ + return item; + } + + /* The item has only been partially fetched, we need to check in all buffers to find the remaining extents*/ + + for (var i = 0; i < item.extents.length; i++) { + var extent = item.extents[i]; + if (extent.alreadyRead === extent.length) { + continue; + } else { + var index = this.stream.findPosition(true, extent.offset + extent.alreadyRead, false); + if (index > -1) { + buffer = this.stream.buffers[index]; + var lengthAfterStart = buffer.byteLength - (extent.offset + extent.alreadyRead - buffer.fileStart); + if (extent.length - extent.alreadyRead <= lengthAfterStart) { + /* the (rest of the) extent is entirely contained in this buffer */ + + Log.debug("ISOFile","Getting item #"+item_id+" extent #"+i+" data (alreadyRead: "+extent.alreadyRead+ + " offset: "+(extent.offset+extent.alreadyRead - buffer.fileStart)+" read size: "+(extent.length - extent.alreadyRead)+ + " full extent size: "+extent.length+" full item size: "+item.size+")"); + + DataStream.memcpy(item.data.buffer, item.alreadyRead, + buffer, extent.offset+extent.alreadyRead - buffer.fileStart, extent.length - extent.alreadyRead); + + /* update the number of bytes used in this buffer and check if it needs to be removed */ + buffer.usedBytes += extent.length - extent.alreadyRead; + this.stream.logBufferLevel(); + + item.alreadyRead += (extent.length - extent.alreadyRead); + extent.alreadyRead = extent.length; + } else { + /* the sample does not end in this buffer */ + + Log.debug("ISOFile","Getting item #"+item_id+" extent #"+i+" partial data (alreadyRead: "+extent.alreadyRead+" offset: "+ + (extent.offset+extent.alreadyRead - buffer.fileStart)+" read size: "+lengthAfterStart+ + " full extent size: "+extent.length+" full item size: "+item.size+")"); + + DataStream.memcpy(item.data.buffer, item.alreadyRead, + buffer, extent.offset+extent.alreadyRead - buffer.fileStart, lengthAfterStart); + extent.alreadyRead += lengthAfterStart; + item.alreadyRead += lengthAfterStart; + + /* update the number of bytes used in this buffer and check if it needs to be removed */ + buffer.usedBytes += lengthAfterStart; + this.stream.logBufferLevel(); + return null; + } + } else { + return null; + } + } + } + if (item.alreadyRead === item.size) { + /* fetched entirely */ + return item; + } else { + return null; + } +} + +/* Release the memory used to store the data of the item */ +ISOFile.prototype.releaseItem = function(item_id) { + var item = this.items[item_id]; + if (item.data) { + this.itemsDataSize -= item.size; + item.data = null; + item.alreadyRead = 0; + for (var i = 0; i < item.extents.length; i++) { + var extent = item.extents[i]; + extent.alreadyRead = 0; + } + return item.size; + } else { + return 0; + } +} + + +ISOFile.prototype.processItems = function(callback) { + for(var i in this.items) { + var item = this.items[i]; + this.getItem(item.id); + if (callback && !item.sent) { + callback(item); + item.sent = true; + item.data = null; + } + } +} + +ISOFile.prototype.hasItem = function(name) { + for(var i in this.items) { + var item = this.items[i]; + if (item.name === name) { + return item.id; + } + } + return -1; +} + +ISOFile.prototype.getMetaHandler = function() { + if (!this.meta) { + return null; + } else { + return this.meta.hdlr.handler; + } +} + +ISOFile.prototype.getPrimaryItem = function() { + if (!this.meta || !this.meta.pitm) { + return null; + } else { + return this.getItem(this.meta.pitm.item_id); + } +} + +ISOFile.prototype.itemToFragmentedTrackFile = function(_options) { + var options = _options || {}; + var item = null; + if (options.itemId) { + item = this.getItem(options.itemId); + } else { + item = this.getPrimaryItem(); + } + if (item == null) return null; + + var file = new ISOFile(); + file.discardMdatData = false; + // assuming the track type is the same as the item type + var trackOptions = { type: item.type, description_boxes: item.properties.boxes}; + if (item.properties.ispe) { + trackOptions.width = item.properties.ispe.image_width; + trackOptions.height = item.properties.ispe.image_height; + } + var trackId = file.addTrack(trackOptions); + if (trackId) { + file.addSample(trackId, item.data); + return file; + } else { + return null; + } +} + +// file:src/isofile-write.js +/* Rewrite the entire file */ +ISOFile.prototype.write = function(outstream) { + for (var i=0; i0 ? this.moov.traks[i].samples[0].duration: 0)); + initSegs.push(seg); + } + return initSegs; +} + +// file:src/box-print.js +/* + * Copyright (c) Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +BoxParser.Box.prototype.printHeader = function(output) { + this.size += 8; + if (this.size > MAX_SIZE) { + this.size += 8; + } + if (this.type === "uuid") { + this.size += 16; + } + output.log(output.indent+"size:"+this.size); + output.log(output.indent+"type:"+this.type); +} + +BoxParser.FullBox.prototype.printHeader = function(output) { + this.size += 4; + BoxParser.Box.prototype.printHeader.call(this, output); + output.log(output.indent+"version:"+this.version); + output.log(output.indent+"flags:"+this.flags); +} + +BoxParser.Box.prototype.print = function(output) { + this.printHeader(output); +} + +BoxParser.ContainerBox.prototype.print = function(output) { + this.printHeader(output); + for (var i=0; i>8)); + output.log(output.indent+"matrix: "+this.matrix.join(", ")); + output.log(output.indent+"next_track_id: "+this.next_track_id); +} + +BoxParser.tkhdBox.prototype.print = function(output) { + BoxParser.FullBox.prototype.printHeader.call(this, output); + output.log(output.indent+"creation_time: "+this.creation_time); + output.log(output.indent+"modification_time: "+this.modification_time); + output.log(output.indent+"track_id: "+this.track_id); + output.log(output.indent+"duration: "+this.duration); + output.log(output.indent+"volume: "+(this.volume>>8)); + output.log(output.indent+"matrix: "+this.matrix.join(", ")); + output.log(output.indent+"layer: "+this.layer); + output.log(output.indent+"alternate_group: "+this.alternate_group); + output.log(output.indent+"width: "+this.width); + output.log(output.indent+"height: "+this.height); +}// file:src/mp4box.js +/* + * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato + * License: BSD-3-Clause (see LICENSE file) + */ +var MP4Box = {}; + +MP4Box.createFile = function (_keepMdatData, _stream) { + /* Boolean indicating if bytes containing media data should be kept in memory */ + var keepMdatData = (_keepMdatData !== undefined ? _keepMdatData : true); + var file = new ISOFile(_stream); + file.discardMdatData = (keepMdatData ? false : true); + return file; +} + +if (typeof exports !== 'undefined') { + exports.createFile = MP4Box.createFile; +} diff --git a/client/src/player.css b/client/src/player.css new file mode 100644 index 0000000..844f581 --- /dev/null +++ b/client/src/player.css @@ -0,0 +1,79 @@ +html, body, #player { + width: 100%; +} + +body { + background: #000000; + color: #ffffff; + padding: 0; + margin: 0; + display: flex; + justify-content: center; + font-family: sans-serif; +} + +#screen { + position: relative; +} + +#play { + position: absolute; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + + display: flex; + justify-content: center; + align-items: center; + + z-index: 1; +} + +#vid { + width: 100%; + height: 100%; + max-height: 100vh; +} + +#controls { + display: flex; + flex-wrap: wrap; + padding: 8px 16px; +} + +#controls > * { + margin-right: 8px; +} + +#controls label { + margin-right: 8px; +} + +#stats { + display: grid; + grid-template-columns: auto 1fr; +} + +#stats label { + padding: 0 1rem; +} + +.buffer { + position: relative; + width: 100%; +} + +.buffer .fill { + position: absolute; + transition-duration: 0.1s; + transition-property: left, right, background-color; + background-color: RebeccaPurple; + height: 100%; + text-align: right; + padding-right: 0.5rem; + overflow: hidden; +} + +.buffer .fill.net { + background-color: Purple; +} diff --git a/client/src/player.ts b/client/src/player.ts new file mode 100644 index 0000000..c31724a --- /dev/null +++ b/client/src/player.ts @@ -0,0 +1,334 @@ +import { Source } from "./source" +import { StreamReader, StreamWriter } from "./stream" +import { InitParser } from "./init" +import { Segment } from "./segment" +import { Track } from "./track" +import { Message, MessageInit, MessageSegment } from "./message" + +/// + +export class Player { + mediaSource: MediaSource; + + init: Map; + audio: Track; + video: Track; + + quic: Promise; + api: Promise; + + // References to elements in the DOM + vidRef: HTMLVideoElement; // The video element itself + statsRef: HTMLElement; // The stats div + throttleRef: HTMLButtonElement; // The throttle button + throttleCount: number; // number of times we've clicked the button in a row + + interval: number; + + timeRef?: DOMHighResTimeStamp; + + constructor(props: any) { + this.vidRef = props.vid + this.statsRef = props.stats + this.throttleRef = props.throttle + this.throttleCount = 0 + + this.mediaSource = new MediaSource() + this.vidRef.src = URL.createObjectURL(this.mediaSource) + + this.init = new Map() + this.audio = new Track(new Source(this.mediaSource)); + this.video = new Track(new Source(this.mediaSource)); + + this.interval = setInterval(this.tick.bind(this), 100) + this.vidRef.addEventListener("waiting", this.tick.bind(this)) + + const quic = new WebTransport(props.url) + this.quic = quic.ready.then(() => { return quic }); + + // Create a unidirectional stream for all of our messages + this.api = this.quic.then((q) => { + return q.createUnidirectionalStream() + }) + + // async functions + this.receiveStreams() + + // Limit to 4Mb/s + this.sendThrottle() + } + + async close() { + clearInterval(this.interval); + (await this.quic).close() + } + + async sendMessage(msg: any) { + const payload = JSON.stringify(msg) + const size = payload.length + 8 + + const stream = await this.api + + const writer = new StreamWriter(stream) + await writer.uint32(size) + await writer.string("warp") + await writer.string(payload) + writer.release() + } + + throttle() { + // Throttle is incremented each time we click the throttle button + this.throttleCount += 1 + this.sendThrottle() + + // After 5 seconds disable the throttling + setTimeout(() => { + this.throttleCount -= 1 + this.sendThrottle() + }, 5000) + } + + sendThrottle() { + // TODO detect the incoming bitrate instead of hard-coding + const bitrate = 4 * 1024 * 1024 // 4Mb/s + + // Right shift by throttle to divide by 2,4,8,16,etc each time + // Right shift by 3 more to divide by 8 to convert bits to bytes + // Right shift by another 2 to divide by 4 to get the number of bytes in a quarter of a second + let rate = bitrate >> (this.throttleCount + 3) + let buffer = bitrate >> (this.throttleCount + 5) // 250ms before dropping + + const str = formatBits(8*rate) + "/s" + this.throttleRef.textContent = `Throttle: ${ str }`; + + // NOTE: We don't use random packet loss because it's not a good simulator of how congestion works. + // Delay-based congestion control like BBR most ignores packet loss, rightfully so. + + // Send the server a message to fake network congestion. + // This is done on the server side at the socket-level for maximum accuracy (impacts all packets). + this.sendMessage({ + "x-throttle": { + rate: rate, + buffer: buffer, + }, + }) + } + + tick() { + // Try skipping ahead if there's no data in the current buffer. + this.trySeek() + + // Try skipping video if it would fix any desync. + this.trySkip() + + // Update the stats at the end + this.updateStats() + } + + goLive() { + const ranges = this.vidRef.buffered + if (!ranges.length) { + return + } + + this.vidRef.currentTime = ranges.end(ranges.length-1); + this.vidRef.play(); + } + + // Try seeking ahead to the next buffered range if there's a gap + trySeek() { + if (this.vidRef.readyState > 2) { // HAVE_CURRENT_DATA + // No need to seek + return + } + + const ranges = this.vidRef.buffered + if (!ranges.length) { + // Video has not started yet + return + } + + for (let i = 0; i < ranges.length; i += 1) { + const pos = ranges.start(i) + + if (this.vidRef.currentTime >= pos) { + // This would involve seeking backwards + continue + } + + console.warn("seeking forward", pos - this.vidRef.currentTime) + + this.vidRef.currentTime = pos + return + } + } + + // Try dropping video frames if there is future data available. + trySkip() { + let playhead: number | undefined + + if (this.vidRef.readyState > 2) { + // If we're not buffering, only skip video if it's before the current playhead + playhead = this.vidRef.currentTime + } + + this.video.advance(playhead) + } + + async receiveStreams() { + const q = await this.quic + const streams = q.incomingUnidirectionalStreams.getReader() + + while (true) { + const result = await streams.read() + if (result.done) break + + const stream = result.value + this.handleStream(stream) // don't await + } + } + + async handleStream(stream: ReadableStream) { + let r = new StreamReader(stream.getReader()) + + while (!await r.done()) { + const size = await r.uint32(); + const typ = new TextDecoder('utf-8').decode(await r.bytes(4)); + + if (typ != "warp") throw "expected warp atom" + if (size < 8) throw "atom too small" + + const payload = new TextDecoder('utf-8').decode(await r.bytes(size - 8)); + const msg = JSON.parse(payload) as Message + + if (msg.init) { + return this.handleInit(r, msg.init) + } else if (msg.segment) { + return this.handleSegment(r, msg.segment) + } + } + } + + async handleInit(stream: StreamReader, msg: MessageInit) { + let init = this.init.get(msg.id); + if (!init) { + init = new InitParser() + this.init.set(msg.id, init) + } + + while (1) { + const data = await stream.read() + if (!data) break + + init.push(data) + } + } + + async handleSegment(stream: StreamReader, msg: MessageSegment) { + let pending = this.init.get(msg.init); + if (!pending) { + pending = new InitParser() + this.init.set(msg.init, pending) + } + + // Wait for the init segment to be fully received and parsed + const init = await pending.ready; + + let track: Track; + if (init.info.videoTracks.length) { + track = this.video + } else { + track = this.audio + } + + const segment = new Segment(track.source, init, msg.timestamp) + + // The track is responsible for flushing the segments in order + track.source.initialize(init) + track.add(segment) + + /* TODO I'm not actually sure why this code doesn't work; something trips up the MP4 parser + while (1) { + const data = await stream.read() + if (!data) break + + segment.push(data) + track.flush() // Flushes if the active segment has samples + } + */ + + // One day I'll figure it out; until then read one top-level atom at a time + while (!await stream.done()) { + const raw = await stream.peek(4) + const size = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0) + const atom = await stream.bytes(size) + + segment.push(atom) + track.flush() // Flushes if the active segment has new samples + } + + segment.finish() + } + + updateStats() { + for (const child of this.statsRef.children) { + if (child.className == "audio buffer") { + const ranges: any = (this.audio) ? this.audio.buffered() : { length: 0 } + this.visualizeBuffer(child as HTMLElement, ranges) + } else if (child.className == "video buffer") { + const ranges: any = (this.video) ? this.video.buffered() : { length: 0 } + this.visualizeBuffer(child as HTMLElement, ranges) + } + } + } + + visualizeBuffer(element: HTMLElement, ranges: TimeRanges) { + const children = element.children + const max = 5 + + let index = 0 + let prev = 0 + + for (let i = 0; i < ranges.length; i += 1) { + let start = ranges.start(i) - this.vidRef.currentTime + let end = ranges.end(i) - this.vidRef.currentTime + + if (end < 0 || start > max) { + continue + } + + let fill: HTMLElement; + + if (index < children.length) { + fill = children[index] as HTMLElement; + } else { + fill = document.createElement("div") + element.appendChild(fill) + } + + fill.className = "fill" + fill.innerHTML = end.toFixed(2) + fill.setAttribute('style', "left: " + (100 * Math.max(start, 0) / max) + "%; right: " + (100 - 100 * Math.min(end, max) / max) + "%") + index += 1 + + prev = end + } + + for (let i = index; i < children.length; i += 1) { + element.removeChild(children[i]) + } + } +} + +// https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript +function formatBits(bits: number, decimals: number = 1) { + if (bits === 0) return '0 bits'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb']; + + const i = Math.floor(Math.log(bits) / Math.log(k)); + + return parseFloat((bits / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} diff --git a/client/src/segment.ts b/client/src/segment.ts new file mode 100644 index 0000000..3e7a63d --- /dev/null +++ b/client/src/segment.ts @@ -0,0 +1,146 @@ +import { Source } from "./source" +import { Init } from "./init" +import { MP4New, MP4File, MP4Sample, MP4Stream, MP4Parser, MP4ArrayBuffer } from "./mp4" + +// Manage a segment download, keeping a buffer of a single sample to potentially rewrite the duration. +export class Segment { + source: Source; // The SourceBuffer used to decode media. + offset: number; // The byte offset in the received file so far + samples: MP4Sample[]; // The samples ready to be flushed to the source. + timestamp: number; // The expected timestamp of the first sample in milliseconds + init: Init; + + dts?: number; // The parsed DTS of the first sample + timescale?: number; // The parsed timescale of the segment + + input: MP4File; // MP4Box file used to parse the incoming atoms. + output: MP4File; // MP4Box file used to write the outgoing atoms after modification. + + done: boolean; // The segment has been completed + + constructor(source: Source, init: Init, timestamp: number) { + this.source = source + this.offset = 0 + this.done = false + this.timestamp = timestamp + this.init = init + + this.input = MP4New(); + this.output = MP4New(); + this.samples = []; + + this.input.onReady = (info: any) => { + this.input.setExtractionOptions(info.tracks[0].id, {}, { nbSamples: 1 }); + + this.input.onSamples = this.onSamples.bind(this) + this.input.start(); + } + + // We have to reparse the init segment to work with mp4box + for (let i = 0; i < init.raw.length; i += 1) { + this.offset = this.input.appendBuffer(init.raw[i]) + + // Also populate the output with our init segment so it knows about tracks + this.output.appendBuffer(init.raw[i]) + } + + this.input.flush() + this.output.flush() + } + + push(data: Uint8Array) { + if (this.done) return; // ignore new data after marked done + + // Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately + let box = new Uint8Array(data.byteLength); + box.set(data) + + // and for some reason we need to modify the underlying ArrayBuffer with offset + let buffer = box.buffer as MP4ArrayBuffer + buffer.fileStart = this.offset + + // Parse the data + this.offset = this.input.appendBuffer(buffer) + this.input.flush() + } + + onSamples(id: number, user: any, samples: MP4Sample[]) { + if (!samples.length) return; + + if (this.dts === undefined) { + this.dts = samples[0].dts; + this.timescale = samples[0].timescale; + } + + // Add the samples to a queue + this.samples.push(...samples) + } + + // Flushes any pending samples, returning true if the stream has finished. + flush(): boolean { + let stream = new MP4Stream(new ArrayBuffer(0), 0, false); // big-endian + + while (this.samples.length) { + // Keep a single sample if we're not done yet + if (!this.done && this.samples.length < 2) break; + + const sample = this.samples.shift() + if (!sample) break; + + let moof = this.output.createSingleSampleMoof(sample); + moof.write(stream); + + // adjusting the data_offset now that the moof size is known + moof.trafs[0].truns[0].data_offset = moof.size+8; //8 is mdat header + stream.adjustUint32(moof.trafs[0].truns[0].data_offset_position, moof.trafs[0].truns[0].data_offset); + + // @ts-ignore + var mdat = new MP4Parser.mdatBox(); + mdat.data = sample.data; + mdat.write(stream); + } + + this.source.appendBuffer(stream.buffer as ArrayBuffer) + + return this.done + } + + // The segment has completed + finish() { + this.done = true + this.flush() + } + + // Extend the last sample so it reaches the provided timestamp + skipTo(pts: number) { + if (this.samples.length == 0) return + let last = this.samples[this.samples.length-1] + + const skip = pts - (last.dts + last.duration); + + if (skip == 0) return; + if (skip < 0) throw "can't skip backwards" + + last.duration += skip + + if (this.timescale) { + console.warn("skipping video", skip / this.timescale) + } + } + + buffered() { + // Ignore if we have a single sample + if (this.samples.length <= 1) return undefined; + if (!this.timescale) return undefined; + + const first = this.samples[0]; + const last = this.samples[this.samples.length-1] + + + return { + length: 1, + start: first.dts / this.timescale, + end: (last.dts + last.duration) / this.timescale, + } + } +} diff --git a/client/src/source.ts b/client/src/source.ts new file mode 100644 index 0000000..90bdd56 --- /dev/null +++ b/client/src/source.ts @@ -0,0 +1,81 @@ +import { Init } from "./init" + +// Create a SourceBuffer with convenience methods +export class Source { + sourceBuffer?: SourceBuffer; + mediaSource: MediaSource; + queue: Array; + mime: string; + + constructor(mediaSource: MediaSource) { + this.mediaSource = mediaSource; + this.queue = []; + this.mime = ""; + } + + initialize(init: Init) { + if (!this.sourceBuffer) { + this.sourceBuffer = this.mediaSource.addSourceBuffer(init.info.mime) + this.sourceBuffer.addEventListener('updateend', this.flush.bind(this)) + + // Add the init data to the front of the queue + for (let i = init.raw.length - 1; i >= 0; i -= 1) { + this.queue.unshift(init.raw[i]) + } + + this.flush() + } else if (init.info.mime != this.mime) { + this.sourceBuffer.changeType(init.info.mime) + + // Add the init data to the front of the queue + for (let i = init.raw.length - 1; i >= 0; i -= 1) { + this.queue.unshift(init.raw[i]) + } + } + + this.mime = init.info.mime + } + + appendBuffer(data: Uint8Array | ArrayBuffer) { + if (!this.sourceBuffer || this.sourceBuffer.updating || this.queue.length) { + this.queue.push(data) + } else { + this.sourceBuffer.appendBuffer(data) + } + } + + buffered() { + if (!this.sourceBuffer) { + return { length: 0 } + } + + return this.sourceBuffer.buffered + } + + flush() { + // Check if we have a mime yet + if (!this.sourceBuffer) { + return + } + + // Check if the buffer is currently busy. + if (this.sourceBuffer.updating) { + return + } + + const data = this.queue.shift() + if (data) { + // If there's data in the queue, flush it. + this.sourceBuffer.appendBuffer(data) + } else if (this.sourceBuffer.buffered.length) { + // Otherwise with no data, trim anything older than 30s. + const end = this.sourceBuffer.buffered.end(this.sourceBuffer.buffered.length - 1) - 30.0 + const start = this.sourceBuffer.buffered.start(0) + + // Remove any range larger than 1s. + if (end > start && end - start > 1.0) { + this.sourceBuffer.remove(start, end) + } + } + } +} diff --git a/client/src/stream.ts b/client/src/stream.ts new file mode 100644 index 0000000..7dd7120 --- /dev/null +++ b/client/src/stream.ts @@ -0,0 +1,254 @@ +// Reader wraps a stream and provides convience methods for reading pieces from a stream +export class StreamReader { + reader: ReadableStreamDefaultReader; // TODO make a separate class without promises when null + buffer: Uint8Array; + + constructor(reader: ReadableStreamDefaultReader, buffer: Uint8Array = new Uint8Array(0)) { + this.reader = reader + this.buffer = buffer + } + + // TODO implementing pipeTo seems more reasonable than releasing the lock + release() { + this.reader.releaseLock() + } + + // Returns any number of bytes + async read(): Promise { + if (this.buffer.byteLength) { + const buffer = this.buffer; + this.buffer = new Uint8Array() + return buffer + } + + const result = await this.reader.read() + return result.value + } + + async bytes(size: number): Promise { + while (this.buffer.byteLength < size) { + const result = await this.reader.read() + if (result.done) { + throw "short buffer" + } + + const buffer = new Uint8Array(result.value) + + if (this.buffer.byteLength == 0) { + this.buffer = buffer + } else { + const temp = new Uint8Array(this.buffer.byteLength + buffer.byteLength) + temp.set(this.buffer) + temp.set(buffer, this.buffer.byteLength) + this.buffer = temp + } + } + + const result = new Uint8Array(this.buffer.buffer, this.buffer.byteOffset, size) + this.buffer = new Uint8Array(this.buffer.buffer, this.buffer.byteOffset + size) + + return result + } + + async peek(size: number): Promise { + while (this.buffer.byteLength < size) { + const result = await this.reader.read() + if (result.done) { + throw "short buffer" + } + + const buffer = new Uint8Array(result.value) + + if (this.buffer.byteLength == 0) { + this.buffer = buffer + } else { + const temp = new Uint8Array(this.buffer.byteLength + buffer.byteLength) + temp.set(this.buffer) + temp.set(buffer, this.buffer.byteLength) + this.buffer = temp + } + } + + return new Uint8Array(this.buffer.buffer, this.buffer.byteOffset, size) + } + + async view(size: number): Promise { + const buf = await this.bytes(size) + return new DataView(buf.buffer, buf.byteOffset, buf.byteLength) + } + + async uint8(): Promise { + const view = await this.view(1) + return view.getUint8(0) + } + + async uint16(): Promise { + const view = await this.view(2) + return view.getUint16(0) + } + + async uint32(): Promise { + const view = await this.view(4) + return view.getUint32(0) + } + + // Returns a Number using 52-bits, the max Javascript can use for integer math + async uint52(): Promise { + const v = await this.uint64() + if (v > Number.MAX_SAFE_INTEGER) { + throw "overflow" + } + + return Number(v) + } + + // Returns a Number using 52-bits, the max Javascript can use for integer math + async vint52(): Promise { + const v = await this.vint64() + if (v > Number.MAX_SAFE_INTEGER) { + throw "overflow" + } + + return Number(v) + } + + // NOTE: Returns a BigInt instead of a Number + async uint64(): Promise { + const view = await this.view(8) + return view.getBigUint64(0) + } + + // NOTE: Returns a BigInt instead of a Number + async vint64(): Promise { + const peek = await this.peek(1) + const first = new DataView(peek.buffer, peek.byteOffset, peek.byteLength).getUint8(0) + const size = (first & 0xc0) >> 6 + + switch (size) { + case 0: + const v0 = await this.uint8() + return BigInt(v0) & 0x3fn + case 1: + const v1 = await this.uint16() + return BigInt(v1) & 0x3fffn + case 2: + const v2 = await this.uint32() + return BigInt(v2) & 0x3fffffffn + case 3: + const v3 = await this.uint64() + return v3 & 0x3fffffffffffffffn + default: + throw "impossible" + } + } + + async done(): Promise { + try { + const peek = await this.peek(1) + return false + } catch (err) { + return true // Assume EOF + } + } +} + +// StreamWriter wraps a stream and writes chunks of data +export class StreamWriter { + buffer: ArrayBuffer; + writer: WritableStreamDefaultWriter; + + constructor(stream: WritableStream) { + this.buffer = new ArrayBuffer(8) + this.writer = stream.getWriter() + } + + release() { + this.writer.releaseLock() + } + + async close() { + return this.writer.close() + } + + async uint8(v: number) { + const view = new DataView(this.buffer, 0, 1) + view.setUint8(0, v) + return this.writer.write(view) + } + + async uint16(v: number) { + const view = new DataView(this.buffer, 0, 2) + view.setUint16(0, v) + return this.writer.write(view) + } + + async uint24(v: number) { + const v1 = (v >> 16) & 0xff + const v2 = (v >> 8) & 0xff + const v3 = (v) & 0xff + + const view = new DataView(this.buffer, 0, 3) + view.setUint8(0, v1) + view.setUint8(1, v2) + view.setUint8(2, v3) + + return this.writer.write(view) + } + + async uint32(v: number) { + const view = new DataView(this.buffer, 0, 4) + view.setUint32(0, v) + return this.writer.write(view) + } + + async uint52(v: number) { + if (v > Number.MAX_SAFE_INTEGER) { + throw "value too large" + } + + this.uint64(BigInt(v)) + } + + async vint52(v: number) { + if (v > Number.MAX_SAFE_INTEGER) { + throw "value too large" + } + + if (v < (1 << 6)) { + return this.uint8(v) + } else if (v < (1 << 14)) { + return this.uint16(v|0x4000) + } else if (v < (1 << 30)) { + return this.uint32(v|0x80000000) + } else { + return this.uint64(BigInt(v) | 0xc000000000000000n) + } + } + + async uint64(v: bigint) { + const view = new DataView(this.buffer, 0, 8) + view.setBigUint64(0, v) + return this.writer.write(view) + } + + async vint64(v: bigint) { + if (v < (1 << 6)) { + return this.uint8(Number(v)) + } else if (v < (1 << 14)) { + return this.uint16(Number(v)|0x4000) + } else if (v < (1 << 30)) { + return this.uint32(Number(v)|0x80000000) + } else { + return this.uint64(v | 0xc000000000000000n) + } + } + + async bytes(buffer: ArrayBuffer) { + return this.writer.write(buffer) + } + + async string(str: string) { + const data = new TextEncoder().encode(str) + return this.writer.write(data) + } +} diff --git a/client/src/track.ts b/client/src/track.ts new file mode 100644 index 0000000..b31cd7f --- /dev/null +++ b/client/src/track.ts @@ -0,0 +1,124 @@ +import { Source } from "./source" +import { Segment } from "./segment" +import { TimeRange } from "./util" + +// An audio or video track that consists of multiple sequential segments. +// +// Instead of buffering, we want to drop video while audio plays uninterupted. +// Chrome actually plays up to 3s of audio without video before buffering when in low latency mode. +// Unforuntately, this does not recover correctly when there are gaps (pls fix). +// Our solution is to flush segments in decode order, buffering a single additional frame. +// We extend the duration of the buffered frame and flush it to cover any gaps. +export class Track { + source: Source; + segments: Segment[]; + + constructor(source: Source) { + this.source = source; + this.segments = []; + } + + add(segment: Segment) { + // TODO don't add if the segment is out of date already + this.segments.push(segment) + + // Sort by timestamp ascending + // NOTE: The timestamp is in milliseconds, and we need to parse the media to get the accurate PTS/DTS. + this.segments.sort((a: Segment, b: Segment): number => { + return a.timestamp - b.timestamp + }) + } + + buffered(): TimeRanges { + let ranges: TimeRange[] = [] + + const buffered = this.source.buffered() as TimeRanges + for (let i = 0; i < buffered.length; i += 1) { + // Convert the TimeRanges into an oject we can modify + ranges.push({ + start: buffered.start(i), + end: buffered.end(i) + }) + } + + // Loop over segments and add in their ranges, merging if possible. + for (let segment of this.segments) { + const buffered = segment.buffered() + if (!buffered) continue; + + if (ranges.length) { + // Try to merge with an existing range + const last = ranges[ranges.length-1]; + if (buffered.start < last.start) { + // Network buffer is old; ignore it + continue + } + + // Extend the end of the last range instead of pushing + if (buffered.start <= last.end && buffered.end > last.end) { + last.end = buffered.end + continue + } + } + + ranges.push(buffered) + } + + // TODO typescript + return { + length: ranges.length, + start: (x) => { return ranges[x].start }, + end: (x) => { return ranges[x].end }, + } + } + + flush() { + while (1) { + if (!this.segments.length) break + + const first = this.segments[0] + const done = first.flush() + if (!done) break + + this.segments.shift() + } + } + + // Given the current playhead, determine if we should drop any segments + // If playhead is undefined, it means we're buffering so skip to anything now. + advance(playhead: number | undefined) { + if (this.segments.length < 2) return + + while (this.segments.length > 1) { + const current = this.segments[0]; + const next = this.segments[1]; + + if (next.dts === undefined || next.timescale == undefined) { + // No samples have been parsed for the next segment yet. + break + } + + if (current.dts === undefined) { + // No samples have been parsed for the current segment yet. + // We can't cover the gap by extending the sample so we have to seek. + // TODO I don't think this can happen, but I guess we have to seek past the gap. + break + } + + if (playhead !== undefined) { + // Check if the next segment has playable media now. + // Otherwise give the current segment more time to catch up. + if ((next.dts / next.timescale) > playhead) { + return + } + } + + current.skipTo(next.dts || 0) // tell typescript that it's not undefined; we already checked + current.finish() + + // TODO cancel the QUIC stream to save bandwidth + + this.segments.shift() + } + } +} diff --git a/client/src/types/webtransport.d.ts b/client/src/types/webtransport.d.ts new file mode 100644 index 0000000..a441740 --- /dev/null +++ b/client/src/types/webtransport.d.ts @@ -0,0 +1,84 @@ +declare module "webtransport" + +/* + There's no WebTransport support in TypeScript yet. Use this script to update definitions: + + npx webidl2ts -i https://www.w3.org/TR/webtransport/ -o webtransport.d.ts + You'll have to fix the constructors by hand. +*/ + +interface WebTransportDatagramDuplexStream { + readonly readable: ReadableStream; + readonly writable: WritableStream; + readonly maxDatagramSize: number; + incomingMaxAge: number; + outgoingMaxAge: number; + incomingHighWaterMark: number; + outgoingHighWaterMark: number; +} + +interface WebTransport { + getStats(): Promise; + readonly ready: Promise; + readonly closed: Promise; + close(closeInfo?: WebTransportCloseInfo): undefined; + readonly datagrams: WebTransportDatagramDuplexStream; + createBidirectionalStream(): Promise; + readonly incomingBidirectionalStreams: ReadableStream; + createUnidirectionalStream(): Promise; + readonly incomingUnidirectionalStreams: ReadableStream; +} + +declare var WebTransport: { + prototype: WebTransport; + new(url: string, options?: WebTransportOptions): WebTransport; +}; + +interface WebTransportHash { + algorithm?: string; + value?: BufferSource; +} + +interface WebTransportOptions { + allowPooling?: boolean; + serverCertificateHashes?: Array; +} + +interface WebTransportCloseInfo { + closeCode?: number; + reason?: string; +} + +interface WebTransportStats { + timestamp?: DOMHighResTimeStamp; + bytesSent?: number; + packetsSent?: number; + numOutgoingStreamsCreated?: number; + numIncomingStreamsCreated?: number; + bytesReceived?: number; + packetsReceived?: number; + minRtt?: DOMHighResTimeStamp; + numReceivedDatagramsDropped?: number; +} + +interface WebTransportBidirectionalStream { + readonly readable: ReadableStream; + readonly writable: WritableStream; +} + +interface WebTransportError extends DOMException { + readonly source: WebTransportErrorSource; + readonly streamErrorCode: number; +} + +declare var WebTransportError: { + prototype: WebTransportError; + new(init?: WebTransportErrorInit): WebTransportError; +}; + +interface WebTransportErrorInit { + streamErrorCode?: number; + message?: string; +} + +type WebTransportErrorSource = "stream" | "session"; diff --git a/client/src/util.ts b/client/src/util.ts new file mode 100644 index 0000000..c3b6bda --- /dev/null +++ b/client/src/util.ts @@ -0,0 +1,4 @@ +export interface TimeRange { + start: number; + end: number; +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..e87a581 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { + "target": "es2021", + "strict": true, + "typeRoots": [ + "src/types" + ], + "allowJs": true + } +} + diff --git a/client/yarn.lock b/client/yarn.lock new file mode 100644 index 0000000..f384fe8 --- /dev/null +++ b/client/yarn.lock @@ -0,0 +1,1354 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + "integrity" "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" + "resolved" "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" + "version" "7.16.7" + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + "integrity" "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" + "version" "7.16.7" + +"@babel/highlight@^7.16.7": + "integrity" "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==" + "resolved" "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz" + "version" "7.17.12" + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + "chalk" "^2.0.0" + "js-tokens" "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0": + "integrity" "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==" + "resolved" "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz" + "version" "0.3.1" + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + "integrity" "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==" + "resolved" "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz" + "version" "3.0.7" + +"@jridgewell/set-array@^1.0.0": + "integrity" "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==" + "resolved" "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz" + "version" "1.1.1" + +"@jridgewell/source-map@^0.3.2": + "integrity" "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==" + "resolved" "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" + "version" "0.3.2" + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + "integrity" "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==" + "resolved" "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz" + "version" "1.4.13" + +"@jridgewell/trace-mapping@^0.3.9": + "integrity" "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==" + "resolved" "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz" + "version" "0.3.13" + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@lezer/common@^0.15.0", "@lezer/common@^0.15.7": + "integrity" "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig==" + "resolved" "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz" + "version" "0.15.12" + +"@lezer/lr@^0.15.4": + "integrity" "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==" + "resolved" "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz" + "version" "0.15.8" + dependencies: + "@lezer/common" "^0.15.0" + +"@mischnic/json-sourcemap@^0.1.0": + "integrity" "sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==" + "resolved" "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz" + "version" "0.1.0" + dependencies: + "@lezer/common" "^0.15.7" + "@lezer/lr" "^0.15.4" + "json5" "^2.2.1" + +"@msgpackr-extract/msgpackr-extract-darwin-x64@2.0.2": + "integrity" "sha512-DznYtF3lHuZDSRaIOYeif4JgO0NtO2Xf8DsngAugMx/bUdTFbg86jDTmkVJBNmV+cxszz6OjGvinnS8AbJ342g==" + "resolved" "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.0.2.tgz" + "version" "2.0.2" + +"@parcel/bundler-default@2.6.0": + "integrity" "sha512-AplEdGm/odV7yGmoeOnglxnY31WlNB5EqGLFGxkgs7uwDaTWoTX/9SWPG6xfvirhjDpms8sLSiVuBdFRCCLtNA==" + "resolved" "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/cache@2.6.0": + "integrity" "sha512-4vbD5uSuf+kRnrFesKhpn0AKnOw8u2UlvCyrplYmp1g9bNAkIooC/nDGdmkb/9SviPEbni9PEanQEHDU2+slpA==" + "resolved" "https://registry.npmjs.org/@parcel/cache/-/cache-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/fs" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/utils" "2.6.0" + "lmdb" "2.3.10" + +"@parcel/codeframe@2.6.0": + "integrity" "sha512-yXXxrO9yyedHKpTwC+Af0+vPmQm+A9xeEhkt4f0yVg1n4t4yUIxYlTedzbM8ygZEEBtkXU9jJ+PkgXbfMf0dqw==" + "resolved" "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "chalk" "^4.1.0" + +"@parcel/compressor-raw@2.6.0": + "integrity" "sha512-rtMU2mGl88bic6Xbq1u5L49bMK4s5185b0k7h3JRdS6/0rR+Xp4k/o9Wog+hHjK/s82z1eF9WmET779ZpIDIQQ==" + "resolved" "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + +"@parcel/config-default@2.6.0": + "integrity" "sha512-DXovFPhZITmTvFaSEdC8RRqROs9FLIJ4u8yFSU6FUyq2wpvtYVRXXoDrvXgClh2csXmK7JTJTp5JF7r0rd2UaA==" + "resolved" "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/bundler-default" "2.6.0" + "@parcel/compressor-raw" "2.6.0" + "@parcel/namer-default" "2.6.0" + "@parcel/optimizer-css" "2.6.0" + "@parcel/optimizer-htmlnano" "2.6.0" + "@parcel/optimizer-image" "2.6.0" + "@parcel/optimizer-svgo" "2.6.0" + "@parcel/optimizer-terser" "2.6.0" + "@parcel/packager-css" "2.6.0" + "@parcel/packager-html" "2.6.0" + "@parcel/packager-js" "2.6.0" + "@parcel/packager-raw" "2.6.0" + "@parcel/packager-svg" "2.6.0" + "@parcel/reporter-dev-server" "2.6.0" + "@parcel/resolver-default" "2.6.0" + "@parcel/runtime-browser-hmr" "2.6.0" + "@parcel/runtime-js" "2.6.0" + "@parcel/runtime-react-refresh" "2.6.0" + "@parcel/runtime-service-worker" "2.6.0" + "@parcel/transformer-babel" "2.6.0" + "@parcel/transformer-css" "2.6.0" + "@parcel/transformer-html" "2.6.0" + "@parcel/transformer-image" "2.6.0" + "@parcel/transformer-js" "2.6.0" + "@parcel/transformer-json" "2.6.0" + "@parcel/transformer-postcss" "2.6.0" + "@parcel/transformer-posthtml" "2.6.0" + "@parcel/transformer-raw" "2.6.0" + "@parcel/transformer-react-refresh-wrap" "2.6.0" + "@parcel/transformer-svg" "2.6.0" + +"@parcel/core@^2.6.0", "@parcel/core@2.6.0": + "integrity" "sha512-8OOWbPuxpFydpwNyKoz6d3e3O4DmxNYmMw4DXwrPSj/jyg7oa+SDtMT0/VXEhujE0HYkQPCHt4npRajkSuf99A==" + "resolved" "https://registry.npmjs.org/@parcel/core/-/core-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/cache" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/events" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/graph" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/package-manager" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "@parcel/workers" "2.6.0" + "abortcontroller-polyfill" "^1.1.9" + "base-x" "^3.0.8" + "browserslist" "^4.6.6" + "clone" "^2.1.1" + "dotenv" "^7.0.0" + "dotenv-expand" "^5.1.0" + "json5" "^2.2.0" + "msgpackr" "^1.5.4" + "nullthrows" "^1.1.1" + "semver" "^5.7.1" + +"@parcel/css-darwin-x64@1.9.0": + "integrity" "sha512-4SpuwiM/4ayOgKflqSLd87XT7YwyC3wd2QuzOOkasjbe38UU+tot/87l2lQYEB538YinLdfwFQuFLDY0x9MxgA==" + "resolved" "https://registry.npmjs.org/@parcel/css-darwin-x64/-/css-darwin-x64-1.9.0.tgz" + "version" "1.9.0" + +"@parcel/css@^1.9.0": + "integrity" "sha512-egCetUQ1H6pgYxOIxVQ8X/YT5e8G0R8eq6aVaUHrqnZ7A8cc6FYgknl9XRmoy2Xxo9h1htrbzdaEShQ5gROwvw==" + "resolved" "https://registry.npmjs.org/@parcel/css/-/css-1.9.0.tgz" + "version" "1.9.0" + dependencies: + "detect-libc" "^1.0.3" + optionalDependencies: + "@parcel/css-darwin-arm64" "1.9.0" + "@parcel/css-darwin-x64" "1.9.0" + "@parcel/css-linux-arm-gnueabihf" "1.9.0" + "@parcel/css-linux-arm64-gnu" "1.9.0" + "@parcel/css-linux-arm64-musl" "1.9.0" + "@parcel/css-linux-x64-gnu" "1.9.0" + "@parcel/css-linux-x64-musl" "1.9.0" + "@parcel/css-win32-x64-msvc" "1.9.0" + +"@parcel/diagnostic@2.6.0": + "integrity" "sha512-+p8gC2FKxSI2veD7SoaNlP572v4kw+nafCQEPDtJuzYYRqywYUGncch25dkpgNApB4W4cXVkZu3ZbtIpCAmjQQ==" + "resolved" "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "nullthrows" "^1.1.1" + +"@parcel/events@2.6.0": + "integrity" "sha512-2WaKtBs4iYwS88j4zRdyTJTgh8iuY4E32FMmjzzbheqETs6I05gWuPReGukJYxk8vc0Ir7tbzp12oAfpgo0Y+g==" + "resolved" "https://registry.npmjs.org/@parcel/events/-/events-2.6.0.tgz" + "version" "2.6.0" + +"@parcel/fs-search@2.6.0": + "integrity" "sha512-1nXzM3H/cA4kzLKvDBvwmNisKCdRqlgkLXh+OR1Zu28Kn4W34KuJMcHWW8cC+WIuuKqDh5oo2WPsC5y65GXBKQ==" + "resolved" "https://registry.npmjs.org/@parcel/fs-search/-/fs-search-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "detect-libc" "^1.0.3" + +"@parcel/fs@2.6.0": + "integrity" "sha512-6vxtx5Zy6MvDvH1EPx9JxjKGF03bR7VE1dUf4HLeX2D8YmpL5hkHJnlRCFdcH08rzOVwaJLzg1QNtblWJXQ9CA==" + "resolved" "https://registry.npmjs.org/@parcel/fs/-/fs-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/fs-search" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "@parcel/watcher" "^2.0.0" + "@parcel/workers" "2.6.0" + +"@parcel/graph@2.6.0": + "integrity" "sha512-rxrAzWm6rwbCRPbu0Z+zwMscpG8omffODniVWPlX2G0jgQGpjKsutBQ6RMfFIcfaQ4MzL3pIQOTf8bkjQOPsbg==" + "resolved" "https://registry.npmjs.org/@parcel/graph/-/graph-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/hash@2.6.0": + "integrity" "sha512-YugWqhLxqK80Lo++3B3Kr5UPCHOdS8iI2zJ1jkzUeH9v6WUzbwWOnmPf6lN2S5m1BrIFFJd8Jc+CbEXWi8zoJA==" + "resolved" "https://registry.npmjs.org/@parcel/hash/-/hash-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "detect-libc" "^1.0.3" + "xxhash-wasm" "^0.4.2" + +"@parcel/logger@2.6.0": + "integrity" "sha512-J1/7kPfSGBvMKSZdi0WCNuN0fIeiWxifnDGn7W/K8KhD422YwFJA8N046ps8nkDOPIXf1osnIECNp4GIR9oSYw==" + "resolved" "https://registry.npmjs.org/@parcel/logger/-/logger-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/events" "2.6.0" + +"@parcel/markdown-ansi@2.6.0": + "integrity" "sha512-fyjkrJQQSfKTUFTTasdZ6WrAkDoQ2+DYDjj+3p+RncYyrIa9zArKx4IiRiipsvNdtMvP0/hTdK8F3BOJ3KSU/g==" + "resolved" "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "chalk" "^4.1.0" + +"@parcel/namer-default@2.6.0": + "integrity" "sha512-r8O12r7ozJBctnFxVdXbf/fK97GIdNj3hiiUNWlXEmED9sw6ZPcChaLcfot0/443g8i87JDmSTKJ8js2tuz5XA==" + "resolved" "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/node-resolver-core@2.6.0": + "integrity" "sha512-AJDj5DZbB58plv0li8bdVSD+zpnkHE36Om3TYyNn1jgXXwgBM64Er/9p8yQn356jBqTQMh7zlJqvbdIyOiMeMg==" + "resolved" "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/optimizer-css@2.6.0": + "integrity" "sha512-VMJknUwfKCw6Woov0lnPGdsGZewcI4ghW8WKmNZzC5uKCetk1XetV55QHBc1RNjGfsjfSTZiSa3guATj2zFJkQ==" + "resolved" "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/css" "^1.9.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "browserslist" "^4.6.6" + "nullthrows" "^1.1.1" + +"@parcel/optimizer-htmlnano@2.6.0": + "integrity" "sha512-HmvcUoYpfdx8ZfID4WOj/SE8N78NEBmzAffZ8f827mYMr4ZrbKzAgg6OG3tBbfF0zxH0bIjZcwqwZYk4SdbG7g==" + "resolved" "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "htmlnano" "^2.0.0" + "nullthrows" "^1.1.1" + "posthtml" "^0.16.5" + "svgo" "^2.4.0" + +"@parcel/optimizer-image@2.6.0": + "integrity" "sha512-FDNr3LJ8SWR9rrtdCrZOlYF1hE9G5pxUWawGxUasbvqwcY5lEQwr2KRmfGZeg+KwOnzlImlY6dP2LGox1NFddQ==" + "resolved" "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "@parcel/workers" "2.6.0" + "detect-libc" "^1.0.3" + +"@parcel/optimizer-svgo@2.6.0": + "integrity" "sha512-LMTDVMd7T/IfLG59yLWl8Uw2HYGbj2C3jIwkMqH9MBUT5KILK66T3t0yV86SoZJnxZ6xBIJ+kCcCRssCzhvanw==" + "resolved" "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "svgo" "^2.4.0" + +"@parcel/optimizer-terser@2.6.0": + "integrity" "sha512-oezRt6Lz/QqcVDXyMfFjzQc7n0ThJowLJ4Lyhu8rMh0ZJYzc4UCFCw/19d4nRnzE+Qg0vj3mQCpdkA9/64E44g==" + "resolved" "https://registry.npmjs.org/@parcel/optimizer-terser/-/optimizer-terser-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + "terser" "^5.2.0" + +"@parcel/package-manager@2.6.0": + "integrity" "sha512-AqFfdkbOw51q/3ia2mIsFTmrpYEyUb3k+2uYC5GsLMz3go6OGn7/Crz0lZLSclv5EtwpRg3TWr9yL7RekVN/Uw==" + "resolved" "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "@parcel/workers" "2.6.0" + "semver" "^5.7.1" + +"@parcel/packager-css@2.6.0": + "integrity" "sha512-iXUttSe+wtnIM2PKCyFC2I4+Szv+8qHpC3wXeJlXlzd8wljm42y+6Fs4FZ0zihTccRxI2UUhFnKu90ag+5AmjA==" + "resolved" "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/packager-html@2.6.0": + "integrity" "sha512-HsiXMkU9AJr3LLjsP2Kteho2jCVpabTwcU/fauwbwirhg0xNlRsKxYZRCllRhPkb0FWAnkjzwjOj01MHD6NJCg==" + "resolved" "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + "posthtml" "^0.16.5" + +"@parcel/packager-js@2.6.0": + "integrity" "sha512-Uz3pqIFchFfKszWnNGDgIwM1uwHHJp7Dts6VzS9lf/2RbRgZT0fmce+NPgnVO5MMKBHzdvm32ShT6gFAABF5Vw==" + "resolved" "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "globals" "^13.2.0" + "nullthrows" "^1.1.1" + +"@parcel/packager-raw@2.6.0": + "integrity" "sha512-ktT6Qc/GgCq8H1+6y+AXufVzQj1s6KRoKf83qswCD0iY3MwCbJoEfc3IsB4K64FpHIL5Eu0z54IId+INvGbOYA==" + "resolved" "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + +"@parcel/packager-svg@2.6.0": + "integrity" "sha512-OF2RShyspXu7H4Dn2PmchfMMYPx+kWjOXiYVQ6OkOI0MZmOydx7p8nrcG5+y7vCJTPlta828BSwva0GdKfn46A==" + "resolved" "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "posthtml" "^0.16.4" + +"@parcel/plugin@2.6.0": + "integrity" "sha512-LzOaiK8R6eFEoov1cb3/W+o0XvXdI/VbDhMDl0L0II+/56M0UeayYtFP5QGTDn/fZqVlYfzPCtt3EMwdG7/dow==" + "resolved" "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/types" "2.6.0" + +"@parcel/reporter-cli@2.6.0": + "integrity" "sha512-QFG957NXx3L0D8Zw0+B2j7IHy8f/UzOVu6VvKE3rMkhq/iR2qLrPohQ+uvxlee+CLC0cG2qRSgJ7Ve/rjQPoJg==" + "resolved" "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "chalk" "^4.1.0" + "term-size" "^2.2.1" + +"@parcel/reporter-dev-server@2.6.0": + "integrity" "sha512-VvygsCA+uzWyijIV8zqU1gFyhAWknuaY4KIWhV4kCT8afRJwsLSwt/tpdaKDPuPU45h3tTsUdXH1wjaIk+dGeQ==" + "resolved" "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + +"@parcel/resolver-default@2.6.0": + "integrity" "sha512-ATk9wXvy5GOHAqyHbnCnU11fUPTtf8dLjpgVqL5XylwugZnyBXbynoTWX4w8h6mffkVtdfmzTJx/o4Lresz9sA==" + "resolved" "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/node-resolver-core" "2.6.0" + "@parcel/plugin" "2.6.0" + +"@parcel/runtime-browser-hmr@2.6.0": + "integrity" "sha512-90xvv/10cFML5dAhClBEJZ/ExiBQVPqQsZcvRmVZmc5mpZVJMKattWCQrd7pAf7FDYl4JAcvsK3DTwvRT/oLNA==" + "resolved" "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + +"@parcel/runtime-js@2.6.0": + "integrity" "sha512-R4tJAIT/SX7VBQ+f7WmeekREQzzLsmgP1j486uKhQNyYrpvsN0HnRbg5aqvZjEjkEmSeJR0mOlWtMK5/m+0yTA==" + "resolved" "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/runtime-react-refresh@2.6.0": + "integrity" "sha512-2sRd13gc2EbMV/O5n2NPVGGhKBasb1fDTXGEY8y7qi9xDKc+ewok/D83T+w243FhCPS9Pf3ur5GkbPlrJGcenQ==" + "resolved" "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "react-error-overlay" "6.0.9" + "react-refresh" "^0.9.0" + +"@parcel/runtime-service-worker@2.6.0": + "integrity" "sha512-nVlknGw5J5Bkd1Wr1TbyWHhUd9CmVVebaRg/lpfVKYhAuE/2r+3N0+J8qbEIgtTRcHaSV7wTNpg4weSWq46VeA==" + "resolved" "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/source-map@^2.0.0": + "integrity" "sha512-DRVlCFKLpqBSIbMxUoVlHgfiv12HTW/U7nnhzw52YgzDVXUX9OA41dXS1PU0pJ1si+D1k8msATUC+AoldN43mg==" + "resolved" "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.0.5.tgz" + "version" "2.0.5" + dependencies: + "detect-libc" "^1.0.3" + +"@parcel/transformer-babel@2.6.0": + "integrity" "sha512-qTDzhLoaTpRJoppCNqaAlcUYxcDEvJffem1h3SAQiwvCLUBQowLyeaBy8sUxu54AU6eHFJyBld5ZocENyHTBCA==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "browserslist" "^4.6.6" + "json5" "^2.2.0" + "nullthrows" "^1.1.1" + "semver" "^5.7.0" + +"@parcel/transformer-css@2.6.0": + "integrity" "sha512-Ei9NPE5Rl9V+MGd8qddfZD0Fsqbvky8J62RwYsqLkptFl9FkhgwOu8Cmokz7IIc4GJ2qzfnG5y54K/Bi7Moq4Q==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/css" "^1.9.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "browserslist" "^4.6.6" + "nullthrows" "^1.1.1" + +"@parcel/transformer-html@2.6.0": + "integrity" "sha512-YQh5WzNFjPhgV09P+zVS++albTCTvbPYAJXp5zUJ4HavzcpV2IB3HAPRk9x+iXUeRBQYYiO5SMMRkdy9a4CzQQ==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/plugin" "2.6.0" + "nullthrows" "^1.1.1" + "posthtml" "^0.16.5" + "posthtml-parser" "^0.10.1" + "posthtml-render" "^3.0.0" + "semver" "^5.7.1" + +"@parcel/transformer-image@2.6.0": + "integrity" "sha512-Zkh1i6nWNOTOReKlZD+bLJCHA16dPLO6Or7ETAHtSF3iRzMNFcVFp+851Awj3l4zeJ6CoCWlyxsR4CEdioRgiQ==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/workers" "2.6.0" + "nullthrows" "^1.1.1" + +"@parcel/transformer-js@2.6.0": + "integrity" "sha512-4v2r3EVdMKowBziVBW9HZqvAv88HaeiezkWyMX4wAfplo9jBtWEp99KEQINzSEdbXROR81M9oJjlGF5+yoVr/w==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/utils" "2.6.0" + "@parcel/workers" "2.6.0" + "@swc/helpers" "^0.3.15" + "browserslist" "^4.6.6" + "detect-libc" "^1.0.3" + "nullthrows" "^1.1.1" + "regenerator-runtime" "^0.13.7" + "semver" "^5.7.1" + +"@parcel/transformer-json@2.6.0": + "integrity" "sha512-zb+TQAdHWdXijKcFhLe+5KN1O0IzXwW1gJhPr8DJEA3qhPaCsncsw5RCVjQlP3a7NXr1mMm1eMtO6bhIMqbXeA==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "json5" "^2.2.0" + +"@parcel/transformer-postcss@2.6.0": + "integrity" "sha512-czmh2mOPJLwYbtnPTFlxKYcaQHH6huIlpfNX1XgdsaEYS+yFs8ZXpzqjxI1wu6rMW0R0q5aon72yB3PJewvqNQ==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "clone" "^2.1.1" + "nullthrows" "^1.1.1" + "postcss-value-parser" "^4.2.0" + "semver" "^5.7.1" + +"@parcel/transformer-posthtml@2.6.0": + "integrity" "sha512-R1FmPMZ0pgrbPZkDppa2pE+6KDK3Wxof6uQo7juHLB2ELGOTaYofsG3nrRdk+chyAHaVv4qWLqXbfZK6pGepEg==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "nullthrows" "^1.1.1" + "posthtml" "^0.16.5" + "posthtml-parser" "^0.10.1" + "posthtml-render" "^3.0.0" + "semver" "^5.7.1" + +"@parcel/transformer-raw@2.6.0": + "integrity" "sha512-QDirlWCS/qy0DQ3WvDIAnFP52n1TJW/uWH+4PGMNnX4/M3/2UchY8xp9CN0tx4NQ4g09S8o3gLlHvNxQqZxFrQ==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + +"@parcel/transformer-react-refresh-wrap@2.6.0": + "integrity" "sha512-G34orfvLDUTumuerqNmA8T8NUHk+R0jwUjbVPO7gpB6VCVQ5ocTABdE9vN9Uu/cUsHij40TUFwqK4R9TFEBIEQ==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/utils" "2.6.0" + "react-refresh" "^0.9.0" + +"@parcel/transformer-svg@2.6.0": + "integrity" "sha512-e7yrb7775A7tEGRsAHQSMhXe+u4yisH5W0PuIzAQQy/a2IwBjaSxNnvyelN7tNX0FYq0BK6An5wRbhK4YmM+xw==" + "resolved" "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/plugin" "2.6.0" + "nullthrows" "^1.1.1" + "posthtml" "^0.16.5" + "posthtml-parser" "^0.10.1" + "posthtml-render" "^3.0.0" + "semver" "^5.7.1" + +"@parcel/ts-utils@2.6.0": + "integrity" "sha512-U2Spr/vdOnxLzztXP6WpMO7JZTsaYO1G6F/cUTG5fReTQ0imM952FAc/WswpZWAPZqXqWCnvC/Z91JIkMDuYrA==" + "resolved" "https://registry.npmjs.org/@parcel/ts-utils/-/ts-utils-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "nullthrows" "^1.1.1" + +"@parcel/types@2.6.0": + "integrity" "sha512-lAMYvOBfNEJMsPJ+plbB50305o0TwNrY1xX5RRIWBqwOa6bYmbW1ZljUk1tQvnkpIE4eAHQwnPR5Z2XWg18wGQ==" + "resolved" "https://registry.npmjs.org/@parcel/types/-/types-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/cache" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/package-manager" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/workers" "2.6.0" + "utility-types" "^3.10.0" + +"@parcel/utils@2.6.0": + "integrity" "sha512-ElXz+QHtT1JQIucbQJBk7SzAGoOlBp4yodEQVvTKS7GA+hEGrSP/cmibl6qm29Rjtd0zgQsdd+2XmP3xvP2gQQ==" + "resolved" "https://registry.npmjs.org/@parcel/utils/-/utils-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/codeframe" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/markdown-ansi" "2.6.0" + "@parcel/source-map" "^2.0.0" + "chalk" "^4.1.0" + +"@parcel/validator-typescript@^2.6.0": + "integrity" "sha512-NMroc+QPoTo436COHsqEQsn+Qd+7HE1s1X6he1Bqb+RMB4rZsvOZI22MgFj1eU5MpfYuM4zTID0Uz221hiS59w==" + "resolved" "https://registry.npmjs.org/@parcel/validator-typescript/-/validator-typescript-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/ts-utils" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + +"@parcel/watcher@^2.0.0": + "integrity" "sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw==" + "resolved" "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.5.tgz" + "version" "2.0.5" + dependencies: + "node-addon-api" "^3.2.1" + "node-gyp-build" "^4.3.0" + +"@parcel/workers@2.6.0": + "integrity" "sha512-3tcI2LF5fd/WZtSnSjyWdDE+G+FitdNrRgSObzSp+axHKMAM23sO0z7KY8s2SYCF40msdYbFUW8eI6JlYNJoWQ==" + "resolved" "https://registry.npmjs.org/@parcel/workers/-/workers-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/diagnostic" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "chrome-trace-event" "^1.0.2" + "nullthrows" "^1.1.1" + +"@swc/helpers@^0.3.15": + "integrity" "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==" + "resolved" "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz" + "version" "0.3.17" + dependencies: + "tslib" "^2.4.0" + +"@trysound/sax@0.2.0": + "integrity" "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" + "resolved" "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" + "version" "0.2.0" + +"@types/parse-json@^4.0.0": + "integrity" "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "resolved" "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" + "version" "4.0.0" + +"abortcontroller-polyfill@^1.1.9": + "integrity" "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + "resolved" "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz" + "version" "1.7.3" + +"acorn@^8.5.0": + "integrity" "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==" + "resolved" "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz" + "version" "8.7.1" + +"ansi-styles@^3.2.1": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "color-convert" "^1.9.0" + +"ansi-styles@^4.1.0": + "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "color-convert" "^2.0.1" + +"base-x@^3.0.8": + "integrity" "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==" + "resolved" "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" + "version" "3.0.9" + dependencies: + "safe-buffer" "^5.0.1" + +"boolbase@^1.0.0": + "integrity" "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "resolved" "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + "version" "1.0.0" + +"browserslist@^4.6.6": + "integrity" "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==" + "resolved" "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz" + "version" "4.20.4" + dependencies: + "caniuse-lite" "^1.0.30001349" + "electron-to-chromium" "^1.4.147" + "escalade" "^3.1.1" + "node-releases" "^2.0.5" + "picocolors" "^1.0.0" + +"buffer-from@^1.0.0": + "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + "version" "1.1.2" + +"callsites@^3.0.0": + "integrity" "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "resolved" "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + "version" "3.1.0" + +"caniuse-lite@^1.0.30001349": + "integrity" "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==" + "resolved" "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz" + "version" "1.0.30001352" + +"chalk@^2.0.0": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chalk@^4.1.0": + "integrity" "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + "version" "4.1.2" + dependencies: + "ansi-styles" "^4.1.0" + "supports-color" "^7.1.0" + +"chrome-trace-event@^1.0.2": + "integrity" "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" + "resolved" "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + "version" "1.0.3" + +"clone@^2.1.1": + "integrity" "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + "resolved" "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" + "version" "2.1.2" + +"color-convert@^1.9.0": + "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + "version" "1.9.3" + dependencies: + "color-name" "1.1.3" + +"color-convert@^2.0.1": + "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "color-name" "~1.1.4" + +"color-name@~1.1.4": + "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + "version" "1.1.4" + +"color-name@1.1.3": + "integrity" "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + "version" "1.1.3" + +"commander@^2.20.0": + "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + "version" "2.20.3" + +"commander@^7.0.0", "commander@^7.2.0": + "integrity" "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + "resolved" "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + "version" "7.2.0" + +"cosmiconfig@^7.0.1": + "integrity" "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==" + "resolved" "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz" + "version" "7.0.1" + dependencies: + "@types/parse-json" "^4.0.0" + "import-fresh" "^3.2.1" + "parse-json" "^5.0.0" + "path-type" "^4.0.0" + "yaml" "^1.10.0" + +"css-select@^4.1.3": + "integrity" "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==" + "resolved" "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" + "version" "4.3.0" + dependencies: + "boolbase" "^1.0.0" + "css-what" "^6.0.1" + "domhandler" "^4.3.1" + "domutils" "^2.8.0" + "nth-check" "^2.0.1" + +"css-tree@^1.1.2", "css-tree@^1.1.3": + "integrity" "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==" + "resolved" "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "mdn-data" "2.0.14" + "source-map" "^0.6.1" + +"css-what@^6.0.1": + "integrity" "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + "resolved" "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + "version" "6.1.0" + +"csso@^4.2.0": + "integrity" "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==" + "resolved" "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz" + "version" "4.2.0" + dependencies: + "css-tree" "^1.1.2" + +"datastream-js@^1.0.7": + "integrity" "sha512-rW7N3QkEx01reZ6/BF1s3sGnh1JdFpektwSqgUz8bmmvfmD+NNGTPhbTePZjs0B3VSizFX26Kr8qNtNJDz0NAQ==" + "resolved" "https://registry.npmjs.org/datastream-js/-/datastream-js-1.0.7.tgz" + "version" "1.0.7" + dependencies: + "text-encoding" "^0.6.4" + +"detect-libc@^1.0.3": + "integrity" "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" + "resolved" "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" + "version" "1.0.3" + +"dom-serializer@^1.0.1": + "integrity" "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==" + "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" + "version" "1.4.1" + dependencies: + "domelementtype" "^2.0.1" + "domhandler" "^4.2.0" + "entities" "^2.0.0" + +"domelementtype@^2.0.1", "domelementtype@^2.2.0": + "integrity" "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + "resolved" "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + "version" "2.3.0" + +"domhandler@^4.2.0", "domhandler@^4.2.2", "domhandler@^4.3.1": + "integrity" "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==" + "resolved" "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" + "version" "4.3.1" + dependencies: + "domelementtype" "^2.2.0" + +"domutils@^2.8.0": + "integrity" "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==" + "resolved" "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + "version" "2.8.0" + dependencies: + "dom-serializer" "^1.0.1" + "domelementtype" "^2.2.0" + "domhandler" "^4.2.0" + +"dotenv-expand@^5.1.0": + "integrity" "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + "resolved" "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz" + "version" "5.1.0" + +"dotenv@^7.0.0": + "integrity" "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==" + "resolved" "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz" + "version" "7.0.0" + +"electron-to-chromium@^1.4.147": + "integrity" "sha512-MP3oBer0X7ZeS9GJ0H6lmkn561UxiwOIY9TTkdxVY7lI9G6GVCKfgJaHaDcakwdKxBXA4T3ybeswH/WBIN/KTA==" + "resolved" "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.150.tgz" + "version" "1.4.150" + +"entities@^2.0.0": + "integrity" "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + "resolved" "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + "version" "2.2.0" + +"entities@^3.0.1": + "integrity" "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + "resolved" "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz" + "version" "3.0.1" + +"error-ex@^1.3.1": + "integrity" "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==" + "resolved" "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + "version" "1.3.2" + dependencies: + "is-arrayish" "^0.2.1" + +"escalade@^3.1.1": + "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + "version" "3.1.1" + +"escape-string-regexp@^1.0.5": + "integrity" "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "version" "1.0.5" + +"get-port@^4.2.0": + "integrity" "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==" + "resolved" "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz" + "version" "4.2.0" + +"globals@^13.2.0": + "integrity" "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==" + "resolved" "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz" + "version" "13.15.0" + dependencies: + "type-fest" "^0.20.2" + +"has-flag@^3.0.0": + "integrity" "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "version" "3.0.0" + +"has-flag@^4.0.0": + "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + "version" "4.0.0" + +"htmlnano@^2.0.0": + "integrity" "sha512-+ZrQFS4Ub+zd+/fWwfvoYCEGNEa0/zrpys6CyXxvZDwtL7Pl+pOtRkiujyvBQ7Lmfp7/iEPxtOFgxWA16Gkj3w==" + "resolved" "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "cosmiconfig" "^7.0.1" + "posthtml" "^0.16.5" + "timsort" "^0.3.0" + +"htmlparser2@^7.1.1": + "integrity" "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==" + "resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "domelementtype" "^2.0.1" + "domhandler" "^4.2.2" + "domutils" "^2.8.0" + "entities" "^3.0.1" + +"import-fresh@^3.2.1": + "integrity" "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==" + "resolved" "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + "version" "3.3.0" + dependencies: + "parent-module" "^1.0.0" + "resolve-from" "^4.0.0" + +"is-arrayish@^0.2.1": + "integrity" "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "resolved" "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + "version" "0.2.1" + +"is-json@^2.0.1": + "integrity" "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==" + "resolved" "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz" + "version" "2.0.1" + +"js-tokens@^4.0.0": + "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + "version" "4.0.0" + +"json-parse-even-better-errors@^2.3.0": + "integrity" "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "resolved" "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + "version" "2.3.1" + +"json5@^2.2.0", "json5@^2.2.1": + "integrity" "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + "resolved" "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" + "version" "2.2.1" + +"lines-and-columns@^1.1.6": + "integrity" "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "resolved" "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + "version" "1.2.4" + +"lmdb-darwin-x64@2.3.10": + "integrity" "sha512-gAc/1b/FZOb9yVOT+o0huA+hdW82oxLo5r22dFTLoRUFG1JMzxdTjmnW6ONVOHdqC9a5bt3vBCEY3jmXNqV26A==" + "resolved" "https://registry.npmjs.org/lmdb-darwin-x64/-/lmdb-darwin-x64-2.3.10.tgz" + "version" "2.3.10" + +"lmdb@2.3.10": + "integrity" "sha512-GtH+nStn9V59CfYeQ5ddx6YTfuFCmu86UJojIjJAweG+/Fm0PDknuk3ovgYDtY/foMeMdZa8/P7oSljW/d5UPw==" + "resolved" "https://registry.npmjs.org/lmdb/-/lmdb-2.3.10.tgz" + "version" "2.3.10" + dependencies: + "msgpackr" "^1.5.4" + "nan" "^2.14.2" + "node-addon-api" "^4.3.0" + "node-gyp-build-optional-packages" "^4.3.2" + "ordered-binary" "^1.2.4" + "weak-lru-cache" "^1.2.2" + optionalDependencies: + "lmdb-darwin-arm64" "2.3.10" + "lmdb-darwin-x64" "2.3.10" + "lmdb-linux-arm" "2.3.10" + "lmdb-linux-arm64" "2.3.10" + "lmdb-linux-x64" "2.3.10" + "lmdb-win32-x64" "2.3.10" + +"mdn-data@2.0.14": + "integrity" "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "resolved" "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" + "version" "2.0.14" + +"msgpackr-extract@^2.0.2": + "integrity" "sha512-coskCeJG2KDny23zWeu+6tNy7BLnAiOGgiwzlgdm4oeSsTpqEJJPguHIuKZcCdB7tzhZbXNYSg6jZAXkZErkJA==" + "resolved" "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-2.0.2.tgz" + "version" "2.0.2" + dependencies: + "node-gyp-build-optional-packages" "5.0.2" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "2.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "2.0.2" + +"msgpackr@^1.5.4": + "integrity" "sha512-Je+xBEfdjtvA4bKaOv8iRhjC8qX2oJwpYH4f7JrG4uMVJVmnmkAT4pjKdbztKprGj3iwjcxPzb5umVZ02Qq3tA==" + "resolved" "https://registry.npmjs.org/msgpackr/-/msgpackr-1.6.1.tgz" + "version" "1.6.1" + optionalDependencies: + "msgpackr-extract" "^2.0.2" + +"nan@^2.14.2": + "integrity" "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==" + "resolved" "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz" + "version" "2.16.0" + +"node-addon-api@^3.2.1": + "integrity" "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + "resolved" "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" + "version" "3.2.1" + +"node-addon-api@^4.3.0": + "integrity" "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + "resolved" "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz" + "version" "4.3.0" + +"node-gyp-build-optional-packages@^4.3.2": + "integrity" "sha512-5ke7D8SiQsTQL7CkHpfR1tLwfqtKc0KYEmlnkwd40jHCASskZeS98qoZ1qDUns2aUQWikcjidRUs6PM/3iyN/w==" + "resolved" "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-4.3.5.tgz" + "version" "4.3.5" + +"node-gyp-build-optional-packages@5.0.2": + "integrity" "sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g==" + "resolved" "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz" + "version" "5.0.2" + +"node-gyp-build@^4.3.0": + "integrity" "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" + "resolved" "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz" + "version" "4.4.0" + +"node-releases@^2.0.5": + "integrity" "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==" + "resolved" "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz" + "version" "2.0.5" + +"nth-check@^2.0.1": + "integrity" "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==" + "resolved" "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "boolbase" "^1.0.0" + +"nullthrows@^1.1.1": + "integrity" "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + "resolved" "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" + "version" "1.1.1" + +"ordered-binary@^1.2.4": + "integrity" "sha512-djRmZoEpOGvIRW7ufsCDHtvcUa18UC9TxnPbHhSVFZHsoyg0dtut1bWtBZ/fmxdPN62oWXrV6adM7NoWU+CneA==" + "resolved" "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.2.5.tgz" + "version" "1.2.5" + +"parcel@^2.6.0": + "integrity" "sha512-pSTJ7wC6uTl16PKLXQV7RfL9FGoIDA1iVpNvaav47n6UkUdKqfx0spcVPpw35kWdRcHJF61YAvkPjP2hTwHQ+Q==" + "resolved" "https://registry.npmjs.org/parcel/-/parcel-2.6.0.tgz" + "version" "2.6.0" + dependencies: + "@parcel/config-default" "2.6.0" + "@parcel/core" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/events" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/package-manager" "2.6.0" + "@parcel/reporter-cli" "2.6.0" + "@parcel/reporter-dev-server" "2.6.0" + "@parcel/utils" "2.6.0" + "chalk" "^4.1.0" + "commander" "^7.0.0" + "get-port" "^4.2.0" + "v8-compile-cache" "^2.0.0" + +"parent-module@^1.0.0": + "integrity" "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==" + "resolved" "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + "version" "1.0.1" + dependencies: + "callsites" "^3.0.0" + +"parse-json@^5.0.0": + "integrity" "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==" + "resolved" "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + "version" "5.2.0" + dependencies: + "@babel/code-frame" "^7.0.0" + "error-ex" "^1.3.1" + "json-parse-even-better-errors" "^2.3.0" + "lines-and-columns" "^1.1.6" + +"path-type@^4.0.0": + "integrity" "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + "version" "4.0.0" + +"picocolors@^1.0.0": + "integrity" "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "resolved" "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + "version" "1.0.0" + +"postcss-value-parser@^4.2.0": + "integrity" "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "resolved" "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + "version" "4.2.0" + +"posthtml-parser@^0.10.1": + "integrity" "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==" + "resolved" "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz" + "version" "0.10.2" + dependencies: + "htmlparser2" "^7.1.1" + +"posthtml-parser@^0.11.0": + "integrity" "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==" + "resolved" "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz" + "version" "0.11.0" + dependencies: + "htmlparser2" "^7.1.1" + +"posthtml-render@^3.0.0": + "integrity" "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==" + "resolved" "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "is-json" "^2.0.1" + +"posthtml@^0.16.4", "posthtml@^0.16.5": + "integrity" "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==" + "resolved" "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz" + "version" "0.16.6" + dependencies: + "posthtml-parser" "^0.11.0" + "posthtml-render" "^3.0.0" + +"react-error-overlay@6.0.9": + "integrity" "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" + "resolved" "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz" + "version" "6.0.9" + +"react-refresh@^0.9.0": + "integrity" "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==" + "resolved" "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz" + "version" "0.9.0" + +"regenerator-runtime@^0.13.7": + "integrity" "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "resolved" "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + "version" "0.13.9" + +"resolve-from@^4.0.0": + "integrity" "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + "version" "4.0.0" + +"safe-buffer@^5.0.1": + "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + "version" "5.2.1" + +"semver@^5.7.0", "semver@^5.7.1": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"source-map-support@~0.5.20": + "integrity" "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==" + "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + "version" "0.5.21" + dependencies: + "buffer-from" "^1.0.0" + "source-map" "^0.6.0" + +"source-map@^0.6.0", "source-map@^0.6.1": + "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "version" "0.6.1" + +"stable@^0.1.8": + "integrity" "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + "resolved" "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" + "version" "0.1.8" + +"supports-color@^5.3.0": + "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "version" "5.5.0" + dependencies: + "has-flag" "^3.0.0" + +"supports-color@^7.1.0": + "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + "version" "7.2.0" + dependencies: + "has-flag" "^4.0.0" + +"svgo@^2.4.0", "svgo@^2.8.0": + "integrity" "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==" + "resolved" "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz" + "version" "2.8.0" + dependencies: + "@trysound/sax" "0.2.0" + "commander" "^7.2.0" + "css-select" "^4.1.3" + "css-tree" "^1.1.3" + "csso" "^4.2.0" + "picocolors" "^1.0.0" + "stable" "^0.1.8" + +"term-size@^2.2.1": + "integrity" "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" + "resolved" "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz" + "version" "2.2.1" + +"terser@^5.10.0", "terser@^5.2.0": + "integrity" "sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g==" + "resolved" "https://registry.npmjs.org/terser/-/terser-5.14.0.tgz" + "version" "5.14.0" + dependencies: + "@jridgewell/source-map" "^0.3.2" + "acorn" "^8.5.0" + "commander" "^2.20.0" + "source-map-support" "~0.5.20" + +"text-encoding@^0.6.4": + "integrity" "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" + "resolved" "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz" + "version" "0.6.4" + +"timsort@^0.3.0": + "integrity" "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + "resolved" "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" + "version" "0.3.0" + +"tslib@^2.4.0": + "integrity" "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" + "version" "2.4.0" + +"type-fest@^0.20.2": + "integrity" "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + "version" "0.20.2" + +"typescript@>=3.0.0": + "integrity" "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz" + "version" "4.7.3" + +"utility-types@^3.10.0": + "integrity" "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + "resolved" "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz" + "version" "3.10.0" + +"v8-compile-cache@^2.0.0": + "integrity" "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + "resolved" "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" + "version" "2.3.0" + +"weak-lru-cache@^1.2.2": + "integrity" "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==" + "resolved" "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz" + "version" "1.2.2" + +"xxhash-wasm@^0.4.2": + "integrity" "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==" + "resolved" "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz" + "version" "0.4.2" + +"yaml@^1.10.0": + "integrity" "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + "resolved" "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + "version" "1.10.2" diff --git a/media/.gitignore b/media/.gitignore new file mode 100644 index 0000000..b8cea22 --- /dev/null +++ b/media/.gitignore @@ -0,0 +1,3 @@ +*.mp4 +*.mpd +*.m4s diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..333c1e9 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +logs/ diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..6b6bf5c --- /dev/null +++ b/server/go.mod @@ -0,0 +1,38 @@ +module github.com/kixelated/warp-sample + +go 1.18 + +require ( + github.com/abema/go-mp4 v0.7.2 + github.com/adriancable/webtransport-go v0.1.0 + github.com/kixelated/invoker v0.9.2 + github.com/lucas-clemente/quic-go v0.27.1 + github.com/zencoder/go-dash/v3 v3.0.2 +) + +require ( + github.com/cheekybits/genny v1.0.0 // indirect + github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/marten-seemann/qpack v0.2.1 // indirect + github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect + github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect + github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 // indirect + golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect +) + +replace github.com/adriancable/webtransport-go => github.com/kixelated/webtransport-go v0.1.1 + +replace github.com/lucas-clemente/quic-go => github.com/kixelated/quic-go v0.28.0 diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..208c2f4 --- /dev/null +++ b/server/go.sum @@ -0,0 +1,325 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/abema/go-mp4 v0.7.2 h1:ugTC8gfEmjyaDKpXs3vi2QzgJbDu9B8m6UMMIpbYbGg= +github.com/abema/go-mp4 v0.7.2/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/errcheck v1.4.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kixelated/invoker v0.9.2 h1:Pz8JDiRs8EzGc4EGVMZ4RYvFh+iQLXGZ4PG2KZyAh/0= +github.com/kixelated/invoker v0.9.2/go.mod h1:RjG3iqm/sKwZjOpcW4SGq+l+4DJCDR/yUtc70VjCRB8= +github.com/kixelated/quic-go v0.28.0 h1:KVA+baVIHNoRc3V6f/zGvfyZGRilAaTmKkkgvi7PEdw= +github.com/kixelated/quic-go v0.28.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= +github.com/kixelated/webtransport-go v0.1.1 h1:giXtM4UNHrVuccTL1WWTTsyQUzGUW9KmEwRkae26T6g= +github.com/kixelated/webtransport-go v0.1.1/go.mod h1:eEJGJfh2zVTLyVEIiqIlBcb1y8ltAl9CprvaZNnE7Fs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= +github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc= +github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y= +github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= +github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE= +github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 h1:2vmJlzGKvQ7e/X9XT0XydeWDxmqx8DnegiIMRT+5ssI= +golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/server/internal/warp/media.go b/server/internal/warp/media.go new file mode 100644 index 0000000..416c184 --- /dev/null +++ b/server/internal/warp/media.go @@ -0,0 +1,358 @@ +package warp + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/abema/go-mp4" + "github.com/kixelated/invoker" + "github.com/zencoder/go-dash/v3/mpd" +) + +// This is a demo; you should actually fetch media from a live backend. +// It's just much easier to read from disk and "fake" being live. +type Media struct { + base fs.FS + audio *mpd.Representation + video *mpd.Representation +} + +func NewMedia(playlistPath string) (m *Media, err error) { + m = new(Media) + + // Create a fs.FS out of the folder holding the playlist + m.base = os.DirFS(filepath.Dir(playlistPath)) + + // Read the playlist file + playlist, err := mpd.ReadFromFile(playlistPath) + if err != nil { + return nil, fmt.Errorf("failed to open playlist: %w", err) + } + + if len(playlist.Periods) > 1 { + return nil, fmt.Errorf("multiple periods not supported") + } + + period := playlist.Periods[0] + + for _, adaption := range period.AdaptationSets { + representation := adaption.Representations[0] + + if representation.MimeType == nil { + return nil, fmt.Errorf("missing representation mime type") + } + + switch *representation.MimeType { + case "video/mp4": + m.video = representation + case "audio/mp4": + m.audio = representation + } + } + + if m.video == nil { + return nil, fmt.Errorf("no video representation found") + } + + if m.audio == nil { + return nil, fmt.Errorf("no audio representation found") + } + + return m, nil +} + +func (m *Media) Start() (audio *MediaStream, video *MediaStream, err error) { + start := time.Now() + + audio, err = newMediaStream(m, m.audio, start) + if err != nil { + return nil, nil, err + } + + video, err = newMediaStream(m, m.video, start) + if err != nil { + return nil, nil, err + } + + return audio, video, nil +} + +type MediaStream struct { + media *Media + init *MediaInit + + start time.Time + rep *mpd.Representation + sequence int +} + +func newMediaStream(m *Media, rep *mpd.Representation, start time.Time) (ms *MediaStream, err error) { + ms = new(MediaStream) + ms.media = m + ms.rep = rep + ms.start = start + + if rep.SegmentTemplate == nil { + return nil, fmt.Errorf("missing segment template") + } + + if rep.SegmentTemplate.StartNumber == nil { + return nil, fmt.Errorf("missing start number") + } + + ms.sequence = int(*rep.SegmentTemplate.StartNumber) + + return ms, nil +} + +// Returns the init segment for the stream +func (ms *MediaStream) Init(ctx context.Context) (init *MediaInit, err error) { + // Cache the init segment + if ms.init != nil { + return ms.init, nil + } + + if ms.rep.SegmentTemplate.Initialization == nil { + return nil, fmt.Errorf("no init template") + } + + path := *ms.rep.SegmentTemplate.Initialization + + // TODO Support the full template engine + path = strings.ReplaceAll(path, "$RepresentationID$", *ms.rep.ID) + + f, err := fs.ReadFile(ms.media.base, path) + if err != nil { + return nil, fmt.Errorf("failed to read init file: %w", err) + } + + ms.init, err = newMediaInit(f) + if err != nil { + return nil, fmt.Errorf("failed to create init segment: %w", err) + } + + return ms.init, nil +} + +// Returns the next segment in the stream +func (ms *MediaStream) Segment(ctx context.Context) (segment *MediaSegment, err error) { + if ms.rep.SegmentTemplate.Media == nil { + return nil, fmt.Errorf("no media template") + } + + path := *ms.rep.SegmentTemplate.Media + + // TODO Support the full template engine + path = strings.ReplaceAll(path, "$RepresentationID$", *ms.rep.ID) + path = strings.ReplaceAll(path, "$Number%05d$", fmt.Sprintf("%05d", ms.sequence)) // TODO TODO + + // Check if this is the first segment in the playlist + first := ms.sequence == int(*ms.rep.SegmentTemplate.StartNumber) + + // Try openning the file + f, err := ms.media.base.Open(path) + if !first && errors.Is(err, os.ErrNotExist) { + // Return EOF if the next file is missing + return nil, nil + } else if err != nil { + return nil, fmt.Errorf("failed to open segment file: %w", err) + } + + offset := ms.sequence - int(*ms.rep.SegmentTemplate.StartNumber) + duration := time.Duration(*ms.rep.SegmentTemplate.Duration) / time.Nanosecond + + timestamp := time.Duration(offset) * duration + + // We need the init segment to properly parse the media segment + init, err := ms.Init(ctx) + if err != nil { + return nil, fmt.Errorf("failed to open init file: %w", err) + } + + segment, err = newMediaSegment(ms, init, f, timestamp) + if err != nil { + return nil, fmt.Errorf("failed to create segment: %w", err) + } + + ms.sequence += 1 + + return segment, nil +} + +type MediaInit struct { + Raw []byte + Timescale int +} + +func newMediaInit(raw []byte) (mi *MediaInit, err error) { + mi = new(MediaInit) + mi.Raw = raw + + err = mi.parse() + if err != nil { + return nil, fmt.Errorf("failed to parse init segment: %w", err) + } + + return mi, nil +} + +// Parse through the init segment, literally just to populate the timescale +func (mi *MediaInit) parse() (err error) { + r := bytes.NewReader(mi.Raw) + + _, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { + if !h.BoxInfo.IsSupportedType() { + return nil, nil + } + + payload, _, err := h.ReadPayload() + if err != nil { + return nil, err + } + + switch box := payload.(type) { + case *mp4.Mdhd: // Media Header; moov -> trak -> mdia > mdhd + if mi.Timescale != 0 { + // verify only one track + return nil, fmt.Errorf("multiple mdhd atoms") + } + + mi.Timescale = int(box.Timescale) + } + + // Expands children + return h.Expand() + }) + + if err != nil { + return fmt.Errorf("failed to parse MP4 file: %w", err) + } + + return nil +} + +type MediaSegment struct { + stream *MediaStream + init *MediaInit + file fs.File + timestamp time.Duration +} + +func newMediaSegment(s *MediaStream, init *MediaInit, file fs.File, timestamp time.Duration) (ms *MediaSegment, err error) { + ms = new(MediaSegment) + ms.stream = s + ms.init = init + ms.file = file + ms.timestamp = timestamp + return ms, nil +} + +// Return the next atom, sleeping based on the PTS to simulate a live stream +func (ms *MediaSegment) Read(ctx context.Context) (chunk []byte, err error) { + // Read the next top-level box + var header [8]byte + + _, err = io.ReadFull(ms.file, header[:]) + if err != nil { + return nil, fmt.Errorf("failed to read header: %w", err) + } + + size := int(binary.BigEndian.Uint32(header[0:4])) + if size < 8 { + return nil, fmt.Errorf("box is too small") + } + + buf := make([]byte, size) + n := copy(buf, header[:]) + + _, err = io.ReadFull(ms.file, buf[n:]) + if err != nil { + return nil, fmt.Errorf("failed to read atom: %w", err) + } + + sample, err := ms.parseAtom(ctx, buf) + if err != nil { + return nil, fmt.Errorf("failed to parse atom: %w", err) + } + + if sample != nil { + // Simulate a live stream by sleeping before we write this sample. + // Figure out how much time has elapsed since the start + elapsed := time.Since(ms.stream.start) + delay := sample.Timestamp - elapsed + + if delay > 0 { + // Sleep until we're supposed to see these samples + err = invoker.Sleep(delay)(ctx) + if err != nil { + return nil, err + } + } + } + + return buf, nil +} + +// Parse through the MP4 atom, returning infomation about the next fragmented sample +func (ms *MediaSegment) parseAtom(ctx context.Context, buf []byte) (sample *mediaSample, err error) { + r := bytes.NewReader(buf) + + _, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { + if !h.BoxInfo.IsSupportedType() { + return nil, nil + } + + payload, _, err := h.ReadPayload() + if err != nil { + return nil, err + } + + switch box := payload.(type) { + case *mp4.Moof: + sample = new(mediaSample) + case *mp4.Tfdt: // Track Fragment Decode Timestamp; moof -> traf -> tfdt + // TODO This box isn't required + // TODO we want the last PTS if there are multiple samples + var dts time.Duration + if box.FullBox.Version == 0 { + dts = time.Duration(box.BaseMediaDecodeTimeV0) + } else { + dts = time.Duration(box.BaseMediaDecodeTimeV1) + } + + if ms.init.Timescale == 0 { + return nil, fmt.Errorf("missing timescale") + } + + // Convert to seconds + // TODO What about PTS? + sample.Timestamp = dts * time.Second / time.Duration(ms.init.Timescale) + } + + // Expands children + return h.Expand() + }) + + if err != nil { + return nil, fmt.Errorf("failed to parse MP4 file: %w", err) + } + + return sample, nil +} + +func (ms *MediaSegment) Close() (err error) { + return ms.file.Close() +} + +type mediaSample struct { + Timestamp time.Duration // The timestamp of the first sample +} diff --git a/server/internal/warp/message.go b/server/internal/warp/message.go new file mode 100644 index 0000000..e84aead --- /dev/null +++ b/server/internal/warp/message.go @@ -0,0 +1,22 @@ +package warp + +type Message struct { + Init *MessageInit `json:"init,omitempty"` + Segment *MessageSegment `json:"segment,omitempty"` + Throttle *MessageThrottle `json:"x-throttle,omitempty"` +} + +type MessageInit struct { + Id int `json:"id"` // ID of the init segment +} + +type MessageSegment struct { + Init int `json:"init"` // ID of the init segment to use for this segment + Timestamp int `json:"timestamp"` // PTS of the first frame in milliseconds +} + +type MessageThrottle struct { + Rate int `json:"rate"` // Artificially limit the socket byte rate per second + Buffer int `json:"buffer"` // Artificially limit the socket buffer to the number of bytes + Loss float64 `json:"loss"` // Artificially increase packet loss percentage from 0.0 - 1.0 +} diff --git a/server/internal/warp/server.go b/server/internal/warp/server.go new file mode 100644 index 0000000..d1451fc --- /dev/null +++ b/server/internal/warp/server.go @@ -0,0 +1,99 @@ +package warp + +import ( + "context" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/adriancable/webtransport-go" + "github.com/kixelated/invoker" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/logging" + "github.com/lucas-clemente/quic-go/qlog" +) + +type Server struct { + inner *webtransport.Server + media *Media + socket *Socket + + sessions invoker.Tasks +} + +type ServerConfig struct { + Addr string + CertFile string + KeyFile string + LogDir string +} + +func NewServer(config ServerConfig, media *Media) (s *Server, err error) { + s = new(Server) + + // Listen using a custom socket that simulates congestion. + s.socket, err = NewSocket(config.Addr) + if err != nil { + return nil, fmt.Errorf("failed to create socket: %w", err) + } + + quicConfig := &quic.Config{} + + if config.LogDir != "" { + quicConfig.Tracer = qlog.NewTracer(func(p logging.Perspective, connectionID []byte) io.WriteCloser { + path := fmt.Sprintf("%s-%s.qlog", p, hex.EncodeToString(connectionID)) + + f, err := os.Create(filepath.Join(config.LogDir, path)) + if err != nil { + // lame + panic(err) + } + + return f + }) + } + + s.inner = &webtransport.Server{ + Listen: s.socket, + TLSCert: webtransport.CertFile{Path: config.CertFile}, + TLSKey: webtransport.CertFile{Path: config.KeyFile}, + QuicConfig: quicConfig, + } + + s.media = media + + http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { + session, ok := r.Body.(*webtransport.Session) + if !ok { + log.Print("http requests not supported") + return + } + + ss, err := NewSession(session, s.media, s.socket) + if err != nil { + // TODO handle better? + log.Printf("failed to create warp session: %v", err) + return + } + + // Run the session in parallel, logging errors instead of crashing + s.sessions.Add(func(ctx context.Context) (err error) { + err = ss.Run(ctx) + if err != nil { + log.Printf("terminated session: %s", err) + } + + return nil + }) + }) + + return s, nil +} + +func (s *Server) Run(ctx context.Context) (err error) { + return invoker.Run(ctx, s.inner.Run, s.socket.Run, s.sessions.Repeat) +} diff --git a/server/internal/warp/session.go b/server/internal/warp/session.go new file mode 100644 index 0000000..3458672 --- /dev/null +++ b/server/internal/warp/session.go @@ -0,0 +1,276 @@ +package warp + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "time" + + "github.com/adriancable/webtransport-go" + "github.com/kixelated/invoker" +) + +// A single WebTransport session +type Session struct { + inner *webtransport.Session + media *Media + socket *Socket + audio *MediaStream + video *MediaStream + + streams invoker.Tasks +} + +func NewSession(session *webtransport.Session, media *Media, socket *Socket) (s *Session, err error) { + s = new(Session) + s.inner = session + s.media = media + s.socket = socket + return s, nil +} + +func (s *Session) Run(ctx context.Context) (err error) { + // TODO validate the session before accepting it + s.inner.AcceptSession() + defer s.inner.CloseSession() + + s.audio, s.video, err = s.media.Start() + if err != nil { + return fmt.Errorf("failed to start media: %w", err) + } + + // Once we've validated the session, now we can start accessing the streams + return invoker.Run(ctx, s.runAccept, s.runAcceptUni, s.runAudio, s.runVideo, s.streams.Repeat) +} + +func (s *Session) runAccept(ctx context.Context) (err error) { + for { + // TODO context support :( + stream, err := s.inner.AcceptStream() + if err != nil { + return fmt.Errorf("failed to accept bidirectional stream: %w", err) + } + + // Warp doesn't utilize bidirectional streams so just close them immediately. + // We might use them in the future so don't close the connection with an error. + stream.CancelRead(1) + } +} + +func (s *Session) runAcceptUni(ctx context.Context) (err error) { + for { + stream, err := s.inner.AcceptUniStream(ctx) + if err != nil { + return fmt.Errorf("failed to accept unidirectional stream: %w", err) + } + + s.streams.Add(func(ctx context.Context) (err error) { + return s.handleStream(ctx, &stream) + }) + } +} + +func (s *Session) handleStream(ctx context.Context, stream *webtransport.ReceiveStream) (err error) { + defer func() { + if err != nil { + stream.CancelRead(1) + } + }() + + var header [8]byte + for { + _, err = io.ReadFull(stream, header[:]) + if errors.Is(io.EOF, err) { + return nil + } else if err != nil { + return fmt.Errorf("failed to read atom header: %w", err) + } + + size := binary.BigEndian.Uint32(header[0:4]) + name := string(header[4:8]) + + if size < 8 { + return fmt.Errorf("atom size is too small") + } else if size > 42069 { // arbitrary limit + return fmt.Errorf("atom size is too large") + } else if name != "warp" { + return fmt.Errorf("only warp atoms are supported") + } + + payload := make([]byte, size-8) + + _, err = io.ReadFull(stream, payload) + if err != nil { + return fmt.Errorf("failed to read atom payload: %w", err) + } + + msg := Message{} + + err = json.Unmarshal(payload, &msg) + if err != nil { + return fmt.Errorf("failed to decode json payload: %w", err) + } + + if msg.Throttle != nil { + s.setThrottle(msg.Throttle) + } + } +} + +func (s *Session) runAudio(ctx context.Context) (err error) { + init, err := s.audio.Init(ctx) + if err != nil { + return fmt.Errorf("failed to fetch init segment: %w", err) + } + + // NOTE: Assumes a single init segment + err = s.writeInit(ctx, init, 1) + if err != nil { + return fmt.Errorf("failed to write init stream: %w", err) + } + + for { + segment, err := s.audio.Segment(ctx) + if err != nil { + return fmt.Errorf("failed to get next segment: %w", err) + } + + if segment == nil { + return nil + } + + err = s.writeSegment(ctx, segment, 1) + if err != nil { + return fmt.Errorf("failed to write segment stream: %w", err) + } + } +} + +func (s *Session) runVideo(ctx context.Context) (err error) { + init, err := s.video.Init(ctx) + if err != nil { + return fmt.Errorf("failed to fetch init segment: %w", err) + } + + // NOTE: Assumes a single init segment + err = s.writeInit(ctx, init, 2) + if err != nil { + return fmt.Errorf("failed to write init stream: %w", err) + } + + for { + segment, err := s.video.Segment(ctx) + if err != nil { + return fmt.Errorf("failed to get next segment: %w", err) + } + + if segment == nil { + return nil + } + + err = s.writeSegment(ctx, segment, 2) + if err != nil { + return fmt.Errorf("failed to write segment stream: %w", err) + } + } +} + +// Create a stream for an INIT segment and write the container. +func (s *Session) writeInit(ctx context.Context, init *MediaInit, id int) (err error) { + temp, err := s.inner.OpenUniStreamSync(ctx) + if err != nil { + return fmt.Errorf("failed to create stream: %w", err) + } + + // Wrap the stream in an object that buffers writes instead of blocking. + stream := NewStream(temp) + s.streams.Add(stream.Run) + + defer func() { + if err != nil { + stream.WriteCancel(1) + } + }() + + stream.SetPriority(math.MaxInt) + + err = stream.WriteMessage(Message{ + Init: &MessageInit{Id: id}, + }) + if err != nil { + return fmt.Errorf("failed to write init header: %w", err) + } + + _, err = stream.Write(init.Raw) + if err != nil { + return fmt.Errorf("failed to write init data: %w", err) + } + + return nil +} + +// Create a stream for a segment and write the contents, chunk by chunk. +func (s *Session) writeSegment(ctx context.Context, segment *MediaSegment, init int) (err error) { + temp, err := s.inner.OpenUniStreamSync(ctx) + if err != nil { + return fmt.Errorf("failed to create stream: %w", err) + } + + // Wrap the stream in an object that buffers writes instead of blocking. + stream := NewStream(temp) + s.streams.Add(stream.Run) + + defer func() { + if err != nil { + stream.WriteCancel(1) + } + }() + + ms := int(segment.timestamp / time.Millisecond) + + // newer segments take priority + stream.SetPriority(ms) + + err = stream.WriteMessage(Message{ + Segment: &MessageSegment{ + Init: init, + Timestamp: ms, + }, + }) + if err != nil { + return fmt.Errorf("failed to write segment header: %w", err) + } + + for { + // Get the next fragment + buf, err := segment.Read(ctx) + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return fmt.Errorf("failed to read segment data: %w", err) + } + + // NOTE: This won't block because of our wrapper + _, err = stream.Write(buf) + if err != nil { + return fmt.Errorf("failed to write segment data: %w", err) + } + } + + err = stream.Close() + if err != nil { + return fmt.Errorf("failed to close segemnt stream: %w", err) + } + + return nil +} + +func (s *Session) setThrottle(msg *MessageThrottle) { + s.socket.SetWriteRate(msg.Rate) + s.socket.SetWriteBuffer(msg.Buffer) + s.socket.SetWriteLoss(msg.Loss) +} diff --git a/server/internal/warp/socket.go b/server/internal/warp/socket.go new file mode 100644 index 0000000..232c331 --- /dev/null +++ b/server/internal/warp/socket.go @@ -0,0 +1,264 @@ +package warp + +import ( + "context" + "fmt" + "math/rand" + "net" + "sync" + "syscall" + "time" + + "github.com/kixelated/invoker" +) + +// Perform network simulation in-process to make a simpler demo. +// You should not use this in production; there are much better ways to throttle a network. +type Socket struct { + inner *net.UDPConn + + writeRate int // bytes per second + writeErr error // return this error on all future writes + + writeQueue []packet // packets ready to be sent + writeQueueSize int // number of bytes in the queue + writeQueueMax int // number of bytes allowed in the queue + + writeLastTime time.Time + writeLastSize int + + writeLoss float64 // packet loss percentage + + writeNotify chan struct{} // closed when rate or queue is changed + writeMutex sync.Mutex +} + +type packet struct { + Addr net.Addr + Data []byte +} + +func NewSocket(addr string) (s *Socket, err error) { + s = new(Socket) + + uaddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, fmt.Errorf("failed to resolve addr: %w", err) + } + + s.inner, err = net.ListenUDP("udp", uaddr) + if err != nil { + return nil, fmt.Errorf("failed to listen: %w", err) + } + + s.writeNotify = make(chan struct{}) + + return s, nil +} + +func (s *Socket) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + // TODO throttle reads? + return s.inner.ReadFrom(p) +} + +// Queue up packets to be sent +func (s *Socket) WriteTo(p []byte, addr net.Addr) (n int, err error) { + s.writeMutex.Lock() + defer s.writeMutex.Unlock() + + if s.writeErr != nil { + return 0, s.writeErr + } + + if s.writeQueueMax > 0 && s.writeQueueSize+len(p) > s.writeQueueMax { + // Gotta drop the packet + return len(p), nil + } + + if len(s.writeQueue) == 0 && s.writeRate == 0 { + // If there's no queue and no throttling, write directly + if s.writeLoss == 0 || rand.Float64() >= s.writeLoss { + _, err = s.inner.WriteTo(p, addr) + if err != nil { + s.writeErr = err + return 0, err + } + } + + return len(p), nil + } + + // Make a copy of the packet + pc := packet{ + Addr: addr, + Data: append([]byte{}, p...), + } + + if len(s.writeQueue) == 0 { + // Wakeup the writer goroutine. + close(s.writeNotify) + s.writeNotify = make(chan struct{}) + } + + s.writeQueue = append(s.writeQueue, pc) + s.writeQueueSize += len(p) + + return len(p), nil +} + +// Perform the writing in another goroutine. +func (s *Socket) runWrite(ctx context.Context) (err error) { + timer := time.NewTimer(time.Second) + timer.Stop() + + s.writeMutex.Lock() + defer s.writeMutex.Unlock() + + for { + // Lock is held at the start of the loop + + lastTime := s.writeLastTime + lastSize := s.writeLastSize + rate := s.writeRate + notify := s.writeNotify + ready := len(s.writeQueue) > 0 + + if !ready { + // Unlock while we wait for changes. + s.writeMutex.Unlock() + + select { + case <-ctx.Done(): + s.writeMutex.Lock() // gotta lock again just for the defer... + return ctx.Err() + case <-notify: + // Something changed, try again + s.writeMutex.Lock() + continue + } + } + + now := time.Now() + + if lastSize > 0 && rate > 0 { + // Compute the amount of time it should take to send lastSize bytes + delay := time.Second * time.Duration(lastSize) / time.Duration(rate) + next := lastTime.Add(delay) + + delay = next.Sub(now) + if delay > 0 { + // Unlock while we sleep. + s.writeMutex.Unlock() + + // Reuse the timer instance + // No need to drain the timer beforehand + timer.Reset(delay) + + select { + case <-ctx.Done(): + s.writeMutex.Lock() // gotta lock again just for the defer... + return ctx.Err() + case <-timer.C: + now = next + s.writeMutex.Lock() + case <-notify: + // Something changed, try again + if !timer.Stop() { + // Drain the timer + <-timer.C + } + + s.writeMutex.Lock() + continue + } + } + } + + // Send the first packet in the queue + p := s.writeQueue[0] + s.writeQueue = s.writeQueue[1:] + s.writeQueueSize -= len(p.Data) + s.writeLastTime = now + s.writeLastSize = len(p.Data) + + loss := s.writeLoss + + if loss > 0 || rand.Float64() >= loss { + _, err = s.inner.WriteTo(p.Data, p.Addr) + if err != nil { + s.writeErr = err + return err + } + } + } +} + +// Set the number of *bytes* that can be written within a second, or -1 for unlimited. +// Defaults to unlimited. +func (s *Socket) SetWriteRate(rate int) { + s.writeMutex.Lock() + defer s.writeMutex.Unlock() + + s.writeRate = rate + close(s.writeNotify) + s.writeNotify = make(chan struct{}) +} + +// Set the maximum number of bytes to queue before we drop packets. +// Defaults to unlimited (!) +func (s *Socket) SetWriteBuffer(size int) { + s.writeMutex.Lock() + defer s.writeMutex.Unlock() + + s.writeQueueMax = size + if s.writeQueueMax > 0 { + // Remove from the queue until the limit has been met + for s.writeQueueSize > s.writeQueueMax { + last := s.writeQueue[len(s.writeQueue)-1] + s.writeQueue = s.writeQueue[:len(s.writeQueue)-1] + s.writeQueueSize -= len(last.Data) + } + } + + close(s.writeNotify) + s.writeNotify = make(chan struct{}) +} + +func (s *Socket) SetWriteLoss(percent float64) { + s.writeMutex.Lock() + defer s.writeMutex.Unlock() + + s.writeLoss = percent +} + +func (s *Socket) Close() (err error) { + return s.inner.Close() +} + +func (s *Socket) LocalAddr() net.Addr { + return s.inner.LocalAddr() +} + +func (s *Socket) SetDeadline(t time.Time) error { + return s.inner.SetDeadline(t) +} + +func (s *Socket) SetReadDeadline(t time.Time) error { + return s.inner.SetReadDeadline(t) +} + +func (s *Socket) SetReadBuffer(size int) error { + return s.inner.SetReadBuffer(size) +} + +func (s *Socket) SetWriteDeadline(t time.Time) error { + return s.inner.SetWriteDeadline(t) +} + +func (s *Socket) SyscallConn() (syscall.RawConn, error) { + return s.inner.SyscallConn() +} + +func (s *Socket) Run(ctx context.Context) (err error) { + return invoker.Run(ctx /*s.runRead, */, s.runWrite) +} diff --git a/server/internal/warp/stream.go b/server/internal/warp/stream.go new file mode 100644 index 0000000..c326637 --- /dev/null +++ b/server/internal/warp/stream.go @@ -0,0 +1,145 @@ +package warp + +import ( + "context" + "encoding/binary" + "encoding/json" + "fmt" + "sync" + + "github.com/adriancable/webtransport-go" + "github.com/lucas-clemente/quic-go" +) + +// Wrapper around quic.SendStream to make Write non-blocking. +// Otherwise we can't write to multiple concurrent streams in the same goroutine. +type Stream struct { + inner webtransport.SendStream + + chunks [][]byte + closed bool + err error + + notify chan struct{} + mutex sync.Mutex +} + +func NewStream(inner webtransport.SendStream) (s *Stream) { + s = new(Stream) + s.inner = inner + s.notify = make(chan struct{}) + return s +} + +func (s *Stream) Run(ctx context.Context) (err error) { + defer func() { + s.mutex.Lock() + s.err = err + s.mutex.Unlock() + }() + + for { + s.mutex.Lock() + + chunks := s.chunks + notify := s.notify + closed := s.closed + + s.chunks = s.chunks[len(s.chunks):] + s.mutex.Unlock() + + for _, chunk := range chunks { + _, err = s.inner.Write(chunk) + if err != nil { + return err + } + } + + if closed { + return s.inner.Close() + } + + if len(chunks) == 0 { + select { + case <-ctx.Done(): + return ctx.Err() + case <-notify: + } + } + } +} + +func (s *Stream) Write(buf []byte) (n int, err error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.err != nil { + return 0, s.err + } + + if s.closed { + return 0, fmt.Errorf("closed") + } + + // Make a copy of the buffer so it's long lived + buf = append([]byte{}, buf...) + s.chunks = append(s.chunks, buf) + + // Wake up the writer + close(s.notify) + s.notify = make(chan struct{}) + + return len(buf), nil +} + +func (s *Stream) WriteMessage(msg Message) (err error) { + payload, err := json.Marshal(msg) + if err != nil { + return fmt.Errorf("failed to marshal message: %w", err) + } + + var size [4]byte + binary.BigEndian.PutUint32(size[:], uint32(len(payload)+8)) + + _, err = s.Write(size[:]) + if err != nil { + return fmt.Errorf("failed to write size: %w", err) + } + + _, err = s.Write([]byte("warp")) + if err != nil { + return fmt.Errorf("failed to write atom header: %w", err) + } + + _, err = s.Write(payload) + if err != nil { + return fmt.Errorf("failed to write payload: %w", err) + } + + return nil +} + +func (s *Stream) WriteCancel(code quic.StreamErrorCode) { + s.inner.CancelWrite(code) +} + +func (s *Stream) SetPriority(prio int) { + s.inner.SetPriority(prio) +} + +func (s *Stream) Close() (err error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.err != nil { + return s.err + } + + s.closed = true + + // Wake up the writer + close(s.notify) + s.notify = make(chan struct{}) + + return nil +} diff --git a/server/warp-server/main.go b/server/warp-server/main.go new file mode 100644 index 0000000..c911a87 --- /dev/null +++ b/server/warp-server/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/kixelated/invoker" + "github.com/kixelated/warp-sample/internal/warp" +) + +func main() { + err := run(context.Background()) + if err == nil { + return + } + + log.Println(err) + + var errPanic invoker.ErrPanic + + // TODO use an interface + if errors.As(err, &errPanic) { + stack := string(errPanic.Stack()) + + scanner := bufio.NewScanner(strings.NewReader(stack)) + for scanner.Scan() { + log.Println(scanner.Text()) + } + } + + os.Exit(1) +} + +func run(ctx context.Context) (err error) { + addr := flag.String("addr", ":4443", "HTTPS server address") + cert := flag.String("tls-cert", "../cert/localhost.warp.demo.crt", "TLS certificate file path") + key := flag.String("tls-key", "../cert/localhost.warp.demo.key", "TLS certificate file path") + logDir := flag.String("log-dir", "", "logs will be written to the provided directory") + + dash := flag.String("dash", "../media/fragmented.mpd", "DASH playlist path") + + flag.Parse() + + media, err := warp.NewMedia(*dash) + if err != nil { + return fmt.Errorf("failed to open media: %w", err) + } + + config := warp.ServerConfig{ + Addr: *addr, + CertFile: *cert, + KeyFile: *key, + LogDir: *logDir, + } + + ws, err := warp.NewServer(config, media) + if err != nil { + return fmt.Errorf("failed to create warp server: %w", err) + } + + log.Printf("listening on %s", *addr) + + return invoker.Run(ctx, invoker.Interrupt, ws.Run) +}