diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6c00c9633 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +webroot/js/web_modules/* linguist-vendored diff --git a/.gitignore b/.gitignore index f717b674f..05403027d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ dist/ transcoder.log chat.db .yp.key + +!webroot/js/web_modules/**/dist diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2a837f385..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "doc"] - path = doc - url = https://github.com/owncast/owncast.github.io/ diff --git a/README.md b/README.md index 3a65adfe6..ccf5a1395 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ View Demo · FAQ - . + · Report Bug

diff --git a/build/javascript/README.md b/build/javascript/README.md new file mode 100644 index 000000000..d6acc8ed9 --- /dev/null +++ b/build/javascript/README.md @@ -0,0 +1,10 @@ +## Third party web dependencies + +Owncast's web frontend utilizes a few third party Javascript and CSS dependencies that we ship with the application. + +To add, remove, or update one of these components: + +1. Perform your `npm install/uninstall/etc`, or edit the `package.json` file to reflect the change you want to make. +2. Edit the `snowpack` `install` block of `package.json` to specify what files you want to add to the Owncast project. This can be an entire library (such as `preact`) or a single file (such as `video.js/dist/video.min.js`). These paths point to files that live in `node_modules`. +3. Run `npm run build`. This will download the requested module from NPM, package up the assets you specified, and then copy them to the Owncast web app in the `webroot/js/web_modules` directory. +4. Your new web dependency is now available for use in your web code. diff --git a/build/javascript/package-lock.json b/build/javascript/package-lock.json new file mode 100644 index 000000000..300c0645c --- /dev/null +++ b/build/javascript/package-lock.json @@ -0,0 +1,3225 @@ +{ + "name": "owncast-dependencies", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.31.tgz", + "integrity": "sha512-lqUWRK+ylHQJG5Kiez4XrAZAfc7snxCc+X59quL3xPfMnxzfyf1lt+/hD7X1ZL4KlzAH2KFzMuEVrolo/rAkog==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.31" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.0.tgz", + "integrity": "sha512-kgdP2N5sFjzs5+XFZTsz8KNJjXm/851Gtjh8FcG+gpzN4weN/pd9S1jrf1e+2naWughHUzo7AKAzFB2CodpJ3g==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.31" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.0.tgz", + "integrity": "sha512-4dGRsOnGBPM7c0fd3LuiU6LgRSLn01rw1LJ370yC2iFMLUcLCLLynZhQbMhsiJmMwQM/YmPQblAdyHKVCgsIAA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.31" + } + }, + "@fullhuman/postcss-purgecss": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz", + "integrity": "sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw==", + "requires": { + "postcss": "7.0.32", + "purgecss": "^2.3.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@joeattardi/emoji-button": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@joeattardi/emoji-button/-/emoji-button-4.2.0.tgz", + "integrity": "sha512-naSbg20LEyenfeeUwPMUScwXzQkSM42hrEgLrQCyq01PP8uTZ30gnRByUkg475jUxUuOmf9G4fv0XJdFsNP2tw==", + "requires": { + "@fortawesome/fontawesome-svg-core": "^1.2.28", + "@fortawesome/free-regular-svg-icons": "^5.13.0", + "@fortawesome/free-solid-svg-icons": "^5.13.0", + "@popperjs/core": "^2.4.0", + "focus-trap": "^5.1.0", + "fuzzysort": "^1.1.4", + "tiny-emitter": "^2.1.0", + "tslib": "^2.0.0", + "twemoji": "^13.0.0" + } + }, + "@justinribeiro/lite-youtube": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@justinribeiro/lite-youtube/-/lite-youtube-0.9.0.tgz", + "integrity": "sha512-qh6WCBLr7thO2BSC29oSCbzJCEGM7Pbke/yFBFT4oxZNx/GiukWifaigVkatoqEx03PnQ/T0Iy3X7p9AZPHOEw==" + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "@popperjs/core": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.5.3.tgz", + "integrity": "sha512-RFwCobxsvZ6j7twS7dHIZQZituMIDJJNHS/qY6iuthVebxS3zhRY+jaC2roEKiAYaVuTcGmX6Luc6YBcf6zJVg==" + }, + "@rollup/plugin-alias": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.1.tgz", + "integrity": "sha512-hNcQY4bpBUIvxekd26DBPgF7BT4mKVNDF5tBG4Zi+3IgwLxGYRY0itHs9D0oLVwXM5pvJDWJlBQro+au8WaUWw==", + "dev": true, + "requires": { + "slash": "^3.0.0" + } + }, + "@rollup/plugin-commonjs": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz", + "integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + } + }, + "@rollup/plugin-inject": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-4.0.2.tgz", + "integrity": "sha512-TSLMA8waJ7Dmgmoc8JfPnwUwVZgLjjIAM6MqeIFqPO2ODK36JqE0Cf2F54UTgCUuW8da93Mvoj75a6KAVWgylw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.4", + "estree-walker": "^1.0.1", + "magic-string": "^0.25.5" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-node-resolve": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz", + "integrity": "sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.17.0" + } + }, + "@rollup/plugin-replace": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz", + "integrity": "sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8", + "magic-string": "^0.25.5" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@sindresorhus/is": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz", + "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==", + "dev": true + }, + "@snowpack/plugin-build-script": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-build-script/-/plugin-build-script-2.0.7.tgz", + "integrity": "sha512-f1fZN0Blsfk25oMBf62wvhHm6v7DUG0W85+JFKzYI6SHOIHrYL37o9xaJX7RJpn2kEKCRymWsQo4PclQdnL77w==", + "dev": true, + "requires": { + "execa": "^4.0.3", + "npm-run-path": "^4.0.1" + } + }, + "@snowpack/plugin-run-script": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-run-script/-/plugin-run-script-2.1.3.tgz", + "integrity": "sha512-DhV7KBHnUULtP7eQGHTAGdYsjI1q+vMuX07X8h/v2JnFVaZqowOBjnjQDfbY9oGtw/PZqiY4KEegAh3qxFqODQ==", + "dev": true, + "requires": { + "execa": "^4.0.3", + "npm-run-path": "^4.0.1" + } + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==", + "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 + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@videojs/http-streaming": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.0.tgz", + "integrity": "sha512-0LlotccmEt19Z03rI5busKGBvtpYNPHsnLNz0wl6zVEpx8trzYxTQSwKDCUD994hfzLeikQTHJc8BqMrfs1OgA==", + "requires": { + "@babel/runtime": "^7.5.5", + "@videojs/vhs-utils": "^2.2.0", + "aes-decrypter": "3.0.2", + "global": "^4.3.2", + "m3u8-parser": "4.4.3", + "mpd-parser": "0.12.0", + "mux.js": "5.6.6", + "video.js": "^6 || ^7" + }, + "dependencies": { + "mux.js": { + "version": "5.6.6", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.6.6.tgz", + "integrity": "sha512-q5VIpqb28UVs5dKsOQkpHrPxqInMjiZ/f/4qW4gEBKlm2xeBasRjRJIokixFWj+r6PWfVSEygvPffXnG7aK99g==" + } + } + }, + "@videojs/themes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@videojs/themes/-/themes-1.0.0.tgz", + "integrity": "sha512-DbRFpO7O0xPLRL4v7dJCcsfdP8Bpwd7jADzIKh/jpGViCl4u6ECmLquBi2OjGLWrqBv5uXMkKtK7RumrHeEyfw==", + "requires": { + "postcss-inline-svg": "^4.1.0" + } + }, + "@videojs/vhs-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-2.2.0.tgz", + "integrity": "sha512-Mtq+doRlbNvis9TyI5kfOg+Vg8aHGXkSXiuNwnkcimqyaP3wO/s/iEVKPcmRUySKivjaWktjdEFVXYfaP+/HTg==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "^4.3.2", + "url-toolkit": "^2.1.6" + } + }, + "@videojs/xhr": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.5.1.tgz", + "integrity": "sha512-wV9nGESHseSK+S9ePEru2+OJZ1jq/ZbbzniGQ4weAmTIepuBMSYPx5zrxxQA0E786T5ykpO8ts+LayV+3/oI2w==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + }, + "dependencies": { + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + } + } + }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", + "dev": true + }, + "aes-decrypter": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.0.2.tgz", + "integrity": "sha512-SBAfPQpGTbHbAR6qSybPjMio+MYuZwdD/a/ltOq6hj53vK94dphAm88rR3FNaZyEa/x3SobYmdT5f46qUwroLQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "@videojs/vhs-utils": "^1.0.0", + "global": "^4.3.2", + "pkcs7": "^1.0.4" + }, + "dependencies": { + "@videojs/vhs-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz", + "integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "^4.3.2", + "url-toolkit": "^2.1.6" + } + } + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browserslist": { + "version": "4.14.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", + "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", + "requires": { + "caniuse-lite": "^1.0.30001135", + "electron-to-chromium": "^1.3.571", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" + } + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "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 + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + }, + "caniuse-lite": { + "version": "1.0.30001143", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001143.tgz", + "integrity": "sha512-p/PO5YbwmCpBJPxjOiKBvAlUPgF8dExhfEpnsH+ys4N/791WHrYrGg0cyHiAURl5hSbx5vIcjKmQAP6sHDYH3w==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "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==", + "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": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "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-modules-loader-core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", + "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=", + "dev": true, + "requires": { + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.1", + "postcss-modules-extract-imports": "1.1.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0" + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-selector-tokenizer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, + "css-what": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.1.tgz", + "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==", + "dev": true + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "detect-port": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", + "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + } + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "electron-to-chromium": { + "version": "1.3.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz", + "integrity": "sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "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" + } + }, + "es-module-lexer": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.25.tgz", + "integrity": "sha512-H9VoFD5H9zEfiOX2LeTWDwMvAbLqcAyA2PIb40TOAvGpScOjit02oTGWgIh+M0rx2eJOKyJVM9wtpKFVgnyC3A==", + "dev": true + }, + "esbuild": { + "version": "0.6.34", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.6.34.tgz", + "integrity": "sha512-InRdL/Q96pUucPqovJzvuLhquZr6jOn81FDVwFjCKz1rYKIm9OdOC+7Fs4vr6x48vKBl5LzKgtjU39BUpO636A==", + "dev": true + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esinstall": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/esinstall/-/esinstall-0.2.0.tgz", + "integrity": "sha512-jLmMiMlKNpZr4Yr0T+3eNvYP2F2FpkfwPKVBmEFzlvKXoWhpvARUTuqv4TRkG3ZbgrhoPcwD0V2sh92ii8T0AA==", + "dev": true, + "requires": { + "@rollup/plugin-alias": "^3.0.1", + "@rollup/plugin-commonjs": "^15.0.0", + "@rollup/plugin-inject": "^4.0.2", + "@rollup/plugin-json": "^4.0.0", + "@rollup/plugin-node-resolve": "^9.0.0", + "@rollup/plugin-replace": "^2.3.3", + "es-module-lexer": "^0.3.24", + "is-builtin-module": "^3.0.0", + "kleur": "^4.1.1", + "mkdirp": "^1.0.3", + "rimraf": "^3.0.0", + "rollup": "^2.23.0", + "rollup-plugin-node-polyfills": "^0.2.1", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "estree-walker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz", + "integrity": "sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "execa": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "focus-trap": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz", + "integrity": "sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==", + "requires": { + "tabbable": "^4.0.0", + "xtend": "^4.0.1" + } + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "fuzzysort": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz", + "integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + }, + "dependencies": { + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + } + } + }, + "got": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.7.0.tgz", + "integrity": "sha512-7en2XwH2MEqOsrK0xaKhbWibBoZqy+f1RSUoIeF1BLcnf+pyQdDsljWMfmOh+QKJwuvDIiKx38GtPh5wFdGGjg==", + "dev": true, + "requires": { + "@sindresorhus/is": "^3.1.1", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "htm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.0.4.tgz", + "integrity": "sha512-VRdvxX3tmrXuT/Ovt59NMp/ORMFi4bceFMDjos1PV4E0mV+5votuID8R60egR9A4U8nLt238R/snlJGz3UYiTQ==" + }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "individual": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", + "integrity": "sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-builtin-module": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.0.0.tgz", + "integrity": "sha512-/93sDihsAD652hrMEbJGbMAVBf1qc96kyThHQ0CAOONHaE3aROLpTjDe4WQ5aoC5ITHFxEq1z8XqSU7km+8amw==", + "dev": true, + "requires": { + "builtin-modules": "^3.0.0" + } + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-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 + }, + "jsonfile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", + "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^0.1.2" + } + }, + "jsonschema": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.8.tgz", + "integrity": "sha512-HZrVvnv9Ri3WK3t53Anu55eS+IYiQm+UcGE23oBEYi3gD1qODW+I7y4R28q2FyVhzGTDhxAEqTjbe5+jNkqmeQ==", + "dev": true + }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kleur": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", + "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==", + "dev": true + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, + "m3u8-parser": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.4.3.tgz", + "integrity": "sha512-o6Z2I0lVeL95gE3BHPB5mSACAA6PTubYCFKlbdQmGNH+Ij0A0oEW8N4V5kqTPgYVnuLcb5UdqNiP7V+xEORhYA==", + "requires": { + "@babel/runtime": "^7.5.5", + "@videojs/vhs-utils": "^1.1.0", + "global": "^4.3.2" + }, + "dependencies": { + "@videojs/vhs-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz", + "integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "^4.3.2", + "url-toolkit": "^2.1.6" + } + } + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + }, + "dependencies": { + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + } + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "mpd-parser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.12.0.tgz", + "integrity": "sha512-Ov5Oz9bw5X/G8V/6PlO+rHuqKywYYjQ6USyv8fqFMs413HkrzlpDjgUKSBD7C+/J19ID5mWtxzrpMf4Yp++iZg==", + "requires": { + "@babel/runtime": "^7.5.5", + "@videojs/vhs-utils": "^1.1.0", + "global": "^4.3.2", + "xmldom": "^0.1.27" + }, + "dependencies": { + "@videojs/vhs-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz", + "integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "^4.3.2", + "url-toolkit": "^2.1.6" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mux.js": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.6.1.tgz", + "integrity": "sha512-iIE3EJURbrPZ9Y4i9ADKTIvxGUcAEBOFhwWUOZGCiKlpXDZrqDgcJLDrOa0PenLhw6WYkOyl18kHFEvwm9JSpg==" + }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-releases": { + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + } + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-hash": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + } + } + }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-queue": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.1.tgz", + "integrity": "sha512-miQiSxLYPYBxGkrldecZC18OTLjdUqnlRebGzPRiVxB8mco7usCmm7hFuxiTvp93K18JnLtE4KMMycjAu/cQQg==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.1.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "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-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "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 + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "requires": { + "@babel/runtime": "^7.5.5" + } + }, + "postcss": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz", + "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-functions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz", + "integrity": "sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=", + "requires": { + "glob": "^7.1.2", + "object-assign": "^4.1.1", + "postcss": "^6.0.9", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-inline-svg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-inline-svg/-/postcss-inline-svg-4.1.0.tgz", + "integrity": "sha512-0pYBJyoQ9/sJViYRc1cNOOTM7DYh0/rmASB0TBeRmWkG8YFK2tmgdkfjHkbRma1iFtBFKFHZFsHwRTDZTMKzSQ==", + "requires": { + "css-select": "^2.0.2", + "dom-serializer": "^0.1.1", + "htmlparser2": "^3.10.1", + "postcss": "^7.0.17", + "postcss-value-parser": "^4.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-2.0.3.tgz", + "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==", + "requires": { + "camelcase-css": "^2.0.1", + "postcss": "^7.0.18" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "postcss-nested": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", + "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", + "requires": { + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "preact": { + "version": "10.5.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.3.tgz", + "integrity": "sha512-g9i3d4obUJ24idLW/wuKWATVj6YFjxXDN+POu4u68YrI5dypUOu9Z3lb8/M4kr2dojcwq3H1GSgfpQkFX7y5Zw==" + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "purgecss": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.3.0.tgz", + "integrity": "sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ==", + "requires": { + "commander": "^5.0.0", + "glob": "^7.0.0", + "postcss": "7.0.32", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reduce-css-calc": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz", + "integrity": "sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==", + "dev": true + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "rollup": { + "version": "2.28.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.28.2.tgz", + "integrity": "sha512-8txbsFBFLmm9Xdt4ByTOGa9Muonmc8MfNjnGAR8U8scJlF1ZW7AgNZa7aqBXaKtlvnYP/ab++fQIq9dB9NWUbg==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "requires": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "rust-result": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", + "integrity": "sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=", + "requires": { + "individual": "^2.0.0" + } + }, + "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==" + }, + "safe-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", + "integrity": "sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=", + "requires": { + "rust-result": "^1.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "showdown": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz", + "integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==", + "requires": { + "yargs": "^14.2" + }, + "dependencies": { + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snowpack": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-2.12.1.tgz", + "integrity": "sha512-zMg7pOn96maiuMbKMXjMSbwTgq/hnZiWkBVIQL3TDXNCICT3uxSEW0vvPFyPNOg0JNvc0sb08Wvz5TyP5F8AjA==", + "dev": true, + "requires": { + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@snowpack/plugin-build-script": "^2.0.7", + "@snowpack/plugin-run-script": "^2.1.3", + "cacache": "^15.0.0", + "cachedir": "^2.3.0", + "chokidar": "^3.4.0", + "compressible": "^2.0.18", + "cosmiconfig": "^7.0.0", + "css-modules-loader-core": "^1.1.0", + "deepmerge": "^4.2.2", + "detect-port": "^1.3.0", + "es-module-lexer": "^0.3.24", + "esbuild": "^0.6.28", + "esinstall": "^0.2.0", + "etag": "^1.8.1", + "execa": "^4.0.3", + "find-cache-dir": "^3.3.1", + "find-up": "^4.1.0", + "glob": "^7.1.4", + "got": "^11.1.4", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.6", + "jsonschema": "^1.2.5", + "kleur": "^4.1.1", + "mime-types": "^2.1.26", + "mkdirp": "^1.0.3", + "npm-run-path": "^4.0.1", + "open": "^7.0.4", + "p-queue": "^6.6.1", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.3", + "strip-ansi": "^6.0.0", + "strip-comments": "^2.0.1", + "tar": "^6.0.1", + "validate-npm-package-name": "^3.0.0", + "ws": "^7.3.0", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + } + } + }, + "strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tabbable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", + "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" + }, + "tailwindcss": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.8.10.tgz", + "integrity": "sha512-7QkERG/cWCzsuMqHMwjOaLMVixOGLNBiXsrkssxlE1aWfkxVbGqiuMokR2162xRyaH2mBIHKxmlf1qb3DvIPqw==", + "requires": { + "@fullhuman/postcss-purgecss": "^2.1.2", + "autoprefixer": "^9.4.5", + "browserslist": "^4.12.0", + "bytes": "^3.0.0", + "chalk": "^3.0.0 || ^4.0.0", + "color": "^3.1.2", + "detective": "^5.2.0", + "fs-extra": "^8.0.0", + "html-tags": "^3.1.0", + "lodash": "^4.17.20", + "node-emoji": "^1.8.1", + "normalize.css": "^8.0.1", + "object-hash": "^2.0.3", + "postcss": "^7.0.11", + "postcss-functions": "^3.0.0", + "postcss-js": "^2.0.0", + "postcss-nested": "^4.1.1", + "postcss-selector-parser": "^6.0.0", + "postcss-value-parser": "^4.1.0", + "pretty-hrtime": "^1.0.3", + "reduce-css-calc": "^2.1.6", + "resolve": "^1.14.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "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==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.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==", + "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==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.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==", + "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": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + }, + "twemoji": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.0.1.tgz", + "integrity": "sha512-mrTBq+XpCLM4zm76NJOjLHoQNV9mHdBt3Cba/T5lS1rxn8ArwpqE47mqTocupNlkvcLxoeZJjYSUW0DU5ZwqZg==", + "requires": { + "fs-extra": "^8.0.1", + "jsonfile": "^5.0.0", + "twemoji-parser": "13.0.0", + "universalify": "^0.1.2" + } + }, + "twemoji-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.0.0.tgz", + "integrity": "sha512-zMaGdskpH8yKjT2RSE/HwE340R4Fm+fbie4AaqjDa4H/l07YUmAvxkSfNl6awVWNRRQ0zdzLQ8SAJZuY5MgstQ==" + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "url-toolkit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.0.tgz", + "integrity": "sha512-Rde0c9S4fJK3FaHim3DSgdQ8IFrSXcZCpAJo9T7/FA+BoQGhK0ow3mpwGQLJCPYsNn6TstpW7/7DzMpSpz9F9w==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "video.js": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.9.6.tgz", + "integrity": "sha512-2Dg0h2IbFCZRJW/1pkNYrTqolZPonR14ajaC30D5gdVwrSLxqR6SgsYDAblXw+mFFJHxleXzoLiM/hu3TfJmEQ==", + "requires": { + "@babel/runtime": "^7.9.2", + "@videojs/http-streaming": "1.13.4", + "@videojs/xhr": "2.5.1", + "global": "4.3.2", + "keycode": "^2.2.0", + "safe-json-parse": "4.0.0", + "videojs-font": "3.2.0", + "videojs-vtt.js": "^0.15.2" + }, + "dependencies": { + "@videojs/http-streaming": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-1.13.4.tgz", + "integrity": "sha512-I/hWi0uiA8aRwB4tfK44FRaWEoOU3uyvjUJW4cNST3TsweuovGGoud7K09WUrlbN4U0EjQvDqNwwXNggNs3niw==", + "requires": { + "aes-decrypter": "3.0.2", + "global": "^4.3.0", + "m3u8-parser": "4.4.0", + "mpd-parser": "0.10.0", + "mux.js": "5.6.1", + "url-toolkit": "^2.1.3", + "video.js": "^6.8.0 || ^7.0.0" + } + }, + "@videojs/vhs-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz", + "integrity": "sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "global": "^4.3.2", + "url-toolkit": "^2.1.6" + } + }, + "m3u8-parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.4.0.tgz", + "integrity": "sha512-iH2AygTFILtato+XAgnoPYzLHM4R3DjATj7Ozbk7EHdB2XoLF2oyOUguM7Kc4UVHbQHHL/QPaw98r7PbWzG0gg==", + "requires": { + "global": "^4.3.2" + } + }, + "mpd-parser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.10.0.tgz", + "integrity": "sha512-eIqkH/2osPr7tIIjhRmDWqm2wdJ7Q8oPfWvdjealzsLV2D2oNe0a0ae2gyYYs1sw5e5hdssDA2V6Sz8MW+Uvvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "@videojs/vhs-utils": "^1.1.0", + "global": "^4.3.2", + "xmldom": "^0.1.27" + } + } + } + }, + "videojs-font": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", + "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==" + }, + "videojs-vtt.js": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.2.tgz", + "integrity": "sha512-kEo4hNMvu+6KhPvVYPKwESruwhHC3oFis133LwhXHO9U7nRnx0RiJYMiqbgwjgazDEXHR6t8oGJiHM6wq5XlAw==", + "requires": { + "global": "^4.3.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true + }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + } + } +} diff --git a/build/javascript/package.json b/build/javascript/package.json new file mode 100644 index 000000000..ec2709956 --- /dev/null +++ b/build/javascript/package.json @@ -0,0 +1,40 @@ +{ + "name": "owncast-dependencies", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "@joeattardi/emoji-button": "^4.2.0", + "@justinribeiro/lite-youtube": "^0.9.0", + "@videojs/http-streaming": "^2.2.0", + "@videojs/themes": "^1.0.0", + "htm": "^3.0.4", + "preact": "^10.5.3", + "showdown": "^1.9.1", + "tailwindcss": "^1.8.10", + "video.js": "^7.9.6" + }, + "devDependencies": { + "snowpack": "^2.12.1" + }, + "snowpack": { + "install": [ + "video.js/dist/video.min.js", + "@videojs/themes/fantasy/*", + "@videojs/http-streaming/dist/videojs-http-streaming.min.js", + "video.js/dist/video-js.min.css", + "@joeattardi/emoji-button", + "@justinribeiro/lite-youtube", + "htm", + "preact", + "showdown", + "tailwindcss/dist/tailwind.min.css" + ] + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "npm install && npx snowpack install && cp -R web_modules ../../webroot/js" + }, + "author": "", + "license": "ISC" +} diff --git a/config/config.go b/config/config.go index 1f80003b2..8ed64f610 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,7 @@ import ( "errors" "io/ioutil" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/utils" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) @@ -15,15 +15,12 @@ var _default config type config struct { ChatDatabaseFilePath string `yaml:"chatDatabaseFile"` - DisableWebFeatures bool `yaml:"disableWebFeatures"` EnableDebugFeatures bool `yaml:"-"` FFMpegPath string `yaml:"ffmpegPath"` Files files `yaml:"files"` InstanceDetails InstanceDetails `yaml:"instanceDetails"` - PrivateHLSPath string `yaml:"privateHLSPath"` - PublicHLSPath string `yaml:"publicHLSPath"` S3 S3 `yaml:"s3"` - VersionInfo string `yaml:"-"` + VersionInfo string `yaml:"-"` // For storing the version/build number VideoSettings videoSettings `yaml:"videoSettings"` WebServerPort int `yaml:"webServerPort"` YP yp `yaml:"yp"` @@ -158,22 +155,6 @@ func (c *config) GetVideoSegmentSecondsLength() int { return _default.GetVideoSegmentSecondsLength() } -func (c *config) GetPublicHLSSavePath() string { - if c.PublicHLSPath != "" { - return c.PublicHLSPath - } - - return _default.PublicHLSPath -} - -func (c *config) GetPrivateHLSSavePath() string { - if c.PrivateHLSPath != "" { - return c.PrivateHLSPath - } - - return _default.PrivateHLSPath -} - func (c *config) GetPublicWebServerPort() int { if c.WebServerPort != 0 { return c.WebServerPort diff --git a/config/constants.go b/config/constants.go new file mode 100644 index 000000000..66bded1b7 --- /dev/null +++ b/config/constants.go @@ -0,0 +1,12 @@ +package config + +import "path/filepath" + +const ( + WebRoot = "webroot" + PrivateHLSStoragePath = "hls" +) + +var ( + PublicHLSStoragePath = filepath.Join(WebRoot, "hls") +) diff --git a/config/defaults.go b/config/defaults.go index 66a654e41..87d170eb4 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -12,8 +12,6 @@ func getDefaults() config { defaults.FFMpegPath = getDefaultFFMpegPath() defaults.VideoSettings.ChunkLengthInSeconds = 4 defaults.Files.MaxNumberInPlaylist = 5 - defaults.PublicHLSPath = "webroot/hls" - defaults.PrivateHLSPath = "hls" defaults.VideoSettings.OfflineContent = "static/offline.m4v" defaults.InstanceDetails.ExtraInfoFile = "/static/content.md" defaults.YP.Enabled = false diff --git a/controllers/admin/changeStreamKey.go b/controllers/admin/changeStreamKey.go index 394998858..9af9da6ae 100644 --- a/controllers/admin/changeStreamKey.go +++ b/controllers/admin/changeStreamKey.go @@ -4,8 +4,8 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/controllers" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/controllers" log "github.com/sirupsen/logrus" ) diff --git a/controllers/admin/disconnect.go b/controllers/admin/disconnect.go index 08fbc4f30..7b1e162a3 100644 --- a/controllers/admin/disconnect.go +++ b/controllers/admin/disconnect.go @@ -3,10 +3,10 @@ package admin import ( "net/http" - "github.com/gabek/owncast/controllers" - "github.com/gabek/owncast/core" + "github.com/owncast/owncast/controllers" + "github.com/owncast/owncast/core" - "github.com/gabek/owncast/core/rtmp" + "github.com/owncast/owncast/core/rtmp" ) // DisconnectInboundConnection will force-disconnect an inbound stream diff --git a/controllers/admin/hardware.go b/controllers/admin/hardware.go new file mode 100644 index 000000000..9f2995a73 --- /dev/null +++ b/controllers/admin/hardware.go @@ -0,0 +1,16 @@ +package admin + +import ( + "encoding/json" + "net/http" + + "github.com/owncast/owncast/metrics" +) + +// GetHardwareStats will return hardware utilization over time +func GetHardwareStats(w http.ResponseWriter, r *http.Request) { + metrics := metrics.Metrics + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(metrics) +} diff --git a/controllers/admin/inboundBroadcasterDetails.go b/controllers/admin/inboundBroadcasterDetails.go index fa20198f3..83581e19d 100644 --- a/controllers/admin/inboundBroadcasterDetails.go +++ b/controllers/admin/inboundBroadcasterDetails.go @@ -4,16 +4,13 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/controllers" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/router/middleware" + "github.com/owncast/owncast/controllers" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/models" ) // GetInboundBroadasterDetails gets the details of the inbound broadcaster func GetInboundBroadasterDetails(w http.ResponseWriter, r *http.Request) { - middleware.EnableCors(&w) - broadcaster := core.GetBroadcaster() if broadcaster == nil { controllers.WriteSimpleResponse(w, false, "no broadcaster connected") diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go index 39771314c..9c99aa9ea 100644 --- a/controllers/admin/serverConfig.go +++ b/controllers/admin/serverConfig.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/config" + "github.com/owncast/owncast/config" ) // GetServerConfig gets the config details of the server diff --git a/controllers/admin/viewers.go b/controllers/admin/viewers.go index fe88e59a5..77fa0ad67 100644 --- a/controllers/admin/viewers.go +++ b/controllers/admin/viewers.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/metrics" + "github.com/owncast/owncast/metrics" ) // GetViewersOverTime will return the number of viewers at points in time diff --git a/controllers/chat.go b/controllers/chat.go index 38b1fafdd..f581bd338 100644 --- a/controllers/chat.go +++ b/controllers/chat.go @@ -4,9 +4,9 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/router/middleware" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/router/middleware" ) //GetChatMessages gets all of the chat messages diff --git a/controllers/config.go b/controllers/config.go index 19685a2b2..114f253bf 100644 --- a/controllers/config.go +++ b/controllers/config.go @@ -4,8 +4,8 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/router/middleware" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/router/middleware" ) //GetWebConfig gets the status of the server diff --git a/controllers/controllers.go b/controllers/controllers.go index bd9c75634..747793b2f 100644 --- a/controllers/controllers.go +++ b/controllers/controllers.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/models" ) type j map[string]interface{} diff --git a/controllers/emoji.go b/controllers/emoji.go index 3c2f64af1..b87f59780 100644 --- a/controllers/emoji.go +++ b/controllers/emoji.go @@ -8,7 +8,8 @@ import ( "path/filepath" "strings" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" log "github.com/sirupsen/logrus" ) @@ -20,7 +21,7 @@ const emojiPath = "/img/emoji" // Relative to webroot func GetCustomEmoji(w http.ResponseWriter, r *http.Request) { emojiList := make([]models.CustomEmoji, 0) - fullPath := filepath.Join("webroot", emojiPath) + fullPath := filepath.Join(config.WebRoot, emojiPath) files, err := ioutil.ReadDir(fullPath) if err != nil { log.Errorln(err) diff --git a/controllers/index.go b/controllers/index.go index 32f8edc41..18011fb40 100644 --- a/controllers/index.go +++ b/controllers/index.go @@ -5,15 +5,16 @@ import ( "net/http" "net/url" "path" + "path/filepath" "strings" "text/template" log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/router/middleware" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/router/middleware" + "github.com/owncast/owncast/utils" ) type MetadataPage struct { @@ -30,13 +31,6 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { isIndexRequest := r.URL.Path == "/" || r.URL.Path == "/index.html" - // Reject requests for the web UI if it's disabled. - if isIndexRequest && config.Config.DisableWebFeatures { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("404 - y u ask 4 this? If this is an error let us know: https://github.com/owncast/owncast/issues")) - return - } - // For search engine bots and social scrapers return a special // server-rendered page. if utils.IsUserAgentABot(r.UserAgent()) && isIndexRequest { @@ -60,7 +54,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { // Set a cache control max-age header middleware.SetCachingHeaders(w, r) - http.ServeFile(w, r, path.Join("webroot", r.URL.Path)) + http.ServeFile(w, r, path.Join(config.WebRoot, r.URL.Path)) } // Return a basic HTML page with server-rendered metadata from the config file @@ -75,7 +69,7 @@ func handleScraperMetadataPage(w http.ResponseWriter, r *http.Request) { // If the thumbnail does not exist or we're offline then just use the logo image var thumbnailURL string - if status.Online && utils.DoesFileExists("webroot/thumbnail.jpg") { + if status.Online && utils.DoesFileExists(filepath.Join(config.WebRoot, "thumbnail.jpg")) { thumbnail, err := url.Parse(fmt.Sprintf("http://%s%s", r.Host, "/thumbnail.jpg")) if err != nil { log.Errorln(err) diff --git a/controllers/status.go b/controllers/status.go index 78e62f1dc..14aa88624 100644 --- a/controllers/status.go +++ b/controllers/status.go @@ -4,8 +4,8 @@ import ( "encoding/json" "net/http" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/router/middleware" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/router/middleware" ) //GetStatus gets the status of the server diff --git a/core/chat/chat.go b/core/chat/chat.go index 69ddb6c4d..4f5698504 100644 --- a/core/chat/chat.go +++ b/core/chat/chat.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/models" ) //Setup sets up the chat server diff --git a/core/chat/client.go b/core/chat/client.go index de37c4a58..74868e6ce 100644 --- a/core/chat/client.go +++ b/core/chat/client.go @@ -9,8 +9,8 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/net/websocket" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" "github.com/teris-io/shortid" ) diff --git a/core/chat/persistence.go b/core/chat/persistence.go index 9795eaf27..317ef4664 100644 --- a/core/chat/persistence.go +++ b/core/chat/persistence.go @@ -5,9 +5,9 @@ import ( "os" "time" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" _ "github.com/mattn/go-sqlite3" log "github.com/sirupsen/logrus" ) diff --git a/core/chat/server.go b/core/chat/server.go index 1ff81c39a..ca50455ae 100644 --- a/core/chat/server.go +++ b/core/chat/server.go @@ -8,8 +8,8 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/net/websocket" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" ) var ( diff --git a/core/chatListener.go b/core/chatListener.go index e9328c932..7857315fe 100644 --- a/core/chatListener.go +++ b/core/chatListener.go @@ -3,8 +3,8 @@ package core import ( "errors" - "github.com/gabek/owncast/core/chat" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/models" ) //ChatListenerImpl the implementation of the chat client diff --git a/core/core.go b/core/core.go index b0c304549..918842c5d 100644 --- a/core/core.go +++ b/core/core.go @@ -3,17 +3,18 @@ package core import ( "os" "path" + "path/filepath" "strconv" "time" log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core/chat" - "github.com/gabek/owncast/core/ffmpeg" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" - "github.com/gabek/owncast/yp" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/core/ffmpeg" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" + "github.com/owncast/owncast/yp" ) var ( @@ -56,8 +57,8 @@ func Start() error { func createInitialOfflineState() error { // Provide default files - if !utils.DoesFileExists("webroot/thumbnail.jpg") { - if err := utils.Copy("static/logo.png", "webroot/thumbnail.jpg"); err != nil { + if !utils.DoesFileExists(filepath.Join(config.WebRoot, "thumbnail.jpg")) { + if err := utils.Copy("static/logo.png", filepath.Join(config.WebRoot, "thumbnail.jpg")); err != nil { return err } } @@ -93,22 +94,22 @@ func resetDirectories() { log.Trace("Resetting file directories to a clean slate.") // Wipe the public, web-accessible hls data directory - os.RemoveAll(config.Config.GetPublicHLSSavePath()) - os.RemoveAll(config.Config.GetPrivateHLSSavePath()) - os.MkdirAll(config.Config.GetPublicHLSSavePath(), 0777) - os.MkdirAll(config.Config.GetPrivateHLSSavePath(), 0777) + os.RemoveAll(config.PublicHLSStoragePath) + os.RemoveAll(config.PrivateHLSStoragePath) + os.MkdirAll(config.PublicHLSStoragePath, 0777) + os.MkdirAll(config.PrivateHLSStoragePath, 0777) // Remove the previous thumbnail - os.Remove("webroot/thumbnail.jpg") + os.Remove(filepath.Join(config.WebRoot, "thumbnail.jpg")) // Create private hls data dirs if len(config.Config.VideoSettings.StreamQualities) != 0 { for index := range config.Config.VideoSettings.StreamQualities { - os.MkdirAll(path.Join(config.Config.GetPrivateHLSSavePath(), strconv.Itoa(index)), 0777) - os.MkdirAll(path.Join(config.Config.GetPublicHLSSavePath(), strconv.Itoa(index)), 0777) + os.MkdirAll(path.Join(config.PrivateHLSStoragePath, strconv.Itoa(index)), 0777) + os.MkdirAll(path.Join(config.PublicHLSStoragePath, strconv.Itoa(index)), 0777) } } else { - os.MkdirAll(path.Join(config.Config.GetPrivateHLSSavePath(), strconv.Itoa(0)), 0777) - os.MkdirAll(path.Join(config.Config.GetPublicHLSSavePath(), strconv.Itoa(0)), 0777) + os.MkdirAll(path.Join(config.PrivateHLSStoragePath, strconv.Itoa(0)), 0777) + os.MkdirAll(path.Join(config.PublicHLSStoragePath, strconv.Itoa(0)), 0777) } } diff --git a/core/ffmpeg/ffmpeg.go b/core/ffmpeg/ffmpeg.go index 21dc8010b..5671ec6cb 100644 --- a/core/ffmpeg/ffmpeg.go +++ b/core/ffmpeg/ffmpeg.go @@ -1,7 +1,7 @@ package ffmpeg import ( - "github.com/gabek/owncast/config" + "github.com/owncast/owncast/config" ) //ShowStreamOfflineState generates and shows the stream's offline state diff --git a/core/ffmpeg/thumbnailGenerator.go b/core/ffmpeg/thumbnailGenerator.go index 892748d91..b98c7f41b 100644 --- a/core/ffmpeg/thumbnailGenerator.go +++ b/core/ffmpeg/thumbnailGenerator.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" + "github.com/owncast/owncast/config" ) //StartThumbnailGenerator starts generating thumbnails @@ -39,8 +39,8 @@ func StartThumbnailGenerator(chunkPath string, variantIndex int) { func fireThumbnailGenerator(chunkPath string, variantIndex int) error { // JPG takes less time to encode than PNG - outputFile := path.Join("webroot", "thumbnail.jpg") - previewGifFile := path.Join("webroot", "preview.gif") + outputFile := path.Join(config.WebRoot, "thumbnail.jpg") + previewGifFile := path.Join(config.WebRoot, "preview.gif") framePath := path.Join(chunkPath, strconv.Itoa(variantIndex)) files, err := ioutil.ReadDir(framePath) diff --git a/core/ffmpeg/transcoder.go b/core/ffmpeg/transcoder.go index d320cdb96..98f724887 100644 --- a/core/ffmpeg/transcoder.go +++ b/core/ffmpeg/transcoder.go @@ -10,8 +10,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/teris-io/shortid" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/utils" ) var _commandExec *exec.Cmd @@ -188,15 +188,15 @@ func NewTranscoder() Transcoder { var outputPath string if config.Config.S3.Enabled { // Segments are not available via the local HTTP server - outputPath = config.Config.GetPrivateHLSSavePath() + outputPath = config.PrivateHLSStoragePath } else { // Segments are available via the local HTTP server - outputPath = config.Config.GetPublicHLSSavePath() + outputPath = config.PublicHLSStoragePath } transcoder.segmentOutputPath = outputPath // Playlists are available via the local HTTP server - transcoder.playlistOutputPath = config.Config.GetPublicHLSSavePath() + transcoder.playlistOutputPath = config.PublicHLSStoragePath transcoder.input = utils.GetTemporaryPipePath() transcoder.segmentLengthSeconds = config.Config.GetVideoSegmentSecondsLength() diff --git a/core/playlist/monitor.go b/core/playlist/monitor.go index cba85dc89..264edc1c3 100644 --- a/core/playlist/monitor.go +++ b/core/playlist/monitor.go @@ -12,9 +12,9 @@ import ( "github.com/radovskyb/watcher" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" ) var ( @@ -26,7 +26,7 @@ var ( func StartVideoContentMonitor(storage models.ChunkStorageProvider) error { _storage = storage - pathToMonitor := config.Config.GetPrivateHLSSavePath() + pathToMonitor := config.PrivateHLSStoragePath // Create at least one structure to store the segments for the different stream variants variants = make([]models.Variant, len(config.Config.VideoSettings.StreamQualities)) @@ -63,11 +63,9 @@ func StartVideoContentMonitor(storage models.ChunkStorageProvider) error { continue } - // fmt.Println(event.Op, relativePath) - // Handle updates to the master playlist by copying it to webroot - if relativePath == path.Join(config.Config.GetPrivateHLSSavePath(), "stream.m3u8") { - utils.Copy(event.Path, path.Join(config.Config.GetPublicHLSSavePath(), "stream.m3u8")) + if relativePath == path.Join(config.PrivateHLSStoragePath, "stream.m3u8") { + utils.Copy(event.Path, path.Join(config.PublicHLSStoragePath, "stream.m3u8")) } else if filepath.Ext(event.Path) == ".m3u8" { // Handle updates to playlists, but not the master playlist @@ -82,7 +80,7 @@ func StartVideoContentMonitor(storage models.ChunkStorageProvider) error { newObjectPathChannel := make(chan string, 1) go func() { - newObjectPath, err := storage.Save(path.Join(config.Config.GetPrivateHLSSavePath(), segment.RelativeUploadPath), 0) + newObjectPath, err := storage.Save(path.Join(config.PrivateHLSStoragePath, segment.RelativeUploadPath), 0) if err != nil { log.Errorln("failed to save the file to the chunk storage.", err) } @@ -155,5 +153,5 @@ func updateVariantPlaylist(fullPath string) error { playlistString := string(playlistBytes) playlistString = _storage.GenerateRemotePlaylist(playlistString, variant) - return WritePlaylist(playlistString, path.Join(config.Config.GetPublicHLSSavePath(), relativePath)) + return WritePlaylist(playlistString, path.Join(config.PublicHLSStoragePath, relativePath)) } diff --git a/core/rtmp/rtmp.go b/core/rtmp/rtmp.go index 53703b8a6..3e8303914 100644 --- a/core/rtmp/rtmp.go +++ b/core/rtmp/rtmp.go @@ -14,11 +14,11 @@ import ( "github.com/nareix/joy5/format/flv/flvio" log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/core/ffmpeg" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/core/ffmpeg" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" "github.com/nareix/joy5/format/rtmp" ) diff --git a/core/rtmp/utils.go b/core/rtmp/utils.go index 00580c01f..a3344bc3c 100644 --- a/core/rtmp/utils.go +++ b/core/rtmp/utils.go @@ -6,7 +6,7 @@ import ( "fmt" "regexp" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/models" "github.com/nareix/joy5/format/flv/flvio" ) diff --git a/core/stats.go b/core/stats.go index 388d2f049..d4cc60dba 100644 --- a/core/stats.go +++ b/core/stats.go @@ -10,9 +10,9 @@ import ( log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" ) const ( diff --git a/core/status.go b/core/status.go index 9a517182e..986ad61b9 100644 --- a/core/status.go +++ b/core/status.go @@ -3,10 +3,10 @@ package core import ( "time" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core/ffmpeg" - "github.com/gabek/owncast/models" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core/ffmpeg" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" ) //GetStatus gets the status of the system @@ -33,9 +33,9 @@ func SetStreamAsConnected() { _stats.LastConnectTime = utils.NullTime{time.Now(), true} _stats.LastDisconnectTime = utils.NullTime{time.Now(), false} - chunkPath := config.Config.GetPublicHLSSavePath() + chunkPath := config.PublicHLSStoragePath if usingExternalStorage { - chunkPath = config.Config.GetPrivateHLSSavePath() + chunkPath = config.PrivateHLSStoragePath } if _yp != nil { diff --git a/core/storage.go b/core/storage.go index 57c6dee5c..8b94afa73 100644 --- a/core/storage.go +++ b/core/storage.go @@ -1,9 +1,9 @@ package core import ( - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core/playlist" - "github.com/gabek/owncast/core/storageproviders" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core/playlist" + "github.com/owncast/owncast/core/storageproviders" ) var ( diff --git a/core/storageproviders/s3Storage.go b/core/storageproviders/s3Storage.go index 77d61a7a5..92a33876c 100644 --- a/core/storageproviders/s3Storage.go +++ b/core/storageproviders/s3Storage.go @@ -13,8 +13,8 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/models" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/models" ) //S3Storage is the s3 implementation of the ChunkStorageProvider @@ -28,7 +28,7 @@ type S3Storage struct { s3Bucket string s3AccessKey string s3Secret string - s3ACL string + s3ACL string } //Setup sets up the s3 storage for saving the video to s3 @@ -94,7 +94,7 @@ func (s *S3Storage) GenerateRemotePlaylist(playlist string, variant models.Varia if fullRemotePath == nil { line = "" } else if s.s3ServingEndpoint != "" { - line = fmt.Sprintf("%s/%s/%s", s.s3ServingEndpoint, config.Config.GetPrivateHLSSavePath(), fullRemotePath.RelativeUploadPath) + line = fmt.Sprintf("%s/%s/%s", s.s3ServingEndpoint, config.PrivateHLSStoragePath, fullRemotePath.RelativeUploadPath) } else { line = fullRemotePath.RemoteID } diff --git a/doc b/doc deleted file mode 160000 index 54a0ee139..000000000 --- a/doc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 54a0ee13964c70585c24a9b5869604373faaa926 diff --git a/go.mod b/go.mod index b10a47304..ad22261ed 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/gabek/owncast +module github.com/owncast/owncast go 1.14 @@ -11,7 +11,6 @@ require ( github.com/mssola/user_agent v0.5.2 github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 github.com/radovskyb/watcher v1.0.7 github.com/shirou/gopsutil v2.20.7+incompatible github.com/sirupsen/logrus v1.6.0 diff --git a/go.sum b/go.sum index 28ccd019c..679481303 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.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/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 h1:CskT+S6Ay54OwxBGB0R3Rsx4Muto6UnEYTyKJbyRIAI= -github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w= diff --git a/main.go b/main.go index f4db04942..2053e036a 100644 --- a/main.go +++ b/main.go @@ -7,10 +7,10 @@ import ( "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/core" - "github.com/gabek/owncast/metrics" - "github.com/gabek/owncast/router" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/core" + "github.com/owncast/owncast/metrics" + "github.com/owncast/owncast/router" ) // the following are injected at build-time diff --git a/metrics/alerting.go b/metrics/alerting.go index b8e7107d0..08994cbeb 100644 --- a/metrics/alerting.go +++ b/metrics/alerting.go @@ -6,12 +6,14 @@ import ( const maxCPUAlertingThresholdPCT = 95 const maxRAMAlertingThresholdPCT = 95 +const maxDiskAlertingThresholdPCT = 95 -const alertingError = "The %s utilization of %d%% is higher than the alerting threshold of %d%%. This can cause issues with video generation and delivery. Please visit the documentation at http://owncast.online/docs/troubleshooting/ to help troubleshoot this issue." +const alertingError = "The %s utilization of %d%% can cause issues with video generation and delivery. Please visit the documentation at http://owncast.online/docs/troubleshooting/ to help troubleshoot this issue." func handleAlerting() { handleCPUAlerting() handleRAMAlerting() + handleDiskAlerting() } func handleCPUAlerting() { @@ -21,7 +23,7 @@ func handleCPUAlerting() { avg := recentAverage(Metrics.CPUUtilizations) if avg > maxCPUAlertingThresholdPCT { - log.Errorf(alertingError, "CPU", avg, maxCPUAlertingThresholdPCT) + log.Errorf(alertingError, "CPU", maxCPUAlertingThresholdPCT) } } @@ -32,7 +34,19 @@ func handleRAMAlerting() { avg := recentAverage(Metrics.RAMUtilizations) if avg > maxRAMAlertingThresholdPCT { - log.Errorf(alertingError, "memory", avg, maxRAMAlertingThresholdPCT) + log.Errorf(alertingError, "memory", maxRAMAlertingThresholdPCT) + } +} + +func handleDiskAlerting() { + if len(Metrics.DiskUtilizations) < 2 { + return + } + + avg := recentAverage(Metrics.DiskUtilizations) + + if avg > maxDiskAlertingThresholdPCT { + log.Errorf(alertingError, "disk", maxRAMAlertingThresholdPCT) } } diff --git a/metrics/hardware.go b/metrics/hardware.go index 4c0687c21..92ee9a175 100644 --- a/metrics/hardware.go +++ b/metrics/hardware.go @@ -4,6 +4,7 @@ import ( "time" "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/mem" ) @@ -33,3 +34,15 @@ func collectRAMUtilization() { metricValue := timestampedValue{time.Now(), int(memoryUsage.UsedPercent)} Metrics.RAMUtilizations = append(Metrics.RAMUtilizations, metricValue) } + +func collectDiskUtilization() { + path := "./" + diskUse, _ := disk.Usage(path) + + if len(Metrics.DiskUtilizations) > maxCollectionValues { + Metrics.DiskUtilizations = Metrics.DiskUtilizations[1:] + } + + metricValue := timestampedValue{time.Now(), int(diskUse.UsedPercent)} + Metrics.DiskUtilizations = append(Metrics.DiskUtilizations, metricValue) +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 4c00594b7..a32345a6d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -5,21 +5,25 @@ import ( ) // How often we poll for updates -const metricsPollingInterval = 15 * time.Second +const metricsPollingInterval = 1 * time.Minute -type metrics struct { - CPUUtilizations []timestampedValue - RAMUtilizations []timestampedValue - Viewers []timestampedValue +// CollectedMetrics stores different collected + timestamped values +type CollectedMetrics struct { + CPUUtilizations []timestampedValue `json:"cpu"` + RAMUtilizations []timestampedValue `json:"memory"` + DiskUtilizations []timestampedValue `json:"disk"` + + Viewers []timestampedValue `json:"-"` } // Metrics is the shared Metrics instance -var Metrics *metrics +var Metrics *CollectedMetrics // Start will begin the metrics collection and alerting func Start() { - Metrics = new(metrics) - startViewerCollectionMetrics() + Metrics = new(CollectedMetrics) + go startViewerCollectionMetrics() + handlePolling() for range time.Tick(metricsPollingInterval) { handlePolling() @@ -30,6 +34,7 @@ func handlePolling() { // Collect hardware stats collectCPUUtilization() collectRAMUtilization() + collectDiskUtilization() // Alerting handleAlerting() diff --git a/metrics/viewers.go b/metrics/viewers.go index 6acc0abc5..0d03da257 100644 --- a/metrics/viewers.go +++ b/metrics/viewers.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/gabek/owncast/core" + "github.com/owncast/owncast/core" ) // How often we poll for updates diff --git a/models/stats.go b/models/stats.go index da6723e66..bd543ac33 100644 --- a/models/stats.go +++ b/models/stats.go @@ -3,7 +3,7 @@ package models import ( "time" - "github.com/gabek/owncast/utils" + "github.com/owncast/owncast/utils" ) //Stats holds the stats for the system diff --git a/models/status.go b/models/status.go index 053f337be..47f6f77f2 100644 --- a/models/status.go +++ b/models/status.go @@ -1,6 +1,6 @@ package models -import "github.com/gabek/owncast/utils" +import "github.com/owncast/owncast/utils" //Status represents the status of the system type Status struct { diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 000000000..833c8a013 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,513 @@ +openapi: 3.0.1 +info: + title: Owncast + description: Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software. + version: '0.0.2' +servers: [] + +tags: + - name: Admin + description: Admin operations requiring authentication. + - name: Chat + description: Endpoints related to the chat interface. + +components: + schemas: + BasicResponse: + type: object + properties: + success: + type: boolean + message: + type: string + InstanceDetails: + type: object + properties: + name: + type: string + title: + type: string + summary: + type: string + description: This is brief summary of whom you are or what the stream is. + logo: + type: object + properties: + large: + type: string + small: + type: string + tags: + type: array + items: + type: string + socialHandles: + type: array + items: + type: object + properties: + platform: + type: string + example: github + url: + type: string + example: http://github.com/owncast/owncast + extraUserInfoFileName: + type: string + description: Path to additional content about the server. + version: + type: string + example: Owncast v0.0.2-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb) + S3: + type: object + properties: + enabled: + type: boolean + endpoint: + type: string + servingEndpoint: + type: string + accessKey: + type: string + secret: + type: string + bucket: + type: string + region: + type: string + acl: + type: string + required: + - enabled + StreamQuality: + type: object + properties: + videoPassthrough: + type: boolean + audioPassthrough: + type: boolean + videoBitrate: + type: integer + audioBitrate: + type: integer + scaledWidth: + type: integer + scaledHeight: + type: integer + framerate: + type: integer + encoderPreset: + type: string + TimestampedValue: + type: object + properties: + time: + type: string + format: date-time + value: + type: integer + + + securitySchemes: + AdminBasicAuth: + type: http + scheme: basic + description: The username for admin basic auth is `admin` and the password is the stream key. + + responses: + BasicResponse: + description: Operation Success/Failure Response + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResponse" + examples: + success: + summary: Operation succeeded. + value: {"success": true, "message": "inbound stream disconnected"} + failure: + summary: Operation failed. + value: {"success": false, "message": "no inbound stream connected"} + +paths: + + /api/config: + get: + summary: Information + description: Get the public information about the server. Adds context to the server, as well as information useful for the user interface. + tags: ["Server"] + responses: + '200': + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/InstanceDetails" + + /api/status: + get: + summary: Current Status + description: This endpoint is used to discover when a server is broadcasting, the number of active viewers as well as other useful information for updating the user interface. + tags: ["Server"] + responses: + '200': + description: "" + content: + application/json: + schema: + type: object + properties: + lastConnectTime: + type: string + nullable: true + format: date-time + overallMaxViewerCount: + type: integer + sessionMaxViewerCount: + type: integer + online: + type: boolean + viewerCount: + type: integer + lastDisconnectTime: + type: string + nullable: true + format: date-time + examples: + online: + value: + lastConnectTime: "2020-10-03T21:36:22-05:00" + lastDisconnectTime: null + online: true + overallMaxViewerCount: 420 + sessionMaxViewerCount: 12 + viewerCount: 7 + + /api/chat: + get: + summary: Historical Chat Messages + description: Used to get all chat messages prior to connecting to the websocket. + tags: ["Chat"] + responses: + '200': + description: "" + content: + application/json: + schema: + type: array + items: + type: object + properties: + author: + type: string + description: Username of the chat message poster. + body: + type: string + description: Escaped HTML of the chat message content. + image: + type: string + description: URL of the chat user avatar. + id: + type: string + description: Unique ID of the chat message. + visible: + type: boolean + description: "TODO" + timestamp: + type: string + format: date-time + + /api/yp: + get: + summary: Yellow Pages Information + description: Information to be used in the Yellow Pages service, a global directory of Owncast servers. + tags: ["Server"] + responses: + '200': + description: "" + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: + type: string + logo: + type: string + nsfw: + type: boolean + tags: + type: array + items: + type: string + online: + type: boolean + viewerCount: + type: integer + overallMaxViewerCount: + type: integer + sessionMaxViewerCount: + type: integer + lastConnectTime: + type: string + nullable: true + format: date-time + + /api/emoji: + get: + summary: Get Custom Emoji + description: Get a list of custom emoji that are supported in chat. + tags: ["Chat"] + responses: + '200': + description: "" + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + description: The name of the Emoji + emoji: + type: string + description: The relative path to the Emoji image file + examples: + default: + value: + items: + - name: nicolas_cage_party + emoji: /img/emoji/nicolas_cage_party.gif + - name: parrot + emoji: /img/emoji/parrot.gif + + /api/admin/broadcaster: + get: + summary: "Broadcaster Details" + tags: ["Admin"] + security: + - AdminBasicAuth: [] + responses: + '200': + description: Connected Broadcaster Details + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + message: + type: string + broadcaster: + type: object + properties: + remoteAddr: + type: string + time: + type: string + format: date-time + streamDetails: + type: object + properties: + width: + type: integer + height: + type: integer + frameRate: + type: integer + videoBitrate: + type: integer + videoCodec: + type: string + audioBitrate: + type: integer + audioCodec: + type: string + encoder: + type: string + examples: + connected: + summary: "Broadcaster Connected" + value: + success: true + message: "" + broadcaster: + remoteAddr: 127.0.0.1 + time: "TODO" + streamDetails: + width: 640 + height: 480 + frameRate: 24 + videoBitrate: 1500 + videoCodec: "todo" + audioBitrate: 256 + audioCodec: "aac" + encoder: "todo" + not-connected: + summary: "Broadcaster Not Connected" + value: + success: false + message: "no broadcaster connected" + + /api/admin/disconnect: + post: + summary: Disconnect Broadcaster + description: Disconnect the active inbound stream, if one exists, and terminate the broadcast. + tags: ["Admin"] + security: + - AdminBasicAuth: [] + responses: + '200': + $ref: "#/components/responses/BasicResponse" + + + /api/admin/changekey: + post: + summary: Update Stream Key + description: Change the stream key in memory, but not in the config file. This will require all broadcasters to be reconfigured to connect again. + tags: ["Admin"] + security: + - AdminBasicAuth: [] + requestBody: + description: "" + required: true + content: + application/json: + schema: + type: object + properties: + key: + type: string + responses: + '200': + description: Stream was disconnected. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + message: + type: string + example: changed + + /api/admin/serverconfig: + get: + summary: Server Configuration + description: Get the current configuration of the Owncast server. + tags: ["Admin"] + security: + - AdminBasicAuth: [] + responses: + '200': + description: "" + content: + application/json: + schema: + type: object + properties: + instanceDetails: + $ref: "#/components/schemas/InstanceDetails" + ffmpegPath: + type: string + webServerPort: + type: integer + s3: + $ref: "#/components/schemas/S3" + videoSettings: + type: object + properties: + videoQualityVariants: + type: array + items: + $ref: "#/components/schemas/StreamQuality" + segmentLengthSeconds: + type: integer + numberOfPlaylistItems: + type: integer + + /api/admin/viewersOverTime: + get: + summary: Viewers Over Time + description: Get the tracked viewer count over the collected period. + tags: ["Admin"] + security: + - AdminBasicAuth: [] + responses: + '200': + description: "" + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TimestampedValue" + examples: + default: + value: + - time: "2020-10-03T21:41:00.381996-05:00" + value: 50 + - time: "2020-10-03T21:42:00.381996-05:00" + value: 52 + + + + + /api/admin/hardwarestats: + get: + summary: Hardware Stats + description: "Get the CPU, Memory and Disk utilization levels over the collected period." + tags: ["Admin"] + security: + - AdminBasicAuth: [] + responses: + '200': + description: "" + content: + application/json: + schema: + type: object + properties: + cpu: + type: array + items: + $ref: "#/components/schemas/TimestampedValue" + memory: + type: array + items: + $ref: "#/components/schemas/TimestampedValue" + disk: + type: array + items: + $ref: "#/components/schemas/TimestampedValue" + examples: + default: + value: + cpu: + - time: "2020-10-03T21:41:00.381996-05:00" + value: 23 + - time: "2020-10-03T21:42:00.381996-05:00" + value: 27 + - time: "2020-10-03T21:43:00.381996-05:00" + value: 22 + memory: + - time: "2020-10-03T21:41:00.381996-05:00" + value: 65 + - time: "2020-10-03T21:42:00.381996-05:00" + value: 66 + - time: "2020-10-03T21:43:00.381996-05:00" + value: 72 + disk: + - time: "2020-10-03T21:41:00.381996-05:00" + value: 11 + - time: "2020-10-03T21:42:00.381996-05:00" + value: 11 + - time: "2020-10-03T21:43:00.381996-05:00" + value: 11 diff --git a/router/middleware/auth.go b/router/middleware/auth.go index e168b0ac9..a3298a0b6 100644 --- a/router/middleware/auth.go +++ b/router/middleware/auth.go @@ -4,7 +4,7 @@ import ( "crypto/subtle" "net/http" - "github.com/gabek/owncast/config" + "github.com/owncast/owncast/config" log "github.com/sirupsen/logrus" ) @@ -13,11 +13,24 @@ import ( func RequireAdminAuth(handler http.HandlerFunc) http.HandlerFunc { username := "admin" password := config.Config.VideoSettings.StreamingKey + realm := "Owncast Authenticated Request" return func(w http.ResponseWriter, r *http.Request) { + // The following line is kind of a work around. + // If you want HTTP Basic Auth + Cors it requires _explicit_ origins to be provided in the + // Access-Control-Allow-Origin header. So we just pull out the origin header and specify it. + // If we want to lock down admin APIs to not be CORS accessible for anywhere, this is where we would do that. + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") + + // For request needing CORS, send a 200. + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } user, pass, ok := r.BasicAuth() - realm := "Owncast Authenticated Request" // Failed if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 { diff --git a/router/middleware/caching.go b/router/middleware/caching.go index 9d1eb73ef..bcc79ff1d 100644 --- a/router/middleware/caching.go +++ b/router/middleware/caching.go @@ -57,8 +57,10 @@ func getCacheDurationSecondsForPath(filePath string) int { // This matters most for local hosting of segments for recordings // and not for live or 3rd party storage. return 31557600 + } else if path.Ext(filePath) == ".m3u8" { + return 0 } // Default cache length in seconds - return 30 * 60 + return 30 } diff --git a/router/router.go b/router/router.go index 6f42f5928..0b3b56b0e 100644 --- a/router/router.go +++ b/router/router.go @@ -6,14 +6,14 @@ import ( log "github.com/sirupsen/logrus" - "github.com/gabek/owncast/config" - "github.com/gabek/owncast/controllers" - "github.com/gabek/owncast/controllers/admin" + "github.com/owncast/owncast/config" + "github.com/owncast/owncast/controllers" + "github.com/owncast/owncast/controllers/admin" - "github.com/gabek/owncast/core/chat" - "github.com/gabek/owncast/core/rtmp" - "github.com/gabek/owncast/router/middleware" - "github.com/gabek/owncast/yp" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/core/rtmp" + "github.com/owncast/owncast/router/middleware" + "github.com/owncast/owncast/yp" ) //Start starts the router for the http, ws, and rtmp @@ -30,24 +30,22 @@ func Start() error { // custom emoji supported in the chat http.HandleFunc("/api/emoji", controllers.GetCustomEmoji) - if !config.Config.DisableWebFeatures { - // websocket chat server - go chat.Start() + // websocket chat server + go chat.Start() - // chat rest api - http.HandleFunc("/api/chat", controllers.GetChatMessages) + // chat rest api + http.HandleFunc("/api/chat", controllers.GetChatMessages) - // web config api - http.HandleFunc("/api/config", controllers.GetWebConfig) + // web config api + http.HandleFunc("/api/config", controllers.GetWebConfig) - // chat embed - http.HandleFunc("/embed/chat", controllers.GetChatEmbed) + // chat embed + http.HandleFunc("/embed/chat", controllers.GetChatEmbed) - // video embed - http.HandleFunc("/embed/video", controllers.GetVideoEmbed) + // video embed + http.HandleFunc("/embed/video", controllers.GetVideoEmbed) - http.HandleFunc("/api/yp", yp.GetYPResponse) - } + http.HandleFunc("/api/yp", yp.GetYPResponse) // Authenticated admin requests @@ -66,6 +64,9 @@ func Start() error { // Get viewer count over time http.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(admin.GetViewersOverTime)) + // Get hardware stats + http.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(admin.GetHardwareStats)) + port := config.Config.GetPublicWebServerPort() log.Infof("Web server running on port: %d", port) diff --git a/scripts/build.sh b/scripts/build.sh index a0738d6b0..51ccd280b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -52,7 +52,7 @@ build() { pushd dist/${NAME} >> /dev/null - CGO_ENABLED=1 ~/go/bin/xgo --branch ${GIT_BRANCH} -ldflags "-s -w -X main.GitCommit=${GIT_COMMIT} -X main.BuildVersion=${VERSION} -X main.BuildType=${NAME}" -targets "${OS}/${ARCH}" github.com/gabek/owncast + CGO_ENABLED=1 ~/go/bin/xgo --branch ${GIT_BRANCH} -ldflags "-s -w -X main.GitCommit=${GIT_COMMIT} -X main.BuildVersion=${VERSION} -X main.BuildType=${NAME}" -targets "${OS}/${ARCH}" github.com/owncast/owncast mv owncast-*-${ARCH} owncast zip -r -q -8 ../owncast-$NAME-$VERSION.zip . @@ -76,7 +76,7 @@ git tag -a "v${VERSION}" -m "Release build v${VERSION}" # On macOS open the Github page for new releases so they can be uploaded if test -f "/usr/bin/open"; then - open "https://github.com/gabek/owncast/releases/new" + open "https://github.com/owncast/owncast/releases/new" open dist fi @@ -90,8 +90,8 @@ cd $(git rev-parse --show-toplevel) # Github Packages docker build --build-arg NAME=docker --build-arg VERSION=${VERSION} --build-arg GIT_COMMIT=$GIT_COMMIT -t owncast . -f scripts/Dockerfile-build -docker tag $DOCKER_IMAGE docker.pkg.github.com/gabek/owncast/$DOCKER_IMAGE:$VERSION -docker push docker.pkg.github.com/gabek/owncast/$DOCKER_IMAGE:$VERSION +docker tag $DOCKER_IMAGE docker.pkg.github.com/owncast/owncast/$DOCKER_IMAGE:$VERSION +docker push docker.pkg.github.com/owncast/owncast/$DOCKER_IMAGE:$VERSION # # Dockerhub # You must be authenticated via `docker login` with your Dockerhub credentials first. diff --git a/webroot/index-standalone-chat.html b/webroot/index-standalone-chat.html index 47586a42a..c9bc5acdc 100644 --- a/webroot/index-standalone-chat.html +++ b/webroot/index-standalone-chat.html @@ -3,20 +3,19 @@ - + - - - -
- + + - - @@ -21,7 +17,10 @@
diff --git a/webroot/index.html b/webroot/index.html index 04eb34aba..60d7a55ea 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -1,6 +1,7 @@ - + + Owncast @@ -23,35 +24,25 @@ - - - - - - - - - - - + + + - - - - - - +
@@ -77,7 +68,7 @@

- This Owncast stream requires Javascript to play. + This Owncast stream requires Javascript to play.

diff --git a/webroot/js/app-standalone-chat.js b/webroot/js/app-standalone-chat.js index ffc64bf9b..b57b32255 100644 --- a/webroot/js/app-standalone-chat.js +++ b/webroot/js/app-standalone-chat.js @@ -1,5 +1,5 @@ -import { h, Component } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); import Chat from './components/chat/chat.js'; diff --git a/webroot/js/app-video-only.js b/webroot/js/app-video-only.js index c45734ffe..8fd5e59b8 100644 --- a/webroot/js/app-video-only.js +++ b/webroot/js/app-video-only.js @@ -1,5 +1,5 @@ -import { h, Component } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); import { OwncastPlayer } from './components/player.js'; diff --git a/webroot/js/app.js b/webroot/js/app.js index c47b4a441..e38e178f4 100644 --- a/webroot/js/app.js +++ b/webroot/js/app.js @@ -1,6 +1,7 @@ -import { h, Component } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); +import showdown from '/js/web_modules/showdown.js'; import { OwncastPlayer } from './components/player.js'; import SocialIconsList from './components/social-icons-list.js'; diff --git a/webroot/js/components/chat/chat-input.js b/webroot/js/components/chat/chat-input.js index 5b9725205..0c0ecc4d6 100644 --- a/webroot/js/components/chat/chat-input.js +++ b/webroot/js/components/chat/chat-input.js @@ -1,8 +1,9 @@ -import { h, Component, createRef } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component, createRef } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); -import { EmojiButton } from 'https://unpkg.com/@joeattardi/emoji-button@4.2.0/dist/index.js'; +import { EmojiButton } from '/js/web_modules/@joeattardi/emoji-button.js'; + import ContentEditable, { replaceCaret } from './content-editable.js'; import { generatePlaceholderText, getCaretPosition, convertToText, convertOnPaste } from '../../utils/chat.js'; import { getLocalStorage, setLocalStorage, classNames } from '../../utils/helpers.js'; @@ -147,8 +148,7 @@ export default class ChatInput extends Component { handleMessageInputKeydown(event) { const formField = this.formMessageInput.current; - - let textValue = formField.innerText.trim(); // get this only to count chars + let textValue = formField.textContent; // get this only to count chars const newStates = {}; let numCharsLeft = CHAT_MAX_MESSAGE_LENGTH - textValue.length; const key = event && event.key; @@ -173,7 +173,7 @@ export default class ChatInput extends Component { event.preventDefault(); // value could have been changed, update char count - textValue = formField.innerText.trim(); + textValue = formField.textContent; numCharsLeft = CHAT_MAX_MESSAGE_LENGTH - textValue.length; } } @@ -192,7 +192,7 @@ export default class ChatInput extends Component { handleMessageInputKeyup(event) { const formField = this.formMessageInput.current; - const textValue = formField.innerText.trim(); // get this only to count chars + const textValue = formField.textContent; // get this only to count chars const { key } = event; diff --git a/webroot/js/components/chat/chat.js b/webroot/js/components/chat/chat.js index 4d2bb274d..c88a80947 100644 --- a/webroot/js/components/chat/chat.js +++ b/webroot/js/components/chat/chat.js @@ -1,5 +1,5 @@ -import { h, Component, createRef } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component, createRef } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); import Message from './message.js'; diff --git a/webroot/js/components/chat/content-editable.js b/webroot/js/components/chat/content-editable.js index b8252c195..fa81bea00 100644 --- a/webroot/js/components/chat/content-editable.js +++ b/webroot/js/components/chat/content-editable.js @@ -6,7 +6,7 @@ and here: https://stackoverflow.com/questions/22677931/react-js-onchange-event-for-contenteditable/27255103#27255103 */ -import { Component, createRef, h } from 'https://unpkg.com/preact?module'; +import { h, Component, createRef } from '/js/web_modules/preact.js'; export function replaceCaret(el) { // Place the caret at the end of the element diff --git a/webroot/js/components/chat/message.js b/webroot/js/components/chat/message.js index e208a72dd..f7e69434e 100644 --- a/webroot/js/components/chat/message.js +++ b/webroot/js/components/chat/message.js @@ -1,9 +1,9 @@ -import { h, Component } from 'https://unpkg.com/preact?module'; -import htm from 'https://unpkg.com/htm?module'; +import { h, Component } from '/js/web_modules/preact.js'; +import htm from '/js/web_modules/htm.js'; const html = htm.bind(h); import { messageBubbleColorForString } from '../../utils/user-colors.js'; -import { formatMessageText } from '../../utils/chat.js'; +import { formatMessageText, formatTimestamp } from '../../utils/chat.js'; import { generateAvatar } from '../../utils/helpers.js'; import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js'; @@ -13,9 +13,10 @@ export default class Message extends Component { const { type } = message; if (type === SOCKET_MESSAGE_TYPES.CHAT) { - const { image, author, body } = message; + const { image, author, body, timestamp } = message; const formattedMessage = formatMessageText(body, username); const avatar = image || generateAvatar(author); + const formattedTimestamp = formatTimestamp(timestamp); const authorColor = messageBubbleColorForString(author); const avatarBgColor = { backgroundColor: authorColor }; @@ -35,6 +36,7 @@ export default class Message extends Component {
= 1) { + return `Sent at ${sentAt.toLocaleDateString('en-US', {dateStyle: 'medium'})} at ` + + sentAt.toLocaleTimeString(); + } + + return `Sent at ${sentAt.toLocaleTimeString()}`; +} diff --git a/webroot/js/utils/constants.js b/webroot/js/utils/constants.js index 980bb9ce4..273a41dd6 100644 --- a/webroot/js/utils/constants.js +++ b/webroot/js/utils/constants.js @@ -18,6 +18,7 @@ export const MESSAGE_OFFLINE = 'Stream is offline.'; export const MESSAGE_ONLINE = 'Stream is online.'; export const URL_OWNCAST = 'https://owncast.online'; // used in footer +export const PLAYER_VOLUME = 'owncast_volume'; export const KEY_USERNAME = 'owncast_username'; diff --git a/webroot/js/web_modules/@joeattardi/emoji-button.js b/webroot/js/web_modules/@joeattardi/emoji-button.js new file mode 100644 index 000000000..c71a25309 --- /dev/null +++ b/webroot/js/web_modules/@joeattardi/emoji-button.js @@ -0,0 +1,3 @@ +!function(e,o){void 0===o&&(o={});var n=o.insertAt;if(e&&"undefined"!=typeof document){var i=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css","top"===n&&i.firstChild?i.insertBefore(a,i.firstChild):i.appendChild(a),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(document.createTextNode(e));}}('@keyframes show {\n 0% {\n opacity: 0;\n transform: scale3d(0.8, 0.8, 0.8);\n }\n\n 50% {\n transform: scale3d(1.05, 1.05, 1.05);\n }\n\n 100% {\n transform: scale3d(1, 1, 1);\n }\n}\n\n@keyframes hide {\n 0% {\n opacity: 1;\n transform: scale3d(1, 1, 1);\n }\n\n 100% {\n opacity: 0;\n transform: scale3d(0.8, 0.8, 0.8);\n }\n}\n\n@keyframes grow {\n 0% {\n opacity: 0;\n transform: scale3d(0.8, 0.8, 0.8); \n }\n\n 100% { \n opacity: 1;\n transform: scale3d(1, 1, 1); \n }\n}\n\n@keyframes shrink {\n 0% { \n opacity: 1;\n transform: scale3d(1, 1, 1);\n }\n\n 100% { \n opacity: 0;\n transform: scale3d(0.8, 0.8, 0.8); \n }\n}\n\n@keyframes fade-in {\n 0% { opacity: 0; }\n 100% { opacity: 1; }\n}\n\n@keyframes fade-out {\n 0% { opacity: 1; }\n 100% { opacity: 0; }\n}\n\n.emoji-picker {\n --animation-duration: 0.2s;\n --animation-easing: ease-in-out;\n\n --emoji-size: 1.8em;\n --emoji-size-multiplier: 1.5;\n --emoji-preview-size: 2em;\n --emoji-per-row: 8;\n --row-count: 6;\n\n --content-height: calc((var(--emoji-size) * var(--emoji-size-multiplier)) * var(--row-count) + var(--category-name-size) + var(--category-button-height) + 0.5em);\n\n --category-name-size: 0.85em;\n\n --category-button-height: 2em;\n --category-button-size: 1.1em;\n --category-border-bottom-size: 4px;\n\n --focus-indicator-color: #999999;\n\n --search-height: 2em;\n\n --blue-color: #4F81E5;\n\n --border-color: #CCCCCC;\n --background-color: #FFFFFF;\n --text-color: #000000;\n --secondary-text-color: #666666;\n --hover-color: #E8F4F9;\n --search-focus-border-color: var(--blue-color);\n --search-icon-color: #CCCCCC;\n --overlay-background-color: rgba(0, 0, 0, 0.8);\n --popup-background-color: #FFFFFF;\n --category-button-color: #666666;\n --category-button-active-color: var(--blue-color);\n\n --dark-border-color: #666666;\n --dark-background-color: #333333;\n --dark-text-color: #FFFFFF;\n --dark-secondary-text-color: #999999;\n --dark-hover-color: #666666;\n --dark-search-background-color: #666666;\n --dark-search-border-color: #999999;\n --dark-search-placeholder-color: #999999;\n --dark-search-focus-border-color: #DBE5F9;\n --dark-popup-background-color: #333333;\n --dark-category-button-color: #FFFFFF;\n}\n\n.emoji-picker {\n font-size: 16px;\n\n border: 1px solid var(--border-color);\n border-radius: 5px;\n background: var(--background-color);\n width: calc(var(--emoji-per-row) * var(--emoji-size) * var(--emoji-size-multiplier) + 1em + 1.5rem);\n font-family: Arial, Helvetica, sans-serif;\n overflow: hidden;\n animation: show var(--animation-duration) var(--animation-easing);\n}\n\n.emoji-picker h2 {\n font-family: Arial, Helvetica, sans-serif;\n}\n\n.emoji-picker__overlay {\n background: rgba(0, 0, 0, 0.75);\n z-index: 1000;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.emoji-picker.hiding {\n animation: hide var(--animation-duration) var(--animation-easing);\n}\n\n.emoji-picker.dark {\n background: var(--dark-background-color);\n color: var(--dark-text-color);\n border-color: var(--dark-border-color);\n}\n\n.emoji-picker__content {\n padding: 0.5em;\n height: var(--content-height);\n position: relative;\n}\n\n.emoji-picker__preview {\n height: var(--emoji-preview-size);\n padding: 0.5em;\n border-top: 1px solid var(--border-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.emoji-picker.dark .emoji-picker__preview {\n border-top-color: var(--dark-border-color);\n}\n\n.emoji-picker__preview-emoji {\n font-size: var(--emoji-preview-size);\n margin-right: 0.25em;\n font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "EmojiOne Color", "Android Emoji";\n}\n\n.emoji-picker__preview-emoji img.emoji {\n height: 1em;\n width: 1em;\n margin: 0 .05em 0 .1em;\n vertical-align: -0.1em;\n}\n\n.emoji-picker__preview-name {\n color: var(--text-color);\n font-size: 0.85em;\n overflow-wrap: break-word;\n word-break: break-all;\n}\n\n.emoji-picker.dark .emoji-picker__preview-name {\n color: var(--dark-text-color);\n}\n\n.emoji-picker__container {\n display: grid;\n justify-content: center;\n grid-template-columns: repeat(var(--emoji-per-row), calc(var(--emoji-size) * var(--emoji-size-multiplier)));\n grid-auto-rows: calc(var(--emoji-size) * var(--emoji-size-multiplier));\n}\n\n.emoji-picker__container.search-results {\n height: var(--content-height);\n overflow-y: auto;\n}\n\n.emoji-picker__custom-emoji {\n width: 1em;\n height: 1em;\n}\n\n.emoji-picker__emoji {\n background: transparent;\n border: none;\n cursor: pointer;\n overflow: hidden;\n font-size: var(--emoji-size);\n width: 1.5em;\n height: 1.5em;\n padding: 0;\n margin: 0;\n outline: none;\n font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "EmojiOne Color", "Android Emoji";\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.emoji-picker__emoji img.emoji {\n height: 1em;\n width: 1em;\n margin: 0 .05em 0 .1em;\n vertical-align: -0.1em;\n}\n\n.emoji-picker__emoji:focus, .emoji-picker__emoji:hover {\n background: var(--hover-color);\n}\n\n.emoji-picker__emoji:focus {\n outline: 1px dotted var(--focus-indicator-color);\n}\n\n.emoji-picker.dark .emoji-picker__emoji:focus, .emoji-picker.dark .emoji-picker__emoji:hover {\n background: var(--dark-hover-color);\n}\n\n.emoji-picker__plugin-container {\n margin: 0.5em;\n display: flex;\n flex-direction: row;\n}\n\n.emoji-picker__search-container {\n margin: 0.5em;\n position: relative;\n height: var(--search-height);\n display: flex;\n}\n\n.emoji-picker__search {\n box-sizing: border-box;\n width: 100%;\n border-radius: 3px;\n border: 1px solid var(--border-color);\n padding-right: 2em;\n padding: 0.5em 2.25em 0.5em 0.5em;\n font-size: 0.85em;\n outline: none;\n}\n\n.emoji-picker.dark .emoji-picker__search {\n background: var(--dark-search-background-color);\n color: var(--dark-text-color);\n border-color: var(--dark-search-border-color);\n}\n\n.emoji-picker.dark .emoji-picker__search::placeholder {\n color: var(--dark-search-placeholder-color);\n}\n\n.emoji-picker__search:focus {\n border: 1px solid var(--search-focus-border-color);\n}\n\n.emoji-picker.dark .emoji-picker__search:focus {\n border-color: var(--dark-search-focus-border-color);\n}\n\n.emoji-picker__search-icon {\n position: absolute;\n color: var(--search-icon-color);\n width: 1em;\n height: 1em;\n right: 0.75em;\n top: calc(50% - 0.5em);\n}\n\n.emoji-picker__search-icon img {\n width: 1em;\n height: 1em;\n}\n\n.emoji-picker__search-not-found {\n color: var(--secondary-text-color);\n text-align: center;\n height: 100%;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.emoji-picker__search-not-found h2 {\n color: var(--secondary-text-color);\n}\n\n.emoji-picker.dark .emoji-picker__search-not-found {\n color: var(--dark-secondary-text-color);\n}\n\n.emoji-picker.dark .emoji-picker__search-not-found h2 {\n color: var(--dark-secondary-text-color);\n}\n\n.emoji-picker__search-not-found-icon {\n font-size: 3em;\n}\n\n.emoji-picker__search-not-found-icon img {\n width: 1em;\n height: 1em;\n}\n\n.emoji-picker__search-not-found h2 {\n margin: 0.5em 0;\n font-size: 1em;\n}\n\n.emoji-picker__variant-overlay {\n background: var(--overlay-background-color);\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border-radius: 5px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n animation: fade-in var(--animation-duration) var(--animation-easing);\n}\n\n.emoji-picker__variant-overlay.hiding {\n animation: fade-out var(--animation-duration) var(--animation-easing);\n}\n\n.emoji-picker__variant-popup {\n background: var(--popup-background-color);\n margin: 0.5em;\n padding: 0.5em;\n text-align: center;\n border-radius: 5px;\n animation: grow var(--animation-duration) var(--animation-easing);\n user-select: none;\n}\n\n.emoji-picker__variant-overlay.hiding .emoji-picker__variant-popup {\n animation: shrink var(--animation-duration) var(--animation-easing);\n}\n\n.emoji-picker.dark .emoji-picker__variant-popup {\n background: var(--dark-popup-background-color);\n}\n\n.emoji-picker__emojis {\n overflow-y: auto;\n position: relative;\n height: calc((var(--emoji-size) * var(--emoji-size-multiplier)) * var(--row-count) + var(--category-name-size));\n}\n\n.emoji-picker__emojis.hiding {\n animation: fade-out 0.05s var(--animation-easing);\n}\n\n.emoji-picker__emojis h2.emoji-picker__category-name {\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n margin: 0.25em 0;\n text-align: left;\n}\n\n.emoji-picker.dark h2.emoji-picker__category-name {\n color: var(--dark-secondary-text-color);\n}\n\n.emoji-picker__category-buttons {\n display: flex;\n flex-direction: row;\n justify-content: space-around;\n height: var(--category-button-height);\n margin-bottom: 0.5em;\n}\n\nbutton.emoji-picker__category-button {\n flex-grow: 1;\n background: transparent;\n padding: 0;\n border: none;\n cursor: pointer;\n font-size: var(--category-button-size);\n vertical-align: middle;\n color: var(--category-button-color);\n border-bottom: var(--category-border-bottom-size) solid transparent;\n outline: none;\n}\n\nbutton.emoji-picker__category-button img {\n width: var(--category-button-size);\n height: var(--category-button-size);\n}\n\n.emoji-picker.keyboard button.emoji-picker__category-button:focus {\n outline: 1px dotted var(--focus-indicator-color);\n}\n\n.emoji-picker.dark button.emoji-picker__category-button.active {\n color: var(--category-button-active-color);\n}\n\n.emoji-picker.dark button.emoji-picker__category-button {\n color: var(--dark-category-button-color);\n}\n\nbutton.emoji-picker__category-button.active {\n color: var(--category-button-active-color);\n border-bottom: var(--category-border-bottom-size) solid var(--category-button-active-color);\n}\n\n@media (prefers-color-scheme: dark) {\n .emoji-picker.auto {\n background: var(--dark-background-color);\n color: var(--dark-text-color);\n border-color: var(--dark-border-color);\n }\n\n .emoji-picker.auto .emoji-picker__preview {\n border-top-color: var(--dark-border-color);\n }\n\n .emoji-picker.auto .emoji-picker__preview-name {\n color: var(--dark-text-color);\n }\n\n .emoji-picker.auto button.emoji-picker__category-button {\n color: var(--dark-category-button-color);\n }\n\n .emoji-picker.auto button.emoji-picker__category-button.active {\n color: var(--category-button-active-color);\n }\n\n .emoji-picker.auto .emoji-picker__emoji:focus, .emoji-picker.auto .emoji-picker__emoji:hover {\n background: var(--dark-hover-color);\n }\n\n .emoji-picker.auto .emoji-picker__search {\n background: var(--dark-search-background-color);\n color: var(--dark-text-color);\n border-color: var(--dark-search-border-color);\n }\n \n .emoji-picker.auto h2.emoji-picker__category-name {\n color: var(--dark-secondary-text-color);\n }\n\n .emoji-picker.auto .emoji-picker__search::placeholder {\n color: var(--dark-search-placeholder-color);\n }\n\n .emoji-picker.auto .emoji-picker__search:focus {\n border-color: var(--dark-search-focus-border-color);\n }\n\n .emoji-picker.auto .emoji-picker__search-not-found {\n color: var(--dark-secondary-text-color);\n }\n\n .emoji-picker.auto .emoji-picker__search-not-found h2 {\n color: var(--dark-secondary-text-color);\n }\n\n .emoji-picker.auto .emoji-picker__variant-popup {\n background: var(--dark-popup-background-color);\n }\n}');var e=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'],o=e.join(","),n="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function i(e,i){i=i||{};var r,t,c,d=[],g=[],u=e.querySelectorAll(o);for(i.includeContainer&&n.call(e,o)&&(u=Array.prototype.slice.apply(u)).unshift(e),r=0;r0){var o=v[v.length-1];o!==e&&o.pause();}var n=v.indexOf(e);-1===n||v.splice(n,1),v.push(e);},deactivateTrap:function(e){var o=v.indexOf(e);-1!==o&&v.splice(o,1),v.length>0&&v[v.length-1].unpause();}});function y(e){return setTimeout(e,0)}var j=function(e,o){var n=document,i="string"==typeof e?n.querySelector(e):e,a=u({returnFocusOnDeactivate:!0,escapeDeactivates:!0},o),r={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1},t={activate:function(e){if(r.active)return;w(),r.active=!0,r.paused=!1,r.nodeFocusedBeforeActivation=n.activeElement;var o=e&&e.onActivate?e.onActivate:a.onActivate;o&&o();return m(),t},deactivate:s,pause:function(){if(r.paused||!r.active)return;r.paused=!0,c();},unpause:function(){if(!r.paused||!r.active)return;r.paused=!1,w(),m();}};return t;function s(e){if(r.active){clearTimeout(d),c(),r.active=!1,r.paused=!1,f.deactivateTrap(t);var o=e&&void 0!==e.onDeactivate?e.onDeactivate:a.onDeactivate;return o&&o(),(e&&void 0!==e.returnFocus?e.returnFocus:a.returnFocusOnDeactivate)&&y((function(){var e;k((e=r.nodeFocusedBeforeActivation,l("setReturnFocus")||e));})),t}}function m(){if(r.active)return f.activateTrap(t),d=y((function(){k(v());})),n.addEventListener("focusin",h,!0),n.addEventListener("mousedown",j,{capture:!0,passive:!1}),n.addEventListener("touchstart",j,{capture:!0,passive:!1}),n.addEventListener("click",b,{capture:!0,passive:!1}),n.addEventListener("keydown",p,{capture:!0,passive:!1}),t}function c(){if(r.active)return n.removeEventListener("focusin",h,!0),n.removeEventListener("mousedown",j,!0),n.removeEventListener("touchstart",j,!0),n.removeEventListener("click",b,!0),n.removeEventListener("keydown",p,!0),t}function l(e){var o=a[e],i=o;if(!o)return null;if("string"==typeof o&&!(i=n.querySelector(o)))throw new Error("`"+e+"` refers to no known node");if("function"==typeof o&&!(i=o()))throw new Error("`"+e+"` did not return a node");return i}function v(){var e;if(!(e=null!==l("initialFocus")?l("initialFocus"):i.contains(n.activeElement)?n.activeElement:r.firstTabbableNode||l("fallbackFocus")))throw new Error("Your focus-trap needs to have at least one focusable element");return e}function j(e){i.contains(e.target)||(a.clickOutsideDeactivates?s({returnFocus:!g.isFocusable(e.target)}):a.allowOutsideClick&&a.allowOutsideClick(e)||e.preventDefault());}function h(e){i.contains(e.target)||e.target instanceof Document||(e.stopImmediatePropagation(),k(r.mostRecentlyFocusedNode||v()));}function p(e){if(!1!==a.escapeDeactivates&&function(e){return "Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e))return e.preventDefault(),void s();(function(e){return "Tab"===e.key||9===e.keyCode})(e)&&function(e){if(w(),e.shiftKey&&e.target===r.firstTabbableNode)return e.preventDefault(),void k(r.lastTabbableNode);if(!e.shiftKey&&e.target===r.lastTabbableNode)e.preventDefault(),k(r.firstTabbableNode);}(e);}function b(e){a.clickOutsideDeactivates||i.contains(e.target)||a.allowOutsideClick&&a.allowOutsideClick(e)||(e.preventDefault(),e.stopImmediatePropagation());}function w(){var e=g(i);r.firstTabbableNode=e[0]||v(),r.lastTabbableNode=e[e.length-1]||v();}function k(e){e!==n.activeElement&&(e&&e.focus?(e.focus(),r.mostRecentlyFocusedNode=e,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(e)&&e.select()):k(v()));}};function h(){}h.prototype={on:function(e,o,n){var i=this.e||(this.e={});return (i[e]||(i[e]=[])).push({fn:o,ctx:n}),this},once:function(e,o,n){var i=this;function a(){i.off(e,a),o.apply(n,arguments);}return a._=o,this.on(e,a,n)},emit:function(e){for(var o=[].slice.call(arguments,1),n=((this.e||(this.e={}))[e]||[]).slice(),i=0,a=n.length;i=0?o.ownerDocument.body:C(o)&&S(o)?o:e(M(o))}(e),i="body"===E(n),a=w(n),r=i?[a].concat(a.visualViewport||[],S(n)?n:[]):n,t=o.concat(r);return i?t:t.concat(P(M(r)))}function T(e){return ["table","td","th"].indexOf(E(e))>=0}function L(e){return C(e)&&"fixed"!==I(e).position?e.offsetParent:null}function N(e){for(var o=w(e),n=L(e);n&&T(n);)n=L(n);return n&&"body"===E(n)&&"static"===I(n).position?o:n||o}h.TinyEmitter=p;var B="top",F="bottom",D="right",q="left",R=[B,F,D,q],V=R.reduce((function(e,o){return e.concat([o+"-start",o+"-end"])}),[]),H=[].concat(R,["auto"]).reduce((function(e,o){return e.concat([o,o+"-start",o+"-end"])}),[]),U=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function W(e){var o=new Map,n=new Set,i=[];return e.forEach((function(e){o.set(e.name,e);})),e.forEach((function(e){n.has(e.name)||function e(a){n.add(a.name),[].concat(a.requires||[],a.requiresIfExists||[]).forEach((function(i){if(!n.has(i)){var a=o.get(i);a&&e(a);}})),i.push(a);}(e);})),i}function K(e){return e.split("-")[0]}var J={placement:"bottom",modifiers:[],strategy:"absolute"};function G(){for(var e=arguments.length,o=new Array(e),n=0;n=0?"x":"y"}function Q(e){var o,n=e.reference,i=e.element,a=e.placement,r=a?K(a):null,t=a?$(a):null,s=n.x+n.width/2-i.width/2,m=n.y+n.height/2-i.height/2;switch(r){case B:o={x:s,y:n.y-i.height};break;case F:o={x:s,y:n.y+n.height};break;case D:o={x:n.x+n.width,y:m};break;case q:o={x:n.x-i.width,y:m};break;default:o={x:n.x,y:n.y};}var c=r?Z(r):null;if(null!=c){var d="y"===c?"height":"width";switch(t){case"start":o[c]=Math.floor(o[c])-Math.floor(n[d]/2-i[d]/2);break;case"end":o[c]=Math.floor(o[c])+Math.ceil(n[d]/2-i[d]/2);}}return o}var ee={top:"auto",right:"auto",bottom:"auto",left:"auto"};function oe(e){var o,n=e.popper,i=e.popperRect,a=e.placement,r=e.offsets,t=e.position,s=e.gpuAcceleration,m=e.adaptive,c=function(e){var o=e.x,n=e.y,i=window.devicePixelRatio||1;return {x:Math.round(o*i)/i||0,y:Math.round(n*i)/i||0}}(r),d=c.x,g=c.y,u=r.hasOwnProperty("x"),l=r.hasOwnProperty("y"),v=q,f=B,y=window;if(m){var j=N(n);j===w(n)&&(j=_(n)),a===B&&(f=F,g-=j.clientHeight-i.height,g*=s?1:-1),a===q&&(v=D,d-=j.clientWidth-i.width,d*=s?1:-1);}var h,p=Object.assign({position:t},m&&ee);return s?Object.assign({},p,((h={})[f]=l?"0":"",h[v]=u?"0":"",h.transform=(y.devicePixelRatio||1)<2?"translate("+d+"px, "+g+"px)":"translate3d("+d+"px, "+g+"px, 0)",h)):Object.assign({},p,((o={})[f]=l?g+"px":"",o[v]=u?d+"px":"",o.transform="",o))}var ne={left:"right",right:"left",bottom:"top",top:"bottom"};function ie(e){return e.replace(/left|right|bottom|top/g,(function(e){return ne[e]}))}var ae={start:"end",end:"start"};function re(e){return e.replace(/start|end/g,(function(e){return ae[e]}))}function te(e){return parseFloat(e)||0}function se(e){var o=w(e),n=function(e){var o=C(e)?I(e):{};return {top:te(o.borderTopWidth),right:te(o.borderRightWidth),bottom:te(o.borderBottomWidth),left:te(o.borderLeftWidth)}}(e),i="html"===E(e),a=z(e),r=e.clientWidth+n.right,t=e.clientHeight+n.bottom;return i&&o.innerHeight-e.clientHeight>50&&(t=o.innerHeight-n.bottom),{top:i?0:e.clientTop,right:e.clientLeft>n.left?n.right:i?o.innerWidth-r-a:e.offsetWidth-r,bottom:i?o.innerHeight-t:e.offsetHeight-t,left:i?a:e.clientLeft}}function me(e,o){var n=Boolean(o.getRootNode&&o.getRootNode().host);if(e.contains(o))return !0;if(n){var i=o;do{if(i&&e.isSameNode(i))return !0;i=i.parentNode||i.host;}while(i)}return !1}function ce(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function de(e,o){return "viewport"===o?ce(function(e){var o=w(e),n=o.visualViewport,i=o.innerWidth,a=o.innerHeight;return n&&/iPhone|iPod|iPad/.test(navigator.platform)&&(i=n.width,a=n.height),{width:i,height:a,x:0,y:0}}(e)):C(o)?b(o):ce(function(e){var o=w(e),n=k(e),i=O(_(e),o);return i.height=Math.max(i.height,o.innerHeight),i.width=Math.max(i.width,o.innerWidth),i.x=-n.scrollLeft,i.y=-n.scrollTop,i}(_(e)))}function ge(e,o,n){var i="clippingParents"===o?function(e){var o=P(e),n=["absolute","fixed"].indexOf(I(e).position)>=0&&C(e)?N(e):e;return x(n)?o.filter((function(e){return x(e)&&me(e,n)})):[]}(e):[].concat(o),a=[].concat(i,[n]),r=a[0],t=a.reduce((function(o,n){var i=de(e,n),a=se(C(n)?n:_(e));return o.top=Math.max(i.top+a.top,o.top),o.right=Math.min(i.right-a.right,o.right),o.bottom=Math.min(i.bottom-a.bottom,o.bottom),o.left=Math.max(i.left+a.left,o.left),o}),de(e,r));return t.width=t.right-t.left,t.height=t.bottom-t.top,t.x=t.left,t.y=t.top,t}function ue(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},{},e)}function le(e,o){return o.reduce((function(o,n){return o[n]=e,o}),{})}function ve(e,o){void 0===o&&(o={});var n=o,i=n.placement,a=void 0===i?e.placement:i,r=n.boundary,t=void 0===r?"clippingParents":r,s=n.rootBoundary,m=void 0===s?"viewport":s,c=n.elementContext,d=void 0===c?"popper":c,g=n.altBoundary,u=void 0!==g&&g,l=n.padding,v=void 0===l?0:l,f=ue("number"!=typeof v?v:le(v,R)),y="popper"===d?"reference":"popper",j=e.elements.reference,h=e.rects.popper,p=e.elements[u?y:d],w=ge(x(p)?p:p.contextElement||_(e.elements.popper),t,m),k=b(j),C=Q({reference:k,element:h,strategy:"absolute",placement:a}),E=ce(Object.assign({},h,{},C)),z="popper"===d?E:k,I={top:w.top-z.top+f.top,bottom:z.bottom-w.bottom+f.bottom,left:w.left-z.left+f.left,right:z.right-w.right+f.right},S=e.modifiersData.offset;if("popper"===d&&S){var O=S[a];Object.keys(I).forEach((function(e){var o=[D,F].indexOf(e)>=0?1:-1,n=[B,F].indexOf(e)>=0?"y":"x";I[e]+=O[n]*o;}));}return I}function fe(e,o){void 0===o&&(o={});var n=o,i=n.placement,a=n.boundary,r=n.rootBoundary,t=n.padding,s=n.flipVariations,m=n.allowedAutoPlacements,c=void 0===m?H:m,d=$(i),g=(d?s?V:V.filter((function(e){return $(e)===d})):R).filter((function(e){return c.indexOf(e)>=0})).reduce((function(o,n){return o[n]=ve(e,{placement:n,boundary:a,rootBoundary:r,padding:t})[K(n)],o}),{});return Object.keys(g).sort((function(e,o){return g[e]-g[o]}))}function ye(e,o,n){return Math.max(e,Math.min(o,n))}function je(e,o,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-o.height-n.y,right:e.right-o.width+n.x,bottom:e.bottom-o.height+n.y,left:e.left-o.width-n.x}}function he(e){return [B,D,F,q].some((function(o){return e[o]>=0}))}var pe=X({defaultModifiers:[{name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var o=e.state,n=e.instance,i=e.options,a=i.scroll,r=void 0===a||a,t=i.resize,s=void 0===t||t,m=w(o.elements.popper),c=[].concat(o.scrollParents.reference,o.scrollParents.popper);return r&&c.forEach((function(e){e.addEventListener("scroll",n.update,Y);})),s&&m.addEventListener("resize",n.update,Y),function(){r&&c.forEach((function(e){e.removeEventListener("scroll",n.update,Y);})),s&&m.removeEventListener("resize",n.update,Y);}},data:{}},{name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var o=e.state,n=e.name;o.modifiersData[n]=Q({reference:o.rects.reference,element:o.rects.popper,strategy:"absolute",placement:o.placement});},data:{}},{name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var o=e.state,n=e.options,i=n.gpuAcceleration,a=void 0===i||i,r=n.adaptive,t=void 0===r||r,s={placement:K(o.placement),popper:o.elements.popper,popperRect:o.rects.popper,gpuAcceleration:a};null!=o.modifiersData.popperOffsets&&(o.styles.popper=Object.assign({},o.styles.popper,{},oe(Object.assign({},s,{offsets:o.modifiersData.popperOffsets,position:o.options.strategy,adaptive:t})))),null!=o.modifiersData.arrow&&(o.styles.arrow=Object.assign({},o.styles.arrow,{},oe(Object.assign({},s,{offsets:o.modifiersData.arrow,position:"absolute",adaptive:!1})))),o.attributes.popper=Object.assign({},o.attributes.popper,{"data-popper-placement":o.placement});},data:{}},{name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var o=e.state;Object.keys(o.elements).forEach((function(e){var n=o.styles[e]||{},i=o.attributes[e]||{},a=o.elements[e];C(a)&&E(a)&&(Object.assign(a.style,n),Object.keys(i).forEach((function(e){var o=i[e];!1===o?a.removeAttribute(e):a.setAttribute(e,!0===o?"":o);})));}));},effect:function(e){var o=e.state,n={popper:{position:o.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(o.elements.popper.style,n.popper),o.elements.arrow&&Object.assign(o.elements.arrow.style,n.arrow),function(){Object.keys(o.elements).forEach((function(e){var i=o.elements[e],a=o.attributes[e]||{},r=Object.keys(o.styles.hasOwnProperty(e)?o.styles[e]:n[e]).reduce((function(e,o){return e[o]="",e}),{});C(i)&&E(i)&&(Object.assign(i.style,r),Object.keys(a).forEach((function(e){i.removeAttribute(e);})));}));}},requires:["computeStyles"]},{name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var o=e.state,n=e.options,i=e.name,a=n.offset,r=void 0===a?[0,0]:a,t=H.reduce((function(e,n){return e[n]=function(e,o,n){var i=K(e),a=[q,B].indexOf(i)>=0?-1:1,r="function"==typeof n?n(Object.assign({},o,{placement:e})):n,t=r[0],s=r[1];return t=t||0,s=(s||0)*a,[q,D].indexOf(i)>=0?{x:s,y:t}:{x:t,y:s}}(n,o.rects,r),e}),{}),s=t[o.placement],m=s.x,c=s.y;null!=o.modifiersData.popperOffsets&&(o.modifiersData.popperOffsets.x+=m,o.modifiersData.popperOffsets.y+=c),o.modifiersData[i]=t;}},{name:"flip",enabled:!0,phase:"main",fn:function(e){var o=e.state,n=e.options,i=e.name;if(!o.modifiersData[i]._skip){for(var a=n.mainAxis,r=void 0===a||a,t=n.altAxis,s=void 0===t||t,m=n.fallbackPlacements,c=n.padding,d=n.boundary,g=n.rootBoundary,u=n.altBoundary,l=n.flipVariations,v=void 0===l||l,f=n.allowedAutoPlacements,y=o.options.placement,j=K(y),h=m||(j===y||!v?[ie(y)]:function(e){if("auto"===K(e))return [];var o=ie(e);return [re(e),o,re(o)]}(y)),p=[y].concat(h).reduce((function(e,n){return e.concat("auto"===K(n)?fe(o,{placement:n,boundary:d,rootBoundary:g,padding:c,flipVariations:v,allowedAutoPlacements:f}):n)}),[]),b=o.rects.reference,w=o.rects.popper,k=new Map,x=!0,C=p[0],E=0;E=0,O=S?"width":"height",A=ve(o,{placement:_,boundary:d,rootBoundary:g,altBoundary:u,padding:c}),M=S?I?D:q:I?F:B;b[O]>w[O]&&(M=ie(M));var P=ie(M),T=[];if(r&&T.push(A[z]<=0),s&&T.push(A[M]<=0,A[P]<=0),T.every((function(e){return e}))){C=_,x=!1;break}k.set(_,T);}if(x)for(var L=function(e){var o=p.find((function(o){var n=k.get(o);if(n)return n.slice(0,e).every((function(e){return e}))}));if(o)return C=o,"break"},N=v?3:1;N>0;N--){if("break"===L(N))break}o.placement!==C&&(o.modifiersData[i]._skip=!0,o.placement=C,o.reset=!0);}},requiresIfExists:["offset"],data:{_skip:!1}},{name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var o=e.state,n=e.options,i=e.name,a=n.mainAxis,r=void 0===a||a,t=n.altAxis,s=void 0!==t&&t,m=n.boundary,c=n.rootBoundary,d=n.altBoundary,g=n.padding,u=n.tether,l=void 0===u||u,v=n.tetherOffset,f=void 0===v?0:v,y=ve(o,{boundary:m,rootBoundary:c,padding:g,altBoundary:d}),j=K(o.placement),h=$(o.placement),p=!h,b=Z(j),w="x"===b?"y":"x",k=o.modifiersData.popperOffsets,x=o.rects.reference,C=o.rects.popper,E="function"==typeof f?f(Object.assign({},o.rects,{placement:o.placement})):f,_={x:0,y:0};if(k){if(r){var z="y"===b?B:q,I="y"===b?F:D,S="y"===b?"height":"width",O=k[b],M=k[b]+y[z],P=k[b]-y[I],T=l?-C[S]/2:0,L="start"===h?x[S]:C[S],R="start"===h?-C[S]:-x[S],V=o.elements.arrow,H=l&&V?A(V):{width:0,height:0},U=o.modifiersData["arrow#persistent"]?o.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},W=U[z],J=U[I],G=ye(0,x[S],H[S]),X=p?x[S]/2-T-G-W-E:L-G-W-E,Y=p?-x[S]/2+T+G+J+E:R+G+J+E,Q=o.elements.arrow&&N(o.elements.arrow),ee=Q?"y"===b?Q.clientTop||0:Q.clientLeft||0:0,oe=o.modifiersData.offset?o.modifiersData.offset[o.placement][b]:0,ne=k[b]+X-oe-ee,ie=k[b]+Y-oe,ae=ye(l?Math.min(M,ne):M,O,l?Math.max(P,ie):P);k[b]=ae,_[b]=ae-O;}if(s){var re="x"===b?B:q,te="x"===b?F:D,se=k[w],me=ye(se+y[re],se,se-y[te]);k[w]=me,_[w]=me-se;}o.modifiersData[i]=_;}},requiresIfExists:["offset"]},{name:"arrow",enabled:!0,phase:"main",fn:function(e){var o,n=e.state,i=e.name,a=n.elements.arrow,r=n.modifiersData.popperOffsets,t=K(n.placement),s=Z(t),m=[q,D].indexOf(t)>=0?"height":"width";if(a&&r){var c=n.modifiersData[i+"#persistent"].padding,d=A(a),g="y"===s?B:q,u="y"===s?F:D,l=n.rects.reference[m]+n.rects.reference[s]-r[s]-n.rects.popper[m],v=r[s]-n.rects.reference[s],f=N(a),y=f?"y"===s?f.clientHeight||0:f.clientWidth||0:0,j=l/2-v/2,h=c[g],p=y-d[m]-c[u],b=y/2-d[m]/2+j,w=ye(h,b,p),k=s;n.modifiersData[i]=((o={})[k]=w,o.centerOffset=w-b,o);}},effect:function(e){var o=e.state,n=e.options,i=e.name,a=n.element,r=void 0===a?"[data-popper-arrow]":a,t=n.padding,s=void 0===t?0:t;null!=r&&("string"!=typeof r||(r=o.elements.popper.querySelector(r)))&&me(o.elements.popper,r)&&(o.elements.arrow=r,o.modifiersData[i+"#persistent"]={padding:ue("number"!=typeof s?s:le(s,R))});},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},{name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var o=e.state,n=e.name,i=o.rects.reference,a=o.rects.popper,r=o.modifiersData.preventOverflow,t=ve(o,{elementContext:"reference"}),s=ve(o,{altBoundary:!0}),m=je(t,i),c=je(s,a,r),d=he(m),g=he(c);o.modifiersData[n]={referenceClippingOffsets:m,popperEscapeOffsets:c,isReferenceHidden:d,hasPopperEscaped:g},o.attributes.popper=Object.assign({},o.attributes.popper,{"data-popper-reference-hidden":d,"data-popper-escaped":g});}}]}),be="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var we=be.location||{},ke=function(){var e={base:"https://twemoji.maxcdn.com/v/13.0.0/",ext:".png",size:"72x72",className:"emoji",convert:{fromCodePoint:function(e){var o="string"==typeof e?parseInt(e,16):e;if(o<65536)return s(o);return s(55296+((o-=65536)>>10),56320+(1023&o))},toCodePoint:y},onerror:function(){this.parentNode&&this.parentNode.replaceChild(m(this.alt,!1),this);},parse:function(o,n){n&&"function"!=typeof n||(n={callback:n});return ("string"==typeof o?u:g)(o,{callback:n.callback||c,attributes:"function"==typeof n.attributes?n.attributes:v,base:"string"==typeof n.base?n.base:e.base,ext:n.ext||e.ext,size:n.folder||(i=n.size||e.size,"number"==typeof i?i+"x"+i:i),className:n.className||e.className,onerror:n.onerror||e.onerror});var i;},replace:f,test:function(e){n.lastIndex=0;var o=n.test(e);return n.lastIndex=0,o}},o={"&":"&","<":"<",">":">","'":"'",'"':"""},n=/(?:\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d])|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd-\uddcf\uddd1-\udddd]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udeeb\udeec\udef4-\udefc\udfe0-\udfeb]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd1d\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78\udd7a-\uddb4\uddb7\uddba\uddbc-\uddcb\uddd0\uddde-\uddff\ude70-\ude74\ude78-\ude7a\ude80-\ude86\ude90-\udea8\udeb0-\udeb6\udec0-\udec2\uded0-\uded6]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g,i=/\uFE0F/g,a=String.fromCharCode(8205),r=/[&<>'"]/g,t=/^(?:iframe|noframes|noscript|script|select|style|textarea)$/,s=String.fromCharCode;return e;function m(e,o){return document.createTextNode(o?e.replace(i,""):e)}function c(e,o){return "".concat(o.base,o.size,"/",e,o.ext)}function d(e){return y(e.indexOf(a)<0?e.replace(i,""):e)}function g(e,o){for(var i,a,r,s,c,g,u,l,v,f,y,j,h,p=function e(o,n){for(var i,a,r=o.childNodes,s=r.length;s--;)3===(a=(i=r[s]).nodeType)?n.push(i):1!==a||"ownerSVGElement"in i||t.test(i.nodeName.toLowerCase())||e(i,n);return n}(e,[]),b=p.length;b--;){for(r=!1,s=document.createDocumentFragment(),g=(c=p[b]).nodeValue,l=0;u=n.exec(g);){if((v=u.index)!==l&&s.appendChild(m(g.slice(l,v),!0)),j=d(y=u[0]),l=v+y.length,h=o.callback(j,o),j&&h){for(a in (f=new Image).onerror=o.onerror,f.setAttribute("draggable","false"),i=o.attributes(y,j))i.hasOwnProperty(a)&&0!==a.indexOf("on")&&!f.hasAttribute(a)&&f.setAttribute(a,i[a]);f.className=o.className,f.alt=y,f.src=h,r=!0,s.appendChild(f);}f||s.appendChild(m(y,!1)),f=null;}r&&(l");}return a}))}function l(e){return o[e]}function v(){return null}function f(e,o){return String(e).replace(n,o)}function y(e,o){for(var n=[],i=0,a=0,r=0;rthis.showPreview(e)),this.events.on("hidePreview",()=>this.hidePreview()),e}showPreview(e){let o=e.emoji;e.custom?o=``:"twemoji"===this.options.style&&(o=xe.parse(e.emoji,Ie)),this.emoji.innerHTML=o,this.name.innerHTML=e.name;}hidePreview(){this.emoji.innerHTML="",this.name.innerHTML="";}}function Oe(e,o){for(var n=0;n0;)o+="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[62*Math.random()|0];return o}function oo(e){return "".concat(e).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function no(e){return Object.keys(e||{}).reduce((function(o,n){return o+"".concat(n,": ").concat(e[n],";")}),"")}function io(e){return e.size!==Qe.size||e.x!==Qe.x||e.y!==Qe.y||e.rotate!==Qe.rotate||e.flipX||e.flipY}function ao(e){var o=e.transform,n=e.containerWidth,i=e.iconWidth,a={transform:"translate(".concat(n/2," 256)")},r="translate(".concat(32*o.x,", ").concat(32*o.y,") "),t="scale(".concat(o.size/16*(o.flipX?-1:1),", ").concat(o.size/16*(o.flipY?-1:1),") "),s="rotate(".concat(o.rotate," 0 0)");return {outer:a,inner:{transform:"".concat(r," ").concat(t," ").concat(s)},path:{transform:"translate(".concat(i/2*-1," -256)")}}}var ro={x:0,y:0,width:"100%",height:"100%"};function to(e){var o=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e.attributes&&(e.attributes.fill||o)&&(e.attributes.fill="black"),e}function so(e){var o=e.icons,n=o.main,i=o.mask,a=e.prefix,r=e.iconName,t=e.transform,s=e.symbol,m=e.title,c=e.maskId,d=e.titleId,g=e.extra,u=e.watchable,l=void 0!==u&&u,v=i.found?i:n,f=v.width,y=v.height,j="fa-w-".concat(Math.ceil(f/y*16)),h=[Xe.replacementClass,r?"".concat(Xe.familyPrefix,"-").concat(r):"",j].filter((function(e){return -1===g.classes.indexOf(e)})).concat(g.classes).join(" "),p={children:[],attributes:Me({},g.attributes,{"data-prefix":a,"data-icon":r,class:h,role:g.attributes.role||"img",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 ".concat(f," ").concat(y)})};l&&(p.attributes["data-fa-i2svg"]=""),m&&p.children.push({tag:"title",attributes:{id:p.attributes["aria-labelledby"]||"title-".concat(d||eo())},children:[m]});var b=Me({},p,{prefix:a,iconName:r,main:n,mask:i,maskId:c,transform:t,symbol:s,styles:g.styles}),w=i.found&&n.found?function(e){var o,n=e.children,i=e.attributes,a=e.main,r=e.mask,t=e.maskId,s=e.transform,m=a.width,c=a.icon,d=r.width,g=r.icon,u=ao({transform:s,containerWidth:d,iconWidth:m}),l={tag:"rect",attributes:Me({},ro,{fill:"white"})},v=c.children?{children:c.children.map(to)}:{},f={tag:"g",attributes:Me({},u.inner),children:[to(Me({tag:c.tag,attributes:Me({},c.attributes,u.path)},v))]},y={tag:"g",attributes:Me({},u.outer),children:[f]},j="mask-".concat(t||eo()),h="clip-".concat(t||eo()),p={tag:"mask",attributes:Me({},ro,{id:j,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[l,y]},b={tag:"defs",children:[{tag:"clipPath",attributes:{id:h},children:(o=g,"g"===o.tag?o.children:[o])},p]};return n.push(b,{tag:"rect",attributes:Me({fill:"currentColor","clip-path":"url(#".concat(h,")"),mask:"url(#".concat(j,")")},ro)}),{children:n,attributes:i}}(b):function(e){var o=e.children,n=e.attributes,i=e.main,a=e.transform,r=no(e.styles);if(r.length>0&&(n.style=r),io(a)){var t=ao({transform:a,containerWidth:i.width,iconWidth:i.width});o.push({tag:"g",attributes:Me({},t.outer),children:[{tag:"g",attributes:Me({},t.inner),children:[{tag:i.icon.tag,children:i.icon.children,attributes:Me({},i.icon.attributes,t.path)}]}]});}else o.push(i.icon);return {children:o,attributes:n}}(b),k=w.children,x=w.attributes;return b.children=k,b.attributes=x,s?function(e){var o=e.prefix,n=e.iconName,i=e.children,a=e.attributes,r=e.symbol;return [{tag:"svg",attributes:{style:"display: none;"},children:[{tag:"symbol",attributes:Me({},a,{id:!0===r?"".concat(o,"-").concat(Xe.familyPrefix,"-").concat(n):r}),children:i}]}]}(b):function(e){var o=e.children,n=e.main,i=e.mask,a=e.attributes,r=e.styles,t=e.transform;if(io(t)&&n.found&&!i.found){var s={x:n.width/n.height/2,y:.5};a.style=no(Me({},r,{"transform-origin":"".concat(s.x+t.x/16,"em ").concat(s.y+t.y/16,"em")}));}return [{tag:"svg",attributes:a,children:o}]}(b)}var co=(Xe.measurePerformance&&Ve&&Ve.mark&&Ve.measure,function(e,o,n,i){var a,r,t,s=Object.keys(e),m=s.length,c=void 0!==i?function(e,o){return function(n,i,a,r){return e.call(o,n,i,a,r)}}(o,i):o;for(void 0===n?(a=1,t=e[s[0]]):(a=0,t=n);a2&&void 0!==arguments[2]?arguments[2]:{},i=n.skipHooks,a=void 0!==i&&i,r=Object.keys(o).reduce((function(e,n){var i=o[n];return !!i.icon?e[i.iconName]=i.icon:e[n]=i,e}),{});"function"!=typeof $e.hooks.addPack||a?$e.styles[e]=Me({},$e.styles[e]||{},r):$e.hooks.addPack(e,r),"fas"===e&&go("fa",o);}var uo=$e.styles,lo=$e.shims,vo=function(){var e=function(e){return co(uo,(function(o,n,i){return o[i]=co(n,e,{}),o}),{})};e((function(e,o,n){return o[3]&&(e[o[3]]=n),e})),e((function(e,o,n){var i=o[2];return e[n]=n,i.forEach((function(o){e[o]=n;})),e}));var o="far"in uo;co(lo,(function(e,n){var i=n[0],a=n[1],r=n[2];return "far"!==a||o||(a="fas"),e[i]={prefix:a,iconName:r},e}),{});};vo();$e.styles;function fo(e,o,n){if(e&&e[o]&&e[o][n])return {prefix:o,iconName:n,icon:e[o][n]}}function yo(e){var o=e.tag,n=e.attributes,i=void 0===n?{}:n,a=e.children,r=void 0===a?[]:a;return "string"==typeof e?oo(e):"<".concat(o," ").concat(function(e){return Object.keys(e||{}).reduce((function(o,n){return o+"".concat(n,'="').concat(oo(e[n]),'" ')}),"").trim()}(i),">").concat(r.map(yo).join(""),"")}function jo(e){this.name="MissingIcon",this.message=e||"Icon unavailable",this.stack=(new Error).stack;}jo.prototype=Object.create(Error.prototype),jo.prototype.constructor=jo;var ho={fill:"currentColor"},po={attributeType:"XML",repeatCount:"indefinite",dur:"2s"},bo=(Me({},ho,{d:"M156.5,447.7l-12.6,29.5c-18.7-9.5-35.9-21.2-51.5-34.9l22.7-22.7C127.6,430.5,141.5,440,156.5,447.7z M40.6,272H8.5 c1.4,21.2,5.4,41.7,11.7,61.1L50,321.2C45.1,305.5,41.8,289,40.6,272z M40.6,240c1.4-18.8,5.2-37,11.1-54.1l-29.5-12.6 C14.7,194.3,10,216.7,8.5,240H40.6z M64.3,156.5c7.8-14.9,17.2-28.8,28.1-41.5L69.7,92.3c-13.7,15.6-25.5,32.8-34.9,51.5 L64.3,156.5z M397,419.6c-13.9,12-29.4,22.3-46.1,30.4l11.9,29.8c20.7-9.9,39.8-22.6,56.9-37.6L397,419.6z M115,92.4 c13.9-12,29.4-22.3,46.1-30.4l-11.9-29.8c-20.7,9.9-39.8,22.6-56.8,37.6L115,92.4z M447.7,355.5c-7.8,14.9-17.2,28.8-28.1,41.5 l22.7,22.7c13.7-15.6,25.5-32.9,34.9-51.5L447.7,355.5z M471.4,272c-1.4,18.8-5.2,37-11.1,54.1l29.5,12.6 c7.5-21.1,12.2-43.5,13.6-66.8H471.4z M321.2,462c-15.7,5-32.2,8.2-49.2,9.4v32.1c21.2-1.4,41.7-5.4,61.1-11.7L321.2,462z M240,471.4c-18.8-1.4-37-5.2-54.1-11.1l-12.6,29.5c21.1,7.5,43.5,12.2,66.8,13.6V471.4z M462,190.8c5,15.7,8.2,32.2,9.4,49.2h32.1 c-1.4-21.2-5.4-41.7-11.7-61.1L462,190.8z M92.4,397c-12-13.9-22.3-29.4-30.4-46.1l-29.8,11.9c9.9,20.7,22.6,39.8,37.6,56.9 L92.4,397z M272,40.6c18.8,1.4,36.9,5.2,54.1,11.1l12.6-29.5C317.7,14.7,295.3,10,272,8.5V40.6z M190.8,50 c15.7-5,32.2-8.2,49.2-9.4V8.5c-21.2,1.4-41.7,5.4-61.1,11.7L190.8,50z M442.3,92.3L419.6,115c12,13.9,22.3,29.4,30.5,46.1 l29.8-11.9C470,128.5,457.3,109.4,442.3,92.3z M397,92.4l22.7-22.7c-15.6-13.7-32.8-25.5-51.5-34.9l-12.6,29.5 C370.4,72.1,384.4,81.5,397,92.4z"}),Me({},po,{attributeName:"opacity"}));Me({},ho,{cx:"256",cy:"364",r:"28"}),Me({},po,{attributeName:"r",values:"28;14;28;28;14;28;"}),Me({},bo,{values:"1;0;1;1;0;1;"}),Me({},ho,{opacity:"1",d:"M263.7,312h-16c-6.6,0-12-5.4-12-12c0-71,77.4-63.9,77.4-107.8c0-20-17.8-40.2-57.4-40.2c-29.1,0-44.3,9.6-59.2,28.7 c-3.9,5-11.1,6-16.2,2.4l-13.1-9.2c-5.6-3.9-6.9-11.8-2.6-17.2c21.2-27.2,46.4-44.7,91.2-44.7c52.3,0,97.4,29.8,97.4,80.2 c0,67.6-77.4,63.5-77.4,107.8C275.7,306.6,270.3,312,263.7,312z"}),Me({},bo,{values:"1;0;0;0;0;1;"}),Me({},ho,{opacity:"0",d:"M232.5,134.5l7,168c0.3,6.4,5.6,11.5,12,11.5h9c6.4,0,11.7-5.1,12-11.5l7-168c0.3-6.8-5.2-12.5-12-12.5h-23 C237.7,122,232.2,127.7,232.5,134.5z"}),Me({},bo,{values:"0;0;1;1;0;0;"}),$e.styles;function wo(e){var o=e[0],n=e[1],i=Pe(e.slice(4),1)[0];return {found:!0,width:o,height:n,icon:Array.isArray(i)?{tag:"g",attributes:{class:"".concat(Xe.familyPrefix,"-").concat(Ue)},children:[{tag:"path",attributes:{class:"".concat(Xe.familyPrefix,"-").concat(Ke),fill:"currentColor",d:i[0]}},{tag:"path",attributes:{class:"".concat(Xe.familyPrefix,"-").concat(We),fill:"currentColor",d:i[1]}}]}:{tag:"path",attributes:{fill:"currentColor",d:i}}}}$e.styles;function ko(){Xe.autoAddCss&&!zo&&(!function(e){if(e&&He){var o=Re.createElement("style");o.setAttribute("type","text/css"),o.innerHTML=e;for(var n=Re.head.childNodes,i=null,a=n.length-1;a>-1;a--){var r=n[a],t=(r.tagName||"").toUpperCase();["STYLE","LINK"].indexOf(t)>-1&&(i=r);}Re.head.insertBefore(o,i);}}(function(){var e="svg-inline--fa",o=Xe.familyPrefix,n=Xe.replacementClass,i='svg:not(:root).svg-inline--fa {\n overflow: visible;\n}\n\n.svg-inline--fa {\n display: inline-block;\n font-size: inherit;\n height: 1em;\n overflow: visible;\n vertical-align: -0.125em;\n}\n.svg-inline--fa.fa-lg {\n vertical-align: -0.225em;\n}\n.svg-inline--fa.fa-w-1 {\n width: 0.0625em;\n}\n.svg-inline--fa.fa-w-2 {\n width: 0.125em;\n}\n.svg-inline--fa.fa-w-3 {\n width: 0.1875em;\n}\n.svg-inline--fa.fa-w-4 {\n width: 0.25em;\n}\n.svg-inline--fa.fa-w-5 {\n width: 0.3125em;\n}\n.svg-inline--fa.fa-w-6 {\n width: 0.375em;\n}\n.svg-inline--fa.fa-w-7 {\n width: 0.4375em;\n}\n.svg-inline--fa.fa-w-8 {\n width: 0.5em;\n}\n.svg-inline--fa.fa-w-9 {\n width: 0.5625em;\n}\n.svg-inline--fa.fa-w-10 {\n width: 0.625em;\n}\n.svg-inline--fa.fa-w-11 {\n width: 0.6875em;\n}\n.svg-inline--fa.fa-w-12 {\n width: 0.75em;\n}\n.svg-inline--fa.fa-w-13 {\n width: 0.8125em;\n}\n.svg-inline--fa.fa-w-14 {\n width: 0.875em;\n}\n.svg-inline--fa.fa-w-15 {\n width: 0.9375em;\n}\n.svg-inline--fa.fa-w-16 {\n width: 1em;\n}\n.svg-inline--fa.fa-w-17 {\n width: 1.0625em;\n}\n.svg-inline--fa.fa-w-18 {\n width: 1.125em;\n}\n.svg-inline--fa.fa-w-19 {\n width: 1.1875em;\n}\n.svg-inline--fa.fa-w-20 {\n width: 1.25em;\n}\n.svg-inline--fa.fa-pull-left {\n margin-right: 0.3em;\n width: auto;\n}\n.svg-inline--fa.fa-pull-right {\n margin-left: 0.3em;\n width: auto;\n}\n.svg-inline--fa.fa-border {\n height: 1.5em;\n}\n.svg-inline--fa.fa-li {\n width: 2em;\n}\n.svg-inline--fa.fa-fw {\n width: 1.25em;\n}\n\n.fa-layers svg.svg-inline--fa {\n bottom: 0;\n left: 0;\n margin: auto;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.fa-layers {\n display: inline-block;\n height: 1em;\n position: relative;\n text-align: center;\n vertical-align: -0.125em;\n width: 1em;\n}\n.fa-layers svg.svg-inline--fa {\n -webkit-transform-origin: center center;\n transform-origin: center center;\n}\n\n.fa-layers-counter, .fa-layers-text {\n display: inline-block;\n position: absolute;\n text-align: center;\n}\n\n.fa-layers-text {\n left: 50%;\n top: 50%;\n -webkit-transform: translate(-50%, -50%);\n transform: translate(-50%, -50%);\n -webkit-transform-origin: center center;\n transform-origin: center center;\n}\n\n.fa-layers-counter {\n background-color: #ff253a;\n border-radius: 1em;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n color: #fff;\n height: 1.5em;\n line-height: 1;\n max-width: 5em;\n min-width: 1.5em;\n overflow: hidden;\n padding: 0.25em;\n right: 0;\n text-overflow: ellipsis;\n top: 0;\n -webkit-transform: scale(0.25);\n transform: scale(0.25);\n -webkit-transform-origin: top right;\n transform-origin: top right;\n}\n\n.fa-layers-bottom-right {\n bottom: 0;\n right: 0;\n top: auto;\n -webkit-transform: scale(0.25);\n transform: scale(0.25);\n -webkit-transform-origin: bottom right;\n transform-origin: bottom right;\n}\n\n.fa-layers-bottom-left {\n bottom: 0;\n left: 0;\n right: auto;\n top: auto;\n -webkit-transform: scale(0.25);\n transform: scale(0.25);\n -webkit-transform-origin: bottom left;\n transform-origin: bottom left;\n}\n\n.fa-layers-top-right {\n right: 0;\n top: 0;\n -webkit-transform: scale(0.25);\n transform: scale(0.25);\n -webkit-transform-origin: top right;\n transform-origin: top right;\n}\n\n.fa-layers-top-left {\n left: 0;\n right: auto;\n top: 0;\n -webkit-transform: scale(0.25);\n transform: scale(0.25);\n -webkit-transform-origin: top left;\n transform-origin: top left;\n}\n\n.fa-lg {\n font-size: 1.3333333333em;\n line-height: 0.75em;\n vertical-align: -0.0667em;\n}\n\n.fa-xs {\n font-size: 0.75em;\n}\n\n.fa-sm {\n font-size: 0.875em;\n}\n\n.fa-1x {\n font-size: 1em;\n}\n\n.fa-2x {\n font-size: 2em;\n}\n\n.fa-3x {\n font-size: 3em;\n}\n\n.fa-4x {\n font-size: 4em;\n}\n\n.fa-5x {\n font-size: 5em;\n}\n\n.fa-6x {\n font-size: 6em;\n}\n\n.fa-7x {\n font-size: 7em;\n}\n\n.fa-8x {\n font-size: 8em;\n}\n\n.fa-9x {\n font-size: 9em;\n}\n\n.fa-10x {\n font-size: 10em;\n}\n\n.fa-fw {\n text-align: center;\n width: 1.25em;\n}\n\n.fa-ul {\n list-style-type: none;\n margin-left: 2.5em;\n padding-left: 0;\n}\n.fa-ul > li {\n position: relative;\n}\n\n.fa-li {\n left: -2em;\n position: absolute;\n text-align: center;\n width: 2em;\n line-height: inherit;\n}\n\n.fa-border {\n border: solid 0.08em #eee;\n border-radius: 0.1em;\n padding: 0.2em 0.25em 0.15em;\n}\n\n.fa-pull-left {\n float: left;\n}\n\n.fa-pull-right {\n float: right;\n}\n\n.fa.fa-pull-left,\n.fas.fa-pull-left,\n.far.fa-pull-left,\n.fal.fa-pull-left,\n.fab.fa-pull-left {\n margin-right: 0.3em;\n}\n.fa.fa-pull-right,\n.fas.fa-pull-right,\n.far.fa-pull-right,\n.fal.fa-pull-right,\n.fab.fa-pull-right {\n margin-left: 0.3em;\n}\n\n.fa-spin {\n -webkit-animation: fa-spin 2s infinite linear;\n animation: fa-spin 2s infinite linear;\n}\n\n.fa-pulse {\n -webkit-animation: fa-spin 1s infinite steps(8);\n animation: fa-spin 1s infinite steps(8);\n}\n\n@-webkit-keyframes fa-spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes fa-spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n.fa-rotate-90 {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";\n -webkit-transform: rotate(90deg);\n transform: rotate(90deg);\n}\n\n.fa-rotate-180 {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";\n -webkit-transform: rotate(180deg);\n transform: rotate(180deg);\n}\n\n.fa-rotate-270 {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";\n -webkit-transform: rotate(270deg);\n transform: rotate(270deg);\n}\n\n.fa-flip-horizontal {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";\n -webkit-transform: scale(-1, 1);\n transform: scale(-1, 1);\n}\n\n.fa-flip-vertical {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";\n -webkit-transform: scale(1, -1);\n transform: scale(1, -1);\n}\n\n.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {\n -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";\n -webkit-transform: scale(-1, -1);\n transform: scale(-1, -1);\n}\n\n:root .fa-rotate-90,\n:root .fa-rotate-180,\n:root .fa-rotate-270,\n:root .fa-flip-horizontal,\n:root .fa-flip-vertical,\n:root .fa-flip-both {\n -webkit-filter: none;\n filter: none;\n}\n\n.fa-stack {\n display: inline-block;\n height: 2em;\n position: relative;\n width: 2.5em;\n}\n\n.fa-stack-1x,\n.fa-stack-2x {\n bottom: 0;\n left: 0;\n margin: auto;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.svg-inline--fa.fa-stack-1x {\n height: 1em;\n width: 1.25em;\n}\n.svg-inline--fa.fa-stack-2x {\n height: 2em;\n width: 2.5em;\n}\n\n.fa-inverse {\n color: #fff;\n}\n\n.sr-only {\n border: 0;\n clip: rect(0, 0, 0, 0);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n clip: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n position: static;\n width: auto;\n}\n\n.svg-inline--fa .fa-primary {\n fill: var(--fa-primary-color, currentColor);\n opacity: 1;\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa .fa-secondary {\n fill: var(--fa-secondary-color, currentColor);\n opacity: 0.4;\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-primary {\n opacity: 0.4;\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-secondary {\n opacity: 1;\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa mask .fa-primary,\n.svg-inline--fa mask .fa-secondary {\n fill: black;\n}\n\n.fad.fa-inverse {\n color: #fff;\n}';if("fa"!==o||n!==e){var a=new RegExp("\\.".concat("fa","\\-"),"g"),r=new RegExp("\\--".concat("fa","\\-"),"g"),t=new RegExp("\\.".concat(e),"g");i=i.replace(a,".".concat(o,"-")).replace(r,"--".concat(o,"-")).replace(t,".".concat(n));}return i}()),zo=!0);}function xo(e,o){return Object.defineProperty(e,"abstract",{get:o}),Object.defineProperty(e,"html",{get:function(){return e.abstract.map((function(e){return yo(e)}))}}),Object.defineProperty(e,"node",{get:function(){if(He){var o=Re.createElement("div");return o.innerHTML=e.html,o.children}}}),e}function Co(e){var o=e.prefix,n=void 0===o?"fa":o,i=e.iconName;if(i)return fo(_o.definitions,n,i)||fo($e.styles,n,i)}var Eo,_o=new(function(){function e(){!function(e,o){if(!(e instanceof o))throw new TypeError("Cannot call a class as a function")}(this,e),this.definitions={};}var o,n;return o=e,(n=[{key:"add",value:function(){for(var e=this,o=arguments.length,n=new Array(o),i=0;i1&&void 0!==arguments[1]?arguments[1]:{},n=o.transform,i=void 0===n?Qe:n,a=o.symbol,r=void 0!==a&&a,t=o.mask,s=void 0===t?null:t,m=o.maskId,c=void 0===m?null:m,d=o.title,g=void 0===d?null:d,u=o.titleId,l=void 0===u?null:u,v=o.classes,f=void 0===v?[]:v,y=o.attributes,j=void 0===y?{}:y,h=o.styles,p=void 0===h?{}:h;if(e){var b=e.prefix,w=e.iconName,k=e.icon;return xo(Me({type:"icon"},e),(function(){return ko(),Xe.autoA11y&&(g?j["aria-labelledby"]="".concat(Xe.replacementClass,"-title-").concat(l||eo()):(j["aria-hidden"]="true",j.focusable="false")),so({icons:{main:wo(k),mask:s?wo(s.icon):{found:!1,width:null,height:null,icon:{}}},prefix:b,iconName:w,transform:Me({},Qe,i),symbol:r,title:g,maskId:c,titleId:l,extra:{attributes:j,styles:p,classes:f}})}))}},function(e){var o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=(e||{}).icon?e:Co(e||{}),i=o.mask;return i&&(i=(i||{}).icon?i:Co(i||{})),Eo(n,Me({},o,{mask:i}))});_o.add({prefix:"far",iconName:"building",icon:[448,512,[],"f1ad","M128 148v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12zm140 12h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm-128 96h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm128 0h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm-76 84v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm76 12h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm180 124v36H0v-36c0-6.6 5.4-12 12-12h19.5V24c0-13.3 10.7-24 24-24h337c13.3 0 24 10.7 24 24v440H436c6.6 0 12 5.4 12 12zM79.5 463H192v-67c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v67h112.5V49L80 48l-.5 415z"]},{prefix:"fas",iconName:"cat",icon:[512,512,[],"f6be","M290.59 192c-20.18 0-106.82 1.98-162.59 85.95V192c0-52.94-43.06-96-96-96-17.67 0-32 14.33-32 32s14.33 32 32 32c17.64 0 32 14.36 32 32v256c0 35.3 28.7 64 64 64h176c8.84 0 16-7.16 16-16v-16c0-17.67-14.33-32-32-32h-32l128-96v144c0 8.84 7.16 16 16 16h32c8.84 0 16-7.16 16-16V289.86c-10.29 2.67-20.89 4.54-32 4.54-61.81 0-113.52-44.05-125.41-102.4zM448 96h-64l-64-64v134.4c0 53.02 42.98 96 96 96s96-42.98 96-96V32l-64 64zm-72 80c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16zm80 0c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16z"]},{prefix:"fas",iconName:"coffee",icon:[640,512,[],"f0f4","M192 384h192c53 0 96-43 96-96h32c70.6 0 128-57.4 128-128S582.6 32 512 32H120c-13.3 0-24 10.7-24 24v232c0 53 43 96 96 96zM512 96c35.3 0 64 28.7 64 64s-28.7 64-64 64h-32V96h32zm47.7 384H48.3c-47.6 0-61-64-36-64h583.3c25 0 11.8 64-35.9 64z"]},{prefix:"far",iconName:"flag",icon:[512,512,[],"f024","M336.174 80c-49.132 0-93.305-32-161.913-32-31.301 0-58.303 6.482-80.721 15.168a48.04 48.04 0 0 0 2.142-20.727C93.067 19.575 74.167 1.594 51.201.104 23.242-1.71 0 20.431 0 48c0 17.764 9.657 33.262 24 41.562V496c0 8.837 7.163 16 16 16h16c8.837 0 16-7.163 16-16v-83.443C109.869 395.28 143.259 384 199.826 384c49.132 0 93.305 32 161.913 32 58.479 0 101.972-22.617 128.548-39.981C503.846 367.161 512 352.051 512 335.855V95.937c0-34.459-35.264-57.768-66.904-44.117C409.193 67.309 371.641 80 336.174 80zM464 336c-21.783 15.412-60.824 32-102.261 32-59.945 0-102.002-32-161.913-32-43.361 0-96.379 9.403-127.826 24V128c21.784-15.412 60.824-32 102.261-32 59.945 0 102.002 32 161.913 32 43.271 0 96.32-17.366 127.826-32v240z"]},{prefix:"far",iconName:"frown",icon:[496,512,[],"f119","M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z"]},{prefix:"fas",iconName:"futbol",icon:[512,512,[],"f1e3","M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zm-48 0l-.003-.282-26.064 22.741-62.679-58.5 16.454-84.355 34.303 3.072c-24.889-34.216-60.004-60.089-100.709-73.141l13.651 31.939L256 139l-74.953-41.525 13.651-31.939c-40.631 13.028-75.78 38.87-100.709 73.141l34.565-3.073 16.192 84.355-62.678 58.5-26.064-22.741-.003.282c0 43.015 13.497 83.952 38.472 117.991l7.704-33.897 85.138 10.447 36.301 77.826-29.902 17.786c40.202 13.122 84.29 13.148 124.572 0l-29.902-17.786 36.301-77.826 85.138-10.447 7.704 33.897C442.503 339.952 456 299.015 456 256zm-248.102 69.571l-29.894-91.312L256 177.732l77.996 56.527-29.622 91.312h-96.476z"]},{prefix:"fas",iconName:"history",icon:[512,512,[],"f1da","M504 255.531c.253 136.64-111.18 248.372-247.82 248.468-59.015.042-113.223-20.53-155.822-54.911-11.077-8.94-11.905-25.541-1.839-35.607l11.267-11.267c8.609-8.609 22.353-9.551 31.891-1.984C173.062 425.135 212.781 440 256 440c101.705 0 184-82.311 184-184 0-101.705-82.311-184-184-184-48.814 0-93.149 18.969-126.068 49.932l50.754 50.754c10.08 10.08 2.941 27.314-11.313 27.314H24c-8.837 0-16-7.163-16-16V38.627c0-14.254 17.234-21.393 27.314-11.314l49.372 49.372C129.209 34.136 189.552 8 256 8c136.81 0 247.747 110.78 248 247.531zm-180.912 78.784l9.823-12.63c8.138-10.463 6.253-25.542-4.21-33.679L288 256.349V152c0-13.255-10.745-24-24-24h-16c-13.255 0-24 10.745-24 24v135.651l65.409 50.874c10.463 8.137 25.541 6.253 33.679-4.21z"]},{prefix:"fas",iconName:"icons",icon:[512,512,[],"f86d","M116.65 219.35a15.68 15.68 0 0 0 22.65 0l96.75-99.83c28.15-29 26.5-77.1-4.91-103.88C203.75-7.7 163-3.5 137.86 22.44L128 32.58l-9.85-10.14C93.05-3.5 52.25-7.7 24.86 15.64c-31.41 26.78-33 74.85-5 103.88zm143.92 100.49h-48l-7.08-14.24a27.39 27.39 0 0 0-25.66-17.78h-71.71a27.39 27.39 0 0 0-25.66 17.78l-7 14.24h-48A27.45 27.45 0 0 0 0 347.3v137.25A27.44 27.44 0 0 0 27.43 512h233.14A27.45 27.45 0 0 0 288 484.55V347.3a27.45 27.45 0 0 0-27.43-27.46zM144 468a52 52 0 1 1 52-52 52 52 0 0 1-52 52zm355.4-115.9h-60.58l22.36-50.75c2.1-6.65-3.93-13.21-12.18-13.21h-75.59c-6.3 0-11.66 3.9-12.5 9.1l-16.8 106.93c-1 6.3 4.88 11.89 12.5 11.89h62.31l-24.2 83c-1.89 6.65 4.2 12.9 12.23 12.9a13.26 13.26 0 0 0 10.92-5.25l92.4-138.91c4.88-6.91-1.16-15.7-10.87-15.7zM478.08.33L329.51 23.17C314.87 25.42 304 38.92 304 54.83V161.6a83.25 83.25 0 0 0-16-1.7c-35.35 0-64 21.48-64 48s28.65 48 64 48c35.2 0 63.73-21.32 64-47.66V99.66l112-17.22v47.18a83.25 83.25 0 0 0-16-1.7c-35.35 0-64 21.48-64 48s28.65 48 64 48c35.2 0 63.73-21.32 64-47.66V32c0-19.48-16-34.42-33.92-31.67z"]},{prefix:"far",iconName:"lightbulb",icon:[352,512,[],"f0eb","M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"]},{prefix:"fas",iconName:"music",icon:[512,512,[],"f001","M470.38 1.51L150.41 96A32 32 0 0 0 128 126.51v261.41A139 139 0 0 0 96 384c-53 0-96 28.66-96 64s43 64 96 64 96-28.66 96-64V214.32l256-75v184.61a138.4 138.4 0 0 0-32-3.93c-53 0-96 28.66-96 64s43 64 96 64 96-28.65 96-64V32a32 32 0 0 0-41.62-30.49z"]},{prefix:"fas",iconName:"search",icon:[512,512,[],"f002","M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"]},{prefix:"far",iconName:"smile",icon:[496,512,[],"f118","M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z"]},{prefix:"fas",iconName:"times",icon:[352,512,[],"f00d","M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"]},{prefix:"fas",iconName:"user",icon:[448,512,[],"f007","M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"]});const So=Io({prefix:"far",iconName:"building"}).html[0],Oo=Io({prefix:"fas",iconName:"cat"}).html[0],Ao=Io({prefix:"fas",iconName:"coffee"}).html[0],Mo=Io({prefix:"far",iconName:"flag"}).html[0],Po=Io({prefix:"fas",iconName:"futbol"}).html[0],To=Io({prefix:"far",iconName:"frown"}).html[0],Lo=Io({prefix:"fas",iconName:"history"}).html[0],No=Io({prefix:"fas",iconName:"icons"}).html[0],Bo=Io({prefix:"far",iconName:"lightbulb"}).html[0],Fo=Io({prefix:"fas",iconName:"music"}).html[0],Do=Io({prefix:"fas",iconName:"search"}).html[0],qo=Io({prefix:"far",iconName:"smile"}).html[0],Ro=Io({prefix:"fas",iconName:"times"}).html[0],Vo=Io({prefix:"fas",iconName:"user"}).html[0];function Ho(e){const o=document.createElement("img");return o.src=e,o}function Uo(){const e=localStorage.getItem("emojiPicker.recent");return (e?JSON.parse(e):[]).filter(e=>!!e.emoji)}class Wo{constructor(e,o,n,i,a,r=!0){this.emoji=e,this.showVariants=o,this.showPreview=n,this.events=i,this.options=a,this.lazy=r;}render(){this.emojiButton=Ee("button",ze);let e=this.emoji.emoji;return this.emoji.custom?e=this.lazy?qo:``:"twemoji"===this.options.style&&(e=this.lazy?qo:xe.parse(this.emoji.emoji)),this.emojiButton.innerHTML=e,this.emojiButton.tabIndex=-1,this.emojiButton.dataset.emoji=this.emoji.emoji,this.emoji.custom&&(this.emojiButton.dataset.custom="true"),this.emojiButton.title=this.emoji.name,this.emojiButton.addEventListener("focus",()=>this.onEmojiHover()),this.emojiButton.addEventListener("blur",()=>this.onEmojiLeave()),this.emojiButton.addEventListener("click",()=>this.onEmojiClick()),this.emojiButton.addEventListener("mouseover",()=>this.onEmojiHover()),this.emojiButton.addEventListener("mouseout",()=>this.onEmojiLeave()),"twemoji"===this.options.style&&this.lazy&&(this.emojiButton.style.opacity="0.25"),this.emojiButton}onEmojiClick(){this.emoji.variations&&this.showVariants&&this.options.showVariants||!this.options.showRecents||function(e,o){const n=Uo(),i={emoji:e.emoji,name:e.name,key:e.key||e.name,custom:e.custom};localStorage.setItem("emojiPicker.recent",JSON.stringify([i,...n.filter(e=>!!e.emoji&&e.key!==i.key)].slice(0,o.recentsCount)));}(this.emoji,this.options),this.events.emit("emoji",{emoji:this.emoji,showVariants:this.showVariants,button:this.emojiButton});}onEmojiHover(){this.showPreview&&this.events.emit("showPreview",this.emoji);}onEmojiLeave(){this.showPreview&&this.events.emit("hidePreview");}}class Ko{constructor(e,o,n,i,a=!0){this.showVariants=o,this.events=n,this.options=i,this.lazy=a,this.emojis=e.filter(e=>!e.version||parseFloat(e.version)<=parseFloat(i.emojiVersion));}render(){const e=Ee("div","emoji-picker__container");return this.emojis.forEach(o=>e.appendChild(new Wo(o,this.showVariants,!0,this.events,this.options,this.lazy).render())),e}}var Jo=function(e,o){return e(o={exports:{}},o.exports),o.exports}((function(e){var o,n;o=be,n=function(){var e="undefined"==typeof window,o=new Map,n=new Map,i=[];i.total=0;var a=[],r=[];function t(){o.clear(),n.clear(),a=[],r=[];}function s(e){for(var o=-9007199254740991,n=e.length-1;n>=0;--n){var i=e[n];if(null!==i){var a=i.score;a>o&&(o=a);}}return -9007199254740991===o?null:o}function m(e,o){var n=e[o];if(void 0!==n)return n;var i=o;Array.isArray(o)||(i=o.split("."));for(var a=i.length,r=-1;e&&++r>1]=e[n],a=1+(n<<1);}for(var t=n-1>>1;n>0&&i.score>1)e[n]=e[t];e[n]=i;}return n.add=function(n){var i=o;e[o++]=n;for(var a=i-1>>1;i>0&&n.score>1)e[i]=e[a];e[i]=n;},n.poll=function(){if(0!==o){var n=e[0];return e[0]=e[--o],i(),n}},n.peek=function(n){if(0!==o)return e[0]},n.replaceTop=function(o){e[0]=o,i();},n},g=d();return function u(l){var v={single:function(e,o,n){return e?(c(e)||(e=v.getPreparedSearch(e)),o?(c(o)||(o=v.getPrepared(o)),((n&&void 0!==n.allowTypo?n.allowTypo:!l||void 0===l.allowTypo||l.allowTypo)?v.algorithm:v.algorithmNoTypo)(e,o,e[0])):null):null},go:function(e,o,n){if(!e)return i;var a=(e=v.prepareSearch(e))[0],r=n&&n.threshold||l&&l.threshold||-9007199254740991,t=n&&n.limit||l&&l.limit||9007199254740991,d=(n&&void 0!==n.allowTypo?n.allowTypo:!l||void 0===l.allowTypo||l.allowTypo)?v.algorithm:v.algorithmNoTypo,u=0,f=0,y=o.length;if(n&&n.keys)for(var j=n.scoreFn||s,h=n.keys,p=h.length,b=y-1;b>=0;--b){for(var w=o[b],k=new Array(p),x=p-1;x>=0;--x)(_=m(w,E=h[x]))?(c(_)||(_=v.getPrepared(_)),k[x]=d(e,_,a)):k[x]=null;k.obj=w;var C=j(k);null!==C&&(Cg.peek().score&&g.replaceTop(k))));}else if(n&&n.key){var E=n.key;for(b=y-1;b>=0;--b)(_=m(w=o[b],E))&&(c(_)||(_=v.getPrepared(_)),null!==(z=d(e,_,a))&&(z.scoreg.peek().score&&g.replaceTop(z)))));}else for(b=y-1;b>=0;--b){var _,z;(_=o[b])&&(c(_)||(_=v.getPrepared(_)),null!==(z=d(e,_,a))&&(z.scoreg.peek().score&&g.replaceTop(z)))));}if(0===u)return i;var I=new Array(u);for(b=u-1;b>=0;--b)I[b]=g.poll();return I.total=u+f,I},goAsync:function(o,n,a){var r=!1,t=new Promise((function(t,g){if(!o)return t(i);var u=(o=v.prepareSearch(o))[0],f=d(),y=n.length-1,j=a&&a.threshold||l&&l.threshold||-9007199254740991,h=a&&a.limit||l&&l.limit||9007199254740991,p=(a&&void 0!==a.allowTypo?a.allowTypo:!l||void 0===l.allowTypo||l.allowTypo)?v.algorithm:v.algorithmNoTypo,b=0,w=0;function k(){if(r)return g("canceled");var d=Date.now();if(a&&a.keys)for(var l=a.scoreFn||s,x=a.keys,C=x.length;y>=0;--y){for(var E=n[y],_=new Array(C),z=C-1;z>=0;--z)(O=m(E,S=x[z]))?(c(O)||(O=v.getPrepared(O)),_[z]=p(o,O,u)):_[z]=null;_.obj=E;var I=l(_);if(null!==I&&!(If.peek().score&&f.replaceTop(_)),y%1e3==0&&Date.now()-d>=10))return void(e?setImmediate(k):setTimeout(k))}else if(a&&a.key){for(var S=a.key;y>=0;--y)if((O=m(E=n[y],S))&&(c(O)||(O=v.getPrepared(O)),null!==(A=p(o,O,u))&&!(A.scoref.peek().score&&f.replaceTop(A)),y%1e3==0&&Date.now()-d>=10)))return void(e?setImmediate(k):setTimeout(k))}else for(;y>=0;--y){var O,A;if((O=n[y])&&(c(O)||(O=v.getPrepared(O)),null!==(A=p(o,O,u))&&!(A.scoref.peek().score&&f.replaceTop(A)),y%1e3==0&&Date.now()-d>=10)))return void(e?setImmediate(k):setTimeout(k))}if(0===b)return t(i);for(var M=new Array(b),P=b-1;P>=0;--P)M[P]=f.poll();M.total=b+w,t(M);}e?setImmediate(k):k();}));return t.cancel=function(){r=!0;},t},highlight:function(e,o,n){if(null===e)return null;void 0===o&&(o=""),void 0===n&&(n="");for(var i="",a=0,r=!1,t=e.target,s=t.length,m=e.indexes,c=0;c999)return v.prepare(e);var n=o.get(e);return void 0!==n||(n=v.prepare(e),o.set(e,n)),n},getPreparedSearch:function(e){if(e.length>999)return v.prepareSearch(e);var o=n.get(e);return void 0!==o||(o=v.prepareSearch(e),n.set(e,o)),o},algorithm:function(e,o,n){for(var i=o._targetLowerCodes,t=e.length,s=i.length,m=0,c=0,d=0,g=0;;){if(n===i[c]){if(a[g++]=c,++m===t)break;n=e[0===d?m:d===m?m+1:d===m-1?m-1:m];}if(++c>=s)for(;;){if(m<=1)return null;if(0===d){if(n===e[--m])continue;d=m;}else {if(1===d)return null;if((n=e[1+(m=--d)])===e[m])continue}c=a[(g=m)-1]+1;break}}m=0;var u=0,l=!1,f=0,y=o._nextBeginningIndexes;null===y&&(y=o._nextBeginningIndexes=v.prepareNextBeginningIndexes(o.target));var j=c=0===a[0]?0:y[a[0]-1];if(c!==s)for(;;)if(c>=s){if(m<=0){if(++u>t-2)break;if(e[u]===e[u+1])continue;c=j;continue}--m,c=y[r[--f]];}else if(e[0===u?m:u===m?m+1:u===m-1?m-1:m]===i[c]){if(r[f++]=c,++m===t){l=!0;break}++c;}else c=y[c];if(l)var h=r,p=f;else h=a,p=g;for(var b=0,w=-1,k=0;k=0;--k)o.indexes[k]=h[k];return o},algorithmNoTypo:function(e,o,n){for(var i=o._targetLowerCodes,t=e.length,s=i.length,m=0,c=0,d=0;;){if(n===i[c]){if(a[d++]=c,++m===t)break;n=e[m];}if(++c>=s)return null}m=0;var g=!1,u=0,l=o._nextBeginningIndexes;if(null===l&&(l=o._nextBeginningIndexes=v.prepareNextBeginningIndexes(o.target)),(c=0===a[0]?0:l[a[0]-1])!==s)for(;;)if(c>=s){if(m<=0)break;--m,c=l[r[--u]];}else if(e[m]===i[c]){if(r[u++]=c,++m===t){g=!0;break}++c;}else c=l[c];if(g)var f=r,y=u;else f=a,y=d;for(var j=0,h=-1,p=0;p=0;--p)o.indexes[p]=f[p];return o},prepareLowerCodes:function(e){for(var o=e.length,n=[],i=e.toLowerCase(),a=0;a=65&&s<=90,c=m||s>=97&&s<=122||s>=48&&s<=57,d=m&&!a||!r||!c;a=m,r=c,d&&(n[i++]=t);}return n},prepareNextBeginningIndexes:function(e){for(var o=e.length,n=v.prepareBeginningIndexes(e),i=[],a=n[0],r=0,t=0;tt?i[t]=a:(a=n[++r],i[t]=void 0===a?o:a);return i},cleanup:t,new:u};return v}()},e.exports?e.exports=n():o.fuzzysort=n();}));class Go{constructor(e,o){this.message=e,this.iconUrl=o;}render(){const e=Ee("div","emoji-picker__search-not-found"),o=Ee("div","emoji-picker__search-not-found-icon");this.iconUrl?o.appendChild(Ho(this.iconUrl)):o.innerHTML=To,e.appendChild(o);const n=Ee("h2");return n.innerHTML=this.message,e.appendChild(n),e}}class Xo{constructor(e,o,n,i,a){if(this.events=e,this.i18n=o,this.options=n,this.focusedEmojiIndex=0,this.emojisPerRow=this.options.emojisPerRow||8,this.emojiData=i.filter(e=>e.version&&parseFloat(e.version)<=parseFloat(n.emojiVersion)&&void 0!==e.category&&a.indexOf(e.category)>=0),this.options.custom){const e=this.options.custom.map(e=>({...e,custom:!0}));this.emojiData=[...this.emojiData,...e];}this.events.on("hideVariantPopup",()=>{setTimeout(()=>this.setFocusedEmoji(this.focusedEmojiIndex));});}render(){return this.searchContainer=Ee("div","emoji-picker__search-container"),this.searchField=Ee("input","emoji-picker__search"),this.searchField.placeholder=this.i18n.search,this.searchContainer.appendChild(this.searchField),this.searchIcon=Ee("span","emoji-picker__search-icon"),this.options.icons&&this.options.icons.search?this.searchIcon.appendChild(Ho(this.options.icons.search)):this.searchIcon.innerHTML=Do,this.searchIcon.addEventListener("click",e=>this.onClearSearch(e)),this.searchContainer.appendChild(this.searchIcon),this.searchField.addEventListener("keydown",e=>this.onKeyDown(e)),this.searchField.addEventListener("keyup",e=>this.onKeyUp(e)),this.searchContainer}onClearSearch(e){e.stopPropagation(),this.searchField.value&&(this.searchField.value="",this.resultsContainer=null,this.options.icons&&this.options.icons.search?(_e(this.searchIcon),this.searchIcon.appendChild(Ho(this.options.icons.search))):this.searchIcon.innerHTML=Do,this.searchIcon.style.cursor="default",this.events.emit("hideSearchResults"),setTimeout(()=>this.searchField.focus()));}setFocusedEmoji(e){if(this.resultsContainer){const o=this.resultsContainer.querySelectorAll("."+ze);o[this.focusedEmojiIndex].tabIndex=-1,this.focusedEmojiIndex=e;const n=o[this.focusedEmojiIndex];n.tabIndex=0,n.focus();}}handleResultsKeydown(e){if(this.resultsContainer){const o=this.resultsContainer.querySelectorAll("."+ze);"ArrowRight"===e.key?this.setFocusedEmoji(Math.min(this.focusedEmojiIndex+1,o.length-1)):"ArrowLeft"===e.key?this.setFocusedEmoji(Math.max(0,this.focusedEmojiIndex-1)):"ArrowDown"===e.key?(e.preventDefault(),this.focusedEmojiIndex=this.emojisPerRow&&this.setFocusedEmoji(this.focusedEmojiIndex-this.emojisPerRow)):"Escape"===e.key&&this.onClearSearch(e);}}onKeyDown(e){"Escape"===e.key&&this.searchField.value&&this.onClearSearch(e);}onKeyUp(e){if("Tab"!==e.key&&"Shift"!==e.key)if(this.searchField.value){this.options.icons&&this.options.icons.clearSearch?(_e(this.searchIcon),this.searchIcon.appendChild(Ho(this.options.icons.clearSearch))):this.searchIcon.innerHTML=Ro,this.searchIcon.style.cursor="pointer";const e=Jo.go(this.searchField.value,this.emojiData,{allowTypo:!0,limit:100,key:"name"}).map(e=>e.obj);this.events.emit("hidePreview"),e.length?(this.resultsContainer=new Ko(e,!0,this.events,this.options,!1).render(),this.resultsContainer&&(this.resultsContainer.querySelector("."+ze).tabIndex=0,this.focusedEmojiIndex=0,this.resultsContainer.addEventListener("keydown",e=>this.handleResultsKeydown(e)),this.events.emit("showSearchResults",this.resultsContainer))):this.events.emit("showSearchResults",new Go(this.i18n.notFound,this.options.icons&&this.options.icons.notFound).render());}else this.options.icons&&this.options.icons.search?(_e(this.searchIcon),this.searchIcon.appendChild(Ho(this.options.icons.search))):this.searchIcon.innerHTML=Do,this.searchIcon.style.cursor="default",this.events.emit("hideSearchResults");}}class Yo{constructor(e,o,n){this.events=e,this.emoji=o,this.options=n,this.focusedEmojiIndex=0;}getEmoji(e){return this.popup.querySelectorAll("."+ze)[e]}setFocusedEmoji(e){this.getEmoji(this.focusedEmojiIndex).tabIndex=-1,this.focusedEmojiIndex=e;const o=this.getEmoji(this.focusedEmojiIndex);o.tabIndex=0,o.focus();}render(){this.popup=Ee("div","emoji-picker__variant-popup");const e=Ee("div","emoji-picker__variant-overlay");e.addEventListener("click",e=>{e.stopPropagation(),this.popup.contains(e.target)||this.events.emit("hideVariantPopup");}),this.popup.appendChild(new Wo(this.emoji,!1,!1,this.events,this.options,!1).render()),(this.emoji.variations||[]).forEach((e,o)=>this.popup.appendChild(new Wo({name:this.emoji.name,emoji:e,key:this.emoji.name+o},!1,!1,this.events,this.options,!1).render()));const o=this.popup.querySelector("."+ze);return this.focusedEmojiIndex=0,o.tabIndex=0,setTimeout(()=>o.focus()),this.popup.addEventListener("keydown",e=>{"ArrowRight"===e.key?this.setFocusedEmoji(Math.min(this.focusedEmojiIndex+1,this.popup.querySelectorAll("."+ze).length-1)):"ArrowLeft"===e.key?this.setFocusedEmoji(Math.max(this.focusedEmojiIndex-1,0)):"Escape"===e.key&&(e.stopPropagation(),this.events.emit("hideVariantPopup"));}),e.appendChild(this.popup),e}}const $o={search:"Search emojis...",categories:{recents:"Recent Emojis",smileys:"Smileys & Emotion",people:"People & Body",animals:"Animals & Nature",food:"Food & Drink",activities:"Activities",travel:"Travel & Places",objects:"Objects",symbols:"Symbols",flags:"Flags",custom:"Custom"},notFound:"No emojis found"},Zo={recents:Lo,smileys:qo,people:Vo,animals:Oo,food:Ao,activities:Po,travel:So,objects:Bo,symbols:Fo,flags:Mo,custom:No};class Qo{constructor(e,o,n){this.options=e,this.events=o,this.i18n=n,this.activeButton=0,this.buttons=[];}render(){const e=Ee("div","emoji-picker__category-buttons");let o=this.options.showRecents?["recents",...this.options.categories||Ce.categories]:this.options.categories||Ce.categories;return this.options.custom&&(o=[...o,"custom"]),o.forEach(o=>{const n=Ee("button","emoji-picker__category-button");this.options.icons&&this.options.icons.categories&&this.options.icons.categories[o]?n.appendChild(Ho(this.options.icons.categories[o])):n.innerHTML=Zo[o],n.tabIndex=-1,n.title=this.i18n.categories[o],e.appendChild(n),this.buttons.push(n),n.addEventListener("click",()=>{this.events.emit("categoryClicked",o);});}),e.addEventListener("keydown",e=>{switch(e.key){case"ArrowRight":this.events.emit("categoryClicked",o[(this.activeButton+1)%this.buttons.length]);break;case"ArrowLeft":this.events.emit("categoryClicked",o[0===this.activeButton?this.buttons.length-1:this.activeButton-1]);break;case"ArrowUp":case"ArrowDown":e.stopPropagation(),e.preventDefault();}}),e}setActiveButton(e,o=!0){let n=this.buttons[this.activeButton];n.classList.remove("active"),n.tabIndex=-1,this.activeButton=e,n=this.buttons[this.activeButton],n.classList.add("active"),n.tabIndex=0,o&&n.focus();}}const en={};Ce.emoji.forEach(e=>{let o=en[Ce.categories[e.category]];o||(o=en[Ce.categories[e.category]]=[]),o.push(e);});class on{constructor(e,o,n){this.events=e,this.i18n=o,this.options=n,this.currentCategory=0,this.headers=[],this.focusedIndex=0,this.handleKeyDown=e=>{switch(this.emojis.removeEventListener("scroll",this.highlightCategory),e.key){case"ArrowRight":this.focusedEmoji.tabIndex=-1,this.focusedIndex===this.currentEmojiCount-1&&this.currentCategory0?(this.options.showCategoryButtons&&this.categoryButtons.setActiveButton(--this.currentCategory),this.setFocusedEmoji(this.currentEmojiCount-1)):this.setFocusedEmoji(Math.max(0,this.focusedIndex-1));break;case"ArrowDown":e.preventDefault(),this.focusedEmoji.tabIndex=-1,this.focusedIndex+this.emojisPerRow>=this.currentEmojiCount&&this.currentCategorythis.emojisPerRow&&this.setFocusedEmoji(this.focusedIndex+this.emojisPerRow);break;case"ArrowUp":if(e.preventDefault(),this.focusedEmoji.tabIndex=-1,this.focusedIndex0){const e=this.getEmojiCount(this.currentCategory-1);let o=e%this.emojisPerRow;0===o&&(o=this.emojisPerRow);const n=this.focusedIndex,i=n>o-1?e-1:e-o+n;this.currentCategory--,this.options.showCategoryButtons&&this.categoryButtons.setActiveButton(this.currentCategory),this.setFocusedEmoji(i);}else this.setFocusedEmoji(this.focusedIndex>=this.emojisPerRow?this.focusedIndex-this.emojisPerRow:this.focusedIndex);}requestAnimationFrame(()=>this.emojis.addEventListener("scroll",this.highlightCategory));},this.addCategory=(e,o)=>{const n=Ee("h2","emoji-picker__category-name");n.innerHTML=this.i18n.categories[e]||$o.categories[e],this.emojis.appendChild(n),this.headers.push(n),this.emojis.appendChild(new Ko(o,!0,this.events,this.options,"recents"!==e).render());},this.selectCategory=(e,o=!0)=>{this.emojis.removeEventListener("scroll",this.highlightCategory),this.focusedEmoji&&(this.focusedEmoji.tabIndex=-1);const n=this.categories.indexOf(e);this.currentCategory=n,this.setFocusedEmoji(0,!1),this.options.showCategoryButtons&&this.categoryButtons.setActiveButton(this.currentCategory,o);const i=this.headerOffsets[n];this.emojis.scrollTop=i,requestAnimationFrame(()=>this.emojis.addEventListener("scroll",this.highlightCategory));},this.highlightCategory=()=>{if(document.activeElement&&document.activeElement.classList.contains("emoji-picker__emoji"))return;let e=this.headerOffsets.findIndex(e=>e>=Math.round(this.emojis.scrollTop));this.emojis.scrollTop+this.emojis.offsetHeight===this.emojis.scrollHeight&&(e=-1),0===e?e=1:e<0&&(e=this.headerOffsets.length),this.headerOffsets[e]===this.emojis.scrollTop&&e++,this.currentCategory=e-1,this.options.showCategoryButtons&&this.categoryButtons.setActiveButton(this.currentCategory);},this.emojisPerRow=n.emojisPerRow||8,this.categories=n.categories||Ce.categories,n.showRecents&&(this.categories=["recents",...this.categories]),n.custom&&(this.categories=[...this.categories,"custom"]);}updateRecents(){if(this.options.showRecents){en.recents=Uo();const e=this.emojis.querySelector(".emoji-picker__container");e&&e.parentNode&&e.parentNode.replaceChild(new Ko(en.recents,!0,this.events,this.options,!1).render(),e);}}render(){this.container=Ee("div","emoji-picker__emoji-area"),this.options.showCategoryButtons&&(this.categoryButtons=new Qo(this.options,this.events,this.i18n),this.container.appendChild(this.categoryButtons.render())),this.emojis=Ee("div","emoji-picker__emojis"),this.options.showRecents&&(en.recents=Uo()),this.options.custom&&(en.custom=this.options.custom.map(e=>({...e,custom:!0}))),this.categories.forEach(e=>this.addCategory(e,en[e])),requestAnimationFrame(()=>{setTimeout(()=>{setTimeout(()=>this.emojis.addEventListener("scroll",this.highlightCategory));});}),this.emojis.addEventListener("keydown",this.handleKeyDown),this.events.on("categoryClicked",this.selectCategory),this.container.appendChild(this.emojis);return this.container.querySelectorAll("."+ze)[0].tabIndex=0,this.container}reset(){this.headerOffsets=Array.prototype.map.call(this.headers,e=>e.offsetTop),this.selectCategory(this.options.initialCategory||"smileys",!1),this.currentCategory=this.categories.indexOf(this.options.initialCategory||"smileys"),this.options.showCategoryButtons&&this.categoryButtons.setActiveButton(this.currentCategory,!1);}get currentCategoryEl(){return this.emojis.querySelectorAll(".emoji-picker__container")[this.currentCategory]}get focusedEmoji(){return this.currentCategoryEl.querySelectorAll("."+ze)[this.focusedIndex]}get currentEmojiCount(){return this.currentCategoryEl.querySelectorAll("."+ze).length}getEmojiCount(e){return this.emojis.querySelectorAll(".emoji-picker__container")[e].querySelectorAll("."+ze).length}setFocusedEmoji(e,o=!0){this.focusedIndex=e,this.focusedEmoji&&(this.focusedEmoji.tabIndex=0,o&&this.focusedEmoji.focus());}}const nn={ext:".svg",folder:"svg"},an={position:"auto",autoHide:!0,autoFocusSearch:!0,showAnimation:!0,showPreview:!0,showSearch:!0,showRecents:!0,showVariants:!0,showCategoryButtons:!0,recentsCount:50,emojiVersion:"12.1",theme:"light",categories:["smileys","people","animals","food","activities","travel","objects","symbols","flags"],style:"native",emojisPerRow:8,rows:6,emojiSize:"1.8em",initialCategory:"smileys"};class rn{constructor(e={}){this.events=new p,this.publicEvents=new p,this.pickerVisible=!1,this.options={...an,...e},this.options.rootElement||(this.options.rootElement=document.body),this.i18n={...$o,...e.i18n},this.onDocumentClick=this.onDocumentClick.bind(this),this.onDocumentKeydown=this.onDocumentKeydown.bind(this),this.theme=this.options.theme||"light",this.buildPicker();}on(e,o){this.publicEvents.on(e,o);}off(e,o){this.publicEvents.off(e,o);}buildPicker(){if(this.pickerEl=Ee("div","emoji-picker"),this.updateTheme(this.theme),this.options.showAnimation||this.pickerEl.style.setProperty("--animation-duration","0s"),this.options.emojisPerRow&&this.pickerEl.style.setProperty("--emoji-per-row",this.options.emojisPerRow.toString()),this.options.rows&&this.pickerEl.style.setProperty("--row-count",this.options.rows.toString()),this.options.emojiSize&&this.pickerEl.style.setProperty("--emoji-size",this.options.emojiSize),this.options.showCategoryButtons||this.pickerEl.style.setProperty("--category-button-height","0"),this.focusTrap=j(this.pickerEl,{clickOutsideDeactivates:!0,initialFocus:this.options.showSearch&&this.options.autoFocusSearch?".emoji-picker__search":'.emoji-picker__emoji[tabindex="0"]'}),this.pickerContent=Ee("div","emoji-picker__content"),this.options.plugins){const e=Ee("div","emoji-picker__plugin-container");this.options.plugins.forEach(o=>{if(!o.render)throw new Error('Emoji Button plugins must have a "render" function.');e.appendChild(o.render(this));}),this.pickerEl.appendChild(e);}if(this.options.showSearch){const e=new Xo(this.events,this.i18n,this.options,Ce.emoji,(this.options.categories||[]).map(e=>Ce.categories.indexOf(e))).render();this.pickerEl.appendChild(e);}this.pickerEl.appendChild(this.pickerContent),this.emojiArea=new on(this.events,this.i18n,this.options),this.pickerContent.appendChild(this.emojiArea.render()),this.events.on("showSearchResults",e=>{_e(this.pickerContent),e.classList.add("search-results"),this.pickerContent.appendChild(e);}),this.events.on("hideSearchResults",()=>{this.pickerContent.firstChild!==this.emojiArea.container&&(_e(this.pickerContent),this.pickerContent.appendChild(this.emojiArea.container)),this.emojiArea.reset();}),this.options.showPreview&&this.pickerEl.appendChild(new Se(this.events,this.options).render()),this.events.on("emoji",({emoji:e,showVariants:o})=>{e.variations&&o&&this.options.showVariants?this.showVariantPopup(e):(setTimeout(()=>this.emojiArea.updateRecents()),e.custom?this.publicEvents.emit("emoji",{url:e.emoji,name:e.name,custom:!0}):"twemoji"===this.options.style?xe.parse(e.emoji,{...nn,callback:(o,n)=>{this.publicEvents.emit("emoji",{url:`${n.base}${n.size}/${o}${n.ext}`,emoji:e.emoji,name:e.name});}}):this.publicEvents.emit("emoji",{emoji:e.emoji,name:e.name}),this.options.autoHide&&this.hidePicker());}),this.wrapper=Ee("div","emoji-picker__wrapper"),this.wrapper.appendChild(this.pickerEl),this.wrapper.style.display="none",this.options.zIndex&&(this.wrapper.style.zIndex=this.options.zIndex+""),this.options.rootElement&&this.options.rootElement.appendChild(this.wrapper),this.observeForLazyLoad();}showVariantPopup(e){const o=new Yo(this.events,e,this.options).render();o&&this.pickerEl.appendChild(o),this.events.on("hideVariantPopup",()=>{o&&(o.classList.add("hiding"),setTimeout(()=>{o&&this.pickerEl.removeChild(o);},175)),this.events.off("hideVariantPopup");});}observeForLazyLoad(){this.observer=new IntersectionObserver(e=>{Array.prototype.filter.call(e,e=>e.intersectionRatio>0).map(e=>e.target).forEach(e=>{if(!e.dataset.loaded)if(e.dataset.custom){const o=Ee("img","emoji-picker__custom-emoji");o.src=e.dataset.emoji,e.innerText="",e.appendChild(o),e.dataset.loaded=!0,e.style.opacity=1;}else "twemoji"===this.options.style&&(e.innerHTML=xe.parse(e.dataset.emoji,nn),e.dataset.loaded=!0,e.style.opacity="1");});},{root:this.emojiArea.emojis});this.emojiArea.emojis.querySelectorAll("."+ze).forEach(e=>{"twemoji"!==this.options.style&&"true"!==e.dataset.custom||this.observer.observe(e);});}onDocumentClick(e){this.pickerEl.contains(e.target)||this.hidePicker();}destroyPicker(){this.events.off("emoji"),this.events.off("hideVariantPopup"),this.options.rootElement&&(this.options.rootElement.removeChild(this.wrapper),this.popper&&this.popper.destroy()),this.observer&&this.observer.disconnect(),this.options.plugins&&this.options.plugins.forEach(e=>{e.destroy&&e.destroy();});}hidePicker(){this.hideInProgress=!0,this.focusTrap.deactivate(),this.pickerVisible=!1,this.overlay&&(document.body.removeChild(this.overlay),this.overlay=void 0),this.emojiArea.emojis.removeEventListener("scroll",this.emojiArea.highlightCategory),this.pickerEl.classList.add("hiding"),setTimeout(()=>{this.wrapper.style.display="none",this.pickerEl.classList.remove("hiding"),this.pickerContent.firstChild!==this.emojiArea.container&&(_e(this.pickerContent),this.pickerContent.appendChild(this.emojiArea.container));const e=this.pickerEl.querySelector(".emoji-picker__search");e&&(e.value="");this.pickerEl.querySelector(".emoji-picker__variant-overlay")&&this.events.emit("hideVariantPopup"),this.hideInProgress=!1,this.popper&&this.popper.destroy(),this.publicEvents.emit("hidden");},this.options.showAnimation?170:0),setTimeout(()=>{document.removeEventListener("click",this.onDocumentClick),document.removeEventListener("keydown",this.onDocumentKeydown);});}showPicker(e){if(this.hideInProgress)setTimeout(()=>this.showPicker(e),100);else {if(this.pickerVisible=!0,this.wrapper.style.display="block",window.matchMedia("screen and (max-width: 450px)").matches){const e=window.getComputedStyle(this.pickerEl),o=document.querySelector("html"),n=o&&o.clientHeight,i=o&&o.clientWidth,a=parseInt(e.height),r=n?n/2-a/2:0,t=parseInt(e.width),s=i?i/2-t/2:0;this.wrapper.style.position="fixed",this.wrapper.style.top=r+"px",this.wrapper.style.left=s+"px",this.wrapper.style.zIndex="5000",this.overlay=Ee("div","emoji-picker__overlay"),document.body.appendChild(this.overlay);}else "string"==typeof this.options.position?this.popper=pe(e,this.wrapper,{placement:this.options.position}):this.options.position&&(this.options.position.top||this.options.position.left)&&(this.wrapper.style.position="fixed",this.options.position.top&&(this.wrapper.style.top=this.options.position.top),this.options.position.bottom&&(this.wrapper.style.bottom=this.options.position.bottom),this.options.position.left&&(this.wrapper.style.left=this.options.position.left),this.options.position.right&&(this.wrapper.style.right=this.options.position.right));this.focusTrap.activate(),setTimeout(()=>{document.addEventListener("click",this.onDocumentClick),document.addEventListener("keydown",this.onDocumentKeydown);this.pickerEl.querySelector(this.options.showSearch&&this.options.autoFocusSearch?".emoji-picker__search":`.${ze}[tabindex="0"]`).focus();}),this.emojiArea.reset();}}togglePicker(e){this.pickerVisible?this.hidePicker():this.showPicker(e);}isPickerVisible(){return this.pickerVisible}onDocumentKeydown(e){if("Escape"===e.key)this.hidePicker();else if("Tab"===e.key)this.pickerEl.classList.add("keyboard");else if(e.key.match(/^[\w]$/)){const e=this.pickerEl.querySelector(".emoji-picker__search");e&&e.focus();}}setTheme(e){e!==this.theme&&(this.pickerEl.classList.remove(this.theme),this.theme=e,this.updateTheme(this.theme));}updateTheme(e){this.pickerEl.classList.add(e);}} + +export { rn as EmojiButton }; diff --git a/webroot/js/web_modules/@justinribeiro/lite-youtube.js b/webroot/js/web_modules/@justinribeiro/lite-youtube.js new file mode 100644 index 000000000..f6a750abf --- /dev/null +++ b/webroot/js/web_modules/@justinribeiro/lite-youtube.js @@ -0,0 +1,301 @@ +/** + * + * The shadowDom / Intersection Observer version of Paul's concept: + * https://github.com/paulirish/lite-youtube-embed + * + * A lightweight YouTube embed. Still should feel the same to the user, just + * MUCH faster to initialize and paint. + * + * Thx to these as the inspiration + * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html + * https://autoplay-youtube-player.glitch.me/ + * + * Once built it, I also found these (👍👍): + * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube + * https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe + */ +class LiteYTEmbed extends HTMLElement { + constructor() { + super(); + this.iframeLoaded = false; + this.setupDom(); + } + static get observedAttributes() { + return ['videoid']; + } + connectedCallback() { + this.addEventListener('pointerover', LiteYTEmbed.warmConnections, { + once: true, + }); + this.addEventListener('click', () => this.addIframe()); + } + get videoId() { + return encodeURIComponent(this.getAttribute('videoid') || ''); + } + set videoId(id) { + this.setAttribute('videoid', id); + } + get videoTitle() { + return this.getAttribute('videotitle') || 'Video'; + } + set videoTitle(title) { + this.setAttribute('videotitle', title); + } + get videoPlay() { + return this.getAttribute('videoPlay') || 'Play'; + } + set videoPlay(name) { + this.setAttribute('videoPlay', name); + } + get videoStartAt() { + return Number(this.getAttribute('videoStartAt') || '0'); + } + set videoStartAt(time) { + this.setAttribute('videoStartAt', String(time)); + } + get autoLoad() { + return this.hasAttribute('autoload'); + } + set autoLoad(value) { + if (value) { + this.setAttribute('autoload', ''); + } + else { + this.removeAttribute('autoload'); + } + } + get params() { + return `start=${this.videoStartAt}&${this.getAttribute('params')}`; + } + /** + * Define our shadowDOM for the component + */ + setupDom() { + const shadowDom = this.attachShadow({ mode: 'open' }); + shadowDom.innerHTML = ` + +
+ + + + + + +
+ `; + this.domRefFrame = this.shadowRoot.querySelector('#frame'); + this.domRefImg = { + fallback: this.shadowRoot.querySelector('#fallbackPlaceholder'), + webp: this.shadowRoot.querySelector('#webpPlaceholder'), + jpeg: this.shadowRoot.querySelector('#jpegPlaceholder'), + }; + this.domRefPlayButton = this.shadowRoot.querySelector('.lty-playbtn'); + } + /** + * Parse our attributes and fire up some placeholders + */ + setupComponent() { + this.initImagePlaceholder(); + this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); + this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`); + if (this.autoLoad) { + this.initIntersectionObserver(); + } + } + /** + * Lifecycle method that we use to listen for attribute changes to period + * @param {*} name + * @param {*} oldVal + * @param {*} newVal + */ + attributeChangedCallback(name, oldVal, newVal) { + switch (name) { + case 'videoid': { + if (oldVal !== newVal) { + this.setupComponent(); + // if we have a previous iframe, remove it and the activated class + if (this.domRefFrame.classList.contains('lyt-activated')) { + this.domRefFrame.classList.remove('lyt-activated'); + this.shadowRoot.querySelector('iframe').remove(); + } + } + break; + } + } + } + /** + * Inject the iframe into the component body + */ + addIframe() { + if (!this.iframeLoaded) { + const iframeHTML = ` +`; + this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML); + this.domRefFrame.classList.add('lyt-activated'); + this.iframeLoaded = true; + } + } + /** + * Setup the placeholder image for the component + */ + initImagePlaceholder() { + // we don't know which image type to preload, so warm the connection + LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/'); + const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`; + const posterUrlJpeg = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`; + this.domRefImg.webp.srcset = posterUrlWebp; + this.domRefImg.jpeg.srcset = posterUrlJpeg; + this.domRefImg.fallback.src = posterUrlJpeg; + this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); + this.domRefImg.fallback.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`); + } + /** + * Setup the Intersection Observer to load the iframe when scrolled into view + */ + initIntersectionObserver() { + if ('IntersectionObserver' in window && + 'IntersectionObserverEntry' in window) { + const options = { + root: null, + rootMargin: '0px', + threshold: 0, + }; + const observer = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting && !this.iframeLoaded) { + LiteYTEmbed.warmConnections(); + this.addIframe(); + observer.unobserve(this); + } + }); + }, options); + observer.observe(this); + } + } + /** + * Add a to the head + * @param {*} kind + * @param {*} url + * @param {*} as + */ + static addPrefetch(kind, url, as) { + const linkElem = document.createElement('link'); + linkElem.rel = kind; + linkElem.href = url; + if (as) { + linkElem.as = as; + } + linkElem.crossOrigin = 'true'; + document.head.append(linkElem); + } + /** + * Begin preconnecting to warm up the iframe load Since the embed's netwok + * requests load within its iframe, preload/prefetch'ing them outside the + * iframe will only cause double-downloads. So, the best we can do is warm up + * a few connections to origins that are in the critical path. + * + * Maybe `` would work, but it's unsupported: + * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site + * Isolation and split caches adding serious complexity. + */ + static warmConnections() { + if (LiteYTEmbed.preconnected) + return; + // Host that YT uses to serve JS needed by player, per amp-youtube + LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com'); + // The iframe document and most of its subresources come right off + // youtube.com + LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com'); + // The botguard script is fetched off from google.com + LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); + // TODO: Not certain if these ad related domains are in the critical path. + // Could verify with domain-specific throttling. + LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); + LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); + LiteYTEmbed.preconnected = true; + } +} +LiteYTEmbed.preconnected = false; +// Register custom element +customElements.define('lite-youtube', LiteYTEmbed); + +export { LiteYTEmbed }; diff --git a/webroot/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js b/webroot/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js new file mode 100644 index 000000000..b6a2b4ef8 --- /dev/null +++ b/webroot/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js @@ -0,0 +1,58119 @@ +import { c as createCommonjsModule, a as commonjsGlobal, g as getDefaultExportFromCjs } from '../../../common/_commonjsHelpers-37fa8da4.js'; +import { d as document_1, w as window_1$1 } from '../../../common/window-2f8a9a85.js'; + +var _extends_1 = createCommonjsModule(function (module) { +function _extends() { + module.exports = _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + return _extends.apply(this, arguments); +} + +module.exports = _extends; +}); + +function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; +} + +var assertThisInitialized = _assertThisInitialized; + +var _typeof_1 = createCommonjsModule(function (module) { +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + module.exports = _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + module.exports = _typeof = function _typeof(obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +module.exports = _typeof; +}); + +var getPrototypeOf = createCommonjsModule(function (module) { +function _getPrototypeOf(o) { + module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); +} + +module.exports = _getPrototypeOf; +}); + +function _inheritsLoose(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + subClass.__proto__ = superClass; +} + +var inheritsLoose = _inheritsLoose; + +var tuple = SafeParseTuple; + +function SafeParseTuple(obj, reviver) { + var json; + var error = null; + + try { + json = JSON.parse(obj, reviver); + } catch (err) { + error = err; + } + + return [error, json] +} + +var keycode = createCommonjsModule(function (module, exports) { +// Source: http://jsfiddle.net/vWx8V/ +// http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes + +/** + * Conenience method returns corresponding value for given keyName or keyCode. + * + * @param {Mixed} keyCode {Number} or keyName {String} + * @return {Mixed} + * @api public + */ + +function keyCode(searchInput) { + // Keyboard Events + if (searchInput && 'object' === typeof searchInput) { + var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; + if (hasKeyCode) searchInput = hasKeyCode; + } + + // Numbers + if ('number' === typeof searchInput) return names[searchInput] + + // Everything else (cast to string) + var search = String(searchInput); + + // check codes + var foundNamedKey = codes[search.toLowerCase()]; + if (foundNamedKey) return foundNamedKey + + // check aliases + var foundNamedKey = aliases[search.toLowerCase()]; + if (foundNamedKey) return foundNamedKey + + // weird character? + if (search.length === 1) return search.charCodeAt(0) + + return undefined +} + +/** + * Compares a keyboard event with a given keyCode or keyName. + * + * @param {Event} event Keyboard event that should be tested + * @param {Mixed} keyCode {Number} or keyName {String} + * @return {Boolean} + * @api public + */ +keyCode.isEventKey = function isEventKey(event, nameOrCode) { + if (event && 'object' === typeof event) { + var keyCode = event.which || event.keyCode || event.charCode; + if (keyCode === null || keyCode === undefined) { return false; } + if (typeof nameOrCode === 'string') { + // check codes + var foundNamedKey = codes[nameOrCode.toLowerCase()]; + if (foundNamedKey) { return foundNamedKey === keyCode; } + + // check aliases + var foundNamedKey = aliases[nameOrCode.toLowerCase()]; + if (foundNamedKey) { return foundNamedKey === keyCode; } + } else if (typeof nameOrCode === 'number') { + return nameOrCode === keyCode; + } + return false; + } +}; + +exports = module.exports = keyCode; + +/** + * Get by name + * + * exports.code['enter'] // => 13 + */ + +var codes = exports.code = exports.codes = { + 'backspace': 8, + 'tab': 9, + 'enter': 13, + 'shift': 16, + 'ctrl': 17, + 'alt': 18, + 'pause/break': 19, + 'caps lock': 20, + 'esc': 27, + 'space': 32, + 'page up': 33, + 'page down': 34, + 'end': 35, + 'home': 36, + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + 'insert': 45, + 'delete': 46, + 'command': 91, + 'left command': 91, + 'right command': 93, + 'numpad *': 106, + 'numpad +': 107, + 'numpad -': 109, + 'numpad .': 110, + 'numpad /': 111, + 'num lock': 144, + 'scroll lock': 145, + 'my computer': 182, + 'my calculator': 183, + ';': 186, + '=': 187, + ',': 188, + '-': 189, + '.': 190, + '/': 191, + '`': 192, + '[': 219, + '\\': 220, + ']': 221, + "'": 222 +}; + +// Helper aliases + +var aliases = exports.aliases = { + 'windows': 91, + '⇧': 16, + '⌥': 18, + '⌃': 17, + '⌘': 91, + 'ctl': 17, + 'control': 17, + 'option': 18, + 'pause': 19, + 'break': 19, + 'caps': 20, + 'return': 13, + 'escape': 27, + 'spc': 32, + 'spacebar': 32, + 'pgup': 33, + 'pgdn': 34, + 'ins': 45, + 'del': 46, + 'cmd': 91 +}; + +/*! + * Programatically add the following + */ + +// lower case chars +for (i = 97; i < 123; i++) codes[String.fromCharCode(i)] = i - 32; + +// numbers +for (var i = 48; i < 58; i++) codes[i - 48] = i; + +// function keys +for (i = 1; i < 13; i++) codes['f'+i] = i + 111; + +// numpad keys +for (i = 0; i < 10; i++) codes['numpad '+i] = i + 96; + +/** + * Get by code + * + * exports.name[13] // => 'Enter' + */ + +var names = exports.names = exports.title = {}; // title for backward compat + +// Create reverse mapping +for (i in codes) names[codes[i]] = i; + +// Add aliases +for (var alias in aliases) { + codes[alias] = aliases[alias]; +} +}); + +var win; + +if (typeof window !== "undefined") { + win = window; +} else if (typeof commonjsGlobal !== "undefined") { + win = commonjsGlobal; +} else if (typeof self !== "undefined"){ + win = self; +} else { + win = {}; +} + +var window_1 = win; + +var isFunction_1 = isFunction; + +var toString = Object.prototype.toString; + +function isFunction (fn) { + if (!fn) { + return false + } + var string = toString.call(fn); + return string === '[object Function]' || + (typeof fn === 'function' && string !== '[object RegExp]') || + (typeof window !== 'undefined' && + // IE8 and below + (fn === window.setTimeout || + fn === window.alert || + fn === window.confirm || + fn === window.prompt)) +} + +/** + * @license + * slighly modified parse-headers 2.0.2 + * Copyright (c) 2014 David Björklund + * Available under the MIT license + * + */ + +var parseHeaders = function(headers) { + var result = {}; + + if (!headers) { + return result; + } + + headers.trim().split('\n').forEach(function(row) { + var index = row.indexOf(':'); + var key = row.slice(0, index).trim().toLowerCase(); + var value = row.slice(index + 1).trim(); + + if (typeof(result[key]) === 'undefined') { + result[key] = value; + } else if (Array.isArray(result[key])) { + result[key].push(value); + } else { + result[key] = [ result[key], value ]; + } + }); + + return result; +}; + +var xhr = createXHR; +// Allow use of default import syntax in TypeScript +var _default = createXHR; +createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; +createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; + +forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { + createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { + options = initParams(uri, options, callback); + options.method = method.toUpperCase(); + return _createXHR(options) + }; +}); + +function forEachArray(array, iterator) { + for (var i = 0; i < array.length; i++) { + iterator(array[i]); + } +} + +function isEmpty(obj){ + for(var i in obj){ + if(obj.hasOwnProperty(i)) return false + } + return true +} + +function initParams(uri, options, callback) { + var params = uri; + + if (isFunction_1(options)) { + callback = options; + if (typeof uri === "string") { + params = {uri:uri}; + } + } else { + params = _extends_1({}, options, {uri: uri}); + } + + params.callback = callback; + return params +} + +function createXHR(uri, options, callback) { + options = initParams(uri, options, callback); + return _createXHR(options) +} + +function _createXHR(options) { + if(typeof options.callback === "undefined"){ + throw new Error("callback argument missing") + } + + var called = false; + var callback = function cbOnce(err, response, body){ + if(!called){ + called = true; + options.callback(err, response, body); + } + }; + + function readystatechange() { + if (xhr.readyState === 4) { + setTimeout(loadFunc, 0); + } + } + + function getBody() { + // Chrome with requestType=blob throws errors arround when even testing access to responseText + var body = undefined; + + if (xhr.response) { + body = xhr.response; + } else { + body = xhr.responseText || getXml(xhr); + } + + if (isJson) { + try { + body = JSON.parse(body); + } catch (e) {} + } + + return body + } + + function errorFunc(evt) { + clearTimeout(timeoutTimer); + if(!(evt instanceof Error)){ + evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); + } + evt.statusCode = 0; + return callback(evt, failureResponse) + } + + // will load the data & process the response in a special response object + function loadFunc() { + if (aborted) return + var status; + clearTimeout(timeoutTimer); + if(options.useXDR && xhr.status===undefined) { + //IE8 CORS GET successful response doesn't have a status field, but body is fine + status = 200; + } else { + status = (xhr.status === 1223 ? 204 : xhr.status); + } + var response = failureResponse; + var err = null; + + if (status !== 0){ + response = { + body: getBody(), + statusCode: status, + method: method, + headers: {}, + url: uri, + rawRequest: xhr + }; + if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE + response.headers = parseHeaders(xhr.getAllResponseHeaders()); + } + } else { + err = new Error("Internal XMLHttpRequest Error"); + } + return callback(err, response, response.body) + } + + var xhr = options.xhr || null; + + if (!xhr) { + if (options.cors || options.useXDR) { + xhr = new createXHR.XDomainRequest(); + }else { + xhr = new createXHR.XMLHttpRequest(); + } + } + + var key; + var aborted; + var uri = xhr.url = options.uri || options.url; + var method = xhr.method = options.method || "GET"; + var body = options.body || options.data; + var headers = xhr.headers = options.headers || {}; + var sync = !!options.sync; + var isJson = false; + var timeoutTimer; + var failureResponse = { + body: undefined, + headers: {}, + statusCode: 0, + method: method, + url: uri, + rawRequest: xhr + }; + + if ("json" in options && options.json !== false) { + isJson = true; + headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user + if (method !== "GET" && method !== "HEAD") { + headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user + body = JSON.stringify(options.json === true ? body : options.json); + } + } + + xhr.onreadystatechange = readystatechange; + xhr.onload = loadFunc; + xhr.onerror = errorFunc; + // IE9 must have onprogress be set to a unique function. + xhr.onprogress = function () { + // IE must die + }; + xhr.onabort = function(){ + aborted = true; + }; + xhr.ontimeout = errorFunc; + xhr.open(method, uri, !sync, options.username, options.password); + //has to be after open + if(!sync) { + xhr.withCredentials = !!options.withCredentials; + } + // Cannot set timeout with sync request + // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly + // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent + if (!sync && options.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + if (aborted) return + aborted = true;//IE9 may still call readystatechange + xhr.abort("timeout"); + var e = new Error("XMLHttpRequest timeout"); + e.code = "ETIMEDOUT"; + errorFunc(e); + }, options.timeout ); + } + + if (xhr.setRequestHeader) { + for(key in headers){ + if(headers.hasOwnProperty(key)){ + xhr.setRequestHeader(key, headers[key]); + } + } + } else if (options.headers && !isEmpty(options.headers)) { + throw new Error("Headers cannot be set on an XDomainRequest object") + } + + if ("responseType" in options) { + xhr.responseType = options.responseType; + } + + if ("beforeSend" in options && + typeof options.beforeSend === "function" + ) { + options.beforeSend(xhr); + } + + // Microsoft Edge browser sends "undefined" when send is called with undefined value. + // XMLHttpRequest spec says to pass null as body to indicate no body + // See https://github.com/naugtur/xhr/issues/100. + xhr.send(body || null); + + return xhr + + +} + +function getXml(xhr) { + // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" + // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. + try { + if (xhr.responseType === "document") { + return xhr.responseXML + } + var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; + if (xhr.responseType === "" && !firefoxBugTakenEffect) { + return xhr.responseXML + } + } catch (e) {} + + return null +} + +function noop() {} +xhr.default = _default; + +/** + * Copyright 2013 vtt.js Contributors + * + * 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. + */ + +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + + +var _objCreate = Object.create || (function() { + function F() {} + return function(o) { + if (arguments.length !== 1) { + throw new Error('Object.create shim only accepts one parameter.'); + } + F.prototype = o; + return new F(); + }; +})(); + +// Creates a new ParserError object from an errorData object. The errorData +// object should have default code and message properties. The default message +// property can be overriden by passing in a message parameter. +// See ParsingError.Errors below for acceptable errors. +function ParsingError(errorData, message) { + this.name = "ParsingError"; + this.code = errorData.code; + this.message = message || errorData.message; +} +ParsingError.prototype = _objCreate(Error.prototype); +ParsingError.prototype.constructor = ParsingError; + +// ParsingError metadata for acceptable ParsingErrors. +ParsingError.Errors = { + BadSignature: { + code: 0, + message: "Malformed WebVTT signature." + }, + BadTimeStamp: { + code: 1, + message: "Malformed time stamp." + } +}; + +// Try to parse input as a time stamp. +function parseTimeStamp(input) { + + function computeSeconds(h, m, s, f) { + return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; + } + + var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/); + if (!m) { + return null; + } + + if (m[3]) { + // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] + return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); + } else if (m[1] > 59) { + // Timestamp takes the form of [hours]:[minutes].[milliseconds] + // First position is hours as it's over 59. + return computeSeconds(m[1], m[2], 0, m[4]); + } else { + // Timestamp takes the form of [minutes]:[seconds].[milliseconds] + return computeSeconds(0, m[1], m[2], m[4]); + } +} + +// A settings object holds key/value pairs and will ignore anything but the first +// assignment to a specific key. +function Settings() { + this.values = _objCreate(null); +} + +Settings.prototype = { + // Only accept the first assignment to any key. + set: function(k, v) { + if (!this.get(k) && v !== "") { + this.values[k] = v; + } + }, + // Return the value for a key, or a default value. + // If 'defaultKey' is passed then 'dflt' is assumed to be an object with + // a number of possible default values as properties where 'defaultKey' is + // the key of the property that will be chosen; otherwise it's assumed to be + // a single value. + get: function(k, dflt, defaultKey) { + if (defaultKey) { + return this.has(k) ? this.values[k] : dflt[defaultKey]; + } + return this.has(k) ? this.values[k] : dflt; + }, + // Check whether we have a value for a key. + has: function(k) { + return k in this.values; + }, + // Accept a setting if its one of the given alternatives. + alt: function(k, v, a) { + for (var n = 0; n < a.length; ++n) { + if (v === a[n]) { + this.set(k, v); + break; + } + } + }, + // Accept a setting if its a valid (signed) integer. + integer: function(k, v) { + if (/^-?\d+$/.test(v)) { // integer + this.set(k, parseInt(v, 10)); + } + }, + // Accept a setting if its a valid percentage. + percent: function(k, v) { + var m; + if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) { + v = parseFloat(v); + if (v >= 0 && v <= 100) { + this.set(k, v); + return true; + } + } + return false; + } +}; + +// Helper function to parse input into groups separated by 'groupDelim', and +// interprete each group as a key/value pair separated by 'keyValueDelim'. +function parseOptions(input, callback, keyValueDelim, groupDelim) { + var groups = groupDelim ? input.split(groupDelim) : [input]; + for (var i in groups) { + if (typeof groups[i] !== "string") { + continue; + } + var kv = groups[i].split(keyValueDelim); + if (kv.length !== 2) { + continue; + } + var k = kv[0]; + var v = kv[1]; + callback(k, v); + } +} + +function parseCue(input, cue, regionList) { + // Remember the original input if we need to throw an error. + var oInput = input; + // 4.1 WebVTT timestamp + function consumeTimeStamp() { + var ts = parseTimeStamp(input); + if (ts === null) { + throw new ParsingError(ParsingError.Errors.BadTimeStamp, + "Malformed timestamp: " + oInput); + } + // Remove time stamp from input. + input = input.replace(/^[^\sa-zA-Z-]+/, ""); + return ts; + } + + // 4.4.2 WebVTT cue settings + function consumeCueSettings(input, cue) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "region": + // Find the last region we parsed with the same region id. + for (var i = regionList.length - 1; i >= 0; i--) { + if (regionList[i].id === v) { + settings.set(k, regionList[i].region); + break; + } + } + break; + case "vertical": + settings.alt(k, v, ["rl", "lr"]); + break; + case "line": + var vals = v.split(","), + vals0 = vals[0]; + settings.integer(k, vals0); + settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; + settings.alt(k, vals0, ["auto"]); + if (vals.length === 2) { + settings.alt("lineAlign", vals[1], ["start", "center", "end"]); + } + break; + case "position": + vals = v.split(","); + settings.percent(k, vals[0]); + if (vals.length === 2) { + settings.alt("positionAlign", vals[1], ["start", "center", "end"]); + } + break; + case "size": + settings.percent(k, v); + break; + case "align": + settings.alt(k, v, ["start", "center", "end", "left", "right"]); + break; + } + }, /:/, /\s/); + + // Apply default values for any missing fields. + cue.region = settings.get("region", null); + cue.vertical = settings.get("vertical", ""); + try { + cue.line = settings.get("line", "auto"); + } catch (e) {} + cue.lineAlign = settings.get("lineAlign", "start"); + cue.snapToLines = settings.get("snapToLines", true); + cue.size = settings.get("size", 100); + // Safari still uses the old middle value and won't accept center + try { + cue.align = settings.get("align", "center"); + } catch (e) { + cue.align = settings.get("align", "middle"); + } + try { + cue.position = settings.get("position", "auto"); + } catch (e) { + cue.position = settings.get("position", { + start: 0, + left: 0, + center: 50, + middle: 50, + end: 100, + right: 100 + }, cue.align); + } + + + cue.positionAlign = settings.get("positionAlign", { + start: "start", + left: "start", + center: "center", + middle: "center", + end: "end", + right: "end" + }, cue.align); + } + + function skipWhitespace() { + input = input.replace(/^\s+/, ""); + } + + // 4.1 WebVTT cue timings. + skipWhitespace(); + cue.startTime = consumeTimeStamp(); // (1) collect cue start time + skipWhitespace(); + if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->" + throw new ParsingError(ParsingError.Errors.BadTimeStamp, + "Malformed time stamp (time stamps must be separated by '-->'): " + + oInput); + } + input = input.substr(3); + skipWhitespace(); + cue.endTime = consumeTimeStamp(); // (5) collect cue end time + + // 4.1 WebVTT cue settings list. + skipWhitespace(); + consumeCueSettings(input, cue); +} + +var TEXTAREA_ELEMENT = document_1.createElement("textarea"); + +var TAG_NAME = { + c: "span", + i: "i", + b: "b", + u: "u", + ruby: "ruby", + rt: "rt", + v: "span", + lang: "span" +}; + +// 5.1 default text color +// 5.2 default text background color is equivalent to text color with bg_ prefix +var DEFAULT_COLOR_CLASS = { + white: 'rgba(255,255,255,1)', + lime: 'rgba(0,255,0,1)', + cyan: 'rgba(0,255,255,1)', + red: 'rgba(255,0,0,1)', + yellow: 'rgba(255,255,0,1)', + magenta: 'rgba(255,0,255,1)', + blue: 'rgba(0,0,255,1)', + black: 'rgba(0,0,0,1)' +}; + +var TAG_ANNOTATION = { + v: "title", + lang: "lang" +}; + +var NEEDS_PARENT = { + rt: "ruby" +}; + +// Parse content into a document fragment. +function parseContent(window, input) { + function nextToken() { + // Check for end-of-string. + if (!input) { + return null; + } + + // Consume 'n' characters from the input. + function consume(result) { + input = input.substr(result.length); + return result; + } + + var m = input.match(/^([^<]*)(<[^>]*>?)?/); + // If there is some text before the next tag, return it, otherwise return + // the tag. + return consume(m[1] ? m[1] : m[2]); + } + + function unescape(s) { + TEXTAREA_ELEMENT.innerHTML = s; + s = TEXTAREA_ELEMENT.textContent; + TEXTAREA_ELEMENT.textContent = ""; + return s; + } + + function shouldAdd(current, element) { + return !NEEDS_PARENT[element.localName] || + NEEDS_PARENT[element.localName] === current.localName; + } + + // Create an element for this tag. + function createElement(type, annotation) { + var tagName = TAG_NAME[type]; + if (!tagName) { + return null; + } + var element = window.document.createElement(tagName); + var name = TAG_ANNOTATION[type]; + if (name && annotation) { + element[name] = annotation.trim(); + } + return element; + } + + var rootDiv = window.document.createElement("div"), + current = rootDiv, + t, + tagStack = []; + + while ((t = nextToken()) !== null) { + if (t[0] === '<') { + if (t[1] === "/") { + // If the closing tag matches, move back up to the parent node. + if (tagStack.length && + tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { + tagStack.pop(); + current = current.parentNode; + } + // Otherwise just ignore the end tag. + continue; + } + var ts = parseTimeStamp(t.substr(1, t.length - 2)); + var node; + if (ts) { + // Timestamps are lead nodes as well. + node = window.document.createProcessingInstruction("timestamp", ts); + current.appendChild(node); + continue; + } + var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); + // If we can't parse the tag, skip to the next tag. + if (!m) { + continue; + } + // Try to construct an element, and ignore the tag if we couldn't. + node = createElement(m[1], m[3]); + if (!node) { + continue; + } + // Determine if the tag should be added based on the context of where it + // is placed in the cuetext. + if (!shouldAdd(current, node)) { + continue; + } + // Set the class list (as a list of classes, separated by space). + if (m[2]) { + var classes = m[2].split('.'); + + classes.forEach(function(cl) { + var bgColor = /^bg_/.test(cl); + // slice out `bg_` if it's a background color + var colorName = bgColor ? cl.slice(3) : cl; + + if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { + var propName = bgColor ? 'background-color' : 'color'; + var propValue = DEFAULT_COLOR_CLASS[colorName]; + + node.style[propName] = propValue; + } + }); + + node.className = classes.join(' '); + } + // Append the node to the current node, and enter the scope of the new + // node. + tagStack.push(m[1]); + current.appendChild(node); + current = node; + continue; + } + + // Text nodes are leaf nodes. + current.appendChild(window.document.createTextNode(unescape(t))); + } + + return rootDiv; +} + +// This is a list of all the Unicode characters that have a strong +// right-to-left category. What this means is that these characters are +// written right-to-left for sure. It was generated by pulling all the strong +// right-to-left characters out of the Unicode data table. That table can +// found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt +var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], + [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], + [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], + [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], + [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], + [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], + [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], + [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], + [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], + [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], + [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], + [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], + [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], + [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], + [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], + [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], + [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], + [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], + [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], + [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], + [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], + [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], + [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], + [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], + [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; + +function isStrongRTLChar(charCode) { + for (var i = 0; i < strongRTLRanges.length; i++) { + var currentRange = strongRTLRanges[i]; + if (charCode >= currentRange[0] && charCode <= currentRange[1]) { + return true; + } + } + + return false; +} + +function determineBidi(cueDiv) { + var nodeStack = [], + text = "", + charCode; + + if (!cueDiv || !cueDiv.childNodes) { + return "ltr"; + } + + function pushNodes(nodeStack, node) { + for (var i = node.childNodes.length - 1; i >= 0; i--) { + nodeStack.push(node.childNodes[i]); + } + } + + function nextTextNode(nodeStack) { + if (!nodeStack || !nodeStack.length) { + return null; + } + + var node = nodeStack.pop(), + text = node.textContent || node.innerText; + if (text) { + // TODO: This should match all unicode type B characters (paragraph + // separator characters). See issue #115. + var m = text.match(/^.*(\n|\r)/); + if (m) { + nodeStack.length = 0; + return m[0]; + } + return text; + } + if (node.tagName === "ruby") { + return nextTextNode(nodeStack); + } + if (node.childNodes) { + pushNodes(nodeStack, node); + return nextTextNode(nodeStack); + } + } + + pushNodes(nodeStack, cueDiv); + while ((text = nextTextNode(nodeStack))) { + for (var i = 0; i < text.length; i++) { + charCode = text.charCodeAt(i); + if (isStrongRTLChar(charCode)) { + return "rtl"; + } + } + } + return "ltr"; +} + +function computeLinePos(cue) { + if (typeof cue.line === "number" && + (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { + return cue.line; + } + if (!cue.track || !cue.track.textTrackList || + !cue.track.textTrackList.mediaElement) { + return -1; + } + var track = cue.track, + trackList = track.textTrackList, + count = 0; + for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { + if (trackList[i].mode === "showing") { + count++; + } + } + return ++count * -1; +} + +function StyleBox() { +} + +// Apply styles to a div. If there is no div passed then it defaults to the +// div on 'this'. +StyleBox.prototype.applyStyles = function(styles, div) { + div = div || this.div; + for (var prop in styles) { + if (styles.hasOwnProperty(prop)) { + div.style[prop] = styles[prop]; + } + } +}; + +StyleBox.prototype.formatStyle = function(val, unit) { + return val === 0 ? 0 : val + unit; +}; + +// Constructs the computed display state of the cue (a div). Places the div +// into the overlay which should be a block level element (usually a div). +function CueStyleBox(window, cue, styleOptions) { + StyleBox.call(this); + this.cue = cue; + + // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will + // have inline positioning and will function as the cue background box. + this.cueDiv = parseContent(window, cue.text); + var styles = { + color: "rgba(255, 255, 255, 1)", + backgroundColor: "rgba(0, 0, 0, 0.8)", + position: "relative", + left: 0, + right: 0, + top: 0, + bottom: 0, + display: "inline", + writingMode: cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl", + unicodeBidi: "plaintext" + }; + + this.applyStyles(styles, this.cueDiv); + + // Create an absolutely positioned div that will be used to position the cue + // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS + // mirrors of them except middle instead of center on Safari. + this.div = window.document.createElement("div"); + styles = { + direction: determineBidi(this.cueDiv), + writingMode: cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl", + unicodeBidi: "plaintext", + textAlign: cue.align === "middle" ? "center" : cue.align, + font: styleOptions.font, + whiteSpace: "pre-line", + position: "absolute" + }; + + this.applyStyles(styles); + this.div.appendChild(this.cueDiv); + + // Calculate the distance from the reference edge of the viewport to the text + // position of the cue box. The reference edge will be resolved later when + // the box orientation styles are applied. + var textPos = 0; + switch (cue.positionAlign) { + case "start": + textPos = cue.position; + break; + case "center": + textPos = cue.position - (cue.size / 2); + break; + case "end": + textPos = cue.position - cue.size; + break; + } + + // Horizontal box orientation; textPos is the distance from the left edge of the + // area to the left edge of the box and cue.size is the distance extending to + // the right from there. + if (cue.vertical === "") { + this.applyStyles({ + left: this.formatStyle(textPos, "%"), + width: this.formatStyle(cue.size, "%") + }); + // Vertical box orientation; textPos is the distance from the top edge of the + // area to the top edge of the box and cue.size is the height extending + // downwards from there. + } else { + this.applyStyles({ + top: this.formatStyle(textPos, "%"), + height: this.formatStyle(cue.size, "%") + }); + } + + this.move = function(box) { + this.applyStyles({ + top: this.formatStyle(box.top, "px"), + bottom: this.formatStyle(box.bottom, "px"), + left: this.formatStyle(box.left, "px"), + right: this.formatStyle(box.right, "px"), + height: this.formatStyle(box.height, "px"), + width: this.formatStyle(box.width, "px") + }); + }; +} +CueStyleBox.prototype = _objCreate(StyleBox.prototype); +CueStyleBox.prototype.constructor = CueStyleBox; + +// Represents the co-ordinates of an Element in a way that we can easily +// compute things with such as if it overlaps or intersects with another Element. +// Can initialize it with either a StyleBox or another BoxPosition. +function BoxPosition(obj) { + // Either a BoxPosition was passed in and we need to copy it, or a StyleBox + // was passed in and we need to copy the results of 'getBoundingClientRect' + // as the object returned is readonly. All co-ordinate values are in reference + // to the viewport origin (top left). + var lh, height, width, top; + if (obj.div) { + height = obj.div.offsetHeight; + width = obj.div.offsetWidth; + top = obj.div.offsetTop; + + var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && + rects.getClientRects && rects.getClientRects(); + obj = obj.div.getBoundingClientRect(); + // In certain cases the outter div will be slightly larger then the sum of + // the inner div's lines. This could be due to bold text, etc, on some platforms. + // In this case we should get the average line height and use that. This will + // result in the desired behaviour. + lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) + : 0; + + } + this.left = obj.left; + this.right = obj.right; + this.top = obj.top || top; + this.height = obj.height || height; + this.bottom = obj.bottom || (top + (obj.height || height)); + this.width = obj.width || width; + this.lineHeight = lh !== undefined ? lh : obj.lineHeight; +} + +// Move the box along a particular axis. Optionally pass in an amount to move +// the box. If no amount is passed then the default is the line height of the +// box. +BoxPosition.prototype.move = function(axis, toMove) { + toMove = toMove !== undefined ? toMove : this.lineHeight; + switch (axis) { + case "+x": + this.left += toMove; + this.right += toMove; + break; + case "-x": + this.left -= toMove; + this.right -= toMove; + break; + case "+y": + this.top += toMove; + this.bottom += toMove; + break; + case "-y": + this.top -= toMove; + this.bottom -= toMove; + break; + } +}; + +// Check if this box overlaps another box, b2. +BoxPosition.prototype.overlaps = function(b2) { + return this.left < b2.right && + this.right > b2.left && + this.top < b2.bottom && + this.bottom > b2.top; +}; + +// Check if this box overlaps any other boxes in boxes. +BoxPosition.prototype.overlapsAny = function(boxes) { + for (var i = 0; i < boxes.length; i++) { + if (this.overlaps(boxes[i])) { + return true; + } + } + return false; +}; + +// Check if this box is within another box. +BoxPosition.prototype.within = function(container) { + return this.top >= container.top && + this.bottom <= container.bottom && + this.left >= container.left && + this.right <= container.right; +}; + +// Check if this box is entirely within the container or it is overlapping +// on the edge opposite of the axis direction passed. For example, if "+x" is +// passed and the box is overlapping on the left edge of the container, then +// return true. +BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { + switch (axis) { + case "+x": + return this.left < container.left; + case "-x": + return this.right > container.right; + case "+y": + return this.top < container.top; + case "-y": + return this.bottom > container.bottom; + } +}; + +// Find the percentage of the area that this box is overlapping with another +// box. +BoxPosition.prototype.intersectPercentage = function(b2) { + var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), + y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), + intersectArea = x * y; + return intersectArea / (this.height * this.width); +}; + +// Convert the positions from this box to CSS compatible positions using +// the reference container's positions. This has to be done because this +// box's positions are in reference to the viewport origin, whereas, CSS +// values are in referecne to their respective edges. +BoxPosition.prototype.toCSSCompatValues = function(reference) { + return { + top: this.top - reference.top, + bottom: reference.bottom - this.bottom, + left: this.left - reference.left, + right: reference.right - this.right, + height: this.height, + width: this.width + }; +}; + +// Get an object that represents the box's position without anything extra. +// Can pass a StyleBox, HTMLElement, or another BoxPositon. +BoxPosition.getSimpleBoxPosition = function(obj) { + var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; + var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; + var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; + + obj = obj.div ? obj.div.getBoundingClientRect() : + obj.tagName ? obj.getBoundingClientRect() : obj; + var ret = { + left: obj.left, + right: obj.right, + top: obj.top || top, + height: obj.height || height, + bottom: obj.bottom || (top + (obj.height || height)), + width: obj.width || width + }; + return ret; +}; + +// Move a StyleBox to its specified, or next best, position. The containerBox +// is the box that contains the StyleBox, such as a div. boxPositions are +// a list of other boxes that the styleBox can't overlap with. +function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { + + // Find the best position for a cue box, b, on the video. The axis parameter + // is a list of axis, the order of which, it will move the box along. For example: + // Passing ["+x", "-x"] will move the box first along the x axis in the positive + // direction. If it doesn't find a good position for it there it will then move + // it along the x axis in the negative direction. + function findBestPosition(b, axis) { + var bestPosition, + specifiedPosition = new BoxPosition(b), + percentage = 1; // Highest possible so the first thing we get is better. + + for (var i = 0; i < axis.length; i++) { + while (b.overlapsOppositeAxis(containerBox, axis[i]) || + (b.within(containerBox) && b.overlapsAny(boxPositions))) { + b.move(axis[i]); + } + // We found a spot where we aren't overlapping anything. This is our + // best position. + if (b.within(containerBox)) { + return b; + } + var p = b.intersectPercentage(containerBox); + // If we're outside the container box less then we were on our last try + // then remember this position as the best position. + if (percentage > p) { + bestPosition = new BoxPosition(b); + percentage = p; + } + // Reset the box position to the specified position. + b = new BoxPosition(specifiedPosition); + } + return bestPosition || specifiedPosition; + } + + var boxPosition = new BoxPosition(styleBox), + cue = styleBox.cue, + linePos = computeLinePos(cue), + axis = []; + + // If we have a line number to align the cue to. + if (cue.snapToLines) { + var size; + switch (cue.vertical) { + case "": + axis = [ "+y", "-y" ]; + size = "height"; + break; + case "rl": + axis = [ "+x", "-x" ]; + size = "width"; + break; + case "lr": + axis = [ "-x", "+x" ]; + size = "width"; + break; + } + + var step = boxPosition.lineHeight, + position = step * Math.round(linePos), + maxPosition = containerBox[size] + step, + initialAxis = axis[0]; + + // If the specified intial position is greater then the max position then + // clamp the box to the amount of steps it would take for the box to + // reach the max position. + if (Math.abs(position) > maxPosition) { + position = position < 0 ? -1 : 1; + position *= Math.ceil(maxPosition / step) * step; + } + + // If computed line position returns negative then line numbers are + // relative to the bottom of the video instead of the top. Therefore, we + // need to increase our initial position by the length or width of the + // video, depending on the writing direction, and reverse our axis directions. + if (linePos < 0) { + position += cue.vertical === "" ? containerBox.height : containerBox.width; + axis = axis.reverse(); + } + + // Move the box to the specified position. This may not be its best + // position. + boxPosition.move(initialAxis, position); + + } else { + // If we have a percentage line value for the cue. + var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; + + switch (cue.lineAlign) { + case "center": + linePos -= (calculatedPercentage / 2); + break; + case "end": + linePos -= calculatedPercentage; + break; + } + + // Apply initial line position to the cue box. + switch (cue.vertical) { + case "": + styleBox.applyStyles({ + top: styleBox.formatStyle(linePos, "%") + }); + break; + case "rl": + styleBox.applyStyles({ + left: styleBox.formatStyle(linePos, "%") + }); + break; + case "lr": + styleBox.applyStyles({ + right: styleBox.formatStyle(linePos, "%") + }); + break; + } + + axis = [ "+y", "-x", "+x", "-y" ]; + + // Get the box position again after we've applied the specified positioning + // to it. + boxPosition = new BoxPosition(styleBox); + } + + var bestPosition = findBestPosition(boxPosition, axis); + styleBox.move(bestPosition.toCSSCompatValues(containerBox)); +} + +function WebVTT$1() { + // Nothing +} + +// Helper to allow strings to be decoded instead of the default binary utf8 data. +WebVTT$1.StringDecoder = function() { + return { + decode: function(data) { + if (!data) { + return ""; + } + if (typeof data !== "string") { + throw new Error("Error - expected string data."); + } + return decodeURIComponent(encodeURIComponent(data)); + } + }; +}; + +WebVTT$1.convertCueToDOMTree = function(window, cuetext) { + if (!window || !cuetext) { + return null; + } + return parseContent(window, cuetext); +}; + +var FONT_SIZE_PERCENT = 0.05; +var FONT_STYLE = "sans-serif"; +var CUE_BACKGROUND_PADDING = "1.5%"; + +// Runs the processing model over the cues and regions passed to it. +// @param overlay A block level element (usually a div) that the computed cues +// and regions will be placed into. +WebVTT$1.processCues = function(window, cues, overlay) { + if (!window || !cues || !overlay) { + return null; + } + + // Remove all previous children. + while (overlay.firstChild) { + overlay.removeChild(overlay.firstChild); + } + + var paddedOverlay = window.document.createElement("div"); + paddedOverlay.style.position = "absolute"; + paddedOverlay.style.left = "0"; + paddedOverlay.style.right = "0"; + paddedOverlay.style.top = "0"; + paddedOverlay.style.bottom = "0"; + paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; + overlay.appendChild(paddedOverlay); + + // Determine if we need to compute the display states of the cues. This could + // be the case if a cue's state has been changed since the last computation or + // if it has not been computed yet. + function shouldCompute(cues) { + for (var i = 0; i < cues.length; i++) { + if (cues[i].hasBeenReset || !cues[i].displayState) { + return true; + } + } + return false; + } + + // We don't need to recompute the cues' display states. Just reuse them. + if (!shouldCompute(cues)) { + for (var i = 0; i < cues.length; i++) { + paddedOverlay.appendChild(cues[i].displayState); + } + return; + } + + var boxPositions = [], + containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), + fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; + var styleOptions = { + font: fontSize + "px " + FONT_STYLE + }; + + (function() { + var styleBox, cue; + + for (var i = 0; i < cues.length; i++) { + cue = cues[i]; + + // Compute the intial position and styles of the cue div. + styleBox = new CueStyleBox(window, cue, styleOptions); + paddedOverlay.appendChild(styleBox.div); + + // Move the cue div to it's correct line position. + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + + // Remember the computed div so that we don't have to recompute it later + // if we don't have too. + cue.displayState = styleBox.div; + + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); + } + })(); +}; + +WebVTT$1.Parser = function(window, vttjs, decoder) { + if (!decoder) { + decoder = vttjs; + vttjs = {}; + } + if (!vttjs) { + vttjs = {}; + } + + this.window = window; + this.vttjs = vttjs; + this.state = "INITIAL"; + this.buffer = ""; + this.decoder = decoder || new TextDecoder("utf8"); + this.regionList = []; +}; + +WebVTT$1.Parser.prototype = { + // If the error is a ParsingError then report it to the consumer if + // possible. If it's not a ParsingError then throw it like normal. + reportOrThrowError: function(e) { + if (e instanceof ParsingError) { + this.onparsingerror && this.onparsingerror(e); + } else { + throw e; + } + }, + parse: function (data) { + var self = this; + + // If there is no data then we won't decode it, but will just try to parse + // whatever is in buffer already. This may occur in circumstances, for + // example when flush() is called. + if (data) { + // Try to decode the data that we received. + self.buffer += self.decoder.decode(data, {stream: true}); + } + + function collectNextLine() { + var buffer = self.buffer; + var pos = 0; + while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { + ++pos; + } + var line = buffer.substr(0, pos); + // Advance the buffer early in case we fail below. + if (buffer[pos] === '\r') { + ++pos; + } + if (buffer[pos] === '\n') { + ++pos; + } + self.buffer = buffer.substr(pos); + return line; + } + + // 3.4 WebVTT region and WebVTT region settings syntax + function parseRegion(input) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "id": + settings.set(k, v); + break; + case "width": + settings.percent(k, v); + break; + case "lines": + settings.integer(k, v); + break; + case "regionanchor": + case "viewportanchor": + var xy = v.split(','); + if (xy.length !== 2) { + break; + } + // We have to make sure both x and y parse, so use a temporary + // settings object here. + var anchor = new Settings(); + anchor.percent("x", xy[0]); + anchor.percent("y", xy[1]); + if (!anchor.has("x") || !anchor.has("y")) { + break; + } + settings.set(k + "X", anchor.get("x")); + settings.set(k + "Y", anchor.get("y")); + break; + case "scroll": + settings.alt(k, v, ["up"]); + break; + } + }, /=/, /\s/); + + // Create the region, using default values for any values that were not + // specified. + if (settings.has("id")) { + var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); + region.width = settings.get("width", 100); + region.lines = settings.get("lines", 3); + region.regionAnchorX = settings.get("regionanchorX", 0); + region.regionAnchorY = settings.get("regionanchorY", 100); + region.viewportAnchorX = settings.get("viewportanchorX", 0); + region.viewportAnchorY = settings.get("viewportanchorY", 100); + region.scroll = settings.get("scroll", ""); + // Register the region. + self.onregion && self.onregion(region); + // Remember the VTTRegion for later in case we parse any VTTCues that + // reference it. + self.regionList.push({ + id: settings.get("id"), + region: region + }); + } + } + + // draft-pantos-http-live-streaming-20 + // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 + // 3.5 WebVTT + function parseTimestampMap(input) { + var settings = new Settings(); + + parseOptions(input, function(k, v) { + switch(k) { + case "MPEGT": + settings.integer(k + 'S', v); + break; + case "LOCA": + settings.set(k + 'L', parseTimeStamp(v)); + break; + } + }, /[^\d]:/, /,/); + + self.ontimestampmap && self.ontimestampmap({ + "MPEGTS": settings.get("MPEGTS"), + "LOCAL": settings.get("LOCAL") + }); + } + + // 3.2 WebVTT metadata header syntax + function parseHeader(input) { + if (input.match(/X-TIMESTAMP-MAP/)) { + // This line contains HLS X-TIMESTAMP-MAP metadata + parseOptions(input, function(k, v) { + switch(k) { + case "X-TIMESTAMP-MAP": + parseTimestampMap(v); + break; + } + }, /=/); + } else { + parseOptions(input, function (k, v) { + switch (k) { + case "Region": + // 3.3 WebVTT region metadata header syntax + parseRegion(v); + break; + } + }, /:/); + } + + } + + // 5.1 WebVTT file parsing. + try { + var line; + if (self.state === "INITIAL") { + // We can't start parsing until we have the first line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } + + line = collectNextLine(); + + var m = line.match(/^WEBVTT([ \t].*)?$/); + if (!m || !m[0]) { + throw new ParsingError(ParsingError.Errors.BadSignature); + } + + self.state = "HEADER"; + } + + var alreadyCollectedLine = false; + while (self.buffer) { + // We can't parse a line until we have the full line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } + + if (!alreadyCollectedLine) { + line = collectNextLine(); + } else { + alreadyCollectedLine = false; + } + + switch (self.state) { + case "HEADER": + // 13-18 - Allow a header (metadata) under the WEBVTT line. + if (/:/.test(line)) { + parseHeader(line); + } else if (!line) { + // An empty line terminates the header and starts the body (cues). + self.state = "ID"; + } + continue; + case "NOTE": + // Ignore NOTE blocks. + if (!line) { + self.state = "ID"; + } + continue; + case "ID": + // Check for the start of NOTE blocks. + if (/^NOTE($|[ \t])/.test(line)) { + self.state = "NOTE"; + break; + } + // 19-29 - Allow any number of line terminators, then initialize new cue values. + if (!line) { + continue; + } + self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); + // Safari still uses the old middle value and won't accept center + try { + self.cue.align = "center"; + } catch (e) { + self.cue.align = "middle"; + } + self.state = "CUE"; + // 30-39 - Check if self line contains an optional identifier or timing data. + if (line.indexOf("-->") === -1) { + self.cue.id = line; + continue; + } + // Process line as start of a cue. + /*falls through*/ + case "CUE": + // 40 - Collect cue timings and settings. + try { + parseCue(line, self.cue, self.regionList); + } catch (e) { + self.reportOrThrowError(e); + // In case of an error ignore rest of the cue. + self.cue = null; + self.state = "BADCUE"; + continue; + } + self.state = "CUETEXT"; + continue; + case "CUETEXT": + var hasSubstring = line.indexOf("-->") !== -1; + // 34 - If we have an empty line then report the cue. + // 35 - If we have the special substring '-->' then report the cue, + // but do not collect the line as we need to process the current + // one as a new cue. + if (!line || hasSubstring && (alreadyCollectedLine = true)) { + // We are done parsing self cue. + self.oncue && self.oncue(self.cue); + self.cue = null; + self.state = "ID"; + continue; + } + if (self.cue.text) { + self.cue.text += "\n"; + } + self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); + continue; + case "BADCUE": // BADCUE + // 54-62 - Collect and discard the remaining cue. + if (!line) { + self.state = "ID"; + } + continue; + } + } + } catch (e) { + self.reportOrThrowError(e); + + // If we are currently parsing a cue, report what we have. + if (self.state === "CUETEXT" && self.cue && self.oncue) { + self.oncue(self.cue); + } + self.cue = null; + // Enter BADWEBVTT state if header was not parsed correctly otherwise + // another exception occurred so enter BADCUE state. + self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; + } + return this; + }, + flush: function () { + var self = this; + try { + // Finish decoding the stream. + self.buffer += self.decoder.decode(); + // Synthesize the end of the current cue or region. + if (self.cue || self.state === "HEADER") { + self.buffer += "\n\n"; + self.parse(); + } + // If we've flushed, parsed, and we're still on the INITIAL state then + // that means we don't have enough of the stream to parse the first + // line. + if (self.state === "INITIAL") { + throw new ParsingError(ParsingError.Errors.BadSignature); + } + } catch(e) { + self.reportOrThrowError(e); + } + self.onflush && self.onflush(); + return this; + } +}; + +var vtt = WebVTT$1; + +/** + * Copyright 2013 vtt.js Contributors + * + * 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. + */ + +var autoKeyword = "auto"; +var directionSetting = { + "": 1, + "lr": 1, + "rl": 1 +}; +var alignSetting = { + "start": 1, + "center": 1, + "end": 1, + "left": 1, + "right": 1, + "auto": 1, + "line-left": 1, + "line-right": 1 +}; + +function findDirectionSetting(value) { + if (typeof value !== "string") { + return false; + } + var dir = directionSetting[value.toLowerCase()]; + return dir ? value.toLowerCase() : false; +} + +function findAlignSetting(value) { + if (typeof value !== "string") { + return false; + } + var align = alignSetting[value.toLowerCase()]; + return align ? value.toLowerCase() : false; +} + +function VTTCue(startTime, endTime, text) { + /** + * Shim implementation specific properties. These properties are not in + * the spec. + */ + + // Lets us know when the VTTCue's data has changed in such a way that we need + // to recompute its display state. This lets us compute its display state + // lazily. + this.hasBeenReset = false; + + /** + * VTTCue and TextTrackCue properties + * http://dev.w3.org/html5/webvtt/#vttcue-interface + */ + + var _id = ""; + var _pauseOnExit = false; + var _startTime = startTime; + var _endTime = endTime; + var _text = text; + var _region = null; + var _vertical = ""; + var _snapToLines = true; + var _line = "auto"; + var _lineAlign = "start"; + var _position = "auto"; + var _positionAlign = "auto"; + var _size = 100; + var _align = "center"; + + Object.defineProperties(this, { + "id": { + enumerable: true, + get: function() { + return _id; + }, + set: function(value) { + _id = "" + value; + } + }, + + "pauseOnExit": { + enumerable: true, + get: function() { + return _pauseOnExit; + }, + set: function(value) { + _pauseOnExit = !!value; + } + }, + + "startTime": { + enumerable: true, + get: function() { + return _startTime; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("Start time must be set to a number."); + } + _startTime = value; + this.hasBeenReset = true; + } + }, + + "endTime": { + enumerable: true, + get: function() { + return _endTime; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("End time must be set to a number."); + } + _endTime = value; + this.hasBeenReset = true; + } + }, + + "text": { + enumerable: true, + get: function() { + return _text; + }, + set: function(value) { + _text = "" + value; + this.hasBeenReset = true; + } + }, + + "region": { + enumerable: true, + get: function() { + return _region; + }, + set: function(value) { + _region = value; + this.hasBeenReset = true; + } + }, + + "vertical": { + enumerable: true, + get: function() { + return _vertical; + }, + set: function(value) { + var setting = findDirectionSetting(value); + // Have to check for false because the setting an be an empty string. + if (setting === false) { + throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); + } + _vertical = setting; + this.hasBeenReset = true; + } + }, + + "snapToLines": { + enumerable: true, + get: function() { + return _snapToLines; + }, + set: function(value) { + _snapToLines = !!value; + this.hasBeenReset = true; + } + }, + + "line": { + enumerable: true, + get: function() { + return _line; + }, + set: function(value) { + if (typeof value !== "number" && value !== autoKeyword) { + throw new SyntaxError("Line: an invalid number or illegal string was specified."); + } + _line = value; + this.hasBeenReset = true; + } + }, + + "lineAlign": { + enumerable: true, + get: function() { + return _lineAlign; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + console.warn("lineAlign: an invalid or illegal string was specified."); + } else { + _lineAlign = setting; + this.hasBeenReset = true; + } + } + }, + + "position": { + enumerable: true, + get: function() { + return _position; + }, + set: function(value) { + if (value < 0 || value > 100) { + throw new Error("Position must be between 0 and 100."); + } + _position = value; + this.hasBeenReset = true; + } + }, + + "positionAlign": { + enumerable: true, + get: function() { + return _positionAlign; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + console.warn("positionAlign: an invalid or illegal string was specified."); + } else { + _positionAlign = setting; + this.hasBeenReset = true; + } + } + }, + + "size": { + enumerable: true, + get: function() { + return _size; + }, + set: function(value) { + if (value < 0 || value > 100) { + throw new Error("Size must be between 0 and 100."); + } + _size = value; + this.hasBeenReset = true; + } + }, + + "align": { + enumerable: true, + get: function() { + return _align; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("align: an invalid or illegal alignment string was specified."); + } + _align = setting; + this.hasBeenReset = true; + } + } + }); + + /** + * Other spec defined properties + */ + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state + this.displayState = undefined; +} + +/** + * VTTCue methods + */ + +VTTCue.prototype.getCueAsHTML = function() { + // Assume WebVTT.convertCueToDOMTree is on the global. + return WebVTT.convertCueToDOMTree(window, this.text); +}; + +var vttcue = VTTCue; + +/** + * Copyright 2013 vtt.js Contributors + * + * 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. + */ + +var scrollSetting = { + "": true, + "up": true +}; + +function findScrollSetting(value) { + if (typeof value !== "string") { + return false; + } + var scroll = scrollSetting[value.toLowerCase()]; + return scroll ? value.toLowerCase() : false; +} + +function isValidPercentValue(value) { + return typeof value === "number" && (value >= 0 && value <= 100); +} + +// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface +function VTTRegion() { + var _width = 100; + var _lines = 3; + var _regionAnchorX = 0; + var _regionAnchorY = 100; + var _viewportAnchorX = 0; + var _viewportAnchorY = 100; + var _scroll = ""; + + Object.defineProperties(this, { + "width": { + enumerable: true, + get: function() { + return _width; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("Width must be between 0 and 100."); + } + _width = value; + } + }, + "lines": { + enumerable: true, + get: function() { + return _lines; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("Lines must be set to a number."); + } + _lines = value; + } + }, + "regionAnchorY": { + enumerable: true, + get: function() { + return _regionAnchorY; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("RegionAnchorX must be between 0 and 100."); + } + _regionAnchorY = value; + } + }, + "regionAnchorX": { + enumerable: true, + get: function() { + return _regionAnchorX; + }, + set: function(value) { + if(!isValidPercentValue(value)) { + throw new Error("RegionAnchorY must be between 0 and 100."); + } + _regionAnchorX = value; + } + }, + "viewportAnchorY": { + enumerable: true, + get: function() { + return _viewportAnchorY; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorY must be between 0 and 100."); + } + _viewportAnchorY = value; + } + }, + "viewportAnchorX": { + enumerable: true, + get: function() { + return _viewportAnchorX; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorX must be between 0 and 100."); + } + _viewportAnchorX = value; + } + }, + "scroll": { + enumerable: true, + get: function() { + return _scroll; + }, + set: function(value) { + var setting = findScrollSetting(value); + // Have to check for false as an empty string is a legal value. + if (setting === false) { + console.warn("Scroll: an invalid or illegal string was specified."); + } else { + _scroll = setting; + } + } + } + }); +} + +var vttregion = VTTRegion; + +var browserIndex = createCommonjsModule(function (module) { +/** + * Copyright 2013 vtt.js Contributors + * + * 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. + */ + +// Default exports for Node. Export the extended versions of VTTCue and +// VTTRegion in Node since we likely want the capability to convert back and +// forth between JSON. If we don't then it's not that big of a deal since we're +// off browser. + + + +var vttjs = module.exports = { + WebVTT: vtt, + VTTCue: vttcue, + VTTRegion: vttregion +}; + +window_1$1.vttjs = vttjs; +window_1$1.WebVTT = vttjs.WebVTT; + +var cueShim = vttjs.VTTCue; +var regionShim = vttjs.VTTRegion; +var nativeVTTCue = window_1$1.VTTCue; +var nativeVTTRegion = window_1$1.VTTRegion; + +vttjs.shim = function() { + window_1$1.VTTCue = cueShim; + window_1$1.VTTRegion = regionShim; +}; + +vttjs.restore = function() { + window_1$1.VTTCue = nativeVTTCue; + window_1$1.VTTRegion = nativeVTTRegion; +}; + +if (!window_1$1.VTTCue) { + vttjs.shim(); +} +}); + +var setPrototypeOf = createCommonjsModule(function (module) { +function _setPrototypeOf(o, p) { + module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + + return _setPrototypeOf(o, p); +} + +module.exports = _setPrototypeOf; +}); + +function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + + try { + Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); + return true; + } catch (e) { + return false; + } +} + +var isNativeReflectConstruct = _isNativeReflectConstruct; + +var construct = createCommonjsModule(function (module) { +function _construct(Parent, args, Class) { + if (isNativeReflectConstruct()) { + module.exports = _construct = Reflect.construct; + } else { + module.exports = _construct = function _construct(Parent, args, Class) { + var a = [null]; + a.push.apply(a, args); + var Constructor = Function.bind.apply(Parent, a); + var instance = new Constructor(); + if (Class) setPrototypeOf(instance, Class.prototype); + return instance; + }; + } + + return _construct.apply(null, arguments); +} + +module.exports = _construct; +}); + +function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function"); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + writable: true, + configurable: true + } + }); + if (superClass) setPrototypeOf(subClass, superClass); +} + +var inherits = _inherits; + +var urlToolkit = createCommonjsModule(function (module, exports) { +// see https://tools.ietf.org/html/rfc1808 + +(function (root) { + var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/?#]*\/)*[^;?#]*)?(;[^?#]*)?(\?[^#]*)?(#.*)?$/; + var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; + var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; + var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g; + + var URLToolkit = { + // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // + // E.g + // With opts.alwaysNormalize = false (default, spec compliant) + // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g + // With opts.alwaysNormalize = true (not spec compliant) + // http://a.com/b/cd + /e/f/../g => http://a.com/e/g + buildAbsoluteURL: function (baseURL, relativeURL, opts) { + opts = opts || {}; + // remove any remaining space and CRLF + baseURL = baseURL.trim(); + relativeURL = relativeURL.trim(); + if (!relativeURL) { + // 2a) If the embedded URL is entirely empty, it inherits the + // entire base URL (i.e., is set equal to the base URL) + // and we are done. + if (!opts.alwaysNormalize) { + return baseURL; + } + var basePartsForNormalise = URLToolkit.parseURL(baseURL); + if (!basePartsForNormalise) { + throw new Error('Error trying to parse base URL.'); + } + basePartsForNormalise.path = URLToolkit.normalizePath( + basePartsForNormalise.path + ); + return URLToolkit.buildURLFromParts(basePartsForNormalise); + } + var relativeParts = URLToolkit.parseURL(relativeURL); + if (!relativeParts) { + throw new Error('Error trying to parse relative URL.'); + } + if (relativeParts.scheme) { + // 2b) If the embedded URL starts with a scheme name, it is + // interpreted as an absolute URL and we are done. + if (!opts.alwaysNormalize) { + return relativeURL; + } + relativeParts.path = URLToolkit.normalizePath(relativeParts.path); + return URLToolkit.buildURLFromParts(relativeParts); + } + var baseParts = URLToolkit.parseURL(baseURL); + if (!baseParts) { + throw new Error('Error trying to parse base URL.'); + } + if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { + // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc + // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' + var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); + baseParts.netLoc = pathParts[1]; + baseParts.path = pathParts[2]; + } + if (baseParts.netLoc && !baseParts.path) { + baseParts.path = '/'; + } + var builtParts = { + // 2c) Otherwise, the embedded URL inherits the scheme of + // the base URL. + scheme: baseParts.scheme, + netLoc: relativeParts.netLoc, + path: null, + params: relativeParts.params, + query: relativeParts.query, + fragment: relativeParts.fragment, + }; + if (!relativeParts.netLoc) { + // 3) If the embedded URL's is non-empty, we skip to + // Step 7. Otherwise, the embedded URL inherits the + // (if any) of the base URL. + builtParts.netLoc = baseParts.netLoc; + // 4) If the embedded URL path is preceded by a slash "/", the + // path is not relative and we skip to Step 7. + if (relativeParts.path[0] !== '/') { + if (!relativeParts.path) { + // 5) If the embedded URL path is empty (and not preceded by a + // slash), then the embedded URL inherits the base URL path + builtParts.path = baseParts.path; + // 5a) if the embedded URL's is non-empty, we skip to + // step 7; otherwise, it inherits the of the base + // URL (if any) and + if (!relativeParts.params) { + builtParts.params = baseParts.params; + // 5b) if the embedded URL's is non-empty, we skip to + // step 7; otherwise, it inherits the of the base + // URL (if any) and we skip to step 7. + if (!relativeParts.query) { + builtParts.query = baseParts.query; + } + } + } else { + // 6) The last segment of the base URL's path (anything + // following the rightmost slash "/", or the entire path if no + // slash is present) is removed and the embedded URL's path is + // appended in its place. + var baseURLPath = baseParts.path; + var newPath = + baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + + relativeParts.path; + builtParts.path = URLToolkit.normalizePath(newPath); + } + } + } + if (builtParts.path === null) { + builtParts.path = opts.alwaysNormalize + ? URLToolkit.normalizePath(relativeParts.path) + : relativeParts.path; + } + return URLToolkit.buildURLFromParts(builtParts); + }, + parseURL: function (url) { + var parts = URL_REGEX.exec(url); + if (!parts) { + return null; + } + return { + scheme: parts[1] || '', + netLoc: parts[2] || '', + path: parts[3] || '', + params: parts[4] || '', + query: parts[5] || '', + fragment: parts[6] || '', + }; + }, + normalizePath: function (path) { + // The following operations are + // then applied, in order, to the new path: + // 6a) All occurrences of "./", where "." is a complete path + // segment, are removed. + // 6b) If the path ends with "." as a complete path segment, + // that "." is removed. + path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); + // 6c) All occurrences of "/../", where is a + // complete path segment not equal to "..", are removed. + // Removal of these path segments is performed iteratively, + // removing the leftmost matching pattern on each iteration, + // until no matching pattern remains. + // 6d) If the path ends with "/..", where is a + // complete path segment not equal to "..", that + // "/.." is removed. + while ( + path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length + ) {} + return path.split('').reverse().join(''); + }, + buildURLFromParts: function (parts) { + return ( + parts.scheme + + parts.netLoc + + parts.path + + parts.params + + parts.query + + parts.fragment + ); + }, + }; + + module.exports = URLToolkit; +})(); +}); + +/*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */ + +function _extends() { + _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + return _extends.apply(this, arguments); +} + +function _inheritsLoose$1(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + subClass.__proto__ = superClass; +} + +function _assertThisInitialized$1(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; +} + +/** + * @file stream.js + */ + +/** + * A lightweight readable stream implementation that handles event dispatching. + * + * @class Stream + */ +var Stream = +/*#__PURE__*/ +function () { + function Stream() { + this.listeners = {}; + } + /** + * Add a listener for a specified event type. + * + * @param {string} type the event name + * @param {Function} listener the callback to be invoked when an event of + * the specified type occurs + */ + + + var _proto = Stream.prototype; + + _proto.on = function on(type, listener) { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + + this.listeners[type].push(listener); + } + /** + * Remove a listener for a specified event type. + * + * @param {string} type the event name + * @param {Function} listener a function previously registered for this + * type of event through `on` + * @return {boolean} if we could turn it off or not + */ + ; + + _proto.off = function off(type, listener) { + if (!this.listeners[type]) { + return false; + } + + var index = this.listeners[type].indexOf(listener); + this.listeners[type].splice(index, 1); + return index > -1; + } + /** + * Trigger an event of the specified type on this stream. Any additional + * arguments to this function are passed as parameters to event listeners. + * + * @param {string} type the event name + */ + ; + + _proto.trigger = function trigger(type) { + var callbacks = this.listeners[type]; + var i; + var length; + var args; + + if (!callbacks) { + return; + } // Slicing the arguments on every invocation of this method + // can add a significant amount of overhead. Avoid the + // intermediate object creation for the common case of a + // single callback argument + + + if (arguments.length === 2) { + length = callbacks.length; + + for (i = 0; i < length; ++i) { + callbacks[i].call(this, arguments[1]); + } + } else { + args = Array.prototype.slice.call(arguments, 1); + length = callbacks.length; + + for (i = 0; i < length; ++i) { + callbacks[i].apply(this, args); + } + } + } + /** + * Destroys the stream and cleans up. + */ + ; + + _proto.dispose = function dispose() { + this.listeners = {}; + } + /** + * Forwards all `data` events on this stream to the destination stream. The + * destination stream should provide a method `push` to receive the data + * events as they arrive. + * + * @param {Stream} destination the stream that will receive all `data` events + * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options + */ + ; + + _proto.pipe = function pipe(destination) { + this.on('data', function (data) { + destination.push(data); + }); + }; + + return Stream; +}(); + +/** + * A stream that buffers string input and generates a `data` event for each + * line. + * + * @class LineStream + * @extends Stream + */ + +var LineStream = +/*#__PURE__*/ +function (_Stream) { + _inheritsLoose$1(LineStream, _Stream); + + function LineStream() { + var _this; + + _this = _Stream.call(this) || this; + _this.buffer = ''; + return _this; + } + /** + * Add new data to be parsed. + * + * @param {string} data the text to process + */ + + + var _proto = LineStream.prototype; + + _proto.push = function push(data) { + var nextNewline; + this.buffer += data; + nextNewline = this.buffer.indexOf('\n'); + + for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { + this.trigger('data', this.buffer.substring(0, nextNewline)); + this.buffer = this.buffer.substring(nextNewline + 1); + } + }; + + return LineStream; +}(Stream); + +/** + * "forgiving" attribute list psuedo-grammar: + * attributes -> keyvalue (',' keyvalue)* + * keyvalue -> key '=' value + * key -> [^=]* + * value -> '"' [^"]* '"' | [^,]* + */ + +var attributeSeparator = function attributeSeparator() { + var key = '[^=]*'; + var value = '"[^"]*"|[^,]*'; + var keyvalue = '(?:' + key + ')=(?:' + value + ')'; + return new RegExp('(?:^|,)(' + keyvalue + ')'); +}; +/** + * Parse attributes from a line given the separator + * + * @param {string} attributes the attribute line to parse + */ + + +var parseAttributes = function parseAttributes(attributes) { + // split the string using attributes as the separator + var attrs = attributes.split(attributeSeparator()); + var result = {}; + var i = attrs.length; + var attr; + + while (i--) { + // filter out unmatched portions of the string + if (attrs[i] === '') { + continue; + } // split the key and value + + + attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value + + attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); + attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); + attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); + result[attr[0]] = attr[1]; + } + + return result; +}; +/** + * A line-level M3U8 parser event stream. It expects to receive input one + * line at a time and performs a context-free parse of its contents. A stream + * interpretation of a manifest can be useful if the manifest is expected to + * be too large to fit comfortably into memory or the entirety of the input + * is not immediately available. Otherwise, it's probably much easier to work + * with a regular `Parser` object. + * + * Produces `data` events with an object that captures the parser's + * interpretation of the input. That object has a property `tag` that is one + * of `uri`, `comment`, or `tag`. URIs only have a single additional + * property, `line`, which captures the entirety of the input without + * interpretation. Comments similarly have a single additional property + * `text` which is the input without the leading `#`. + * + * Tags always have a property `tagType` which is the lower-cased version of + * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, + * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized + * tags are given the tag type `unknown` and a single additional property + * `data` with the remainder of the input. + * + * @class ParseStream + * @extends Stream + */ + + +var ParseStream = +/*#__PURE__*/ +function (_Stream) { + _inheritsLoose$1(ParseStream, _Stream); + + function ParseStream() { + var _this; + + _this = _Stream.call(this) || this; + _this.customParsers = []; + _this.tagMappers = []; + return _this; + } + /** + * Parses an additional line of input. + * + * @param {string} line a single line of an M3U8 file to parse + */ + + + var _proto = ParseStream.prototype; + + _proto.push = function push(line) { + var _this2 = this; + + var match; + var event; // strip whitespace + + line = line.trim(); + + if (line.length === 0) { + // ignore empty lines + return; + } // URIs + + + if (line[0] !== '#') { + this.trigger('data', { + type: 'uri', + uri: line + }); + return; + } // map tags + + + var newLines = this.tagMappers.reduce(function (acc, mapper) { + var mappedLine = mapper(line); // skip if unchanged + + if (mappedLine === line) { + return acc; + } + + return acc.concat([mappedLine]); + }, [line]); + newLines.forEach(function (newLine) { + for (var i = 0; i < _this2.customParsers.length; i++) { + if (_this2.customParsers[i].call(_this2, newLine)) { + return; + } + } // Comments + + + if (newLine.indexOf('#EXT') !== 0) { + _this2.trigger('data', { + type: 'comment', + text: newLine.slice(1) + }); + + return; + } // strip off any carriage returns here so the regex matching + // doesn't have to account for them. + + + newLine = newLine.replace('\r', ''); // Tags + + match = /^#EXTM3U/.exec(newLine); + + if (match) { + _this2.trigger('data', { + type: 'tag', + tagType: 'm3u' + }); + + return; + } + + match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'inf' + }; + + if (match[1]) { + event.duration = parseFloat(match[1]); + } + + if (match[2]) { + event.title = match[2]; + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'targetduration' + }; + + if (match[1]) { + event.duration = parseInt(match[1], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'totalduration' + }; + + if (match[1]) { + event.duration = parseInt(match[1], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'version' + }; + + if (match[1]) { + event.version = parseInt(match[1], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'media-sequence' + }; + + if (match[1]) { + event.number = parseInt(match[1], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'discontinuity-sequence' + }; + + if (match[1]) { + event.number = parseInt(match[1], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'playlist-type' + }; + + if (match[1]) { + event.playlistType = match[1]; + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'byterange' + }; + + if (match[1]) { + event.length = parseInt(match[1], 10); + } + + if (match[2]) { + event.offset = parseInt(match[2], 10); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'allow-cache' + }; + + if (match[1]) { + event.allowed = !/NO/.test(match[1]); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'map' + }; + + if (match[1]) { + var attributes = parseAttributes(match[1]); + + if (attributes.URI) { + event.uri = attributes.URI; + } + + if (attributes.BYTERANGE) { + var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), + length = _attributes$BYTERANGE[0], + offset = _attributes$BYTERANGE[1]; + + event.byterange = {}; + + if (length) { + event.byterange.length = parseInt(length, 10); + } + + if (offset) { + event.byterange.offset = parseInt(offset, 10); + } + } + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'stream-inf' + }; + + if (match[1]) { + event.attributes = parseAttributes(match[1]); + + if (event.attributes.RESOLUTION) { + var split = event.attributes.RESOLUTION.split('x'); + var resolution = {}; + + if (split[0]) { + resolution.width = parseInt(split[0], 10); + } + + if (split[1]) { + resolution.height = parseInt(split[1], 10); + } + + event.attributes.RESOLUTION = resolution; + } + + if (event.attributes.BANDWIDTH) { + event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); + } + + if (event.attributes['PROGRAM-ID']) { + event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); + } + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'media' + }; + + if (match[1]) { + event.attributes = parseAttributes(match[1]); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-ENDLIST/.exec(newLine); + + if (match) { + _this2.trigger('data', { + type: 'tag', + tagType: 'endlist' + }); + + return; + } + + match = /^#EXT-X-DISCONTINUITY/.exec(newLine); + + if (match) { + _this2.trigger('data', { + type: 'tag', + tagType: 'discontinuity' + }); + + return; + } + + match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'program-date-time' + }; + + if (match[1]) { + event.dateTimeString = match[1]; + event.dateTimeObject = new Date(match[1]); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'key' + }; + + if (match[1]) { + event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array + + if (event.attributes.IV) { + if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { + event.attributes.IV = event.attributes.IV.substring(2); + } + + event.attributes.IV = event.attributes.IV.match(/.{8}/g); + event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); + event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); + event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); + event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); + event.attributes.IV = new Uint32Array(event.attributes.IV); + } + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-START:?(.*)$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'start' + }; + + if (match[1]) { + event.attributes = parseAttributes(match[1]); + event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); + event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'cue-out-cont' + }; + + if (match[1]) { + event.data = match[1]; + } else { + event.data = ''; + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'cue-out' + }; + + if (match[1]) { + event.data = match[1]; + } else { + event.data = ''; + } + + _this2.trigger('data', event); + + return; + } + + match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); + + if (match) { + event = { + type: 'tag', + tagType: 'cue-in' + }; + + if (match[1]) { + event.data = match[1]; + } else { + event.data = ''; + } + + _this2.trigger('data', event); + + return; + } // unknown tag type + + + _this2.trigger('data', { + type: 'tag', + data: newLine.slice(4) + }); + }); + } + /** + * Add a parser for custom headers + * + * @param {Object} options a map of options for the added parser + * @param {RegExp} options.expression a regular expression to match the custom header + * @param {string} options.customType the custom type to register to the output + * @param {Function} [options.dataParser] function to parse the line into an object + * @param {boolean} [options.segment] should tag data be attached to the segment object + */ + ; + + _proto.addParser = function addParser(_ref) { + var _this3 = this; + + var expression = _ref.expression, + customType = _ref.customType, + dataParser = _ref.dataParser, + segment = _ref.segment; + + if (typeof dataParser !== 'function') { + dataParser = function dataParser(line) { + return line; + }; + } + + this.customParsers.push(function (line) { + var match = expression.exec(line); + + if (match) { + _this3.trigger('data', { + type: 'custom', + data: dataParser(line), + customType: customType, + segment: segment + }); + + return true; + } + }); + } + /** + * Add a custom header mapper + * + * @param {Object} options + * @param {RegExp} options.expression a regular expression to match the custom header + * @param {Function} options.map function to translate tag into a different tag + */ + ; + + _proto.addTagMapper = function addTagMapper(_ref2) { + var expression = _ref2.expression, + map = _ref2.map; + + var mapFn = function mapFn(line) { + if (expression.test(line)) { + return map(line); + } + + return line; + }; + + this.tagMappers.push(mapFn); + }; + + return ParseStream; +}(Stream); + +function decodeB64ToUint8Array(b64Text) { + var decodedString = window_1$1.atob(b64Text || ''); + var array = new Uint8Array(decodedString.length); + + for (var i = 0; i < decodedString.length; i++) { + array[i] = decodedString.charCodeAt(i); + } + + return array; +} + +/** + * A parser for M3U8 files. The current interpretation of the input is + * exposed as a property `manifest` on parser objects. It's just two lines to + * create and parse a manifest once you have the contents available as a string: + * + * ```js + * var parser = new m3u8.Parser(); + * parser.push(xhr.responseText); + * ``` + * + * New input can later be applied to update the manifest object by calling + * `push` again. + * + * The parser attempts to create a usable manifest object even if the + * underlying input is somewhat nonsensical. It emits `info` and `warning` + * events during the parse if it encounters input that seems invalid or + * requires some property of the manifest object to be defaulted. + * + * @class Parser + * @extends Stream + */ + +var Parser = +/*#__PURE__*/ +function (_Stream) { + _inheritsLoose$1(Parser, _Stream); + + function Parser() { + var _this; + + _this = _Stream.call(this) || this; + _this.lineStream = new LineStream(); + _this.parseStream = new ParseStream(); + + _this.lineStream.pipe(_this.parseStream); + /* eslint-disable consistent-this */ + + + var self = _assertThisInitialized$1(_this); + /* eslint-enable consistent-this */ + + + var uris = []; + var currentUri = {}; // if specified, the active EXT-X-MAP definition + + var currentMap; // if specified, the active decryption key + + var _key; + + var noop = function noop() {}; + + var defaultMediaGroups = { + 'AUDIO': {}, + 'VIDEO': {}, + 'CLOSED-CAPTIONS': {}, + 'SUBTITLES': {} + }; // This is the Widevine UUID from DASH IF IOP. The same exact string is + // used in MPDs with Widevine encrypted streams. + + var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities + + var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data + + _this.manifest = { + allowCache: true, + discontinuityStarts: [], + segments: [] + }; // update the manifest with the m3u8 entry from the parse stream + + _this.parseStream.on('data', function (entry) { + var mediaGroup; + var rendition; + ({ + tag: function tag() { + // switch based on the tag type + (({ + 'allow-cache': function allowCache() { + this.manifest.allowCache = entry.allowed; + + if (!('allowed' in entry)) { + this.trigger('info', { + message: 'defaulting allowCache to YES' + }); + this.manifest.allowCache = true; + } + }, + byterange: function byterange() { + var byterange = {}; + + if ('length' in entry) { + currentUri.byterange = byterange; + byterange.length = entry.length; + + if (!('offset' in entry)) { + this.trigger('info', { + message: 'defaulting offset to zero' + }); + entry.offset = 0; + } + } + + if ('offset' in entry) { + currentUri.byterange = byterange; + byterange.offset = entry.offset; + } + }, + endlist: function endlist() { + this.manifest.endList = true; + }, + inf: function inf() { + if (!('mediaSequence' in this.manifest)) { + this.manifest.mediaSequence = 0; + this.trigger('info', { + message: 'defaulting media sequence to zero' + }); + } + + if (!('discontinuitySequence' in this.manifest)) { + this.manifest.discontinuitySequence = 0; + this.trigger('info', { + message: 'defaulting discontinuity sequence to zero' + }); + } + + if (entry.duration > 0) { + currentUri.duration = entry.duration; + } + + if (entry.duration === 0) { + currentUri.duration = 0.01; + this.trigger('info', { + message: 'updating zero segment duration to a small value' + }); + } + + this.manifest.segments = uris; + }, + key: function key() { + if (!entry.attributes) { + this.trigger('warn', { + message: 'ignoring key declaration without attribute list' + }); + return; + } // clear the active encryption key + + + if (entry.attributes.METHOD === 'NONE') { + _key = null; + return; + } + + if (!entry.attributes.URI) { + this.trigger('warn', { + message: 'ignoring key declaration without URI' + }); + return; + } // check if the content is encrypted for Widevine + // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf + + + if (entry.attributes.KEYFORMAT === widevineUuid) { + var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; + + if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { + this.trigger('warn', { + message: 'invalid key method provided for Widevine' + }); + return; + } + + if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { + this.trigger('warn', { + message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' + }); + } + + if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { + this.trigger('warn', { + message: 'invalid key URI provided for Widevine' + }); + return; + } + + if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { + this.trigger('warn', { + message: 'invalid key ID provided for Widevine' + }); + return; + } // if Widevine key attributes are valid, store them as `contentProtection` + // on the manifest to emulate Widevine tag structure in a DASH mpd + + + this.manifest.contentProtection = { + 'com.widevine.alpha': { + attributes: { + schemeIdUri: entry.attributes.KEYFORMAT, + // remove '0x' from the key id string + keyId: entry.attributes.KEYID.substring(2) + }, + // decode the base64-encoded PSSH box + pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) + } + }; + return; + } + + if (!entry.attributes.METHOD) { + this.trigger('warn', { + message: 'defaulting key method to AES-128' + }); + } // setup an encryption key for upcoming segments + + + _key = { + method: entry.attributes.METHOD || 'AES-128', + uri: entry.attributes.URI + }; + + if (typeof entry.attributes.IV !== 'undefined') { + _key.iv = entry.attributes.IV; + } + }, + 'media-sequence': function mediaSequence() { + if (!isFinite(entry.number)) { + this.trigger('warn', { + message: 'ignoring invalid media sequence: ' + entry.number + }); + return; + } + + this.manifest.mediaSequence = entry.number; + }, + 'discontinuity-sequence': function discontinuitySequence() { + if (!isFinite(entry.number)) { + this.trigger('warn', { + message: 'ignoring invalid discontinuity sequence: ' + entry.number + }); + return; + } + + this.manifest.discontinuitySequence = entry.number; + currentTimeline = entry.number; + }, + 'playlist-type': function playlistType() { + if (!/VOD|EVENT/.test(entry.playlistType)) { + this.trigger('warn', { + message: 'ignoring unknown playlist type: ' + entry.playlist + }); + return; + } + + this.manifest.playlistType = entry.playlistType; + }, + map: function map() { + currentMap = {}; + + if (entry.uri) { + currentMap.uri = entry.uri; + } + + if (entry.byterange) { + currentMap.byterange = entry.byterange; + } + }, + 'stream-inf': function streamInf() { + this.manifest.playlists = uris; + this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; + + if (!entry.attributes) { + this.trigger('warn', { + message: 'ignoring empty stream-inf attributes' + }); + return; + } + + if (!currentUri.attributes) { + currentUri.attributes = {}; + } + + _extends(currentUri.attributes, entry.attributes); + }, + media: function media() { + this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; + + if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { + this.trigger('warn', { + message: 'ignoring incomplete or missing media group' + }); + return; + } // find the media group, creating defaults as necessary + + + var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; + mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; + mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata + + rendition = { + default: /yes/i.test(entry.attributes.DEFAULT) + }; + + if (rendition.default) { + rendition.autoselect = true; + } else { + rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); + } + + if (entry.attributes.LANGUAGE) { + rendition.language = entry.attributes.LANGUAGE; + } + + if (entry.attributes.URI) { + rendition.uri = entry.attributes.URI; + } + + if (entry.attributes['INSTREAM-ID']) { + rendition.instreamId = entry.attributes['INSTREAM-ID']; + } + + if (entry.attributes.CHARACTERISTICS) { + rendition.characteristics = entry.attributes.CHARACTERISTICS; + } + + if (entry.attributes.FORCED) { + rendition.forced = /yes/i.test(entry.attributes.FORCED); + } // insert the new rendition + + + mediaGroup[entry.attributes.NAME] = rendition; + }, + discontinuity: function discontinuity() { + currentTimeline += 1; + currentUri.discontinuity = true; + this.manifest.discontinuityStarts.push(uris.length); + }, + 'program-date-time': function programDateTime() { + if (typeof this.manifest.dateTimeString === 'undefined') { + // PROGRAM-DATE-TIME is a media-segment tag, but for backwards + // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag + // to the manifest object + // TODO: Consider removing this in future major version + this.manifest.dateTimeString = entry.dateTimeString; + this.manifest.dateTimeObject = entry.dateTimeObject; + } + + currentUri.dateTimeString = entry.dateTimeString; + currentUri.dateTimeObject = entry.dateTimeObject; + }, + targetduration: function targetduration() { + if (!isFinite(entry.duration) || entry.duration < 0) { + this.trigger('warn', { + message: 'ignoring invalid target duration: ' + entry.duration + }); + return; + } + + this.manifest.targetDuration = entry.duration; + }, + totalduration: function totalduration() { + if (!isFinite(entry.duration) || entry.duration < 0) { + this.trigger('warn', { + message: 'ignoring invalid total duration: ' + entry.duration + }); + return; + } + + this.manifest.totalDuration = entry.duration; + }, + start: function start() { + if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { + this.trigger('warn', { + message: 'ignoring start declaration without appropriate attribute list' + }); + return; + } + + this.manifest.start = { + timeOffset: entry.attributes['TIME-OFFSET'], + precise: entry.attributes.PRECISE + }; + }, + 'cue-out': function cueOut() { + currentUri.cueOut = entry.data; + }, + 'cue-out-cont': function cueOutCont() { + currentUri.cueOutCont = entry.data; + }, + 'cue-in': function cueIn() { + currentUri.cueIn = entry.data; + } + })[entry.tagType] || noop).call(self); + }, + uri: function uri() { + currentUri.uri = entry.uri; + uris.push(currentUri); // if no explicit duration was declared, use the target duration + + if (this.manifest.targetDuration && !('duration' in currentUri)) { + this.trigger('warn', { + message: 'defaulting segment duration to the target duration' + }); + currentUri.duration = this.manifest.targetDuration; + } // annotate with encryption information, if necessary + + + if (_key) { + currentUri.key = _key; + } + + currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary + + if (currentMap) { + currentUri.map = currentMap; + } // prepare for the next URI + + + currentUri = {}; + }, + comment: function comment() {// comments are not important for playback + }, + custom: function custom() { + // if this is segment-level data attach the output to the segment + if (entry.segment) { + currentUri.custom = currentUri.custom || {}; + currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object + } else { + this.manifest.custom = this.manifest.custom || {}; + this.manifest.custom[entry.customType] = entry.data; + } + } + })[entry.type].call(self); + }); + + return _this; + } + /** + * Parse the input string and update the manifest object. + * + * @param {string} chunk a potentially incomplete portion of the manifest + */ + + + var _proto = Parser.prototype; + + _proto.push = function push(chunk) { + this.lineStream.push(chunk); + } + /** + * Flush any remaining input. This can be handy if the last line of an M3U8 + * manifest did not contain a trailing newline but the file has been + * completely received. + */ + ; + + _proto.end = function end() { + // flush any buffered input + this.lineStream.push('\n'); + } + /** + * Add an additional parser for non-standard tags + * + * @param {Object} options a map of options for the added parser + * @param {RegExp} options.expression a regular expression to match the custom header + * @param {string} options.type the type to register to the output + * @param {Function} [options.dataParser] function to parse the line into an object + * @param {boolean} [options.segment] should tag data be attached to the segment object + */ + ; + + _proto.addParser = function addParser(options) { + this.parseStream.addParser(options); + } + /** + * Add a custom header mapper + * + * @param {Object} options + * @param {RegExp} options.expression a regular expression to match the custom header + * @param {Function} options.map function to translate tag into a different tag + */ + ; + + _proto.addTagMapper = function addTagMapper(options) { + this.parseStream.addTagMapper(options); + }; + + return Parser; +}(Stream); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var URLToolkit = _interopDefault(urlToolkit); +var window$1 = _interopDefault(window_1$1); + +var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { + // return early if we don't need to resolve + if (/^[a-z]+:/i.test(relativeUrl)) { + return relativeUrl; + } // if the base URL is relative then combine with the current location + + + if (!/\/\//i.test(baseUrl)) { + baseUrl = URLToolkit.buildAbsoluteURL(window$1.location && window$1.location.href || '', baseUrl); + } + + return URLToolkit.buildAbsoluteURL(baseUrl, relativeUrl); +}; + +var resolveUrl_1 = resolveUrl; + +function _interopDefault$1 (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var window$2 = _interopDefault$1(window_1$1); + +var atob = function atob(s) { + return window$2.atob ? window$2.atob(s) : Buffer.from(s, 'base64').toString('binary'); +}; + +function decodeB64ToUint8Array$1(b64Text) { + var decodedString = atob(b64Text); + var array = new Uint8Array(decodedString.length); + + for (var i = 0; i < decodedString.length; i++) { + array[i] = decodedString.charCodeAt(i); + } + + return array; +} + +var decodeB64ToUint8Array_1 = decodeB64ToUint8Array$1; + +//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] +//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] +//[5] Name ::= NameStartChar (NameChar)* +var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/;//\u10000-\uEFFFF +var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); +var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$'); +//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ +//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') + +//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE +//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE +var S_TAG = 0;//tag name offerring +var S_ATTR = 1;//attr name offerring +var S_ATTR_SPACE=2;//attr name end and space offer +var S_EQ = 3;//=space? +var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only) +var S_ATTR_END = 5;//attr value end and no space(quot end) +var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer) +var S_TAG_CLOSE = 7;//closed el + +function XMLReader(){ + +} + +XMLReader.prototype = { + parse:function(source,defaultNSMap,entityMap){ + var domBuilder = this.domBuilder; + domBuilder.startDocument(); + _copy(defaultNSMap ,defaultNSMap = {}); + parse(source,defaultNSMap,entityMap, + domBuilder,this.errorHandler); + domBuilder.endDocument(); + } +}; +function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){ + function fixedFromCharCode(code) { + // String.prototype.fromCharCode does not supports + // > 2 bytes unicode chars directly + if (code > 0xffff) { + code -= 0x10000; + var surrogate1 = 0xd800 + (code >> 10) + , surrogate2 = 0xdc00 + (code & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } else { + return String.fromCharCode(code); + } + } + function entityReplacer(a){ + var k = a.slice(1,-1); + if(k in entityMap){ + return entityMap[k]; + }else if(k.charAt(0) === '#'){ + return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x'))) + }else { + errorHandler.error('entity not found:'+a); + return a; + } + } + function appendText(end){//has some bugs + if(end>start){ + var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer); + locator&&position(start); + domBuilder.characters(xt,0,end-start); + start = end; + } + } + function position(p,m){ + while(p>=lineEnd && (m = linePattern.exec(source))){ + lineStart = m.index; + lineEnd = lineStart + m[0].length; + locator.lineNumber++; + //console.log('line++:',locator,startPos,endPos) + } + locator.columnNumber = p-lineStart+1; + } + var lineStart = 0; + var lineEnd = 0; + var linePattern = /.*(?:\r\n?|\n)|.*$/g; + var locator = domBuilder.locator; + + var parseStack = [{currentNSMap:defaultNSMapCopy}]; + var closeMap = {}; + var start = 0; + while(true){ + try{ + var tagStart = source.indexOf('<',start); + if(tagStart<0){ + if(!source.substr(start).match(/^\s*$/)){ + var doc = domBuilder.doc; + var text = doc.createTextNode(source.substr(start)); + doc.appendChild(text); + domBuilder.currentElement = text; + } + return; + } + if(tagStart>start){ + appendText(tagStart); + } + switch(source.charAt(tagStart+1)){ + case '/': + var end = source.indexOf('>',tagStart+3); + var tagName = source.substring(tagStart+2,end); + var config = parseStack.pop(); + if(end<0){ + + tagName = source.substring(tagStart+2).replace(/[\s<].*/,''); + //console.error('#@@@@@@'+tagName) + errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName); + end = tagStart+1+tagName.length; + }else if(tagName.match(/\s + locator&&position(tagStart); + end = parseInstruction(source,tagStart,domBuilder); + break; + case '!':// start){ + start = end; + }else { + //TODO: 这里有可能sax回退,有位置错误风险 + appendText(Math.max(tagStart,start)+1); + } + } +} +function copyLocator(f,t){ + t.lineNumber = f.lineNumber; + t.columnNumber = f.columnNumber; + return t; +} + +/** + * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); + * @return end of the elementStartPart(end of elementEndPart for selfClosed el) + */ +function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){ + var attrName; + var value; + var p = ++start; + var s = S_TAG;//status + while(true){ + var c = source.charAt(p); + switch(c){ + case '=': + if(s === S_ATTR){//attrName + attrName = source.slice(start,p); + s = S_EQ; + }else if(s === S_ATTR_SPACE){ + s = S_EQ; + }else { + //fatalError: equal must after attrName or space after attrName + throw new Error('attribute equal must after attrName'); + } + break; + case '\'': + case '"': + if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE + ){//equal + if(s === S_ATTR){ + errorHandler.warning('attribute value must after "="'); + attrName = source.slice(start,p); + } + start = p+1; + p = source.indexOf(c,start); + if(p>0){ + value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); + el.add(attrName,value,start-1); + s = S_ATTR_END; + }else { + //fatalError: no end quot match + throw new Error('attribute value no end \''+c+'\' match'); + } + }else if(s == S_ATTR_NOQUOT_VALUE){ + value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); + //console.log(attrName,value,start,p) + el.add(attrName,value,start); + //console.dir(el) + errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!'); + start = p+1; + s = S_ATTR_END; + }else { + //fatalError: no equal before + throw new Error('attribute value must after "="'); + } + break; + case '/': + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + s =S_TAG_CLOSE; + el.closed = true; + case S_ATTR_NOQUOT_VALUE: + case S_ATTR: + case S_ATTR_SPACE: + break; + //case S_EQ: + default: + throw new Error("attribute invalid close char('/')") + } + break; + case ''://end document + //throw new Error('unexpected end of input') + errorHandler.error('unexpected end of input'); + if(s == S_TAG){ + el.setTagName(source.slice(start,p)); + } + return p; + case '>': + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + break;//normal + case S_ATTR_NOQUOT_VALUE://Compatible state + case S_ATTR: + value = source.slice(start,p); + if(value.slice(-1) === '/'){ + el.closed = true; + value = value.slice(0,-1); + } + case S_ATTR_SPACE: + if(s === S_ATTR_SPACE){ + value = attrName; + } + if(s == S_ATTR_NOQUOT_VALUE){ + errorHandler.warning('attribute "'+value+'" missed quot(")!!'); + el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start); + }else { + if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)){ + errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!'); + } + el.add(value,value,start); + } + break; + case S_EQ: + throw new Error('attribute value missed!!'); + } +// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) + return p; + /*xml space '\x20' | #x9 | #xD | #xA; */ + case '\u0080': + c = ' '; + default: + if(c<= ' '){//space + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p));//tagName + s = S_TAG_SPACE; + break; + case S_ATTR: + attrName = source.slice(start,p); + s = S_ATTR_SPACE; + break; + case S_ATTR_NOQUOT_VALUE: + var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); + errorHandler.warning('attribute "'+value+'" missed quot(")!!'); + el.add(attrName,value,start); + case S_ATTR_END: + s = S_TAG_SPACE; + break; + //case S_TAG_SPACE: + //case S_EQ: + //case S_ATTR_SPACE: + // void();break; + //case S_TAG_CLOSE: + //ignore warning + } + }else {//not space +//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE +//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE + switch(s){ + //case S_TAG:void();break; + //case S_ATTR:void();break; + //case S_ATTR_NOQUOT_VALUE:void();break; + case S_ATTR_SPACE: + var tagName = el.tagName; + if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)){ + errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!'); + } + el.add(attrName,attrName,start); + start = p; + s = S_ATTR; + break; + case S_ATTR_END: + errorHandler.warning('attribute space is required"'+attrName+'"!!'); + case S_TAG_SPACE: + s = S_ATTR; + start = p; + break; + case S_EQ: + s = S_ATTR_NOQUOT_VALUE; + start = p; + break; + case S_TAG_CLOSE: + throw new Error("elements closed character '/' and '>' must be connected to"); + } + } + }//end outer switch + //console.log('p++',p) + p++; + } +} +/** + * @return true if has new namespace define + */ +function appendElement(el,domBuilder,currentNSMap){ + var tagName = el.tagName; + var localNSMap = null; + //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; + var i = el.length; + while(i--){ + var a = el[i]; + var qName = a.qName; + var value = a.value; + var nsp = qName.indexOf(':'); + if(nsp>0){ + var prefix = a.prefix = qName.slice(0,nsp); + var localName = qName.slice(nsp+1); + var nsPrefix = prefix === 'xmlns' && localName; + }else { + localName = qName; + prefix = null; + nsPrefix = qName === 'xmlns' && ''; + } + //can not set prefix,because prefix !== '' + a.localName = localName ; + //prefix == null for no ns prefix attribute + if(nsPrefix !== false){//hack!! + if(localNSMap == null){ + localNSMap = {}; + //console.log(currentNSMap,0) + _copy(currentNSMap,currentNSMap={}); + //console.log(currentNSMap,1) + } + currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; + a.uri = 'http://www.w3.org/2000/xmlns/'; + domBuilder.startPrefixMapping(nsPrefix, value); + } + } + var i = el.length; + while(i--){ + a = el[i]; + var prefix = a.prefix; + if(prefix){//no prefix attribute has no namespace + if(prefix === 'xml'){ + a.uri = 'http://www.w3.org/XML/1998/namespace'; + }if(prefix !== 'xmlns'){ + a.uri = currentNSMap[prefix || '']; + + //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} + } + } + } + var nsp = tagName.indexOf(':'); + if(nsp>0){ + prefix = el.prefix = tagName.slice(0,nsp); + localName = el.localName = tagName.slice(nsp+1); + }else { + prefix = null;//important!! + localName = el.localName = tagName; + } + //no prefix element has default namespace + var ns = el.uri = currentNSMap[prefix || '']; + domBuilder.startElement(ns,localName,tagName,el); + //endPrefixMapping and startPrefixMapping have not any help for dom builder + //localNSMap = null + if(el.closed){ + domBuilder.endElement(ns,localName,tagName); + if(localNSMap){ + for(prefix in localNSMap){ + domBuilder.endPrefixMapping(prefix); + } + } + }else { + el.currentNSMap = currentNSMap; + el.localNSMap = localNSMap; + //parseStack.push(el); + return true; + } +} +function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){ + if(/^(?:script|textarea)$/i.test(tagName)){ + var elEndStart = source.indexOf('',elStartEnd); + var text = source.substring(elStartEnd+1,elEndStart); + if(/[&<]/.test(text)){ + if(/^script$/i.test(tagName)){ + //if(!/\]\]>/.test(text)){ + //lexHandler.startCDATA(); + domBuilder.characters(text,0,text.length); + //lexHandler.endCDATA(); + return elEndStart; + //} + }//}else{//text area + text = text.replace(/&#?\w+;/g,entityReplacer); + domBuilder.characters(text,0,text.length); + return elEndStart; + //} + + } + } + return elStartEnd+1; +} +function fixSelfClosed(source,elStartEnd,tagName,closeMap){ + //if(tagName in closeMap){ + var pos = closeMap[tagName]; + if(pos == null){ + //console.log(tagName) + pos = source.lastIndexOf(''); + if(pos',start+4); + //append comment source.substring(4,end)//"); + case DOCUMENT_TYPE_NODE: + var pubid = node.publicId; + var sysid = node.systemId; + buf.push(''); + }else if(sysid && sysid!='.'){ + buf.push(' SYSTEM "',sysid,'">'); + }else { + var sub = node.internalSubset; + if(sub){ + buf.push(" [",sub,"]"); + } + buf.push(">"); + } + return; + case PROCESSING_INSTRUCTION_NODE: + return buf.push( ""); + case ENTITY_REFERENCE_NODE: + return buf.push( '&',node.nodeName,';'); + //case ENTITY_NODE: + //case NOTATION_NODE: + default: + buf.push('??',node.nodeName); + } +} +function importNode(doc,node,deep){ + var node2; + switch (node.nodeType) { + case ELEMENT_NODE: + node2 = node.cloneNode(false); + node2.ownerDocument = doc; + //var attrs = node2.attributes; + //var len = attrs.length; + //for(var i=0;i','amp':'&','quot':'"','apos':"'"}; + if(locator){ + domBuilder.setDocumentLocator(locator); + } + + sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); + sax.domBuilder = options.domBuilder || domBuilder; + if(/\/x?html?$/.test(mimeType)){ + entityMap.nbsp = '\xa0'; + entityMap.copy = '\xa9'; + defaultNSMap['']= 'http://www.w3.org/1999/xhtml'; + } + defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace'; + if(source){ + sax.parse(source,defaultNSMap,entityMap); + }else { + sax.errorHandler.error("invalid doc source"); + } + return domBuilder.doc; +}; +function buildErrorHandler(errorImpl,domBuilder,locator){ + if(!errorImpl){ + if(domBuilder instanceof DOMHandler){ + return domBuilder; + } + errorImpl = domBuilder ; + } + var errorHandler = {}; + var isCallback = errorImpl instanceof Function; + locator = locator||{}; + function build(key){ + var fn = errorImpl[key]; + if(!fn && isCallback){ + fn = errorImpl.length == 2?function(msg){errorImpl(key,msg);}:errorImpl; + } + errorHandler[key] = fn && function(msg){ + fn('[xmldom '+key+']\t'+msg+_locator(locator)); + }||function(){}; + } + build('warning'); + build('error'); + build('fatalError'); + return errorHandler; +} + +//console.log('#\n\n\n\n\n\n\n####') +/** + * +ContentHandler+ErrorHandler + * +LexicalHandler+EntityResolver2 + * -DeclHandler-DTDHandler + * + * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler + * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 + * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html + */ +function DOMHandler() { + this.cdata = false; +} +function position(locator,node){ + node.lineNumber = locator.lineNumber; + node.columnNumber = locator.columnNumber; +} +/** + * @see org.xml.sax.ContentHandler#startDocument + * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html + */ +DOMHandler.prototype = { + startDocument : function() { + this.doc = new DOMImplementation().createDocument(null, null, null); + if (this.locator) { + this.doc.documentURI = this.locator.systemId; + } + }, + startElement:function(namespaceURI, localName, qName, attrs) { + var doc = this.doc; + var el = doc.createElementNS(namespaceURI, qName||localName); + var len = attrs.length; + appendElement(this, el); + this.currentElement = el; + + this.locator && position(this.locator,el); + for (var i = 0 ; i < len; i++) { + var namespaceURI = attrs.getURI(i); + var value = attrs.getValue(i); + var qName = attrs.getQName(i); + var attr = doc.createAttributeNS(namespaceURI, qName); + this.locator &&position(attrs.getLocator(i),attr); + attr.value = attr.nodeValue = value; + el.setAttributeNode(attr); + } + }, + endElement:function(namespaceURI, localName, qName) { + var current = this.currentElement; + var tagName = current.tagName; + this.currentElement = current.parentNode; + }, + startPrefixMapping:function(prefix, uri) { + }, + endPrefixMapping:function(prefix) { + }, + processingInstruction:function(target, data) { + var ins = this.doc.createProcessingInstruction(target, data); + this.locator && position(this.locator,ins); + appendElement(this, ins); + }, + ignorableWhitespace:function(ch, start, length) { + }, + characters:function(chars, start, length) { + chars = _toString.apply(this,arguments); + //console.log(chars) + if(chars){ + if (this.cdata) { + var charNode = this.doc.createCDATASection(chars); + } else { + var charNode = this.doc.createTextNode(chars); + } + if(this.currentElement){ + this.currentElement.appendChild(charNode); + }else if(/^\s*$/.test(chars)){ + this.doc.appendChild(charNode); + //process xml + } + this.locator && position(this.locator,charNode); + } + }, + skippedEntity:function(name) { + }, + endDocument:function() { + this.doc.normalize(); + }, + setDocumentLocator:function (locator) { + if(this.locator = locator){// && !('lineNumber' in locator)){ + locator.lineNumber = 0; + } + }, + //LexicalHandler + comment:function(chars, start, length) { + chars = _toString.apply(this,arguments); + var comm = this.doc.createComment(chars); + this.locator && position(this.locator,comm); + appendElement(this, comm); + }, + + startCDATA:function() { + //used in characters() methods + this.cdata = true; + }, + endCDATA:function() { + this.cdata = false; + }, + + startDTD:function(name, publicId, systemId) { + var impl = this.doc.implementation; + if (impl && impl.createDocumentType) { + var dt = impl.createDocumentType(name, publicId, systemId); + this.locator && position(this.locator,dt); + appendElement(this, dt); + } + }, + /** + * @see org.xml.sax.ErrorHandler + * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html + */ + warning:function(error) { + console.warn('[xmldom warning]\t'+error,_locator(this.locator)); + }, + error:function(error) { + console.error('[xmldom error]\t'+error,_locator(this.locator)); + }, + fatalError:function(error) { + console.error('[xmldom fatalError]\t'+error,_locator(this.locator)); + throw error; + } +}; +function _locator(l){ + if(l){ + return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' + } +} +function _toString(chars,start,length){ + if(typeof chars == 'string'){ + return chars.substr(start,length) + }else {//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") + if(chars.length >= start+length || start){ + return new java.lang.String(chars,start,length)+''; + } + return chars; + } +} + +/* + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html + * used method of org.xml.sax.ext.LexicalHandler: + * #comment(chars, start, length) + * #startCDATA() + * #endCDATA() + * #startDTD(name, publicId, systemId) + * + * + * IGNORED method of org.xml.sax.ext.LexicalHandler: + * #endDTD() + * #startEntity(name) + * #endEntity(name) + * + * + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html + * IGNORED method of org.xml.sax.ext.DeclHandler + * #attributeDecl(eName, aName, type, mode, value) + * #elementDecl(name, model) + * #externalEntityDecl(name, publicId, systemId) + * #internalEntityDecl(name, value) + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html + * IGNORED method of org.xml.sax.EntityResolver2 + * #resolveEntity(String name,String publicId,String baseURI,String systemId) + * #resolveEntity(publicId, systemId) + * #getExternalSubset(name, baseURI) + * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html + * IGNORED method of org.xml.sax.DTDHandler + * #notationDecl(name, publicId, systemId) {}; + * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; + */ +"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ + DOMHandler.prototype[key] = function(){return null}; +}); + +/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ +function appendElement (hander,node) { + if (!hander.currentElement) { + hander.doc.appendChild(node); + } else { + hander.currentElement.appendChild(node); + } +}//appendChild and setAttributeNS are preformance key + +//if(typeof require == 'function'){ + var XMLReader = sax.XMLReader; + var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation; + exports.XMLSerializer = dom.XMLSerializer ; + exports.DOMParser = DOMParser; +//} +}); + +/*! @name mpd-parser @version 0.10.0 @license Apache-2.0 */ + +var isObject = function isObject(obj) { + return !!obj && typeof obj === 'object'; +}; + +var merge = function merge() { + for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { + objects[_key] = arguments[_key]; + } + + return objects.reduce(function (result, source) { + Object.keys(source).forEach(function (key) { + if (Array.isArray(result[key]) && Array.isArray(source[key])) { + result[key] = result[key].concat(source[key]); + } else if (isObject(result[key]) && isObject(source[key])) { + result[key] = merge(result[key], source[key]); + } else { + result[key] = source[key]; + } + }); + return result; + }, {}); +}; +var values = function values(o) { + return Object.keys(o).map(function (k) { + return o[k]; + }); +}; + +var range = function range(start, end) { + var result = []; + + for (var i = start; i < end; i++) { + result.push(i); + } + + return result; +}; +var flatten = function flatten(lists) { + return lists.reduce(function (x, y) { + return x.concat(y); + }, []); +}; +var from = function from(list) { + if (!list.length) { + return []; + } + + var result = []; + + for (var i = 0; i < list.length; i++) { + result.push(list[i]); + } + + return result; +}; +var findIndexes = function findIndexes(l, key) { + return l.reduce(function (a, e, i) { + if (e[key]) { + a.push(i); + } + + return a; + }, []); +}; + +var errors = { + INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', + DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', + DASH_INVALID_XML: 'DASH_INVALID_XML', + NO_BASE_URL: 'NO_BASE_URL', + MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', + SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', + UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' +}; + +/** + * @typedef {Object} SingleUri + * @property {string} uri - relative location of segment + * @property {string} resolvedUri - resolved location of segment + * @property {Object} byterange - Object containing information on how to make byte range + * requests following byte-range-spec per RFC2616. + * @property {String} byterange.length - length of range request + * @property {String} byterange.offset - byte offset of range request + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 + */ + +/** + * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object + * that conforms to how m3u8-parser is structured + * + * @see https://github.com/videojs/m3u8-parser + * + * @param {string} baseUrl - baseUrl provided by nodes + * @param {string} source - source url for segment + * @param {string} range - optional range used for range calls, + * follows RFC 2616, Clause 14.35.1 + * @return {SingleUri} full segment information transformed into a format similar + * to m3u8-parser + */ + +var urlTypeToSegment = function urlTypeToSegment(_ref) { + var _ref$baseUrl = _ref.baseUrl, + baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, + _ref$source = _ref.source, + source = _ref$source === void 0 ? '' : _ref$source, + _ref$range = _ref.range, + range = _ref$range === void 0 ? '' : _ref$range, + _ref$indexRange = _ref.indexRange, + indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; + var segment = { + uri: source, + resolvedUri: resolveUrl_1(baseUrl || '', source) + }; + + if (range || indexRange) { + var rangeStr = range ? range : indexRange; + var ranges = rangeStr.split('-'); + var startRange = parseInt(ranges[0], 10); + var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to + // RFC 2616, Clause 14.35.1 + + segment.byterange = { + length: endRange - startRange + 1, + offset: startRange + }; + } + + return segment; +}; +var byteRangeToString = function byteRangeToString(byterange) { + // `endRange` is one less than `offset + length` because the HTTP range + // header uses inclusive ranges + var endRange = byterange.offset + byterange.length - 1; + return byterange.offset + "-" + endRange; +}; + +/** + * Functions for calculating the range of available segments in static and dynamic + * manifests. + */ + +var segmentRange = { + /** + * Returns the entire range of available segments for a static MPD + * + * @param {Object} attributes + * Inheritied MPD attributes + * @return {{ start: number, end: number }} + * The start and end numbers for available segments + */ + static: function _static(attributes) { + var duration = attributes.duration, + _attributes$timescale = attributes.timescale, + timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, + sourceDuration = attributes.sourceDuration; + return { + start: 0, + end: Math.ceil(sourceDuration / (duration / timescale)) + }; + }, + + /** + * Returns the current live window range of available segments for a dynamic MPD + * + * @param {Object} attributes + * Inheritied MPD attributes + * @return {{ start: number, end: number }} + * The start and end numbers for available segments + */ + dynamic: function dynamic(attributes) { + var NOW = attributes.NOW, + clientOffset = attributes.clientOffset, + availabilityStartTime = attributes.availabilityStartTime, + _attributes$timescale2 = attributes.timescale, + timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, + duration = attributes.duration, + _attributes$start = attributes.start, + start = _attributes$start === void 0 ? 0 : _attributes$start, + _attributes$minimumUp = attributes.minimumUpdatePeriod, + minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, + _attributes$timeShift = attributes.timeShiftBufferDepth, + timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; + var now = (NOW + clientOffset) / 1000; + var periodStartWC = availabilityStartTime + start; + var periodEndWC = now + minimumUpdatePeriod; + var periodDuration = periodEndWC - periodStartWC; + var segmentCount = Math.ceil(periodDuration * timescale / duration); + var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); + var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); + return { + start: Math.max(0, availableStart), + end: Math.min(segmentCount, availableEnd) + }; + } +}; +/** + * Maps a range of numbers to objects with information needed to build the corresponding + * segment list + * + * @name toSegmentsCallback + * @function + * @param {number} number + * Number of the segment + * @param {number} index + * Index of the number in the range list + * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} + * Object with segment timing and duration info + */ + +/** + * Returns a callback for Array.prototype.map for mapping a range of numbers to + * information needed to build the segment list. + * + * @param {Object} attributes + * Inherited MPD attributes + * @return {toSegmentsCallback} + * Callback map function + */ + +var toSegments = function toSegments(attributes) { + return function (number, index) { + var duration = attributes.duration, + _attributes$timescale3 = attributes.timescale, + timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, + periodIndex = attributes.periodIndex, + _attributes$startNumb = attributes.startNumber, + startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; + return { + number: startNumber + number, + duration: duration / timescale, + timeline: periodIndex, + time: index * duration + }; + }; +}; +/** + * Returns a list of objects containing segment timing and duration info used for + * building the list of segments. This uses the @duration attribute specified + * in the MPD manifest to derive the range of segments. + * + * @param {Object} attributes + * Inherited MPD attributes + * @return {{number: number, duration: number, time: number, timeline: number}[]} + * List of Objects with segment timing and duration info + */ + +var parseByDuration = function parseByDuration(attributes) { + var _attributes$type = attributes.type, + type = _attributes$type === void 0 ? 'static' : _attributes$type, + duration = attributes.duration, + _attributes$timescale4 = attributes.timescale, + timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, + sourceDuration = attributes.sourceDuration; + + var _segmentRange$type = segmentRange[type](attributes), + start = _segmentRange$type.start, + end = _segmentRange$type.end; + + var segments = range(start, end).map(toSegments(attributes)); + + if (type === 'static') { + var index = segments.length - 1; // final segment may be less than full segment duration + + segments[index].duration = sourceDuration - duration / timescale * index; + } + + return segments; +}; + +/** + * Translates SegmentBase into a set of segments. + * (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each + * node should be translated into segment. + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @return {Object.} list of segments + */ + +var segmentsFromBase = function segmentsFromBase(attributes) { + var baseUrl = attributes.baseUrl, + _attributes$initializ = attributes.initialization, + initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ, + sourceDuration = attributes.sourceDuration, + _attributes$timescale = attributes.timescale, + timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, + _attributes$indexRang = attributes.indexRange, + indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, + duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) + + if (!baseUrl) { + throw new Error(errors.NO_BASE_URL); + } + + var initSegment = urlTypeToSegment({ + baseUrl: baseUrl, + source: initialization.sourceURL, + range: initialization.range + }); + var segment = urlTypeToSegment({ + baseUrl: baseUrl, + source: baseUrl, + indexRange: indexRange + }); + segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source + // (since SegmentBase is only for one total segment) + + if (duration) { + var segmentTimeInfo = parseByDuration(attributes); + + if (segmentTimeInfo.length) { + segment.duration = segmentTimeInfo[0].duration; + segment.timeline = segmentTimeInfo[0].timeline; + } + } else if (sourceDuration) { + segment.duration = sourceDuration / timescale; + segment.timeline = 0; + } // This is used for mediaSequence + + + segment.number = 0; + return [segment]; +}; +/** + * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist + * according to the sidx information given. + * + * playlist.sidx has metadadata about the sidx where-as the sidx param + * is the parsed sidx box itself. + * + * @param {Object} playlist the playlist to update the sidx information for + * @param {Object} sidx the parsed sidx box + * @return {Object} the playlist object with the updated sidx information + */ + +var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) { + // Retain init segment information + var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing + + var sourceDuration = playlist.sidx.duration; // Retain source timeline + + var timeline = playlist.timeline || 0; + var sidxByteRange = playlist.sidx.byterange; + var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx + + var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes + + var mediaReferences = sidx.references.filter(function (r) { + return r.referenceType !== 1; + }); + var segments = []; // firstOffset is the offset from the end of the sidx box + + var startIndex = sidxEnd + sidx.firstOffset; + + for (var i = 0; i < mediaReferences.length; i++) { + var reference = sidx.references[i]; // size of the referenced (sub)segment + + var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale + // this will be converted to seconds when generating segments + + var duration = reference.subsegmentDuration; // should be an inclusive range + + var endIndex = startIndex + size - 1; + var indexRange = startIndex + "-" + endIndex; + var attributes = { + baseUrl: baseUrl, + timescale: timescale, + timeline: timeline, + // this is used in parseByDuration + periodIndex: timeline, + duration: duration, + sourceDuration: sourceDuration, + indexRange: indexRange + }; + var segment = segmentsFromBase(attributes)[0]; + + if (initSegment) { + segment.map = initSegment; + } + + segments.push(segment); + startIndex += size; + } + + playlist.segments = segments; + return playlist; +}; + +var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { + var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { + // assuming playlist IDs are the same across periods + // TODO: handle multiperiod where representation sets are not the same + // across periods + var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first + + if (acc[name]) { + var _acc$name$segments; + + // first segment of subsequent periods signal a discontinuity + if (playlist.segments[0]) { + playlist.segments[0].discontinuity = true; + } + + (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content + // has the same contentProtection + + + if (playlist.attributes.contentProtection) { + acc[name].attributes.contentProtection = playlist.attributes.contentProtection; + } + } else { + // first Period + acc[name] = playlist; + } + + return acc; + }, {})); + return mergedPlaylists.map(function (playlist) { + playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); + return playlist; + }); +}; + +var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) { + if (sidxMapping === void 0) { + sidxMapping = {}; + } + + if (!Object.keys(sidxMapping).length) { + return playlists; + } + + for (var i in playlists) { + var playlist = playlists[i]; + + if (!playlist.sidx) { + continue; + } + + var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange); + var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; + + if (playlist.sidx && sidxMatch) { + addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); + } + } + + return playlists; +}; + +var formatAudioPlaylist = function formatAudioPlaylist(_ref) { + var _attributes; + + var attributes = _ref.attributes, + segments = _ref.segments, + sidx = _ref.sidx; + var playlist = { + attributes: (_attributes = { + NAME: attributes.id, + BANDWIDTH: attributes.bandwidth, + CODECS: attributes.codecs + }, _attributes['PROGRAM-ID'] = 1, _attributes), + uri: '', + endList: (attributes.type || 'static') === 'static', + timeline: attributes.periodIndex, + resolvedUri: '', + targetDuration: attributes.duration, + segments: segments, + mediaSequence: segments.length ? segments[0].number : 1 + }; + + if (attributes.contentProtection) { + playlist.contentProtection = attributes.contentProtection; + } + + if (sidx) { + playlist.sidx = sidx; + } + + return playlist; +}; +var formatVttPlaylist = function formatVttPlaylist(_ref2) { + var _attributes2; + + var attributes = _ref2.attributes, + segments = _ref2.segments; + + if (typeof segments === 'undefined') { + // vtt tracks may use single file in BaseURL + segments = [{ + uri: attributes.baseUrl, + timeline: attributes.periodIndex, + resolvedUri: attributes.baseUrl || '', + duration: attributes.sourceDuration, + number: 0 + }]; // targetDuration should be the same duration as the only segment + + attributes.duration = attributes.sourceDuration; + } + + return { + attributes: (_attributes2 = { + NAME: attributes.id, + BANDWIDTH: attributes.bandwidth + }, _attributes2['PROGRAM-ID'] = 1, _attributes2), + uri: '', + endList: (attributes.type || 'static') === 'static', + timeline: attributes.periodIndex, + resolvedUri: attributes.baseUrl || '', + targetDuration: attributes.duration, + segments: segments, + mediaSequence: segments.length ? segments[0].number : 1 + }; +}; +var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) { + if (sidxMapping === void 0) { + sidxMapping = {}; + } + + var mainPlaylist; + var formattedPlaylists = playlists.reduce(function (a, playlist) { + var role = playlist.attributes.role && playlist.attributes.role.value || ''; + var language = playlist.attributes.lang || ''; + var label = 'main'; + + if (language) { + var roleLabel = role ? " (" + role + ")" : ''; + label = "" + playlist.attributes.lang + roleLabel; + } // skip if we already have the highest quality audio for a language + + + if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { + return a; + } + + a[label] = { + language: language, + autoselect: true, + default: role === 'main', + playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping), + uri: '' + }; + + if (typeof mainPlaylist === 'undefined' && role === 'main') { + mainPlaylist = playlist; + mainPlaylist.default = true; + } + + return a; + }, {}); // if no playlists have role "main", mark the first as main + + if (!mainPlaylist) { + var firstLabel = Object.keys(formattedPlaylists)[0]; + formattedPlaylists[firstLabel].default = true; + } + + return formattedPlaylists; +}; +var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { + if (sidxMapping === void 0) { + sidxMapping = {}; + } + + return playlists.reduce(function (a, playlist) { + var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles + + if (a[label]) { + return a; + } + + a[label] = { + language: label, + default: false, + autoselect: false, + playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping), + uri: '' + }; + return a; + }, {}); +}; +var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { + var _attributes3; + + var attributes = _ref3.attributes, + segments = _ref3.segments, + sidx = _ref3.sidx; + var playlist = { + attributes: (_attributes3 = { + NAME: attributes.id, + AUDIO: 'audio', + SUBTITLES: 'subs', + RESOLUTION: { + width: attributes.width, + height: attributes.height + }, + CODECS: attributes.codecs, + BANDWIDTH: attributes.bandwidth + }, _attributes3['PROGRAM-ID'] = 1, _attributes3), + uri: '', + endList: (attributes.type || 'static') === 'static', + timeline: attributes.periodIndex, + resolvedUri: '', + targetDuration: attributes.duration, + segments: segments, + mediaSequence: segments.length ? segments[0].number : 1 + }; + + if (attributes.contentProtection) { + playlist.contentProtection = attributes.contentProtection; + } + + if (sidx) { + playlist.sidx = sidx; + } + + return playlist; +}; +var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) { + var _mediaGroups; + + if (sidxMapping === void 0) { + sidxMapping = {}; + } + + if (!dashPlaylists.length) { + return {}; + } // grab all master attributes + + + var _dashPlaylists$0$attr = dashPlaylists[0].attributes, + duration = _dashPlaylists$0$attr.sourceDuration, + _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.type, + type = _dashPlaylists$0$attr2 === void 0 ? 'static' : _dashPlaylists$0$attr2, + suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay, + _dashPlaylists$0$attr3 = _dashPlaylists$0$attr.minimumUpdatePeriod, + minimumUpdatePeriod = _dashPlaylists$0$attr3 === void 0 ? 0 : _dashPlaylists$0$attr3; + + var videoOnly = function videoOnly(_ref4) { + var attributes = _ref4.attributes; + return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; + }; + + var audioOnly = function audioOnly(_ref5) { + var attributes = _ref5.attributes; + return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; + }; + + var vttOnly = function vttOnly(_ref6) { + var attributes = _ref6.attributes; + return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; + }; + + var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); + var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); + var vttPlaylists = dashPlaylists.filter(vttOnly); + var master = { + allowCache: true, + discontinuityStarts: [], + segments: [], + endList: true, + mediaGroups: (_mediaGroups = { + AUDIO: {}, + VIDEO: {} + }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), + uri: '', + duration: duration, + playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping), + minimumUpdatePeriod: minimumUpdatePeriod * 1000 + }; + + if (type === 'dynamic') { + master.suggestedPresentationDelay = suggestedPresentationDelay; + } + + if (audioPlaylists.length) { + master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping); + } + + if (vttPlaylists.length) { + master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); + } + + return master; +}; + +/** + * Calculates the R (repetition) value for a live stream (for the final segment + * in a manifest where the r value is negative 1) + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {number} time + * current time (typically the total time up until the final segment) + * @param {number} duration + * duration property for the given + * + * @return {number} + * R value to reach the end of the given period + */ +var getLiveRValue = function getLiveRValue(attributes, time, duration) { + var NOW = attributes.NOW, + clientOffset = attributes.clientOffset, + availabilityStartTime = attributes.availabilityStartTime, + _attributes$timescale = attributes.timescale, + timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, + _attributes$start = attributes.start, + start = _attributes$start === void 0 ? 0 : _attributes$start, + _attributes$minimumUp = attributes.minimumUpdatePeriod, + minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; + var now = (NOW + clientOffset) / 1000; + var periodStartWC = availabilityStartTime + start; + var periodEndWC = now + minimumUpdatePeriod; + var periodDuration = periodEndWC - periodStartWC; + return Math.ceil((periodDuration * timescale - time) / duration); +}; +/** + * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment + * timing and duration + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {Object[]} segmentTimeline + * List of objects representing the attributes of each S element contained within + * + * @return {{number: number, duration: number, time: number, timeline: number}[]} + * List of Objects with segment timing and duration info + */ + + +var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { + var _attributes$type = attributes.type, + type = _attributes$type === void 0 ? 'static' : _attributes$type, + _attributes$minimumUp2 = attributes.minimumUpdatePeriod, + minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, + _attributes$media = attributes.media, + media = _attributes$media === void 0 ? '' : _attributes$media, + sourceDuration = attributes.sourceDuration, + _attributes$timescale2 = attributes.timescale, + timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, + _attributes$startNumb = attributes.startNumber, + startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, + timeline = attributes.periodIndex; + var segments = []; + var time = -1; + + for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { + var S = segmentTimeline[sIndex]; + var duration = S.d; + var repeat = S.r || 0; + var segmentTime = S.t || 0; + + if (time < 0) { + // first segment + time = segmentTime; + } + + if (segmentTime && segmentTime > time) { + // discontinuity + // TODO: How to handle this type of discontinuity + // timeline++ here would treat it like HLS discontuity and content would + // get appended without gap + // E.G. + // + // + // + // + // would have $Time$ values of [0, 1, 2, 5] + // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) + // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) + // does the value of sourceDuration consider this when calculating arbitrary + // negative @r repeat value? + // E.G. Same elements as above with this added at the end + // + // with a sourceDuration of 10 + // Would the 2 gaps be included in the time duration calculations resulting in + // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments + // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? + time = segmentTime; + } + + var count = void 0; + + if (repeat < 0) { + var nextS = sIndex + 1; + + if (nextS === segmentTimeline.length) { + // last segment + if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { + count = getLiveRValue(attributes, time, duration); + } else { + // TODO: This may be incorrect depending on conclusion of TODO above + count = (sourceDuration * timescale - time) / duration; + } + } else { + count = (segmentTimeline[nextS].t - time) / duration; + } + } else { + count = repeat + 1; + } + + var end = startNumber + segments.length + count; + var number = startNumber + segments.length; + + while (number < end) { + segments.push({ + number: number, + duration: duration / timescale, + time: time, + timeline: timeline + }); + time += duration; + number++; + } + } + + return segments; +}; + +var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; +/** + * Replaces template identifiers with corresponding values. To be used as the callback + * for String.prototype.replace + * + * @name replaceCallback + * @function + * @param {string} match + * Entire match of identifier + * @param {string} identifier + * Name of matched identifier + * @param {string} format + * Format tag string. Its presence indicates that padding is expected + * @param {string} width + * Desired length of the replaced value. Values less than this width shall be left + * zero padded + * @return {string} + * Replacement for the matched identifier + */ + +/** + * Returns a function to be used as a callback for String.prototype.replace to replace + * template identifiers + * + * @param {Obect} values + * Object containing values that shall be used to replace known identifiers + * @param {number} values.RepresentationID + * Value of the Representation@id attribute + * @param {number} values.Number + * Number of the corresponding segment + * @param {number} values.Bandwidth + * Value of the Representation@bandwidth attribute. + * @param {number} values.Time + * Timestamp value of the corresponding segment + * @return {replaceCallback} + * Callback to be used with String.prototype.replace to replace identifiers + */ + +var identifierReplacement = function identifierReplacement(values) { + return function (match, identifier, format, width) { + if (match === '$$') { + // escape sequence + return '$'; + } + + if (typeof values[identifier] === 'undefined') { + return match; + } + + var value = '' + values[identifier]; + + if (identifier === 'RepresentationID') { + // Format tag shall not be present with RepresentationID + return value; + } + + if (!format) { + width = 1; + } else { + width = parseInt(width, 10); + } + + if (value.length >= width) { + return value; + } + + return "" + new Array(width - value.length + 1).join('0') + value; + }; +}; +/** + * Constructs a segment url from a template string + * + * @param {string} url + * Template string to construct url from + * @param {Obect} values + * Object containing values that shall be used to replace known identifiers + * @param {number} values.RepresentationID + * Value of the Representation@id attribute + * @param {number} values.Number + * Number of the corresponding segment + * @param {number} values.Bandwidth + * Value of the Representation@bandwidth attribute. + * @param {number} values.Time + * Timestamp value of the corresponding segment + * @return {string} + * Segment url with identifiers replaced + */ + +var constructTemplateUrl = function constructTemplateUrl(url, values) { + return url.replace(identifierPattern, identifierReplacement(values)); +}; +/** + * Generates a list of objects containing timing and duration information about each + * segment needed to generate segment uris and the complete segment object + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {Object[]|undefined} segmentTimeline + * List of objects representing the attributes of each S element contained within + * the SegmentTimeline element + * @return {{number: number, duration: number, time: number, timeline: number}[]} + * List of Objects with segment timing and duration info + */ + +var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { + if (!attributes.duration && !segmentTimeline) { + // if neither @duration or SegmentTimeline are present, then there shall be exactly + // one media segment + return [{ + number: attributes.startNumber || 1, + duration: attributes.sourceDuration, + time: 0, + timeline: attributes.periodIndex + }]; + } + + if (attributes.duration) { + return parseByDuration(attributes); + } + + return parseByTimeline(attributes, segmentTimeline); +}; +/** + * Generates a list of segments using information provided by the SegmentTemplate element + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {Object[]|undefined} segmentTimeline + * List of objects representing the attributes of each S element contained within + * the SegmentTimeline element + * @return {Object[]} + * List of segment objects + */ + +var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { + var templateValues = { + RepresentationID: attributes.id, + Bandwidth: attributes.bandwidth || 0 + }; + var _attributes$initializ = attributes.initialization, + initialization = _attributes$initializ === void 0 ? { + sourceURL: '', + range: '' + } : _attributes$initializ; + var mapSegment = urlTypeToSegment({ + baseUrl: attributes.baseUrl, + source: constructTemplateUrl(initialization.sourceURL, templateValues), + range: initialization.range + }); + var segments = parseTemplateInfo(attributes, segmentTimeline); + return segments.map(function (segment) { + templateValues.Number = segment.number; + templateValues.Time = segment.time; + var uri = constructTemplateUrl(attributes.media || '', templateValues); + return { + uri: uri, + timeline: segment.timeline, + duration: segment.duration, + resolvedUri: resolveUrl_1(attributes.baseUrl || '', uri), + map: mapSegment, + number: segment.number + }; + }); +}; + +/** + * Converts a (of type URLType from the DASH spec 5.3.9.2 Table 14) + * to an object that matches the output of a segment in videojs/mpd-parser + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {Object} segmentUrl + * node to translate into a segment object + * @return {Object} translated segment object + */ + +var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { + var baseUrl = attributes.baseUrl, + _attributes$initializ = attributes.initialization, + initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ; + var initSegment = urlTypeToSegment({ + baseUrl: baseUrl, + source: initialization.sourceURL, + range: initialization.range + }); + var segment = urlTypeToSegment({ + baseUrl: baseUrl, + source: segmentUrl.media, + range: segmentUrl.mediaRange + }); + segment.map = initSegment; + return segment; +}; +/** + * Generates a list of segments using information provided by the SegmentList element + * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each + * node should be translated into segment. + * + * @param {Object} attributes + * Object containing all inherited attributes from parent elements with attribute + * names as keys + * @param {Object[]|undefined} segmentTimeline + * List of objects representing the attributes of each S element contained within + * the SegmentTimeline element + * @return {Object.} list of segments + */ + + +var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { + var duration = attributes.duration, + _attributes$segmentUr = attributes.segmentUrls, + segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR + // if both SegmentTimeline and @duration are defined, it is outside of spec. + + if (!duration && !segmentTimeline || duration && segmentTimeline) { + throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); + } + + var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { + return SegmentURLToSegmentObject(attributes, segmentUrlObject); + }); + var segmentTimeInfo; + + if (duration) { + segmentTimeInfo = parseByDuration(attributes); + } + + if (segmentTimeline) { + segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); + } + + var segments = segmentTimeInfo.map(function (segmentTime, index) { + if (segmentUrlMap[index]) { + var segment = segmentUrlMap[index]; + segment.timeline = segmentTime.timeline; + segment.duration = segmentTime.duration; + segment.number = segmentTime.number; + return segment; + } // Since we're mapping we should get rid of any blank segments (in case + // the given SegmentTimeline is handling for more elements than we have + // SegmentURLs for). + + }).filter(function (segment) { + return segment; + }); + return segments; +}; + +var generateSegments = function generateSegments(_ref) { + var attributes = _ref.attributes, + segmentInfo = _ref.segmentInfo; + var segmentAttributes; + var segmentsFn; + + if (segmentInfo.template) { + segmentsFn = segmentsFromTemplate; + segmentAttributes = merge(attributes, segmentInfo.template); + } else if (segmentInfo.base) { + segmentsFn = segmentsFromBase; + segmentAttributes = merge(attributes, segmentInfo.base); + } else if (segmentInfo.list) { + segmentsFn = segmentsFromList; + segmentAttributes = merge(attributes, segmentInfo.list); + } + + var segmentsInfo = { + attributes: attributes + }; + + if (!segmentsFn) { + return segmentsInfo; + } + + var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which + // must be in seconds. Since we've generated the segment list, we no longer need + // @duration to be in @timescale units, so we can convert it here. + + if (segmentAttributes.duration) { + var _segmentAttributes = segmentAttributes, + duration = _segmentAttributes.duration, + _segmentAttributes$ti = _segmentAttributes.timescale, + timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; + segmentAttributes.duration = duration / timescale; + } else if (segments.length) { + // if there is no @duration attribute, use the largest segment duration as + // as target duration + segmentAttributes.duration = segments.reduce(function (max, segment) { + return Math.max(max, Math.ceil(segment.duration)); + }, 0); + } else { + segmentAttributes.duration = 0; + } + + segmentsInfo.attributes = segmentAttributes; + segmentsInfo.segments = segments; // This is a sidx box without actual segment information + + if (segmentInfo.base && segmentAttributes.indexRange) { + segmentsInfo.sidx = segments[0]; + segmentsInfo.segments = []; + } + + return segmentsInfo; +}; +var toPlaylists = function toPlaylists(representations) { + return representations.map(generateSegments); +}; + +var findChildren = function findChildren(element, name) { + return from(element.childNodes).filter(function (_ref) { + var tagName = _ref.tagName; + return tagName === name; + }); +}; +var getContent = function getContent(element) { + return element.textContent.trim(); +}; + +var parseDuration = function parseDuration(str) { + var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; + var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; + var SECONDS_IN_DAY = 24 * 60 * 60; + var SECONDS_IN_HOUR = 60 * 60; + var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S + + var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; + var match = durationRegex.exec(str); + + if (!match) { + return 0; + } + + var _match$slice = match.slice(1), + year = _match$slice[0], + month = _match$slice[1], + day = _match$slice[2], + hour = _match$slice[3], + minute = _match$slice[4], + second = _match$slice[5]; + + return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0); +}; +var parseDate = function parseDate(str) { + // Date format without timezone according to ISO 8601 + // YYY-MM-DDThh:mm:ss.ssssss + var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is + // expressed by ending with 'Z' + + if (dateRegex.test(str)) { + str += 'Z'; + } + + return Date.parse(str); +}; + +var parsers = { + /** + * Specifies the duration of the entire Media Presentation. Format is a duration string + * as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The duration in seconds + */ + mediaPresentationDuration: function mediaPresentationDuration(value) { + return parseDuration(value); + }, + + /** + * Specifies the Segment availability start time for all Segments referred to in this + * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability + * time. Format is a date string as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The date as seconds from unix epoch + */ + availabilityStartTime: function availabilityStartTime(value) { + return parseDate(value) / 1000; + }, + + /** + * Specifies the smallest period between potential changes to the MPD. Format is a + * duration string as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The duration in seconds + */ + minimumUpdatePeriod: function minimumUpdatePeriod(value) { + return parseDuration(value); + }, + + /** + * Specifies the suggested presentation delay. Format is a + * duration string as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The duration in seconds + */ + suggestedPresentationDelay: function suggestedPresentationDelay(value) { + return parseDuration(value); + }, + + /** + * specifices the type of mpd. Can be either "static" or "dynamic" + * + * @param {string} value + * value of attribute as a string + * + * @return {string} + * The type as a string + */ + type: function type(value) { + return value; + }, + + /** + * Specifies the duration of the smallest time shifting buffer for any Representation + * in the MPD. Format is a duration string as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The duration in seconds + */ + timeShiftBufferDepth: function timeShiftBufferDepth(value) { + return parseDuration(value); + }, + + /** + * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. + * Format is a duration string as specified in ISO 8601 + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The duration in seconds + */ + start: function start(value) { + return parseDuration(value); + }, + + /** + * Specifies the width of the visual presentation + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed width + */ + width: function width(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the height of the visual presentation + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed height + */ + height: function height(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the bitrate of the representation + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed bandwidth + */ + bandwidth: function bandwidth(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the number of the first Media Segment in this Representation in the Period + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed number + */ + startNumber: function startNumber(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the timescale in units per seconds + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The aprsed timescale + */ + timescale: function timescale(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the constant approximate Segment duration + * NOTE: The element also contains an @duration attribute. This duration + * specifies the duration of the Period. This attribute is currently not + * supported by the rest of the parser, however we still check for it to prevent + * errors. + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed duration + */ + duration: function duration(value) { + var parsedValue = parseInt(value, 10); + + if (isNaN(parsedValue)) { + return parseDuration(value); + } + + return parsedValue; + }, + + /** + * Specifies the Segment duration, in units of the value of the @timescale. + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed duration + */ + d: function d(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the MPD start time, in @timescale units, the first Segment in the series + * starts relative to the beginning of the Period + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed time + */ + t: function t(value) { + return parseInt(value, 10); + }, + + /** + * Specifies the repeat count of the number of following contiguous Segments with the + * same duration expressed by the value of @d + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed number + */ + r: function r(value) { + return parseInt(value, 10); + }, + + /** + * Default parser for all other attributes. Acts as a no-op and just returns the value + * as a string + * + * @param {string} value + * value of attribute as a string + * @return {string} + * Unparsed value + */ + DEFAULT: function DEFAULT(value) { + return value; + } +}; +/** + * Gets all the attributes and values of the provided node, parses attributes with known + * types, and returns an object with attribute names mapped to values. + * + * @param {Node} el + * The node to parse attributes from + * @return {Object} + * Object with all attributes of el parsed + */ + +var parseAttributes$1 = function parseAttributes(el) { + if (!(el && el.attributes)) { + return {}; + } + + return from(el.attributes).reduce(function (a, e) { + var parseFn = parsers[e.name] || parsers.DEFAULT; + a[e.name] = parseFn(e.value); + return a; + }, {}); +}; + +var keySystemsMap = { + 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', + 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', + 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', + 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' +}; +/** + * Builds a list of urls that is the product of the reference urls and BaseURL values + * + * @param {string[]} referenceUrls + * List of reference urls to resolve to + * @param {Node[]} baseUrlElements + * List of BaseURL nodes from the mpd + * @return {string[]} + * List of resolved urls + */ + +var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { + if (!baseUrlElements.length) { + return referenceUrls; + } + + return flatten(referenceUrls.map(function (reference) { + return baseUrlElements.map(function (baseUrlElement) { + return resolveUrl_1(reference, getContent(baseUrlElement)); + }); + })); +}; +/** + * Contains all Segment information for its containing AdaptationSet + * + * @typedef {Object} SegmentInformation + * @property {Object|undefined} template + * Contains the attributes for the SegmentTemplate node + * @property {Object[]|undefined} timeline + * Contains a list of atrributes for each S node within the SegmentTimeline node + * @property {Object|undefined} list + * Contains the attributes for the SegmentList node + * @property {Object|undefined} base + * Contains the attributes for the SegmentBase node + */ + +/** + * Returns all available Segment information contained within the AdaptationSet node + * + * @param {Node} adaptationSet + * The AdaptationSet node to get Segment information from + * @return {SegmentInformation} + * The Segment information contained within the provided AdaptationSet + */ + +var getSegmentInformation = function getSegmentInformation(adaptationSet) { + var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; + var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; + var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { + return merge({ + tag: 'SegmentURL' + }, parseAttributes$1(s)); + }); + var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; + var segmentTimelineParentNode = segmentList || segmentTemplate; + var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; + var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; + var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both + // @initialization and an node. @initialization can be templated, + // while the node can have a url and range specified. If the has + // both @initialization and an subelement we opt to override with + // the node, as this interaction is not defined in the spec. + + var template = segmentTemplate && parseAttributes$1(segmentTemplate); + + if (template && segmentInitialization) { + template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); + } else if (template && template.initialization) { + // If it is @initialization we convert it to an object since this is the format that + // later functions will rely on for the initialization segment. This is only valid + // for + template.initialization = { + sourceURL: template.initialization + }; + } + + var segmentInfo = { + template: template, + timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { + return parseAttributes$1(s); + }), + list: segmentList && merge(parseAttributes$1(segmentList), { + segmentUrls: segmentUrls, + initialization: parseAttributes$1(segmentInitialization) + }), + base: segmentBase && merge(parseAttributes$1(segmentBase), { + initialization: parseAttributes$1(segmentInitialization) + }) + }; + Object.keys(segmentInfo).forEach(function (key) { + if (!segmentInfo[key]) { + delete segmentInfo[key]; + } + }); + return segmentInfo; +}; +/** + * Contains Segment information and attributes needed to construct a Playlist object + * from a Representation + * + * @typedef {Object} RepresentationInformation + * @property {SegmentInformation} segmentInfo + * Segment information for this Representation + * @property {Object} attributes + * Inherited attributes for this Representation + */ + +/** + * Maps a Representation node to an object containing Segment information and attributes + * + * @name inheritBaseUrlsCallback + * @function + * @param {Node} representation + * Representation node from the mpd + * @return {RepresentationInformation} + * Representation information needed to construct a Playlist object + */ + +/** + * Returns a callback for Array.prototype.map for mapping Representation nodes to + * Segment information and attributes using inherited BaseURL nodes. + * + * @param {Object} adaptationSetAttributes + * Contains attributes inherited by the AdaptationSet + * @param {string[]} adaptationSetBaseUrls + * Contains list of resolved base urls inherited by the AdaptationSet + * @param {SegmentInformation} adaptationSetSegmentInfo + * Contains Segment information for the AdaptationSet + * @return {inheritBaseUrlsCallback} + * Callback map function + */ + +var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { + return function (representation) { + var repBaseUrlElements = findChildren(representation, 'BaseURL'); + var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); + var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); + var representationSegmentInfo = getSegmentInformation(representation); + return repBaseUrls.map(function (baseUrl) { + return { + segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), + attributes: merge(attributes, { + baseUrl: baseUrl + }) + }; + }); + }; +}; +/** + * Tranforms a series of content protection nodes to + * an object containing pssh data by key system + * + * @param {Node[]} contentProtectionNodes + * Content protection nodes + * @return {Object} + * Object containing pssh data by key system + */ + +var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { + return contentProtectionNodes.reduce(function (acc, node) { + var attributes = parseAttributes$1(node); + var keySystem = keySystemsMap[attributes.schemeIdUri]; + + if (keySystem) { + acc[keySystem] = { + attributes: attributes + }; + var psshNode = findChildren(node, 'cenc:pssh')[0]; + + if (psshNode) { + var pssh = getContent(psshNode); + var psshBuffer = pssh && decodeB64ToUint8Array_1(pssh); + acc[keySystem].pssh = psshBuffer; + } + } + + return acc; + }, {}); +}; +/** + * Maps an AdaptationSet node to a list of Representation information objects + * + * @name toRepresentationsCallback + * @function + * @param {Node} adaptationSet + * AdaptationSet node from the mpd + * @return {RepresentationInformation[]} + * List of objects containing Representaion information + */ + +/** + * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of + * Representation information objects + * + * @param {Object} periodAttributes + * Contains attributes inherited by the Period + * @param {string[]} periodBaseUrls + * Contains list of resolved base urls inherited by the Period + * @param {string[]} periodSegmentInfo + * Contains Segment Information at the period level + * @return {toRepresentationsCallback} + * Callback map function + */ + + +var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { + return function (adaptationSet) { + var adaptationSetAttributes = parseAttributes$1(adaptationSet); + var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); + var role = findChildren(adaptationSet, 'Role')[0]; + var roleAttributes = { + role: parseAttributes$1(role) + }; + var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); + var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); + + if (Object.keys(contentProtection).length) { + attrs = merge(attrs, { + contentProtection: contentProtection + }); + } + + var segmentInfo = getSegmentInformation(adaptationSet); + var representations = findChildren(adaptationSet, 'Representation'); + var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); + return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); + }; +}; +/** + * Maps an Period node to a list of Representation inforamtion objects for all + * AdaptationSet nodes contained within the Period + * + * @name toAdaptationSetsCallback + * @function + * @param {Node} period + * Period node from the mpd + * @param {number} periodIndex + * Index of the Period within the mpd + * @return {RepresentationInformation[]} + * List of objects containing Representaion information + */ + +/** + * Returns a callback for Array.prototype.map for mapping Period nodes to a list of + * Representation information objects + * + * @param {Object} mpdAttributes + * Contains attributes inherited by the mpd + * @param {string[]} mpdBaseUrls + * Contains list of resolved base urls inherited by the mpd + * @return {toAdaptationSetsCallback} + * Callback map function + */ + +var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { + return function (period, index) { + var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); + var periodAtt = parseAttributes$1(period); + var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number + + var periodIndex = window_1$1.isNaN(parsedPeriodId) ? index : parsedPeriodId; + var periodAttributes = merge(mpdAttributes, { + periodIndex: periodIndex + }); + var adaptationSets = findChildren(period, 'AdaptationSet'); + var periodSegmentInfo = getSegmentInformation(period); + return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); + }; +}; +/** + * Traverses the mpd xml tree to generate a list of Representation information objects + * that have inherited attributes from parent nodes + * + * @param {Node} mpd + * The root node of the mpd + * @param {Object} options + * Available options for inheritAttributes + * @param {string} options.manifestUri + * The uri source of the mpd + * @param {number} options.NOW + * Current time per DASH IOP. Default is current time in ms since epoch + * @param {number} options.clientOffset + * Client time difference from NOW (in milliseconds) + * @return {RepresentationInformation[]} + * List of objects containing Representation information + */ + +var inheritAttributes = function inheritAttributes(mpd, options) { + if (options === void 0) { + options = {}; + } + + var _options = options, + _options$manifestUri = _options.manifestUri, + manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, + _options$NOW = _options.NOW, + NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, + _options$clientOffset = _options.clientOffset, + clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset; + var periods = findChildren(mpd, 'Period'); + + if (!periods.length) { + throw new Error(errors.INVALID_NUMBER_OF_PERIOD); + } + + var mpdAttributes = parseAttributes$1(mpd); + var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); + mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; + mpdAttributes.NOW = NOW; + mpdAttributes.clientOffset = clientOffset; + return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))); +}; + +var stringToMpdXml = function stringToMpdXml(manifestString) { + if (manifestString === '') { + throw new Error(errors.DASH_EMPTY_MANIFEST); + } + + var parser = new domParser.DOMParser(); + var xml = parser.parseFromString(manifestString, 'application/xml'); + var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; + + if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { + throw new Error(errors.DASH_INVALID_XML); + } + + return mpd; +}; + +/** + * Parses the manifest for a UTCTiming node, returning the nodes attributes if found + * + * @param {string} mpd + * XML string of the MPD manifest + * @return {Object|null} + * Attributes of UTCTiming node specified in the manifest. Null if none found + */ + +var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { + var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; + + if (!UTCTimingNode) { + return null; + } + + var attributes = parseAttributes$1(UTCTimingNode); + + switch (attributes.schemeIdUri) { + case 'urn:mpeg:dash:utc:http-head:2014': + case 'urn:mpeg:dash:utc:http-head:2012': + attributes.method = 'HEAD'; + break; + + case 'urn:mpeg:dash:utc:http-xsdate:2014': + case 'urn:mpeg:dash:utc:http-iso:2014': + case 'urn:mpeg:dash:utc:http-xsdate:2012': + case 'urn:mpeg:dash:utc:http-iso:2012': + attributes.method = 'GET'; + break; + + case 'urn:mpeg:dash:utc:direct:2014': + case 'urn:mpeg:dash:utc:direct:2012': + attributes.method = 'DIRECT'; + attributes.value = Date.parse(attributes.value); + break; + + case 'urn:mpeg:dash:utc:http-ntp:2014': + case 'urn:mpeg:dash:utc:ntp:2014': + case 'urn:mpeg:dash:utc:sntp:2014': + default: + throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); + } + + return attributes; +}; + +var parse$1 = function parse(manifestString, options) { + if (options === void 0) { + options = {}; + } + + return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping); +}; +/** + * Parses the manifest for a UTCTiming node, returning the nodes attributes if found + * + * @param {string} manifestString + * XML string of the MPD manifest + * @return {Object|null} + * Attributes of UTCTiming node specified in the manifest. Null if none found + */ + + +var parseUTCTiming = function parseUTCTiming(manifestString) { + return parseUTCTimingScheme(stringToMpdXml(manifestString)); +}; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + */ +var toUnsigned = function(value) { + return value >>> 0; +}; + +var toHexString = function(value) { + return ('00' + value.toString(16)).slice(-2); +}; + +var bin = { + toUnsigned: toUnsigned, + toHexString: toHexString +}; + +var + inspectMp4, + textifyMp4, + toUnsigned$1 = bin.toUnsigned, + parseMp4Date = function(seconds) { + return new Date(seconds * 1000 - 2082844800000); + }, + parseSampleFlags = function(flags) { + return { + isLeading: (flags[0] & 0x0c) >>> 2, + dependsOn: flags[0] & 0x03, + isDependedOn: (flags[1] & 0xc0) >>> 6, + hasRedundancy: (flags[1] & 0x30) >>> 4, + paddingValue: (flags[1] & 0x0e) >>> 1, + isNonSyncSample: flags[1] & 0x01, + degradationPriority: (flags[2] << 8) | flags[3] + }; + }, + /** + * Returns the string representation of an ASCII encoded four byte buffer. + * @param buffer {Uint8Array} a four-byte buffer to translate + * @return {string} the corresponding string + */ + parseType = function(buffer) { + var result = ''; + result += String.fromCharCode(buffer[0]); + result += String.fromCharCode(buffer[1]); + result += String.fromCharCode(buffer[2]); + result += String.fromCharCode(buffer[3]); + return result; + }, + // Find the data for a box specified by its path + findBox = function(data, path) { + var results = [], + i, size, type, end, subresults; + + if (!path.length) { + // short-circuit the search for empty paths + return null; + } + + for (i = 0; i < data.byteLength;) { + size = toUnsigned$1(data[i] << 24 | + data[i + 1] << 16 | + data[i + 2] << 8 | + data[i + 3]); + + type = parseType(data.subarray(i + 4, i + 8)); + + end = size > 1 ? i + size : data.byteLength; + + if (type === path[0]) { + if (path.length === 1) { + // this is the end of the path and we've found the box we were + // looking for + results.push(data.subarray(i + 8, end)); + } else { + // recursively search for the next box along the path + subresults = findBox(data.subarray(i + 8, end), path.slice(1)); + if (subresults.length) { + results = results.concat(subresults); + } + } + } + i = end; + } + + // we've finished searching all of data + return results; + }, + nalParse = function(avcStream) { + var + avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), + result = [], + i, + length; + for (i = 0; i + 4 < avcStream.length; i += length) { + length = avcView.getUint32(i); + i += 4; + + // bail if this doesn't appear to be an H264 stream + if (length <= 0) { + result.push('MALFORMED DATA'); + continue; + } + + switch (avcStream[i] & 0x1F) { + case 0x01: + result.push('slice_layer_without_partitioning_rbsp'); + break; + case 0x05: + result.push('slice_layer_without_partitioning_rbsp_idr'); + break; + case 0x06: + result.push('sei_rbsp'); + break; + case 0x07: + result.push('seq_parameter_set_rbsp'); + break; + case 0x08: + result.push('pic_parameter_set_rbsp'); + break; + case 0x09: + result.push('access_unit_delimiter_rbsp'); + break; + default: + result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); + break; + } + } + return result; + }, + + // registry of handlers for individual mp4 box types + parse$2 = { + // codingname, not a first-class box type. stsd entries share the + // same format as real boxes so the parsing infrastructure can be + // shared + avc1: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return { + dataReferenceIndex: view.getUint16(6), + width: view.getUint16(24), + height: view.getUint16(26), + horizresolution: view.getUint16(28) + (view.getUint16(30) / 16), + vertresolution: view.getUint16(32) + (view.getUint16(34) / 16), + frameCount: view.getUint16(40), + depth: view.getUint16(74), + config: inspectMp4(data.subarray(78, data.byteLength)) + }; + }, + avcC: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + configurationVersion: data[0], + avcProfileIndication: data[1], + profileCompatibility: data[2], + avcLevelIndication: data[3], + lengthSizeMinusOne: data[4] & 0x03, + sps: [], + pps: [] + }, + numOfSequenceParameterSets = data[5] & 0x1f, + numOfPictureParameterSets, + nalSize, + offset, + i; + + // iterate past any SPSs + offset = 6; + for (i = 0; i < numOfSequenceParameterSets; i++) { + nalSize = view.getUint16(offset); + offset += 2; + result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); + offset += nalSize; + } + // iterate past any PPSs + numOfPictureParameterSets = data[offset]; + offset++; + for (i = 0; i < numOfPictureParameterSets; i++) { + nalSize = view.getUint16(offset); + offset += 2; + result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); + offset += nalSize; + } + return result; + }, + btrt: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return { + bufferSizeDB: view.getUint32(0), + maxBitrate: view.getUint32(4), + avgBitrate: view.getUint32(8) + }; + }, + esds: function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + esId: (data[6] << 8) | data[7], + streamPriority: data[8] & 0x1f, + decoderConfig: { + objectProfileIndication: data[11], + streamType: (data[12] >>> 2) & 0x3f, + bufferSize: (data[13] << 16) | (data[14] << 8) | data[15], + maxBitrate: (data[16] << 24) | + (data[17] << 16) | + (data[18] << 8) | + data[19], + avgBitrate: (data[20] << 24) | + (data[21] << 16) | + (data[22] << 8) | + data[23], + decoderConfigDescriptor: { + tag: data[24], + length: data[25], + audioObjectType: (data[26] >>> 3) & 0x1f, + samplingFrequencyIndex: ((data[26] & 0x07) << 1) | + ((data[27] >>> 7) & 0x01), + channelConfiguration: (data[27] >>> 3) & 0x0f + } + } + }; + }, + ftyp: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + majorBrand: parseType(data.subarray(0, 4)), + minorVersion: view.getUint32(4), + compatibleBrands: [] + }, + i = 8; + while (i < data.byteLength) { + result.compatibleBrands.push(parseType(data.subarray(i, i + 4))); + i += 4; + } + return result; + }, + dinf: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + dref: function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + dataReferences: inspectMp4(data.subarray(8)) + }; + }, + hdlr: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: view.getUint8(0), + flags: new Uint8Array(data.subarray(1, 4)), + handlerType: parseType(data.subarray(8, 12)), + name: '' + }, + i = 8; + + // parse out the name field + for (i = 24; i < data.byteLength; i++) { + if (data[i] === 0x00) { + // the name field is null-terminated + i++; + break; + } + result.name += String.fromCharCode(data[i]); + } + // decode UTF-8 to javascript's internal representation + // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html + result.name = decodeURIComponent(escape(result.name)); + + return result; + }, + mdat: function(data) { + return { + byteLength: data.byteLength, + nals: nalParse(data) + }; + }, + mdhd: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + i = 4, + language, + result = { + version: view.getUint8(0), + flags: new Uint8Array(data.subarray(1, 4)), + language: '' + }; + if (result.version === 1) { + i += 4; + result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 8; + result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 4; + result.timescale = view.getUint32(i); + i += 8; + result.duration = view.getUint32(i); // truncating top 4 bytes + } else { + result.creationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.modificationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.timescale = view.getUint32(i); + i += 4; + result.duration = view.getUint32(i); + } + i += 4; + // language is stored as an ISO-639-2/T code in an array of three 5-bit fields + // each field is the packed difference between its ASCII value and 0x60 + language = view.getUint16(i); + result.language += String.fromCharCode((language >> 10) + 0x60); + result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); + result.language += String.fromCharCode((language & 0x1f) + 0x60); + + return result; + }, + mdia: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + mfhd: function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + sequenceNumber: (data[4] << 24) | + (data[5] << 16) | + (data[6] << 8) | + (data[7]) + }; + }, + minf: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + // codingname, not a first-class box type. stsd entries share the + // same format as real boxes so the parsing infrastructure can be + // shared + mp4a: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + // 6 bytes reserved + dataReferenceIndex: view.getUint16(6), + // 4 + 4 bytes reserved + channelcount: view.getUint16(16), + samplesize: view.getUint16(18), + // 2 bytes pre_defined + // 2 bytes reserved + samplerate: view.getUint16(24) + (view.getUint16(26) / 65536) + }; + + // if there are more bytes to process, assume this is an ISO/IEC + // 14496-14 MP4AudioSampleEntry and parse the ESDBox + if (data.byteLength > 28) { + result.streamDescriptor = inspectMp4(data.subarray(28))[0]; + } + return result; + }, + moof: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + moov: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + mvex: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + mvhd: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + i = 4, + result = { + version: view.getUint8(0), + flags: new Uint8Array(data.subarray(1, 4)) + }; + + if (result.version === 1) { + i += 4; + result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 8; + result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 4; + result.timescale = view.getUint32(i); + i += 8; + result.duration = view.getUint32(i); // truncating top 4 bytes + } else { + result.creationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.modificationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.timescale = view.getUint32(i); + i += 4; + result.duration = view.getUint32(i); + } + i += 4; + + // convert fixed-point, base 16 back to a number + result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16); + i += 4; + result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8); + i += 2; + i += 2; + i += 2 * 4; + result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4))); + i += 9 * 4; + i += 6 * 4; + result.nextTrackId = view.getUint32(i); + return result; + }, + pdin: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return { + version: view.getUint8(0), + flags: new Uint8Array(data.subarray(1, 4)), + rate: view.getUint32(4), + initialDelay: view.getUint32(8) + }; + }, + sdtp: function(data) { + var + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + samples: [] + }, i; + + for (i = 4; i < data.byteLength; i++) { + result.samples.push({ + dependsOn: (data[i] & 0x30) >> 4, + isDependedOn: (data[i] & 0x0c) >> 2, + hasRedundancy: data[i] & 0x03 + }); + } + return result; + }, + sidx: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + references: [], + referenceId: view.getUint32(4), + timescale: view.getUint32(8), + earliestPresentationTime: view.getUint32(12), + firstOffset: view.getUint32(16) + }, + referenceCount = view.getUint16(22), + i; + + for (i = 24; referenceCount; i += 12, referenceCount--) { + result.references.push({ + referenceType: (data[i] & 0x80) >>> 7, + referencedSize: view.getUint32(i) & 0x7FFFFFFF, + subsegmentDuration: view.getUint32(i + 4), + startsWithSap: !!(data[i + 8] & 0x80), + sapType: (data[i + 8] & 0x70) >>> 4, + sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF + }); + } + + return result; + }, + smhd: function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + balance: data[4] + (data[5] / 256) + }; + }, + stbl: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + stco: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + chunkOffsets: [] + }, + entryCount = view.getUint32(4), + i; + for (i = 8; entryCount; i += 4, entryCount--) { + result.chunkOffsets.push(view.getUint32(i)); + } + return result; + }, + stsc: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + entryCount = view.getUint32(4), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + sampleToChunks: [] + }, + i; + for (i = 8; entryCount; i += 12, entryCount--) { + result.sampleToChunks.push({ + firstChunk: view.getUint32(i), + samplesPerChunk: view.getUint32(i + 4), + sampleDescriptionIndex: view.getUint32(i + 8) + }); + } + return result; + }, + stsd: function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + sampleDescriptions: inspectMp4(data.subarray(8)) + }; + }, + stsz: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + sampleSize: view.getUint32(4), + entries: [] + }, + i; + for (i = 12; i < data.byteLength; i += 4) { + result.entries.push(view.getUint32(i)); + } + return result; + }, + stts: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + timeToSamples: [] + }, + entryCount = view.getUint32(4), + i; + + for (i = 8; entryCount; i += 8, entryCount--) { + result.timeToSamples.push({ + sampleCount: view.getUint32(i), + sampleDelta: view.getUint32(i + 4) + }); + } + return result; + }, + styp: function(data) { + return parse$2.ftyp(data); + }, + tfdt: function(data) { + var result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) + }; + if (result.version === 1) { + result.baseMediaDecodeTime *= Math.pow(2, 32); + result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); + } + return result; + }, + tfhd: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + trackId: view.getUint32(4) + }, + baseDataOffsetPresent = result.flags[2] & 0x01, + sampleDescriptionIndexPresent = result.flags[2] & 0x02, + defaultSampleDurationPresent = result.flags[2] & 0x08, + defaultSampleSizePresent = result.flags[2] & 0x10, + defaultSampleFlagsPresent = result.flags[2] & 0x20, + durationIsEmpty = result.flags[0] & 0x010000, + defaultBaseIsMoof = result.flags[0] & 0x020000, + i; + + i = 8; + if (baseDataOffsetPresent) { + i += 4; // truncate top 4 bytes + // FIXME: should we read the full 64 bits? + result.baseDataOffset = view.getUint32(12); + i += 4; + } + if (sampleDescriptionIndexPresent) { + result.sampleDescriptionIndex = view.getUint32(i); + i += 4; + } + if (defaultSampleDurationPresent) { + result.defaultSampleDuration = view.getUint32(i); + i += 4; + } + if (defaultSampleSizePresent) { + result.defaultSampleSize = view.getUint32(i); + i += 4; + } + if (defaultSampleFlagsPresent) { + result.defaultSampleFlags = view.getUint32(i); + } + if (durationIsEmpty) { + result.durationIsEmpty = true; + } + if (!baseDataOffsetPresent && defaultBaseIsMoof) { + result.baseDataOffsetIsMoof = true; + } + return result; + }, + tkhd: function(data) { + var + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + i = 4, + result = { + version: view.getUint8(0), + flags: new Uint8Array(data.subarray(1, 4)) + }; + if (result.version === 1) { + i += 4; + result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 8; + result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes + i += 4; + result.trackId = view.getUint32(i); + i += 4; + i += 8; + result.duration = view.getUint32(i); // truncating top 4 bytes + } else { + result.creationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.modificationTime = parseMp4Date(view.getUint32(i)); + i += 4; + result.trackId = view.getUint32(i); + i += 4; + i += 4; + result.duration = view.getUint32(i); + } + i += 4; + i += 2 * 4; + result.layer = view.getUint16(i); + i += 2; + result.alternateGroup = view.getUint16(i); + i += 2; + // convert fixed-point, base 16 back to a number + result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8); + i += 2; + i += 2; + result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4))); + i += 9 * 4; + result.width = view.getUint16(i) + (view.getUint16(i + 2) / 65536); + i += 4; + result.height = view.getUint16(i) + (view.getUint16(i + 2) / 65536); + return result; + }, + traf: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + trak: function(data) { + return { + boxes: inspectMp4(data) + }; + }, + trex: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + trackId: view.getUint32(4), + defaultSampleDescriptionIndex: view.getUint32(8), + defaultSampleDuration: view.getUint32(12), + defaultSampleSize: view.getUint32(16), + sampleDependsOn: data[20] & 0x03, + sampleIsDependedOn: (data[21] & 0xc0) >> 6, + sampleHasRedundancy: (data[21] & 0x30) >> 4, + samplePaddingValue: (data[21] & 0x0e) >> 1, + sampleIsDifferenceSample: !!(data[21] & 0x01), + sampleDegradationPriority: view.getUint16(22) + }; + }, + trun: function(data) { + var + result = { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + samples: [] + }, + view = new DataView(data.buffer, data.byteOffset, data.byteLength), + // Flag interpretation + dataOffsetPresent = result.flags[2] & 0x01, // compare with 2nd byte of 0x1 + firstSampleFlagsPresent = result.flags[2] & 0x04, // compare with 2nd byte of 0x4 + sampleDurationPresent = result.flags[1] & 0x01, // compare with 2nd byte of 0x100 + sampleSizePresent = result.flags[1] & 0x02, // compare with 2nd byte of 0x200 + sampleFlagsPresent = result.flags[1] & 0x04, // compare with 2nd byte of 0x400 + sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, // compare with 2nd byte of 0x800 + sampleCount = view.getUint32(4), + offset = 8, + sample; + + if (dataOffsetPresent) { + // 32 bit signed integer + result.dataOffset = view.getInt32(offset); + offset += 4; + } + + // Overrides the flags for the first sample only. The order of + // optional values will be: duration, size, compositionTimeOffset + if (firstSampleFlagsPresent && sampleCount) { + sample = { + flags: parseSampleFlags(data.subarray(offset, offset + 4)) + }; + offset += 4; + if (sampleDurationPresent) { + sample.duration = view.getUint32(offset); + offset += 4; + } + if (sampleSizePresent) { + sample.size = view.getUint32(offset); + offset += 4; + } + if (sampleCompositionTimeOffsetPresent) { + // Note: this should be a signed int if version is 1 + sample.compositionTimeOffset = view.getUint32(offset); + offset += 4; + } + result.samples.push(sample); + sampleCount--; + } + + while (sampleCount--) { + sample = {}; + if (sampleDurationPresent) { + sample.duration = view.getUint32(offset); + offset += 4; + } + if (sampleSizePresent) { + sample.size = view.getUint32(offset); + offset += 4; + } + if (sampleFlagsPresent) { + sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); + offset += 4; + } + if (sampleCompositionTimeOffsetPresent) { + // Note: this should be a signed int if version is 1 + sample.compositionTimeOffset = view.getUint32(offset); + offset += 4; + } + result.samples.push(sample); + } + return result; + }, + 'url ': function(data) { + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)) + }; + }, + vmhd: function(data) { + var view = new DataView(data.buffer, data.byteOffset, data.byteLength); + return { + version: data[0], + flags: new Uint8Array(data.subarray(1, 4)), + graphicsmode: view.getUint16(4), + opcolor: new Uint16Array([view.getUint16(6), + view.getUint16(8), + view.getUint16(10)]) + }; + } + }; + + +/** + * Return a javascript array of box objects parsed from an ISO base + * media file. + * @param data {Uint8Array} the binary data of the media to be inspected + * @return {array} a javascript array of potentially nested box objects + */ +inspectMp4 = function(data) { + var + i = 0, + result = [], + view, + size, + type, + end, + box; + + // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API + var ab = new ArrayBuffer(data.length); + var v = new Uint8Array(ab); + for (var z = 0; z < data.length; ++z) { + v[z] = data[z]; + } + view = new DataView(ab); + + while (i < data.byteLength) { + // parse box data + size = view.getUint32(i); + type = parseType(data.subarray(i + 4, i + 8)); + end = size > 1 ? i + size : data.byteLength; + + // parse type-specific data + box = (parse$2[type] || function(data) { + return { + data: data + }; + })(data.subarray(i + 8, end)); + box.size = size; + box.type = type; + + // store this box and move to the next + result.push(box); + i = end; + } + return result; +}; + +/** + * Returns a textual representation of the javascript represtentation + * of an MP4 file. You can use it as an alternative to + * JSON.stringify() to compare inspected MP4s. + * @param inspectedMp4 {array} the parsed array of boxes in an MP4 + * file + * @param depth {number} (optional) the number of ancestor boxes of + * the elements of inspectedMp4. Assumed to be zero if unspecified. + * @return {string} a text representation of the parsed MP4 + */ +textifyMp4 = function(inspectedMp4, depth) { + var indent; + depth = depth || 0; + indent = new Array(depth * 2 + 1).join(' '); + + // iterate over all the boxes + return inspectedMp4.map(function(box, index) { + + // list the box type first at the current indentation level + return indent + box.type + '\n' + + + // the type is already included and handle child boxes separately + Object.keys(box).filter(function(key) { + return key !== 'type' && key !== 'boxes'; + + // output all the box properties + }).map(function(key) { + var prefix = indent + ' ' + key + ': ', + value = box[key]; + + // print out raw bytes as hexademical + if (value instanceof Uint8Array || value instanceof Uint32Array) { + var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)) + .map(function(byte) { + return ' ' + ('00' + byte.toString(16)).slice(-2); + }).join('').match(/.{1,24}/g); + if (!bytes) { + return prefix + '<>'; + } + if (bytes.length === 1) { + return prefix + '<' + bytes.join('').slice(1) + '>'; + } + return prefix + '<\n' + bytes.map(function(line) { + return indent + ' ' + line; + }).join('\n') + '\n' + indent + ' >'; + } + + // stringify generic objects + return prefix + + JSON.stringify(value, null, 2) + .split('\n').map(function(line, index) { + if (index === 0) { + return line; + } + return indent + ' ' + line; + }).join('\n'); + }).join('\n') + + + // recursively textify the child boxes + (box.boxes ? '\n' + textifyMp4(box.boxes, depth + 1) : ''); + }).join('\n'); +}; + +var mp4Inspector = { + inspect: inspectMp4, + textify: textifyMp4, + parseType: parseType, + findBox: findBox, + parseTraf: parse$2.traf, + parseTfdt: parse$2.tfdt, + parseHdlr: parse$2.hdlr, + parseTfhd: parse$2.tfhd, + parseTrun: parse$2.trun, + parseSidx: parse$2.sidx +}; + +var toUnsigned$2 = bin.toUnsigned; +var toHexString$1 = bin.toHexString; + +var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks; + +/** + * Parses an MP4 initialization segment and extracts the timescale + * values for any declared tracks. Timescale values indicate the + * number of clock ticks per second to assume for time-based values + * elsewhere in the MP4. + * + * To determine the start time of an MP4, you need two pieces of + * information: the timescale unit and the earliest base media decode + * time. Multiple timescales can be specified within an MP4 but the + * base media decode time is always expressed in the timescale from + * the media header box for the track: + * ``` + * moov > trak > mdia > mdhd.timescale + * ``` + * @param init {Uint8Array} the bytes of the init segment + * @return {object} a hash of track ids to timescale values or null if + * the init segment is malformed. + */ +timescale = function(init) { + var + result = {}, + traks = mp4Inspector.findBox(init, ['moov', 'trak']); + + // mdhd timescale + return traks.reduce(function(result, trak) { + var tkhd, version, index, id, mdhd; + + tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; + if (!tkhd) { + return null; + } + version = tkhd[0]; + index = version === 0 ? 12 : 20; + id = toUnsigned$2(tkhd[index] << 24 | + tkhd[index + 1] << 16 | + tkhd[index + 2] << 8 | + tkhd[index + 3]); + + mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; + if (!mdhd) { + return null; + } + version = mdhd[0]; + index = version === 0 ? 12 : 20; + result[id] = toUnsigned$2(mdhd[index] << 24 | + mdhd[index + 1] << 16 | + mdhd[index + 2] << 8 | + mdhd[index + 3]); + return result; + }, result); +}; + +/** + * Determine the base media decode start time, in seconds, for an MP4 + * fragment. If multiple fragments are specified, the earliest time is + * returned. + * + * The base media decode time can be parsed from track fragment + * metadata: + * ``` + * moof > traf > tfdt.baseMediaDecodeTime + * ``` + * It requires the timescale value from the mdhd to interpret. + * + * @param timescale {object} a hash of track ids to timescale values. + * @return {number} the earliest base media decode start time for the + * fragment, in seconds + */ +startTime = function(timescale, fragment) { + var trafs, baseTimes, result; + + // we need info from two childrend of each track fragment box + trafs = mp4Inspector.findBox(fragment, ['moof', 'traf']); + + // determine the start times for each track + baseTimes = [].concat.apply([], trafs.map(function(traf) { + return mp4Inspector.findBox(traf, ['tfhd']).map(function(tfhd) { + var id, scale, baseTime; + + // get the track id from the tfhd + id = toUnsigned$2(tfhd[4] << 24 | + tfhd[5] << 16 | + tfhd[6] << 8 | + tfhd[7]); + // assume a 90kHz clock if no timescale was specified + scale = timescale[id] || 90e3; + + // get the base media decode time from the tfdt + baseTime = mp4Inspector.findBox(traf, ['tfdt']).map(function(tfdt) { + var version, result; + + version = tfdt[0]; + result = toUnsigned$2(tfdt[4] << 24 | + tfdt[5] << 16 | + tfdt[6] << 8 | + tfdt[7]); + if (version === 1) { + result *= Math.pow(2, 32); + result += toUnsigned$2(tfdt[8] << 24 | + tfdt[9] << 16 | + tfdt[10] << 8 | + tfdt[11]); + } + return result; + })[0]; + baseTime = baseTime || Infinity; + + // convert base time to seconds + return baseTime / scale; + }); + })); + + // return the minimum + result = Math.min.apply(null, baseTimes); + return isFinite(result) ? result : 0; +}; + +/** + * Determine the composition start, in seconds, for an MP4 + * fragment. + * + * The composition start time of a fragment can be calculated using the base + * media decode time, composition time offset, and timescale, as follows: + * + * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale + * + * All of the aforementioned information is contained within a media fragment's + * `traf` box, except for timescale info, which comes from the initialization + * segment, so a track id (also contained within a `traf`) is also necessary to + * associate it with a timescale + * + * + * @param timescales {object} - a hash of track ids to timescale values. + * @param fragment {Unit8Array} - the bytes of a media segment + * @return {number} the composition start time for the fragment, in seconds + **/ +compositionStartTime = function(timescales, fragment) { + var trafBoxes = mp4Inspector.findBox(fragment, ['moof', 'traf']); + var baseMediaDecodeTime = 0; + var compositionTimeOffset = 0; + var trackId; + + if (trafBoxes && trafBoxes.length) { + // The spec states that track run samples contained within a `traf` box are contiguous, but + // it does not explicitly state whether the `traf` boxes themselves are contiguous. + // We will assume that they are, so we only need the first to calculate start time. + var parsedTraf = mp4Inspector.parseTraf(trafBoxes[0]); + + for (var i = 0; i < parsedTraf.boxes.length; i++) { + if (parsedTraf.boxes[i].type === 'tfhd') { + trackId = parsedTraf.boxes[i].trackId; + } else if (parsedTraf.boxes[i].type === 'tfdt') { + baseMediaDecodeTime = parsedTraf.boxes[i].baseMediaDecodeTime; + } else if (parsedTraf.boxes[i].type === 'trun' && parsedTraf.boxes[i].samples.length) { + compositionTimeOffset = parsedTraf.boxes[i].samples[0].compositionTimeOffset || 0; + } + } + } + + // Get timescale for this specific track. Assume a 90kHz clock if no timescale was + // specified. + var timescale = timescales[trackId] || 90e3; + + // return the composition start time, in seconds + return (baseMediaDecodeTime + compositionTimeOffset) / timescale; +}; + +/** + * Find the trackIds of the video tracks in this source. + * Found by parsing the Handler Reference and Track Header Boxes: + * moov > trak > mdia > hdlr + * moov > trak > tkhd + * + * @param {Uint8Array} init - The bytes of the init segment for this source + * @return {Number[]} A list of trackIds + * + * @see ISO-BMFF-12/2015, Section 8.4.3 + **/ +getVideoTrackIds = function(init) { + var traks = mp4Inspector.findBox(init, ['moov', 'trak']); + var videoTrackIds = []; + + traks.forEach(function(trak) { + var hdlrs = mp4Inspector.findBox(trak, ['mdia', 'hdlr']); + var tkhds = mp4Inspector.findBox(trak, ['tkhd']); + + hdlrs.forEach(function(hdlr, index) { + var handlerType = mp4Inspector.parseType(hdlr.subarray(8, 12)); + var tkhd = tkhds[index]; + var view; + var version; + var trackId; + + if (handlerType === 'vide') { + view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); + version = view.getUint8(0); + trackId = (version === 0) ? view.getUint32(12) : view.getUint32(20); + + videoTrackIds.push(trackId); + } + }); + }); + + return videoTrackIds; +}; + +/** + * Get all the video, audio, and hint tracks from a non fragmented + * mp4 segment + */ +getTracks = function(init) { + var traks = mp4Inspector.findBox(init, ['moov', 'trak']); + var tracks = []; + + traks.forEach(function(trak) { + var track = {}; + var tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; + var view, version; + + // id + if (tkhd) { + view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); + version = view.getUint8(0); + + track.id = (version === 0) ? view.getUint32(12) : view.getUint32(20); + } + + var hdlr = mp4Inspector.findBox(trak, ['mdia', 'hdlr'])[0]; + + // type + if (hdlr) { + var type = mp4Inspector.parseType(hdlr.subarray(8, 12)); + + if (type === 'vide') { + track.type = 'video'; + } else if (type === 'soun') { + track.type = 'audio'; + } else { + track.type = type; + } + } + + + // codec + var stsd = mp4Inspector.findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; + + if (stsd) { + var sampleDescriptions = stsd.subarray(8); + // gives the codec type string + track.codec = mp4Inspector.parseType(sampleDescriptions.subarray(4, 8)); + + var codecBox = mp4Inspector.findBox(sampleDescriptions, [track.codec])[0]; + var codecConfig, codecConfigType; + + if (codecBox) { + // https://tools.ietf.org/html/rfc6381#section-3.3 + if ((/^[a-z]vc[1-9]$/i).test(track.codec)) { + // we don't need anything but the "config" parameter of the + // avc1 codecBox + codecConfig = codecBox.subarray(78); + codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); + + if (codecConfigType === 'avcC' && codecConfig.length > 11) { + track.codec += '.'; + + // left padded with zeroes for single digit hex + // profile idc + track.codec += toHexString$1(codecConfig[9]); + // the byte containing the constraint_set flags + track.codec += toHexString$1(codecConfig[10]); + // level idc + track.codec += toHexString$1(codecConfig[11]); + } else { + // TODO: show a warning that we couldn't parse the codec + // and are using the default + track.codec = 'avc1.4d400d'; + } + } else if ((/^mp4[a,v]$/i).test(track.codec)) { + // we do not need anything but the streamDescriptor of the mp4a codecBox + codecConfig = codecBox.subarray(28); + codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); + + if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { + track.codec += '.' + toHexString$1(codecConfig[19]); + // this value is only a single digit + track.codec += '.' + toHexString$1((codecConfig[20] >>> 2) & 0x3f).replace(/^0/, ''); + } else { + // TODO: show a warning that we couldn't parse the codec + // and are using the default + track.codec = 'mp4a.40.2'; + } + } else ; + } + } + + var mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; + + if (mdhd && tkhd) { + var index = version === 0 ? 12 : 20; + + track.timescale = toUnsigned$2(mdhd[index] << 24 | + mdhd[index + 1] << 16 | + mdhd[index + 2] << 8 | + mdhd[index + 3]); + } + + tracks.push(track); + }); + + return tracks; +}; + +var probe = { + // export mp4 inspector's findBox and parseType for backwards compatibility + findBox: mp4Inspector.findBox, + parseType: mp4Inspector.parseType, + timescale: timescale, + startTime: startTime, + compositionStartTime: compositionStartTime, + videoTrackIds: getVideoTrackIds, + tracks: getTracks +}; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + * + * Reads in-band caption information from a video elementary + * stream. Captions must follow the CEA-708 standard for injection + * into an MPEG-2 transport streams. + * @see https://en.wikipedia.org/wiki/CEA-708 + * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf + */ + +// Supplemental enhancement information (SEI) NAL units have a +// payload type field to indicate how they are to be +// interpreted. CEAS-708 caption content is always transmitted with +// payload type 0x04. +var USER_DATA_REGISTERED_ITU_T_T35 = 4, + RBSP_TRAILING_BITS = 128; + +/** + * Parse a supplemental enhancement information (SEI) NAL unit. + * Stops parsing once a message of type ITU T T35 has been found. + * + * @param bytes {Uint8Array} the bytes of a SEI NAL unit + * @return {object} the parsed SEI payload + * @see Rec. ITU-T H.264, 7.3.2.3.1 + */ +var parseSei = function(bytes) { + var + i = 0, + result = { + payloadType: -1, + payloadSize: 0 + }, + payloadType = 0, + payloadSize = 0; + + // go through the sei_rbsp parsing each each individual sei_message + while (i < bytes.byteLength) { + // stop once we have hit the end of the sei_rbsp + if (bytes[i] === RBSP_TRAILING_BITS) { + break; + } + + // Parse payload type + while (bytes[i] === 0xFF) { + payloadType += 255; + i++; + } + payloadType += bytes[i++]; + + // Parse payload size + while (bytes[i] === 0xFF) { + payloadSize += 255; + i++; + } + payloadSize += bytes[i++]; + + // this sei_message is a 608/708 caption so save it and break + // there can only ever be one caption message in a frame's sei + if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { + var userIdentifier = String.fromCharCode( + bytes[i + 3], + bytes[i + 4], + bytes[i + 5], + bytes[i + 6]); + + if (userIdentifier === 'GA94') { + result.payloadType = payloadType; + result.payloadSize = payloadSize; + result.payload = bytes.subarray(i, i + payloadSize); + break; + } else { + result.payload = void 0; + } + } + + // skip the payload and parse the next message + i += payloadSize; + payloadType = 0; + payloadSize = 0; + } + + return result; +}; + +// see ANSI/SCTE 128-1 (2013), section 8.1 +var parseUserData = function(sei) { + // itu_t_t35_contry_code must be 181 (United States) for + // captions + if (sei.payload[0] !== 181) { + return null; + } + + // itu_t_t35_provider_code should be 49 (ATSC) for captions + if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) { + return null; + } + + // the user_identifier should be "GA94" to indicate ATSC1 data + if (String.fromCharCode(sei.payload[3], + sei.payload[4], + sei.payload[5], + sei.payload[6]) !== 'GA94') { + return null; + } + + // finally, user_data_type_code should be 0x03 for caption data + if (sei.payload[7] !== 0x03) { + return null; + } + + // return the user_data_type_structure and strip the trailing + // marker bits + return sei.payload.subarray(8, sei.payload.length - 1); +}; + +// see CEA-708-D, section 4.4 +var parseCaptionPackets = function(pts, userData) { + var results = [], i, count, offset, data; + + // if this is just filler, return immediately + if (!(userData[0] & 0x40)) { + return results; + } + + // parse out the cc_data_1 and cc_data_2 fields + count = userData[0] & 0x1f; + for (i = 0; i < count; i++) { + offset = i * 3; + data = { + type: userData[offset + 2] & 0x03, + pts: pts + }; + + // capture cc data when cc_valid is 1 + if (userData[offset + 2] & 0x04) { + data.ccData = (userData[offset + 3] << 8) | userData[offset + 4]; + results.push(data); + } + } + return results; +}; + +var discardEmulationPreventionBytes = function(data) { + var + length = data.byteLength, + emulationPreventionBytesPositions = [], + i = 1, + newLength, newData; + + // Find all `Emulation Prevention Bytes` + while (i < length - 2) { + if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { + emulationPreventionBytesPositions.push(i + 2); + i += 2; + } else { + i++; + } + } + + // If no Emulation Prevention Bytes were found just return the original + // array + if (emulationPreventionBytesPositions.length === 0) { + return data; + } + + // Create a new array to hold the NAL unit data + newLength = length - emulationPreventionBytesPositions.length; + newData = new Uint8Array(newLength); + var sourceIndex = 0; + + for (i = 0; i < newLength; sourceIndex++, i++) { + if (sourceIndex === emulationPreventionBytesPositions[0]) { + // Skip this byte + sourceIndex++; + // Remove this position index + emulationPreventionBytesPositions.shift(); + } + newData[i] = data[sourceIndex]; + } + + return newData; +}; + +// exports +var captionPacketParser = { + parseSei: parseSei, + parseUserData: parseUserData, + parseCaptionPackets: parseCaptionPackets, + discardEmulationPreventionBytes: discardEmulationPreventionBytes, + USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 +}; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + * + * A lightweight readable stream implemention that handles event dispatching. + * Objects that inherit from streams should call init in their constructors. + */ + +var Stream$1 = function() { + this.init = function() { + var listeners = {}; + /** + * Add a listener for a specified event type. + * @param type {string} the event name + * @param listener {function} the callback to be invoked when an event of + * the specified type occurs + */ + this.on = function(type, listener) { + if (!listeners[type]) { + listeners[type] = []; + } + listeners[type] = listeners[type].concat(listener); + }; + /** + * Remove a listener for a specified event type. + * @param type {string} the event name + * @param listener {function} a function previously registered for this + * type of event through `on` + */ + this.off = function(type, listener) { + var index; + if (!listeners[type]) { + return false; + } + index = listeners[type].indexOf(listener); + listeners[type] = listeners[type].slice(); + listeners[type].splice(index, 1); + return index > -1; + }; + /** + * Trigger an event of the specified type on this stream. Any additional + * arguments to this function are passed as parameters to event listeners. + * @param type {string} the event name + */ + this.trigger = function(type) { + var callbacks, i, length, args; + callbacks = listeners[type]; + if (!callbacks) { + return; + } + // Slicing the arguments on every invocation of this method + // can add a significant amount of overhead. Avoid the + // intermediate object creation for the common case of a + // single callback argument + if (arguments.length === 2) { + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].call(this, arguments[1]); + } + } else { + args = []; + i = arguments.length; + for (i = 1; i < arguments.length; ++i) { + args.push(arguments[i]); + } + length = callbacks.length; + for (i = 0; i < length; ++i) { + callbacks[i].apply(this, args); + } + } + }; + /** + * Destroys the stream and cleans up. + */ + this.dispose = function() { + listeners = {}; + }; + }; +}; + +/** + * Forwards all `data` events on this stream to the destination stream. The + * destination stream should provide a method `push` to receive the data + * events as they arrive. + * @param destination {stream} the stream that will receive all `data` events + * @param autoFlush {boolean} if false, we will not call `flush` on the destination + * when the current stream emits a 'done' event + * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options + */ +Stream$1.prototype.pipe = function(destination) { + this.on('data', function(data) { + destination.push(data); + }); + + this.on('done', function(flushSource) { + destination.flush(flushSource); + }); + + this.on('partialdone', function(flushSource) { + destination.partialFlush(flushSource); + }); + + this.on('endedtimeline', function(flushSource) { + destination.endTimeline(flushSource); + }); + + this.on('reset', function(flushSource) { + destination.reset(flushSource); + }); + + return destination; +}; + +// Default stream functions that are expected to be overridden to perform +// actual work. These are provided by the prototype as a sort of no-op +// implementation so that we don't have to check for their existence in the +// `pipe` function above. +Stream$1.prototype.push = function(data) { + this.trigger('data', data); +}; + +Stream$1.prototype.flush = function(flushSource) { + this.trigger('done', flushSource); +}; + +Stream$1.prototype.partialFlush = function(flushSource) { + this.trigger('partialdone', flushSource); +}; + +Stream$1.prototype.endTimeline = function(flushSource) { + this.trigger('endedtimeline', flushSource); +}; + +Stream$1.prototype.reset = function(flushSource) { + this.trigger('reset', flushSource); +}; + +var stream = Stream$1; + +// ----------------- +// Link To Transport +// ----------------- + + + + +var CaptionStream = function() { + + CaptionStream.prototype.init.call(this); + + this.captionPackets_ = []; + + this.ccStreams_ = [ + new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define + new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define + new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define + new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define + ]; + + this.reset(); + + // forward data and done events from CCs to this CaptionStream + this.ccStreams_.forEach(function(cc) { + cc.on('data', this.trigger.bind(this, 'data')); + cc.on('partialdone', this.trigger.bind(this, 'partialdone')); + cc.on('done', this.trigger.bind(this, 'done')); + }, this); + +}; + +CaptionStream.prototype = new stream(); +CaptionStream.prototype.push = function(event) { + var sei, userData, newCaptionPackets; + + // only examine SEI NALs + if (event.nalUnitType !== 'sei_rbsp') { + return; + } + + // parse the sei + sei = captionPacketParser.parseSei(event.escapedRBSP); + + // ignore everything but user_data_registered_itu_t_t35 + if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { + return; + } + + // parse out the user data payload + userData = captionPacketParser.parseUserData(sei); + + // ignore unrecognized userData + if (!userData) { + return; + } + + // Sometimes, the same segment # will be downloaded twice. To stop the + // caption data from being processed twice, we track the latest dts we've + // received and ignore everything with a dts before that. However, since + // data for a specific dts can be split across packets on either side of + // a segment boundary, we need to make sure we *don't* ignore the packets + // from the *next* segment that have dts === this.latestDts_. By constantly + // tracking the number of packets received with dts === this.latestDts_, we + // know how many should be ignored once we start receiving duplicates. + if (event.dts < this.latestDts_) { + // We've started getting older data, so set the flag. + this.ignoreNextEqualDts_ = true; + return; + } else if ((event.dts === this.latestDts_) && (this.ignoreNextEqualDts_)) { + this.numSameDts_--; + if (!this.numSameDts_) { + // We've received the last duplicate packet, time to start processing again + this.ignoreNextEqualDts_ = false; + } + return; + } + + // parse out CC data packets and save them for later + newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); + this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); + if (this.latestDts_ !== event.dts) { + this.numSameDts_ = 0; + } + this.numSameDts_++; + this.latestDts_ = event.dts; +}; + +CaptionStream.prototype.flushCCStreams = function(flushType) { + this.ccStreams_.forEach(function(cc) { + return flushType === 'flush' ? cc.flush() : cc.partialFlush(); + }, this); +}; + +CaptionStream.prototype.flushStream = function(flushType) { + // make sure we actually parsed captions before proceeding + if (!this.captionPackets_.length) { + this.flushCCStreams(flushType); + return; + } + + // In Chrome, the Array#sort function is not stable so add a + // presortIndex that we can use to ensure we get a stable-sort + this.captionPackets_.forEach(function(elem, idx) { + elem.presortIndex = idx; + }); + + // sort caption byte-pairs based on their PTS values + this.captionPackets_.sort(function(a, b) { + if (a.pts === b.pts) { + return a.presortIndex - b.presortIndex; + } + return a.pts - b.pts; + }); + + this.captionPackets_.forEach(function(packet) { + if (packet.type < 2) { + // Dispatch packet to the right Cea608Stream + this.dispatchCea608Packet(packet); + } + // this is where an 'else' would go for a dispatching packets + // to a theoretical Cea708Stream that handles SERVICEn data + }, this); + + this.captionPackets_.length = 0; + this.flushCCStreams(flushType); +}; + +CaptionStream.prototype.flush = function() { + return this.flushStream('flush'); +}; + +// Only called if handling partial data +CaptionStream.prototype.partialFlush = function() { + return this.flushStream('partialFlush'); +}; + +CaptionStream.prototype.reset = function() { + this.latestDts_ = null; + this.ignoreNextEqualDts_ = false; + this.numSameDts_ = 0; + this.activeCea608Channel_ = [null, null]; + this.ccStreams_.forEach(function(ccStream) { + ccStream.reset(); + }); +}; + +// From the CEA-608 spec: +/* + * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed + * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is + * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair + * and subsequent data should then be processed according to the FCC rules. It may be necessary for the + * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) + * to switch to captioning or Text. +*/ +// With that in mind, we ignore any data between an XDS control code and a +// subsequent closed-captioning control code. +CaptionStream.prototype.dispatchCea608Packet = function(packet) { + // NOTE: packet.type is the CEA608 field + if (this.setsTextOrXDSActive(packet)) { + this.activeCea608Channel_[packet.type] = null; + } else if (this.setsChannel1Active(packet)) { + this.activeCea608Channel_[packet.type] = 0; + } else if (this.setsChannel2Active(packet)) { + this.activeCea608Channel_[packet.type] = 1; + } + if (this.activeCea608Channel_[packet.type] === null) { + // If we haven't received anything to set the active channel, or the + // packets are Text/XDS data, discard the data; we don't want jumbled + // captions + return; + } + this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); +}; + +CaptionStream.prototype.setsChannel1Active = function(packet) { + return ((packet.ccData & 0x7800) === 0x1000); +}; +CaptionStream.prototype.setsChannel2Active = function(packet) { + return ((packet.ccData & 0x7800) === 0x1800); +}; +CaptionStream.prototype.setsTextOrXDSActive = function(packet) { + return ((packet.ccData & 0x7100) === 0x0100) || + ((packet.ccData & 0x78fe) === 0x102a) || + ((packet.ccData & 0x78fe) === 0x182a); +}; + +// ---------------------- +// Session to Application +// ---------------------- + +// This hash maps non-ASCII, special, and extended character codes to their +// proper Unicode equivalent. The first keys that are only a single byte +// are the non-standard ASCII characters, which simply map the CEA608 byte +// to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 +// character codes, but have their MSB bitmasked with 0x03 so that a lookup +// can be performed regardless of the field and data channel on which the +// character code was received. +var CHARACTER_TRANSLATION = { + 0x2a: 0xe1, // á + 0x5c: 0xe9, // é + 0x5e: 0xed, // í + 0x5f: 0xf3, // ó + 0x60: 0xfa, // ú + 0x7b: 0xe7, // ç + 0x7c: 0xf7, // ÷ + 0x7d: 0xd1, // Ñ + 0x7e: 0xf1, // ñ + 0x7f: 0x2588, // █ + 0x0130: 0xae, // ® + 0x0131: 0xb0, // ° + 0x0132: 0xbd, // ½ + 0x0133: 0xbf, // ¿ + 0x0134: 0x2122, // ™ + 0x0135: 0xa2, // ¢ + 0x0136: 0xa3, // £ + 0x0137: 0x266a, // ♪ + 0x0138: 0xe0, // à + 0x0139: 0xa0, // + 0x013a: 0xe8, // è + 0x013b: 0xe2, // â + 0x013c: 0xea, // ê + 0x013d: 0xee, // î + 0x013e: 0xf4, // ô + 0x013f: 0xfb, // û + 0x0220: 0xc1, // Á + 0x0221: 0xc9, // É + 0x0222: 0xd3, // Ó + 0x0223: 0xda, // Ú + 0x0224: 0xdc, // Ü + 0x0225: 0xfc, // ü + 0x0226: 0x2018, // ‘ + 0x0227: 0xa1, // ¡ + 0x0228: 0x2a, // * + 0x0229: 0x27, // ' + 0x022a: 0x2014, // — + 0x022b: 0xa9, // © + 0x022c: 0x2120, // ℠ + 0x022d: 0x2022, // • + 0x022e: 0x201c, // “ + 0x022f: 0x201d, // ” + 0x0230: 0xc0, // À + 0x0231: 0xc2, // Â + 0x0232: 0xc7, // Ç + 0x0233: 0xc8, // È + 0x0234: 0xca, // Ê + 0x0235: 0xcb, // Ë + 0x0236: 0xeb, // ë + 0x0237: 0xce, // Î + 0x0238: 0xcf, // Ï + 0x0239: 0xef, // ï + 0x023a: 0xd4, // Ô + 0x023b: 0xd9, // Ù + 0x023c: 0xf9, // ù + 0x023d: 0xdb, // Û + 0x023e: 0xab, // « + 0x023f: 0xbb, // » + 0x0320: 0xc3, // Ã + 0x0321: 0xe3, // ã + 0x0322: 0xcd, // Í + 0x0323: 0xcc, // Ì + 0x0324: 0xec, // ì + 0x0325: 0xd2, // Ò + 0x0326: 0xf2, // ò + 0x0327: 0xd5, // Õ + 0x0328: 0xf5, // õ + 0x0329: 0x7b, // { + 0x032a: 0x7d, // } + 0x032b: 0x5c, // \ + 0x032c: 0x5e, // ^ + 0x032d: 0x5f, // _ + 0x032e: 0x7c, // | + 0x032f: 0x7e, // ~ + 0x0330: 0xc4, // Ä + 0x0331: 0xe4, // ä + 0x0332: 0xd6, // Ö + 0x0333: 0xf6, // ö + 0x0334: 0xdf, // ß + 0x0335: 0xa5, // ¥ + 0x0336: 0xa4, // ¤ + 0x0337: 0x2502, // │ + 0x0338: 0xc5, // Å + 0x0339: 0xe5, // å + 0x033a: 0xd8, // Ø + 0x033b: 0xf8, // ø + 0x033c: 0x250c, // ┌ + 0x033d: 0x2510, // ┐ + 0x033e: 0x2514, // └ + 0x033f: 0x2518 // ┘ +}; + +var getCharFromCode = function(code) { + if (code === null) { + return ''; + } + code = CHARACTER_TRANSLATION[code] || code; + return String.fromCharCode(code); +}; + +// the index of the last row in a CEA-608 display buffer +var BOTTOM_ROW = 14; + +// This array is used for mapping PACs -> row #, since there's no way of +// getting it through bit logic. +var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, + 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; + +// CEA-608 captions are rendered onto a 34x15 matrix of character +// cells. The "bottom" row is the last element in the outer array. +var createDisplayBuffer = function() { + var result = [], i = BOTTOM_ROW + 1; + while (i--) { + result.push(''); + } + return result; +}; + +var Cea608Stream = function(field, dataChannel) { + Cea608Stream.prototype.init.call(this); + + this.field_ = field || 0; + this.dataChannel_ = dataChannel || 0; + + this.name_ = 'CC' + (((this.field_ << 1) | this.dataChannel_) + 1); + + this.setConstants(); + this.reset(); + + this.push = function(packet) { + var data, swap, char0, char1, text; + // remove the parity bits + data = packet.ccData & 0x7f7f; + + // ignore duplicate control codes; the spec demands they're sent twice + if (data === this.lastControlCode_) { + this.lastControlCode_ = null; + return; + } + + // Store control codes + if ((data & 0xf000) === 0x1000) { + this.lastControlCode_ = data; + } else if (data !== this.PADDING_) { + this.lastControlCode_ = null; + } + + char0 = data >>> 8; + char1 = data & 0xff; + + if (data === this.PADDING_) { + return; + + } else if (data === this.RESUME_CAPTION_LOADING_) { + this.mode_ = 'popOn'; + + } else if (data === this.END_OF_CAPTION_) { + // If an EOC is received while in paint-on mode, the displayed caption + // text should be swapped to non-displayed memory as if it was a pop-on + // caption. Because of that, we should explicitly switch back to pop-on + // mode + this.mode_ = 'popOn'; + this.clearFormatting(packet.pts); + // if a caption was being displayed, it's gone now + this.flushDisplayed(packet.pts); + + // flip memory + swap = this.displayed_; + this.displayed_ = this.nonDisplayed_; + this.nonDisplayed_ = swap; + + // start measuring the time to display the caption + this.startPts_ = packet.pts; + + } else if (data === this.ROLL_UP_2_ROWS_) { + this.rollUpRows_ = 2; + this.setRollUp(packet.pts); + } else if (data === this.ROLL_UP_3_ROWS_) { + this.rollUpRows_ = 3; + this.setRollUp(packet.pts); + } else if (data === this.ROLL_UP_4_ROWS_) { + this.rollUpRows_ = 4; + this.setRollUp(packet.pts); + } else if (data === this.CARRIAGE_RETURN_) { + this.clearFormatting(packet.pts); + this.flushDisplayed(packet.pts); + this.shiftRowsUp_(); + this.startPts_ = packet.pts; + + } else if (data === this.BACKSPACE_) { + if (this.mode_ === 'popOn') { + this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); + } else { + this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); + } + } else if (data === this.ERASE_DISPLAYED_MEMORY_) { + this.flushDisplayed(packet.pts); + this.displayed_ = createDisplayBuffer(); + } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { + this.nonDisplayed_ = createDisplayBuffer(); + + } else if (data === this.RESUME_DIRECT_CAPTIONING_) { + if (this.mode_ !== 'paintOn') { + // NOTE: This should be removed when proper caption positioning is + // implemented + this.flushDisplayed(packet.pts); + this.displayed_ = createDisplayBuffer(); + } + this.mode_ = 'paintOn'; + this.startPts_ = packet.pts; + + // Append special characters to caption text + } else if (this.isSpecialCharacter(char0, char1)) { + // Bitmask char0 so that we can apply character transformations + // regardless of field and data channel. + // Then byte-shift to the left and OR with char1 so we can pass the + // entire character code to `getCharFromCode`. + char0 = (char0 & 0x03) << 8; + text = getCharFromCode(char0 | char1); + this[this.mode_](packet.pts, text); + this.column_++; + + // Append extended characters to caption text + } else if (this.isExtCharacter(char0, char1)) { + // Extended characters always follow their "non-extended" equivalents. + // IE if a "è" is desired, you'll always receive "eè"; non-compliant + // decoders are supposed to drop the "è", while compliant decoders + // backspace the "e" and insert "è". + + // Delete the previous character + if (this.mode_ === 'popOn') { + this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); + } else { + this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); + } + + // Bitmask char0 so that we can apply character transformations + // regardless of field and data channel. + // Then byte-shift to the left and OR with char1 so we can pass the + // entire character code to `getCharFromCode`. + char0 = (char0 & 0x03) << 8; + text = getCharFromCode(char0 | char1); + this[this.mode_](packet.pts, text); + this.column_++; + + // Process mid-row codes + } else if (this.isMidRowCode(char0, char1)) { + // Attributes are not additive, so clear all formatting + this.clearFormatting(packet.pts); + + // According to the standard, mid-row codes + // should be replaced with spaces, so add one now + this[this.mode_](packet.pts, ' '); + this.column_++; + + if ((char1 & 0xe) === 0xe) { + this.addFormatting(packet.pts, ['i']); + } + + if ((char1 & 0x1) === 0x1) { + this.addFormatting(packet.pts, ['u']); + } + + // Detect offset control codes and adjust cursor + } else if (this.isOffsetControlCode(char0, char1)) { + // Cursor position is set by indent PAC (see below) in 4-column + // increments, with an additional offset code of 1-3 to reach any + // of the 32 columns specified by CEA-608. So all we need to do + // here is increment the column cursor by the given offset. + this.column_ += (char1 & 0x03); + + // Detect PACs (Preamble Address Codes) + } else if (this.isPAC(char0, char1)) { + + // There's no logic for PAC -> row mapping, so we have to just + // find the row code in an array and use its index :( + var row = ROWS.indexOf(data & 0x1f20); + + // Configure the caption window if we're in roll-up mode + if (this.mode_ === 'rollUp') { + // This implies that the base row is incorrectly set. + // As per the recommendation in CEA-608(Base Row Implementation), defer to the number + // of roll-up rows set. + if (row - this.rollUpRows_ + 1 < 0) { + row = this.rollUpRows_ - 1; + } + + this.setRollUp(packet.pts, row); + } + + if (row !== this.row_) { + // formatting is only persistent for current row + this.clearFormatting(packet.pts); + this.row_ = row; + } + // All PACs can apply underline, so detect and apply + // (All odd-numbered second bytes set underline) + if ((char1 & 0x1) && (this.formatting_.indexOf('u') === -1)) { + this.addFormatting(packet.pts, ['u']); + } + + if ((data & 0x10) === 0x10) { + // We've got an indent level code. Each successive even number + // increments the column cursor by 4, so we can get the desired + // column position by bit-shifting to the right (to get n/2) + // and multiplying by 4. + this.column_ = ((data & 0xe) >> 1) * 4; + } + + if (this.isColorPAC(char1)) { + // it's a color code, though we only support white, which + // can be either normal or italicized. white italics can be + // either 0x4e or 0x6e depending on the row, so we just + // bitwise-and with 0xe to see if italics should be turned on + if ((char1 & 0xe) === 0xe) { + this.addFormatting(packet.pts, ['i']); + } + } + + // We have a normal character in char0, and possibly one in char1 + } else if (this.isNormalChar(char0)) { + if (char1 === 0x00) { + char1 = null; + } + text = getCharFromCode(char0); + text += getCharFromCode(char1); + this[this.mode_](packet.pts, text); + this.column_ += text.length; + + } // finish data processing + + }; +}; +Cea608Stream.prototype = new stream(); +// Trigger a cue point that captures the current state of the +// display buffer +Cea608Stream.prototype.flushDisplayed = function(pts) { + var content = this.displayed_ + // remove spaces from the start and end of the string + .map(function(row) { + try { + return row.trim(); + } catch (e) { + // Ordinarily, this shouldn't happen. However, caption + // parsing errors should not throw exceptions and + // break playback. + // eslint-disable-next-line no-console + console.error('Skipping malformed caption.'); + return ''; + } + }) + // combine all text rows to display in one cue + .join('\n') + // and remove blank rows from the start and end, but not the middle + .replace(/^\n+|\n+$/g, ''); + + if (content.length) { + this.trigger('data', { + startPts: this.startPts_, + endPts: pts, + text: content, + stream: this.name_ + }); + } +}; + +/** + * Zero out the data, used for startup and on seek + */ +Cea608Stream.prototype.reset = function() { + this.mode_ = 'popOn'; + // When in roll-up mode, the index of the last row that will + // actually display captions. If a caption is shifted to a row + // with a lower index than this, it is cleared from the display + // buffer + this.topRow_ = 0; + this.startPts_ = 0; + this.displayed_ = createDisplayBuffer(); + this.nonDisplayed_ = createDisplayBuffer(); + this.lastControlCode_ = null; + + // Track row and column for proper line-breaking and spacing + this.column_ = 0; + this.row_ = BOTTOM_ROW; + this.rollUpRows_ = 2; + + // This variable holds currently-applied formatting + this.formatting_ = []; +}; + +/** + * Sets up control code and related constants for this instance + */ +Cea608Stream.prototype.setConstants = function() { + // The following attributes have these uses: + // ext_ : char0 for mid-row codes, and the base for extended + // chars (ext_+0, ext_+1, and ext_+2 are char0s for + // extended codes) + // control_: char0 for control codes, except byte-shifted to the + // left so that we can do this.control_ | CONTROL_CODE + // offset_: char0 for tab offset codes + // + // It's also worth noting that control codes, and _only_ control codes, + // differ between field 1 and field2. Field 2 control codes are always + // their field 1 value plus 1. That's why there's the "| field" on the + // control value. + if (this.dataChannel_ === 0) { + this.BASE_ = 0x10; + this.EXT_ = 0x11; + this.CONTROL_ = (0x14 | this.field_) << 8; + this.OFFSET_ = 0x17; + } else if (this.dataChannel_ === 1) { + this.BASE_ = 0x18; + this.EXT_ = 0x19; + this.CONTROL_ = (0x1c | this.field_) << 8; + this.OFFSET_ = 0x1f; + } + + // Constants for the LSByte command codes recognized by Cea608Stream. This + // list is not exhaustive. For a more comprehensive listing and semantics see + // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf + // Padding + this.PADDING_ = 0x0000; + // Pop-on Mode + this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; + this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; + // Roll-up Mode + this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; + this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; + this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; + this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; + // paint-on mode + this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; + // Erasure + this.BACKSPACE_ = this.CONTROL_ | 0x21; + this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; + this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; +}; + +/** + * Detects if the 2-byte packet data is a special character + * + * Special characters have a second byte in the range 0x30 to 0x3f, + * with the first byte being 0x11 (for data channel 1) or 0x19 (for + * data channel 2). + * + * @param {Integer} char0 The first byte + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the 2 bytes are an special character + */ +Cea608Stream.prototype.isSpecialCharacter = function(char0, char1) { + return (char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f); +}; + +/** + * Detects if the 2-byte packet data is an extended character + * + * Extended characters have a second byte in the range 0x20 to 0x3f, + * with the first byte being 0x12 or 0x13 (for data channel 1) or + * 0x1a or 0x1b (for data channel 2). + * + * @param {Integer} char0 The first byte + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the 2 bytes are an extended character + */ +Cea608Stream.prototype.isExtCharacter = function(char0, char1) { + return ((char0 === (this.EXT_ + 1) || char0 === (this.EXT_ + 2)) && + (char1 >= 0x20 && char1 <= 0x3f)); +}; + +/** + * Detects if the 2-byte packet is a mid-row code + * + * Mid-row codes have a second byte in the range 0x20 to 0x2f, with + * the first byte being 0x11 (for data channel 1) or 0x19 (for data + * channel 2). + * + * @param {Integer} char0 The first byte + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the 2 bytes are a mid-row code + */ +Cea608Stream.prototype.isMidRowCode = function(char0, char1) { + return (char0 === this.EXT_ && (char1 >= 0x20 && char1 <= 0x2f)); +}; + +/** + * Detects if the 2-byte packet is an offset control code + * + * Offset control codes have a second byte in the range 0x21 to 0x23, + * with the first byte being 0x17 (for data channel 1) or 0x1f (for + * data channel 2). + * + * @param {Integer} char0 The first byte + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the 2 bytes are an offset control code + */ +Cea608Stream.prototype.isOffsetControlCode = function(char0, char1) { + return (char0 === this.OFFSET_ && (char1 >= 0x21 && char1 <= 0x23)); +}; + +/** + * Detects if the 2-byte packet is a Preamble Address Code + * + * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) + * or 0x18 to 0x1f (for data channel 2), with the second byte in the + * range 0x40 to 0x7f. + * + * @param {Integer} char0 The first byte + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the 2 bytes are a PAC + */ +Cea608Stream.prototype.isPAC = function(char0, char1) { + return (char0 >= this.BASE_ && char0 < (this.BASE_ + 8) && + (char1 >= 0x40 && char1 <= 0x7f)); +}; + +/** + * Detects if a packet's second byte is in the range of a PAC color code + * + * PAC color codes have the second byte be in the range 0x40 to 0x4f, or + * 0x60 to 0x6f. + * + * @param {Integer} char1 The second byte + * @return {Boolean} Whether the byte is a color PAC + */ +Cea608Stream.prototype.isColorPAC = function(char1) { + return ((char1 >= 0x40 && char1 <= 0x4f) || (char1 >= 0x60 && char1 <= 0x7f)); +}; + +/** + * Detects if a single byte is in the range of a normal character + * + * Normal text bytes are in the range 0x20 to 0x7f. + * + * @param {Integer} char The byte + * @return {Boolean} Whether the byte is a normal character + */ +Cea608Stream.prototype.isNormalChar = function(char) { + return (char >= 0x20 && char <= 0x7f); +}; + +/** + * Configures roll-up + * + * @param {Integer} pts Current PTS + * @param {Integer} newBaseRow Used by PACs to slide the current window to + * a new position + */ +Cea608Stream.prototype.setRollUp = function(pts, newBaseRow) { + // Reset the base row to the bottom row when switching modes + if (this.mode_ !== 'rollUp') { + this.row_ = BOTTOM_ROW; + this.mode_ = 'rollUp'; + // Spec says to wipe memories when switching to roll-up + this.flushDisplayed(pts); + this.nonDisplayed_ = createDisplayBuffer(); + this.displayed_ = createDisplayBuffer(); + } + + if (newBaseRow !== undefined && newBaseRow !== this.row_) { + // move currently displayed captions (up or down) to the new base row + for (var i = 0; i < this.rollUpRows_; i++) { + this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; + this.displayed_[this.row_ - i] = ''; + } + } + + if (newBaseRow === undefined) { + newBaseRow = this.row_; + } + + this.topRow_ = newBaseRow - this.rollUpRows_ + 1; +}; + +// Adds the opening HTML tag for the passed character to the caption text, +// and keeps track of it for later closing +Cea608Stream.prototype.addFormatting = function(pts, format) { + this.formatting_ = this.formatting_.concat(format); + var text = format.reduce(function(text, format) { + return text + '<' + format + '>'; + }, ''); + this[this.mode_](pts, text); +}; + +// Adds HTML closing tags for current formatting to caption text and +// clears remembered formatting +Cea608Stream.prototype.clearFormatting = function(pts) { + if (!this.formatting_.length) { + return; + } + var text = this.formatting_.reverse().reduce(function(text, format) { + return text + ''; + }, ''); + this.formatting_ = []; + this[this.mode_](pts, text); +}; + +// Mode Implementations +Cea608Stream.prototype.popOn = function(pts, text) { + var baseRow = this.nonDisplayed_[this.row_]; + + // buffer characters + baseRow += text; + this.nonDisplayed_[this.row_] = baseRow; +}; + +Cea608Stream.prototype.rollUp = function(pts, text) { + var baseRow = this.displayed_[this.row_]; + + baseRow += text; + this.displayed_[this.row_] = baseRow; + +}; + +Cea608Stream.prototype.shiftRowsUp_ = function() { + var i; + // clear out inactive rows + for (i = 0; i < this.topRow_; i++) { + this.displayed_[i] = ''; + } + for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { + this.displayed_[i] = ''; + } + // shift displayed rows up + for (i = this.topRow_; i < this.row_; i++) { + this.displayed_[i] = this.displayed_[i + 1]; + } + // clear out the bottom row + this.displayed_[this.row_] = ''; +}; + +Cea608Stream.prototype.paintOn = function(pts, text) { + var baseRow = this.displayed_[this.row_]; + + baseRow += text; + this.displayed_[this.row_] = baseRow; +}; + +// exports +var captionStream = { + CaptionStream: CaptionStream, + Cea608Stream: Cea608Stream +}; + +var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; +var CaptionStream$1 = captionStream.CaptionStream; + + + +/** + * Maps an offset in the mdat to a sample based on the the size of the samples. + * Assumes that `parseSamples` has been called first. + * + * @param {Number} offset - The offset into the mdat + * @param {Object[]} samples - An array of samples, parsed using `parseSamples` + * @return {?Object} The matching sample, or null if no match was found. + * + * @see ISO-BMFF-12/2015, Section 8.8.8 + **/ +var mapToSample = function(offset, samples) { + var approximateOffset = offset; + + for (var i = 0; i < samples.length; i++) { + var sample = samples[i]; + + if (approximateOffset < sample.size) { + return sample; + } + + approximateOffset -= sample.size; + } + + return null; +}; + +/** + * Finds SEI nal units contained in a Media Data Box. + * Assumes that `parseSamples` has been called first. + * + * @param {Uint8Array} avcStream - The bytes of the mdat + * @param {Object[]} samples - The samples parsed out by `parseSamples` + * @param {Number} trackId - The trackId of this video track + * @return {Object[]} seiNals - the parsed SEI NALUs found. + * The contents of the seiNal should match what is expected by + * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) + * + * @see ISO-BMFF-12/2015, Section 8.1.1 + * @see Rec. ITU-T H.264, 7.3.2.3.1 + **/ +var findSeiNals = function(avcStream, samples, trackId) { + var + avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), + result = [], + seiNal, + i, + length, + lastMatchedSample; + + for (i = 0; i + 4 < avcStream.length; i += length) { + length = avcView.getUint32(i); + i += 4; + + // Bail if this doesn't appear to be an H264 stream + if (length <= 0) { + continue; + } + + switch (avcStream[i] & 0x1F) { + case 0x06: + var data = avcStream.subarray(i + 1, i + 1 + length); + var matchingSample = mapToSample(i, samples); + + seiNal = { + nalUnitType: 'sei_rbsp', + size: length, + data: data, + escapedRBSP: discardEmulationPreventionBytes$1(data), + trackId: trackId + }; + + if (matchingSample) { + seiNal.pts = matchingSample.pts; + seiNal.dts = matchingSample.dts; + lastMatchedSample = matchingSample; + } else if (lastMatchedSample) { + // If a matching sample cannot be found, use the last + // sample's values as they should be as close as possible + seiNal.pts = lastMatchedSample.pts; + seiNal.dts = lastMatchedSample.dts; + } else { + // eslint-disable-next-line no-console + console.log("We've encountered a nal unit without data. See mux.js#233."); + break; + } + + result.push(seiNal); + break; + } + } + + return result; +}; + +/** + * Parses sample information out of Track Run Boxes and calculates + * the absolute presentation and decode timestamps of each sample. + * + * @param {Array} truns - The Trun Run boxes to be parsed + * @param {Number} baseMediaDecodeTime - base media decode time from tfdt + @see ISO-BMFF-12/2015, Section 8.8.12 + * @param {Object} tfhd - The parsed Track Fragment Header + * @see inspect.parseTfhd + * @return {Object[]} the parsed samples + * + * @see ISO-BMFF-12/2015, Section 8.8.8 + **/ +var parseSamples = function(truns, baseMediaDecodeTime, tfhd) { + var currentDts = baseMediaDecodeTime; + var defaultSampleDuration = tfhd.defaultSampleDuration || 0; + var defaultSampleSize = tfhd.defaultSampleSize || 0; + var trackId = tfhd.trackId; + var allSamples = []; + + truns.forEach(function(trun) { + // Note: We currently do not parse the sample table as well + // as the trun. It's possible some sources will require this. + // moov > trak > mdia > minf > stbl + var trackRun = mp4Inspector.parseTrun(trun); + var samples = trackRun.samples; + + samples.forEach(function(sample) { + if (sample.duration === undefined) { + sample.duration = defaultSampleDuration; + } + if (sample.size === undefined) { + sample.size = defaultSampleSize; + } + sample.trackId = trackId; + sample.dts = currentDts; + if (sample.compositionTimeOffset === undefined) { + sample.compositionTimeOffset = 0; + } + sample.pts = currentDts + sample.compositionTimeOffset; + + currentDts += sample.duration; + }); + + allSamples = allSamples.concat(samples); + }); + + return allSamples; +}; + +/** + * Parses out caption nals from an FMP4 segment's video tracks. + * + * @param {Uint8Array} segment - The bytes of a single segment + * @param {Number} videoTrackId - The trackId of a video track in the segment + * @return {Object.} A mapping of video trackId to + * a list of seiNals found in that track + **/ +var parseCaptionNals = function(segment, videoTrackId) { + // To get the samples + var trafs = probe.findBox(segment, ['moof', 'traf']); + // To get SEI NAL units + var mdats = probe.findBox(segment, ['mdat']); + var captionNals = {}; + var mdatTrafPairs = []; + + // Pair up each traf with a mdat as moofs and mdats are in pairs + mdats.forEach(function(mdat, index) { + var matchingTraf = trafs[index]; + mdatTrafPairs.push({ + mdat: mdat, + traf: matchingTraf + }); + }); + + mdatTrafPairs.forEach(function(pair) { + var mdat = pair.mdat; + var traf = pair.traf; + var tfhd = probe.findBox(traf, ['tfhd']); + // Exactly 1 tfhd per traf + var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); + var trackId = headerInfo.trackId; + var tfdt = probe.findBox(traf, ['tfdt']); + // Either 0 or 1 tfdt per traf + var baseMediaDecodeTime = (tfdt.length > 0) ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; + var truns = probe.findBox(traf, ['trun']); + var samples; + var seiNals; + + // Only parse video data for the chosen video track + if (videoTrackId === trackId && truns.length > 0) { + samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); + + seiNals = findSeiNals(mdat, samples, trackId); + + if (!captionNals[trackId]) { + captionNals[trackId] = []; + } + + captionNals[trackId] = captionNals[trackId].concat(seiNals); + } + }); + + return captionNals; +}; + +/** + * Parses out inband captions from an MP4 container and returns + * caption objects that can be used by WebVTT and the TextTrack API. + * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue + * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack + * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first + * + * @param {Uint8Array} segment - The fmp4 segment containing embedded captions + * @param {Number} trackId - The id of the video track to parse + * @param {Number} timescale - The timescale for the video track from the init segment + * + * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks + * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds + * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds + * @return {String} parsedCaptions[].text - The visible content of the caption + **/ +var parseEmbeddedCaptions = function(segment, trackId, timescale) { + var seiNals; + + // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there + if (trackId === null) { + return null; + } + + seiNals = parseCaptionNals(segment, trackId); + + return { + seiNals: seiNals[trackId], + timescale: timescale + }; +}; + +/** + * Converts SEI NALUs into captions that can be used by video.js + **/ +var CaptionParser = function() { + var isInitialized = false; + var captionStream; + + // Stores segments seen before trackId and timescale are set + var segmentCache; + // Stores video track ID of the track being parsed + var trackId; + // Stores the timescale of the track being parsed + var timescale; + // Stores captions parsed so far + var parsedCaptions; + // Stores whether we are receiving partial data or not + var parsingPartial; + + /** + * A method to indicate whether a CaptionParser has been initalized + * @returns {Boolean} + **/ + this.isInitialized = function() { + return isInitialized; + }; + + /** + * Initializes the underlying CaptionStream, SEI NAL parsing + * and management, and caption collection + **/ + this.init = function(options) { + captionStream = new CaptionStream$1(); + isInitialized = true; + parsingPartial = options ? options.isPartial : false; + + // Collect dispatched captions + captionStream.on('data', function(event) { + // Convert to seconds in the source's timescale + event.startTime = event.startPts / timescale; + event.endTime = event.endPts / timescale; + + parsedCaptions.captions.push(event); + parsedCaptions.captionStreams[event.stream] = true; + }); + }; + + /** + * Determines if a new video track will be selected + * or if the timescale changed + * @return {Boolean} + **/ + this.isNewInit = function(videoTrackIds, timescales) { + if ((videoTrackIds && videoTrackIds.length === 0) || + (timescales && typeof timescales === 'object' && + Object.keys(timescales).length === 0)) { + return false; + } + + return trackId !== videoTrackIds[0] || + timescale !== timescales[trackId]; + }; + + /** + * Parses out SEI captions and interacts with underlying + * CaptionStream to return dispatched captions + * + * @param {Uint8Array} segment - The fmp4 segment containing embedded captions + * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment + * @param {Object.} timescales - The timescales found in the init segment + * @see parseEmbeddedCaptions + * @see m2ts/caption-stream.js + **/ + this.parse = function(segment, videoTrackIds, timescales) { + var parsedData; + + if (!this.isInitialized()) { + return null; + + // This is not likely to be a video segment + } else if (!videoTrackIds || !timescales) { + return null; + + } else if (this.isNewInit(videoTrackIds, timescales)) { + // Use the first video track only as there is no + // mechanism to switch to other video tracks + trackId = videoTrackIds[0]; + timescale = timescales[trackId]; + + // If an init segment has not been seen yet, hold onto segment + // data until we have one. + // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there + } else if (trackId === null || !timescale) { + segmentCache.push(segment); + return null; + } + + // Now that a timescale and trackId is set, parse cached segments + while (segmentCache.length > 0) { + var cachedSegment = segmentCache.shift(); + + this.parse(cachedSegment, videoTrackIds, timescales); + } + + parsedData = parseEmbeddedCaptions(segment, trackId, timescale); + + if (parsedData === null || !parsedData.seiNals) { + return null; + } + + this.pushNals(parsedData.seiNals); + // Force the parsed captions to be dispatched + this.flushStream(); + + return parsedCaptions; + }; + + /** + * Pushes SEI NALUs onto CaptionStream + * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` + * Assumes that `parseCaptionNals` has been called first + * @see m2ts/caption-stream.js + **/ + this.pushNals = function(nals) { + if (!this.isInitialized() || !nals || nals.length === 0) { + return null; + } + + nals.forEach(function(nal) { + captionStream.push(nal); + }); + }; + + /** + * Flushes underlying CaptionStream to dispatch processed, displayable captions + * @see m2ts/caption-stream.js + **/ + this.flushStream = function() { + if (!this.isInitialized()) { + return null; + } + + if (!parsingPartial) { + captionStream.flush(); + } else { + captionStream.partialFlush(); + } + }; + + /** + * Reset caption buckets for new data + **/ + this.clearParsedCaptions = function() { + parsedCaptions.captions = []; + parsedCaptions.captionStreams = {}; + }; + + /** + * Resets underlying CaptionStream + * @see m2ts/caption-stream.js + **/ + this.resetCaptionStream = function() { + if (!this.isInitialized()) { + return null; + } + + captionStream.reset(); + }; + + /** + * Convenience method to clear all captions flushed from the + * CaptionStream and still being parsed + * @see m2ts/caption-stream.js + **/ + this.clearAllCaptions = function() { + this.clearParsedCaptions(); + this.resetCaptionStream(); + }; + + /** + * Reset caption parser + **/ + this.reset = function() { + segmentCache = []; + trackId = null; + timescale = null; + + if (!parsedCaptions) { + parsedCaptions = { + captions: [], + // CC1, CC2, CC3, CC4 + captionStreams: {} + }; + } else { + this.clearParsedCaptions(); + } + + this.resetCaptionStream(); + }; + + this.reset(); +}; + +var captionParser = CaptionParser; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + */ + +var streamTypes = { + H264_STREAM_TYPE: 0x1B, + ADTS_STREAM_TYPE: 0x0F, + METADATA_STREAM_TYPE: 0x15 +}; + +var MAX_TS = 8589934592; + +var RO_THRESH = 4294967296; + +var TYPE_SHARED = 'shared'; + +var handleRollover = function(value, reference) { + var direction = 1; + + if (value > reference) { + // If the current timestamp value is greater than our reference timestamp and we detect a + // timestamp rollover, this means the roll over is happening in the opposite direction. + // Example scenario: Enter a long stream/video just after a rollover occurred. The reference + // point will be set to a small number, e.g. 1. The user then seeks backwards over the + // rollover point. In loading this segment, the timestamp values will be very large, + // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust + // the time stamp to be `value - 2^33`. + direction = -1; + } + + // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will + // cause an incorrect adjustment. + while (Math.abs(reference - value) > RO_THRESH) { + value += (direction * MAX_TS); + } + + return value; +}; + +var TimestampRolloverStream = function(type) { + var lastDTS, referenceDTS; + + TimestampRolloverStream.prototype.init.call(this); + + // The "shared" type is used in cases where a stream will contain muxed + // video and audio. We could use `undefined` here, but having a string + // makes debugging a little clearer. + this.type_ = type || TYPE_SHARED; + + this.push = function(data) { + + // Any "shared" rollover streams will accept _all_ data. Otherwise, + // streams will only accept data that matches their type. + if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { + return; + } + + if (referenceDTS === undefined) { + referenceDTS = data.dts; + } + + data.dts = handleRollover(data.dts, referenceDTS); + data.pts = handleRollover(data.pts, referenceDTS); + + lastDTS = data.dts; + + this.trigger('data', data); + }; + + this.flush = function() { + referenceDTS = lastDTS; + this.trigger('done'); + }; + + this.endTimeline = function() { + this.flush(); + this.trigger('endedtimeline'); + }; + + this.discontinuity = function() { + referenceDTS = void 0; + lastDTS = void 0; + }; + + this.reset = function() { + this.discontinuity(); + this.trigger('reset'); + }; +}; + +TimestampRolloverStream.prototype = new stream(); + +var timestampRolloverStream = { + TimestampRolloverStream: TimestampRolloverStream, + handleRollover: handleRollover +}; + +var parsePid = function(packet) { + var pid = packet[1] & 0x1f; + pid <<= 8; + pid |= packet[2]; + return pid; +}; + +var parsePayloadUnitStartIndicator = function(packet) { + return !!(packet[1] & 0x40); +}; + +var parseAdaptionField = function(packet) { + var offset = 0; + // if an adaption field is present, its length is specified by the + // fifth byte of the TS packet header. The adaptation field is + // used to add stuffing to PES packets that don't fill a complete + // TS packet, and to specify some forms of timing and control data + // that we do not currently use. + if (((packet[3] & 0x30) >>> 4) > 0x01) { + offset += packet[4] + 1; + } + return offset; +}; + +var parseType$1 = function(packet, pmtPid) { + var pid = parsePid(packet); + if (pid === 0) { + return 'pat'; + } else if (pid === pmtPid) { + return 'pmt'; + } else if (pmtPid) { + return 'pes'; + } + return null; +}; + +var parsePat = function(packet) { + var pusi = parsePayloadUnitStartIndicator(packet); + var offset = 4 + parseAdaptionField(packet); + + if (pusi) { + offset += packet[offset] + 1; + } + + return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; +}; + +var parsePmt = function(packet) { + var programMapTable = {}; + var pusi = parsePayloadUnitStartIndicator(packet); + var payloadOffset = 4 + parseAdaptionField(packet); + + if (pusi) { + payloadOffset += packet[payloadOffset] + 1; + } + + // PMTs can be sent ahead of the time when they should actually + // take effect. We don't believe this should ever be the case + // for HLS but we'll ignore "forward" PMT declarations if we see + // them. Future PMT declarations have the current_next_indicator + // set to zero. + if (!(packet[payloadOffset + 5] & 0x01)) { + return; + } + + var sectionLength, tableEnd, programInfoLength; + // the mapping table ends at the end of the current section + sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; + tableEnd = 3 + sectionLength - 4; + + // to determine where the table is, we have to figure out how + // long the program info descriptors are + programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; + + // advance the offset to the first entry in the mapping table + var offset = 12 + programInfoLength; + while (offset < tableEnd) { + var i = payloadOffset + offset; + // add an entry that maps the elementary_pid to the stream_type + programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; + + // move to the next table entry + // skip past the elementary stream descriptors, if present + offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; + } + return programMapTable; +}; + +var parsePesType = function(packet, programMapTable) { + var pid = parsePid(packet); + var type = programMapTable[pid]; + switch (type) { + case streamTypes.H264_STREAM_TYPE: + return 'video'; + case streamTypes.ADTS_STREAM_TYPE: + return 'audio'; + case streamTypes.METADATA_STREAM_TYPE: + return 'timed-metadata'; + default: + return null; + } +}; + +var parsePesTime = function(packet) { + var pusi = parsePayloadUnitStartIndicator(packet); + if (!pusi) { + return null; + } + + var offset = 4 + parseAdaptionField(packet); + + if (offset >= packet.byteLength) { + // From the H 222.0 MPEG-TS spec + // "For transport stream packets carrying PES packets, stuffing is needed when there + // is insufficient PES packet data to completely fill the transport stream packet + // payload bytes. Stuffing is accomplished by defining an adaptation field longer than + // the sum of the lengths of the data elements in it, so that the payload bytes + // remaining after the adaptation field exactly accommodates the available PES packet + // data." + // + // If the offset is >= the length of the packet, then the packet contains no data + // and instead is just adaption field stuffing bytes + return null; + } + + var pes = null; + var ptsDtsFlags; + + // PES packets may be annotated with a PTS value, or a PTS value + // and a DTS value. Determine what combination of values is + // available to work with. + ptsDtsFlags = packet[offset + 7]; + + // PTS and DTS are normally stored as a 33-bit number. Javascript + // performs all bitwise operations on 32-bit integers but javascript + // supports a much greater range (52-bits) of integer using standard + // mathematical operations. + // We construct a 31-bit value using bitwise operators over the 31 + // most significant bits and then multiply by 4 (equal to a left-shift + // of 2) before we add the final 2 least significant bits of the + // timestamp (equal to an OR.) + if (ptsDtsFlags & 0xC0) { + pes = {}; + // the PTS and DTS are not written out directly. For information + // on how they are encoded, see + // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html + pes.pts = (packet[offset + 9] & 0x0E) << 27 | + (packet[offset + 10] & 0xFF) << 20 | + (packet[offset + 11] & 0xFE) << 12 | + (packet[offset + 12] & 0xFF) << 5 | + (packet[offset + 13] & 0xFE) >>> 3; + pes.pts *= 4; // Left shift by 2 + pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs + pes.dts = pes.pts; + if (ptsDtsFlags & 0x40) { + pes.dts = (packet[offset + 14] & 0x0E) << 27 | + (packet[offset + 15] & 0xFF) << 20 | + (packet[offset + 16] & 0xFE) << 12 | + (packet[offset + 17] & 0xFF) << 5 | + (packet[offset + 18] & 0xFE) >>> 3; + pes.dts *= 4; // Left shift by 2 + pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs + } + } + return pes; +}; + +var parseNalUnitType = function(type) { + switch (type) { + case 0x05: + return 'slice_layer_without_partitioning_rbsp_idr'; + case 0x06: + return 'sei_rbsp'; + case 0x07: + return 'seq_parameter_set_rbsp'; + case 0x08: + return 'pic_parameter_set_rbsp'; + case 0x09: + return 'access_unit_delimiter_rbsp'; + default: + return null; + } +}; + +var videoPacketContainsKeyFrame = function(packet) { + var offset = 4 + parseAdaptionField(packet); + var frameBuffer = packet.subarray(offset); + var frameI = 0; + var frameSyncPoint = 0; + var foundKeyFrame = false; + var nalType; + + // advance the sync point to a NAL start, if necessary + for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { + if (frameBuffer[frameSyncPoint + 2] === 1) { + // the sync point is properly aligned + frameI = frameSyncPoint + 5; + break; + } + } + + while (frameI < frameBuffer.byteLength) { + // look at the current byte to determine if we've hit the end of + // a NAL unit boundary + switch (frameBuffer[frameI]) { + case 0: + // skip past non-sync sequences + if (frameBuffer[frameI - 1] !== 0) { + frameI += 2; + break; + } else if (frameBuffer[frameI - 2] !== 0) { + frameI++; + break; + } + + if (frameSyncPoint + 3 !== frameI - 2) { + nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); + if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { + foundKeyFrame = true; + } + } + + // drop trailing zeroes + do { + frameI++; + } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); + frameSyncPoint = frameI - 2; + frameI += 3; + break; + case 1: + // skip past non-sync sequences + if (frameBuffer[frameI - 1] !== 0 || + frameBuffer[frameI - 2] !== 0) { + frameI += 3; + break; + } + + nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); + if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { + foundKeyFrame = true; + } + frameSyncPoint = frameI - 2; + frameI += 3; + break; + default: + // the current byte isn't a one or zero, so it cannot be part + // of a sync sequence + frameI += 3; + break; + } + } + frameBuffer = frameBuffer.subarray(frameSyncPoint); + frameI -= frameSyncPoint; + frameSyncPoint = 0; + // parse the final nal + if (frameBuffer && frameBuffer.byteLength > 3) { + nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); + if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { + foundKeyFrame = true; + } + } + + return foundKeyFrame; +}; + + +var probe$1 = { + parseType: parseType$1, + parsePat: parsePat, + parsePmt: parsePmt, + parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, + parsePesType: parsePesType, + parsePesTime: parsePesTime, + videoPacketContainsKeyFrame: videoPacketContainsKeyFrame +}; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + * + * Utilities to detect basic properties and metadata about Aac data. + */ + +var ADTS_SAMPLING_FREQUENCIES = [ + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350 +]; + +var isLikelyAacData = function(data) { + if ((data[0] === 'I'.charCodeAt(0)) && + (data[1] === 'D'.charCodeAt(0)) && + (data[2] === '3'.charCodeAt(0))) { + return true; + } + return false; +}; + +var parseSyncSafeInteger = function(data) { + return (data[0] << 21) | + (data[1] << 14) | + (data[2] << 7) | + (data[3]); +}; + +// return a percent-encoded representation of the specified byte range +// @see http://en.wikipedia.org/wiki/Percent-encoding +var percentEncode = function(bytes, start, end) { + var i, result = ''; + for (i = start; i < end; i++) { + result += '%' + ('00' + bytes[i].toString(16)).slice(-2); + } + return result; +}; + +// return the string representation of the specified byte range, +// interpreted as ISO-8859-1. +var parseIso88591 = function(bytes, start, end) { + return unescape(percentEncode(bytes, start, end)); // jshint ignore:line +}; + +var parseId3TagSize = function(header, byteIndex) { + var + returnSize = (header[byteIndex + 6] << 21) | + (header[byteIndex + 7] << 14) | + (header[byteIndex + 8] << 7) | + (header[byteIndex + 9]), + flags = header[byteIndex + 5], + footerPresent = (flags & 16) >> 4; + + if (footerPresent) { + return returnSize + 20; + } + return returnSize + 10; +}; + +var parseAdtsSize = function(header, byteIndex) { + var + lowThree = (header[byteIndex + 5] & 0xE0) >> 5, + middle = header[byteIndex + 4] << 3, + highTwo = header[byteIndex + 3] & 0x3 << 11; + + return (highTwo | middle) | lowThree; +}; + +var parseType$2 = function(header, byteIndex) { + if ((header[byteIndex] === 'I'.charCodeAt(0)) && + (header[byteIndex + 1] === 'D'.charCodeAt(0)) && + (header[byteIndex + 2] === '3'.charCodeAt(0))) { + return 'timed-metadata'; + } else if ((header[byteIndex] & 0xff === 0xff) && + ((header[byteIndex + 1] & 0xf0) === 0xf0)) { + return 'audio'; + } + return null; +}; + +var parseSampleRate = function(packet) { + var i = 0; + + while (i + 5 < packet.length) { + if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { + // If a valid header was not found, jump one forward and attempt to + // find a valid ADTS header starting at the next byte + i++; + continue; + } + return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; + } + + return null; +}; + +var parseAacTimestamp = function(packet) { + var frameStart, frameSize, frame, frameHeader; + + // find the start of the first frame and the end of the tag + frameStart = 10; + if (packet[5] & 0x40) { + // advance the frame start past the extended header + frameStart += 4; // header size field + frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); + } + + // parse one or more ID3 frames + // http://id3.org/id3v2.3.0#ID3v2_frame_overview + do { + // determine the number of bytes in this frame + frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); + if (frameSize < 1) { + return null; + } + frameHeader = String.fromCharCode(packet[frameStart], + packet[frameStart + 1], + packet[frameStart + 2], + packet[frameStart + 3]); + + if (frameHeader === 'PRIV') { + frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); + + for (var i = 0; i < frame.byteLength; i++) { + if (frame[i] === 0) { + var owner = parseIso88591(frame, 0, i); + if (owner === 'com.apple.streaming.transportStreamTimestamp') { + var d = frame.subarray(i + 1); + var size = ((d[3] & 0x01) << 30) | + (d[4] << 22) | + (d[5] << 14) | + (d[6] << 6) | + (d[7] >>> 2); + size *= 4; + size += d[7] & 0x03; + + return size; + } + break; + } + } + } + + frameStart += 10; // advance past the frame header + frameStart += frameSize; // advance past the frame body + } while (frameStart < packet.byteLength); + return null; +}; + +var utils = { + isLikelyAacData: isLikelyAacData, + parseId3TagSize: parseId3TagSize, + parseAdtsSize: parseAdtsSize, + parseType: parseType$2, + parseSampleRate: parseSampleRate, + parseAacTimestamp: parseAacTimestamp +}; + +/** + * mux.js + * + * Copyright (c) Brightcove + * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE + */ +var + ONE_SECOND_IN_TS = 90000, // 90kHz clock + secondsToVideoTs, + secondsToAudioTs, + videoTsToSeconds, + audioTsToSeconds, + audioTsToVideoTs, + videoTsToAudioTs, + metadataTsToSeconds; + +secondsToVideoTs = function(seconds) { + return seconds * ONE_SECOND_IN_TS; +}; + +secondsToAudioTs = function(seconds, sampleRate) { + return seconds * sampleRate; +}; + +videoTsToSeconds = function(timestamp) { + return timestamp / ONE_SECOND_IN_TS; +}; + +audioTsToSeconds = function(timestamp, sampleRate) { + return timestamp / sampleRate; +}; + +audioTsToVideoTs = function(timestamp, sampleRate) { + return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); +}; + +videoTsToAudioTs = function(timestamp, sampleRate) { + return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); +}; + +/** + * Adjust ID3 tag or caption timing information by the timeline pts values + * (if keepOriginalTimestamps is false) and convert to seconds + */ +metadataTsToSeconds = function(timestamp, timelineStartPts, keepOriginalTimestamps) { + return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); +}; + +var clock = { + ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, + secondsToVideoTs: secondsToVideoTs, + secondsToAudioTs: secondsToAudioTs, + videoTsToSeconds: videoTsToSeconds, + audioTsToSeconds: audioTsToSeconds, + audioTsToVideoTs: audioTsToVideoTs, + videoTsToAudioTs: videoTsToAudioTs, + metadataTsToSeconds: metadataTsToSeconds +}; + +var handleRollover$1 = timestampRolloverStream.handleRollover; +var probe$2 = {}; +probe$2.ts = probe$1; +probe$2.aac = utils; +var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; + +var + MP2T_PACKET_LENGTH = 188, // bytes + SYNC_BYTE = 0x47; + +/** + * walks through segment data looking for pat and pmt packets to parse out + * program map table information + */ +var parsePsi_ = function(bytes, pmt) { + var + startIndex = 0, + endIndex = MP2T_PACKET_LENGTH, + packet, type; + + while (endIndex < bytes.byteLength) { + // Look for a pair of start and end sync bytes in the data.. + if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { + // We found a packet + packet = bytes.subarray(startIndex, endIndex); + type = probe$2.ts.parseType(packet, pmt.pid); + + switch (type) { + case 'pat': + if (!pmt.pid) { + pmt.pid = probe$2.ts.parsePat(packet); + } + break; + case 'pmt': + if (!pmt.table) { + pmt.table = probe$2.ts.parsePmt(packet); + } + break; + } + + // Found the pat and pmt, we can stop walking the segment + if (pmt.pid && pmt.table) { + return; + } + + startIndex += MP2T_PACKET_LENGTH; + endIndex += MP2T_PACKET_LENGTH; + continue; + } + + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex++; + endIndex++; + } +}; + +/** + * walks through the segment data from the start and end to get timing information + * for the first and last audio pes packets + */ +var parseAudioPes_ = function(bytes, pmt, result) { + var + startIndex = 0, + endIndex = MP2T_PACKET_LENGTH, + packet, type, pesType, pusi, parsed; + + var endLoop = false; + + // Start walking from start of segment to get first audio packet + while (endIndex <= bytes.byteLength) { + // Look for a pair of start and end sync bytes in the data.. + if (bytes[startIndex] === SYNC_BYTE && + (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { + // We found a packet + packet = bytes.subarray(startIndex, endIndex); + type = probe$2.ts.parseType(packet, pmt.pid); + + switch (type) { + case 'pes': + pesType = probe$2.ts.parsePesType(packet, pmt.table); + pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); + if (pesType === 'audio' && pusi) { + parsed = probe$2.ts.parsePesTime(packet); + if (parsed) { + parsed.type = 'audio'; + result.audio.push(parsed); + endLoop = true; + } + } + break; + } + + if (endLoop) { + break; + } + + startIndex += MP2T_PACKET_LENGTH; + endIndex += MP2T_PACKET_LENGTH; + continue; + } + + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex++; + endIndex++; + } + + // Start walking from end of segment to get last audio packet + endIndex = bytes.byteLength; + startIndex = endIndex - MP2T_PACKET_LENGTH; + endLoop = false; + while (startIndex >= 0) { + // Look for a pair of start and end sync bytes in the data.. + if (bytes[startIndex] === SYNC_BYTE && + (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { + // We found a packet + packet = bytes.subarray(startIndex, endIndex); + type = probe$2.ts.parseType(packet, pmt.pid); + + switch (type) { + case 'pes': + pesType = probe$2.ts.parsePesType(packet, pmt.table); + pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); + if (pesType === 'audio' && pusi) { + parsed = probe$2.ts.parsePesTime(packet); + if (parsed) { + parsed.type = 'audio'; + result.audio.push(parsed); + endLoop = true; + } + } + break; + } + + if (endLoop) { + break; + } + + startIndex -= MP2T_PACKET_LENGTH; + endIndex -= MP2T_PACKET_LENGTH; + continue; + } + + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex--; + endIndex--; + } +}; + +/** + * walks through the segment data from the start and end to get timing information + * for the first and last video pes packets as well as timing information for the first + * key frame. + */ +var parseVideoPes_ = function(bytes, pmt, result) { + var + startIndex = 0, + endIndex = MP2T_PACKET_LENGTH, + packet, type, pesType, pusi, parsed, frame, i, pes; + + var endLoop = false; + + var currentFrame = { + data: [], + size: 0 + }; + + // Start walking from start of segment to get first video packet + while (endIndex < bytes.byteLength) { + // Look for a pair of start and end sync bytes in the data.. + if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { + // We found a packet + packet = bytes.subarray(startIndex, endIndex); + type = probe$2.ts.parseType(packet, pmt.pid); + + switch (type) { + case 'pes': + pesType = probe$2.ts.parsePesType(packet, pmt.table); + pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); + if (pesType === 'video') { + if (pusi && !endLoop) { + parsed = probe$2.ts.parsePesTime(packet); + if (parsed) { + parsed.type = 'video'; + result.video.push(parsed); + endLoop = true; + } + } + if (!result.firstKeyFrame) { + if (pusi) { + if (currentFrame.size !== 0) { + frame = new Uint8Array(currentFrame.size); + i = 0; + while (currentFrame.data.length) { + pes = currentFrame.data.shift(); + frame.set(pes, i); + i += pes.byteLength; + } + if (probe$2.ts.videoPacketContainsKeyFrame(frame)) { + var firstKeyFrame = probe$2.ts.parsePesTime(frame); + + // PTS/DTS may not be available. Simply *not* setting + // the keyframe seems to work fine with HLS playback + // and definitely preferable to a crash with TypeError... + if (firstKeyFrame) { + result.firstKeyFrame = firstKeyFrame; + result.firstKeyFrame.type = 'video'; + } else { + // eslint-disable-next-line + console.warn( + 'Failed to extract PTS/DTS from PES at first keyframe. ' + + 'This could be an unusual TS segment, or else mux.js did not ' + + 'parse your TS segment correctly. If you know your TS ' + + 'segments do contain PTS/DTS on keyframes please file a bug ' + + 'report! You can try ffprobe to double check for yourself.' + ); + } + } + currentFrame.size = 0; + } + } + currentFrame.data.push(packet); + currentFrame.size += packet.byteLength; + } + } + break; + } + + if (endLoop && result.firstKeyFrame) { + break; + } + + startIndex += MP2T_PACKET_LENGTH; + endIndex += MP2T_PACKET_LENGTH; + continue; + } + + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex++; + endIndex++; + } + + // Start walking from end of segment to get last video packet + endIndex = bytes.byteLength; + startIndex = endIndex - MP2T_PACKET_LENGTH; + endLoop = false; + while (startIndex >= 0) { + // Look for a pair of start and end sync bytes in the data.. + if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { + // We found a packet + packet = bytes.subarray(startIndex, endIndex); + type = probe$2.ts.parseType(packet, pmt.pid); + + switch (type) { + case 'pes': + pesType = probe$2.ts.parsePesType(packet, pmt.table); + pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); + if (pesType === 'video' && pusi) { + parsed = probe$2.ts.parsePesTime(packet); + if (parsed) { + parsed.type = 'video'; + result.video.push(parsed); + endLoop = true; + } + } + break; + } + + if (endLoop) { + break; + } + + startIndex -= MP2T_PACKET_LENGTH; + endIndex -= MP2T_PACKET_LENGTH; + continue; + } + + // If we get here, we have somehow become de-synchronized and we need to step + // forward one byte at a time until we find a pair of sync bytes that denote + // a packet + startIndex--; + endIndex--; + } +}; + +/** + * Adjusts the timestamp information for the segment to account for + * rollover and convert to seconds based on pes packet timescale (90khz clock) + */ +var adjustTimestamp_ = function(segmentInfo, baseTimestamp) { + if (segmentInfo.audio && segmentInfo.audio.length) { + var audioBaseTimestamp = baseTimestamp; + if (typeof audioBaseTimestamp === 'undefined') { + audioBaseTimestamp = segmentInfo.audio[0].dts; + } + segmentInfo.audio.forEach(function(info) { + info.dts = handleRollover$1(info.dts, audioBaseTimestamp); + info.pts = handleRollover$1(info.pts, audioBaseTimestamp); + // time in seconds + info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; + info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; + }); + } + + if (segmentInfo.video && segmentInfo.video.length) { + var videoBaseTimestamp = baseTimestamp; + if (typeof videoBaseTimestamp === 'undefined') { + videoBaseTimestamp = segmentInfo.video[0].dts; + } + segmentInfo.video.forEach(function(info) { + info.dts = handleRollover$1(info.dts, videoBaseTimestamp); + info.pts = handleRollover$1(info.pts, videoBaseTimestamp); + // time in seconds + info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; + info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; + }); + if (segmentInfo.firstKeyFrame) { + var frame = segmentInfo.firstKeyFrame; + frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); + frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); + // time in seconds + frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1; + frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1; + } + } +}; + +/** + * inspects the aac data stream for start and end time information + */ +var inspectAac_ = function(bytes) { + var + endLoop = false, + audioCount = 0, + sampleRate = null, + timestamp = null, + frameSize = 0, + byteIndex = 0, + packet; + + while (bytes.length - byteIndex >= 3) { + var type = probe$2.aac.parseType(bytes, byteIndex); + switch (type) { + case 'timed-metadata': + // Exit early because we don't have enough to parse + // the ID3 tag header + if (bytes.length - byteIndex < 10) { + endLoop = true; + break; + } + + frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); + + // Exit early if we don't have enough in the buffer + // to emit a full packet + if (frameSize > bytes.length) { + endLoop = true; + break; + } + if (timestamp === null) { + packet = bytes.subarray(byteIndex, byteIndex + frameSize); + timestamp = probe$2.aac.parseAacTimestamp(packet); + } + byteIndex += frameSize; + break; + case 'audio': + // Exit early because we don't have enough to parse + // the ADTS frame header + if (bytes.length - byteIndex < 7) { + endLoop = true; + break; + } + + frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); + + // Exit early if we don't have enough in the buffer + // to emit a full packet + if (frameSize > bytes.length) { + endLoop = true; + break; + } + if (sampleRate === null) { + packet = bytes.subarray(byteIndex, byteIndex + frameSize); + sampleRate = probe$2.aac.parseSampleRate(packet); + } + audioCount++; + byteIndex += frameSize; + break; + default: + byteIndex++; + break; + } + if (endLoop) { + return null; + } + } + if (sampleRate === null || timestamp === null) { + return null; + } + + var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate; + + var result = { + audio: [ + { + type: 'audio', + dts: timestamp, + pts: timestamp + }, + { + type: 'audio', + dts: timestamp + (audioCount * 1024 * audioTimescale), + pts: timestamp + (audioCount * 1024 * audioTimescale) + } + ] + }; + + return result; +}; + +/** + * inspects the transport stream segment data for start and end time information + * of the audio and video tracks (when present) as well as the first key frame's + * start time. + */ +var inspectTs_ = function(bytes) { + var pmt = { + pid: null, + table: null + }; + + var result = {}; + + parsePsi_(bytes, pmt); + + for (var pid in pmt.table) { + if (pmt.table.hasOwnProperty(pid)) { + var type = pmt.table[pid]; + switch (type) { + case streamTypes.H264_STREAM_TYPE: + result.video = []; + parseVideoPes_(bytes, pmt, result); + if (result.video.length === 0) { + delete result.video; + } + break; + case streamTypes.ADTS_STREAM_TYPE: + result.audio = []; + parseAudioPes_(bytes, pmt, result); + if (result.audio.length === 0) { + delete result.audio; + } + break; + } + } + } + return result; +}; + +/** + * Inspects segment byte data and returns an object with start and end timing information + * + * @param {Uint8Array} bytes The segment byte data + * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame + * timestamps for rollover. This value must be in 90khz clock. + * @return {Object} Object containing start and end frame timing info of segment. + */ +var inspect = function(bytes, baseTimestamp) { + var isAacData = probe$2.aac.isLikelyAacData(bytes); + + var result; + + if (isAacData) { + result = inspectAac_(bytes); + } else { + result = inspectTs_(bytes); + } + + if (!result || (!result.audio && !result.video)) { + return null; + } + + adjustTimestamp_(result, baseTimestamp); + + return result; +}; + +var tsInspector = { + inspect: inspect, + parseAudioPes_: parseAudioPes_ +}; + +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} + +var createClass = _createClass; + +/*! @name @videojs/vhs-utils @version 1.3.0 @license MIT */ + +/** + * @file stream.js + */ + +/** + * A lightweight readable stream implemention that handles event dispatching. + * + * @class Stream + */ +var Stream$2 = +/*#__PURE__*/ +function () { + function Stream() { + this.listeners = {}; + } + /** + * Add a listener for a specified event type. + * + * @param {string} type the event name + * @param {Function} listener the callback to be invoked when an event of + * the specified type occurs + */ + + + var _proto = Stream.prototype; + + _proto.on = function on(type, listener) { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + + this.listeners[type].push(listener); + } + /** + * Remove a listener for a specified event type. + * + * @param {string} type the event name + * @param {Function} listener a function previously registered for this + * type of event through `on` + * @return {boolean} if we could turn it off or not + */ + ; + + _proto.off = function off(type, listener) { + if (!this.listeners[type]) { + return false; + } + + var index = this.listeners[type].indexOf(listener); // TODO: which is better? + // In Video.js we slice listener functions + // on trigger so that it does not mess up the order + // while we loop through. + // + // Here we slice on off so that the loop in trigger + // can continue using it's old reference to loop without + // messing up the order. + + this.listeners[type] = this.listeners[type].slice(0); + this.listeners[type].splice(index, 1); + return index > -1; + } + /** + * Trigger an event of the specified type on this stream. Any additional + * arguments to this function are passed as parameters to event listeners. + * + * @param {string} type the event name + */ + ; + + _proto.trigger = function trigger(type) { + var callbacks = this.listeners[type]; + + if (!callbacks) { + return; + } // Slicing the arguments on every invocation of this method + // can add a significant amount of overhead. Avoid the + // intermediate object creation for the common case of a + // single callback argument + + + if (arguments.length === 2) { + var length = callbacks.length; + + for (var i = 0; i < length; ++i) { + callbacks[i].call(this, arguments[1]); + } + } else { + var args = Array.prototype.slice.call(arguments, 1); + var _length = callbacks.length; + + for (var _i = 0; _i < _length; ++_i) { + callbacks[_i].apply(this, args); + } + } + } + /** + * Destroys the stream and cleans up. + */ + ; + + _proto.dispose = function dispose() { + this.listeners = {}; + } + /** + * Forwards all `data` events on this stream to the destination stream. The + * destination stream should provide a method `push` to receive the data + * events as they arrive. + * + * @param {Stream} destination the stream that will receive all `data` events + * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options + */ + ; + + _proto.pipe = function pipe(destination) { + this.on('data', function (data) { + destination.push(data); + }); + }; + + return Stream; +}(); + +var stream$1 = Stream$2; + +/*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */ + +/** + * Returns the subarray of a Uint8Array without PKCS#7 padding. + * + * @param padded {Uint8Array} unencrypted bytes that have been padded + * @return {Uint8Array} the unpadded bytes + * @see http://tools.ietf.org/html/rfc5652 + */ +function unpad(padded) { + return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); +} + +/*! @name aes-decrypter @version 3.0.2 @license Apache-2.0 */ + +/** + * @file aes.js + * + * This file contains an adaptation of the AES decryption algorithm + * from the Standford Javascript Cryptography Library. That work is + * covered by the following copyright and permissions notice: + * + * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the authors. + */ + +/** + * Expand the S-box tables. + * + * @private + */ +var precompute = function precompute() { + var tables = [[[], [], [], [], []], [[], [], [], [], []]]; + var encTable = tables[0]; + var decTable = tables[1]; + var sbox = encTable[4]; + var sboxInv = decTable[4]; + var i; + var x; + var xInv; + var d = []; + var th = []; + var x2; + var x4; + var x8; + var s; + var tEnc; + var tDec; // Compute double and third tables + + for (i = 0; i < 256; i++) { + th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; + } + + for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { + // Compute sbox + s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; + s = s >> 8 ^ s & 255 ^ 99; + sbox[x] = s; + sboxInv[s] = x; // Compute MixColumns + + x8 = d[x4 = d[x2 = d[x]]]; + tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; + tEnc = d[s] * 0x101 ^ s * 0x1010100; + + for (i = 0; i < 4; i++) { + encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; + decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; + } + } // Compactify. Considerable speedup on Firefox. + + + for (i = 0; i < 5; i++) { + encTable[i] = encTable[i].slice(0); + decTable[i] = decTable[i].slice(0); + } + + return tables; +}; + +var aesTables = null; +/** + * Schedule out an AES key for both encryption and decryption. This + * is a low-level class. Use a cipher mode to do bulk encryption. + * + * @class AES + * @param key {Array} The key as an array of 4, 6 or 8 words. + */ + +var AES = +/*#__PURE__*/ +function () { + function AES(key) { + /** + * The expanded S-box and inverse S-box tables. These will be computed + * on the client so that we don't have to send them down the wire. + * + * There are two tables, _tables[0] is for encryption and + * _tables[1] is for decryption. + * + * The first 4 sub-tables are the expanded S-box with MixColumns. The + * last (_tables[01][4]) is the S-box itself. + * + * @private + */ + // if we have yet to precompute the S-box tables + // do so now + if (!aesTables) { + aesTables = precompute(); + } // then make a copy of that object for use + + + this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]]; + var i; + var j; + var tmp; + var sbox = this._tables[0][4]; + var decTable = this._tables[1]; + var keyLen = key.length; + var rcon = 1; + + if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { + throw new Error('Invalid aes key size'); + } + + var encKey = key.slice(0); + var decKey = []; + this._key = [encKey, decKey]; // schedule encryption keys + + for (i = keyLen; i < 4 * keyLen + 28; i++) { + tmp = encKey[i - 1]; // apply sbox + + if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { + tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon + + if (i % keyLen === 0) { + tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; + rcon = rcon << 1 ^ (rcon >> 7) * 283; + } + } + + encKey[i] = encKey[i - keyLen] ^ tmp; + } // schedule decryption keys + + + for (j = 0; i; j++, i--) { + tmp = encKey[j & 3 ? i : i - 4]; + + if (i <= 4 || j < 4) { + decKey[j] = tmp; + } else { + decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; + } + } + } + /** + * Decrypt 16 bytes, specified as four 32-bit words. + * + * @param {number} encrypted0 the first word to decrypt + * @param {number} encrypted1 the second word to decrypt + * @param {number} encrypted2 the third word to decrypt + * @param {number} encrypted3 the fourth word to decrypt + * @param {Int32Array} out the array to write the decrypted words + * into + * @param {number} offset the offset into the output array to start + * writing results + * @return {Array} The plaintext. + */ + + + var _proto = AES.prototype; + + _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { + var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data + + var a = encrypted0 ^ key[0]; + var b = encrypted3 ^ key[1]; + var c = encrypted2 ^ key[2]; + var d = encrypted1 ^ key[3]; + var a2; + var b2; + var c2; // key.length === 2 ? + + var nInnerRounds = key.length / 4 - 2; + var i; + var kIndex = 4; + var table = this._tables[1]; // load up the tables + + var table0 = table[0]; + var table1 = table[1]; + var table2 = table[2]; + var table3 = table[3]; + var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. + + for (i = 0; i < nInnerRounds; i++) { + a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; + b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; + c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; + d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; + kIndex += 4; + a = a2; + b = b2; + c = c2; + } // Last round. + + + for (i = 0; i < 4; i++) { + out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; + a2 = a; + a = b; + b = c; + c = d; + d = a2; + } + }; + + return AES; +}(); + +/** + * A wrapper around the Stream class to use setTimeout + * and run stream "jobs" Asynchronously + * + * @class AsyncStream + * @extends Stream + */ + +var AsyncStream = +/*#__PURE__*/ +function (_Stream) { + inheritsLoose(AsyncStream, _Stream); + + function AsyncStream() { + var _this; + + _this = _Stream.call(this, stream$1) || this; + _this.jobs = []; + _this.delay = 1; + _this.timeout_ = null; + return _this; + } + /** + * process an async job + * + * @private + */ + + + var _proto = AsyncStream.prototype; + + _proto.processJob_ = function processJob_() { + this.jobs.shift()(); + + if (this.jobs.length) { + this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); + } else { + this.timeout_ = null; + } + } + /** + * push a job into the stream + * + * @param {Function} job the job to push into the stream + */ + ; + + _proto.push = function push(job) { + this.jobs.push(job); + + if (!this.timeout_) { + this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); + } + }; + + return AsyncStream; +}(stream$1); + +/** + * Convert network-order (big-endian) bytes into their little-endian + * representation. + */ + +var ntoh = function ntoh(word) { + return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; +}; +/** + * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. + * + * @param {Uint8Array} encrypted the encrypted bytes + * @param {Uint32Array} key the bytes of the decryption key + * @param {Uint32Array} initVector the initialization vector (IV) to + * use for the first round of CBC. + * @return {Uint8Array} the decrypted bytes + * + * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard + * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 + * @see https://tools.ietf.org/html/rfc2315 + */ + + +var decrypt = function decrypt(encrypted, key, initVector) { + // word-level access to the encrypted bytes + var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); + var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output + + var decrypted = new Uint8Array(encrypted.byteLength); + var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and + // decrypted data + + var init0; + var init1; + var init2; + var init3; + var encrypted0; + var encrypted1; + var encrypted2; + var encrypted3; // iteration variable + + var wordIx; // pull out the words of the IV to ensure we don't modify the + // passed-in reference and easier access + + init0 = initVector[0]; + init1 = initVector[1]; + init2 = initVector[2]; + init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) + // to each decrypted block + + for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { + // convert big-endian (network order) words into little-endian + // (javascript order) + encrypted0 = ntoh(encrypted32[wordIx]); + encrypted1 = ntoh(encrypted32[wordIx + 1]); + encrypted2 = ntoh(encrypted32[wordIx + 2]); + encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block + + decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the + // plaintext + + decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); + decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); + decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); + decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round + + init0 = encrypted0; + init1 = encrypted1; + init2 = encrypted2; + init3 = encrypted3; + } + + return decrypted; +}; +/** + * The `Decrypter` class that manages decryption of AES + * data through `AsyncStream` objects and the `decrypt` + * function + * + * @param {Uint8Array} encrypted the encrypted bytes + * @param {Uint32Array} key the bytes of the decryption key + * @param {Uint32Array} initVector the initialization vector (IV) to + * @param {Function} done the function to run when done + * @class Decrypter + */ + + +var Decrypter = +/*#__PURE__*/ +function () { + function Decrypter(encrypted, key, initVector, done) { + var step = Decrypter.STEP; + var encrypted32 = new Int32Array(encrypted.buffer); + var decrypted = new Uint8Array(encrypted.byteLength); + var i = 0; + this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously + + this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); + + for (i = step; i < encrypted32.length; i += step) { + initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); + this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); + } // invoke the done() callback when everything is finished + + + this.asyncStream_.push(function () { + // remove pkcs#7 padding from the decrypted bytes + done(null, unpad(decrypted)); + }); + } + /** + * a getter for step the maximum number of bytes to process at one time + * + * @return {number} the value of step 32000 + */ + + + var _proto = Decrypter.prototype; + + /** + * @private + */ + _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { + return function () { + var bytes = decrypt(encrypted, key, initVector); + decrypted.set(bytes, encrypted.byteOffset); + }; + }; + + createClass(Decrypter, null, [{ + key: "STEP", + get: function get() { + // 4 * 8000; + return 32000; + } + }]); + + return Decrypter; +}(); + +/** + * @license + * Video.js 7.9.6 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 2.0 + * + */ + +var version = "7.9.6"; + +/** + * @file create-logger.js + * @module create-logger + */ + +var history = []; +/** + * Log messages to the console and history based on the type of message + * + * @private + * @param {string} type + * The name of the console method to use. + * + * @param {Array} args + * The arguments to be passed to the matching console method. + */ + +var LogByTypeFactory = function LogByTypeFactory(name, log) { + return function (type, level, args) { + var lvl = log.levels[level]; + var lvlRegExp = new RegExp("^(" + lvl + ")$"); + + if (type !== 'log') { + // Add the type to the front of the message when it's not "log". + args.unshift(type.toUpperCase() + ':'); + } // Add console prefix after adding to history. + + + args.unshift(name + ':'); // Add a clone of the args at this point to history. + + if (history) { + history.push([].concat(args)); // only store 1000 history entries + + var splice = history.length - 1000; + history.splice(0, splice > 0 ? splice : 0); + } // If there's no console then don't try to output messages, but they will + // still be stored in history. + + + if (!window_1$1.console) { + return; + } // Was setting these once outside of this function, but containing them + // in the function makes it easier to test cases where console doesn't exist + // when the module is executed. + + + var fn = window_1$1.console[type]; + + if (!fn && type === 'debug') { + // Certain browsers don't have support for console.debug. For those, we + // should default to the closest comparable log. + fn = window_1$1.console.info || window_1$1.console.log; + } // Bail out if there's no console or if this type is not allowed by the + // current logging level. + + + if (!fn || !lvl || !lvlRegExp.test(type)) { + return; + } + + fn[Array.isArray(args) ? 'apply' : 'call'](window_1$1.console, args); + }; +}; + +function createLogger(name) { + // This is the private tracking variable for logging level. + var level = 'info'; // the curried logByType bound to the specific log and history + + var logByType; + /** + * Logs plain debug messages. Similar to `console.log`. + * + * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) + * of our JSDoc template, we cannot properly document this as both a function + * and a namespace, so its function signature is documented here. + * + * #### Arguments + * ##### *args + * Mixed[] + * + * Any combination of values that could be passed to `console.log()`. + * + * #### Return Value + * + * `undefined` + * + * @namespace + * @param {Mixed[]} args + * One or more messages or objects that should be logged. + */ + + var log = function log() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + logByType('log', level, args); + }; // This is the logByType helper that the logging methods below use + + + logByType = LogByTypeFactory(name, log); + /** + * Create a new sublogger which chains the old name to the new name. + * + * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: + * ```js + * mylogger('foo'); + * // > VIDEOJS: player: foo + * ``` + * + * @param {string} name + * The name to add call the new logger + * @return {Object} + */ + + log.createLogger = function (subname) { + return createLogger(name + ': ' + subname); + }; + /** + * Enumeration of available logging levels, where the keys are the level names + * and the values are `|`-separated strings containing logging methods allowed + * in that logging level. These strings are used to create a regular expression + * matching the function name being called. + * + * Levels provided by Video.js are: + * + * - `off`: Matches no calls. Any value that can be cast to `false` will have + * this effect. The most restrictive. + * - `all`: Matches only Video.js-provided functions (`debug`, `log`, + * `log.warn`, and `log.error`). + * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. + * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. + * - `warn`: Matches `log.warn` and `log.error` calls. + * - `error`: Matches only `log.error` calls. + * + * @type {Object} + */ + + + log.levels = { + all: 'debug|log|warn|error', + off: '', + debug: 'debug|log|warn|error', + info: 'log|warn|error', + warn: 'warn|error', + error: 'error', + DEFAULT: level + }; + /** + * Get or set the current logging level. + * + * If a string matching a key from {@link module:log.levels} is provided, acts + * as a setter. + * + * @param {string} [lvl] + * Pass a valid level to set a new logging level. + * + * @return {string} + * The current logging level. + */ + + log.level = function (lvl) { + if (typeof lvl === 'string') { + if (!log.levels.hasOwnProperty(lvl)) { + throw new Error("\"" + lvl + "\" in not a valid log level"); + } + + level = lvl; + } + + return level; + }; + /** + * Returns an array containing everything that has been logged to the history. + * + * This array is a shallow clone of the internal history record. However, its + * contents are _not_ cloned; so, mutating objects inside this array will + * mutate them in history. + * + * @return {Array} + */ + + + log.history = function () { + return history ? [].concat(history) : []; + }; + /** + * Allows you to filter the history by the given logger name + * + * @param {string} fname + * The name to filter by + * + * @return {Array} + * The filtered list to return + */ + + + log.history.filter = function (fname) { + return (history || []).filter(function (historyItem) { + // if the first item in each historyItem includes `fname`, then it's a match + return new RegExp(".*" + fname + ".*").test(historyItem[0]); + }); + }; + /** + * Clears the internal history tracking, but does not prevent further history + * tracking. + */ + + + log.history.clear = function () { + if (history) { + history.length = 0; + } + }; + /** + * Disable history tracking if it is currently enabled. + */ + + + log.history.disable = function () { + if (history !== null) { + history.length = 0; + history = null; + } + }; + /** + * Enable history tracking if it is currently disabled. + */ + + + log.history.enable = function () { + if (history === null) { + history = []; + } + }; + /** + * Logs error messages. Similar to `console.error`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as an error + */ + + + log.error = function () { + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return logByType('error', level, args); + }; + /** + * Logs warning messages. Similar to `console.warn`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as a warning. + */ + + + log.warn = function () { + for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return logByType('warn', level, args); + }; + /** + * Logs debug messages. Similar to `console.debug`, but may also act as a comparable + * log if `console.debug` is not available + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as debug. + */ + + + log.debug = function () { + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; + } + + return logByType('debug', level, args); + }; + + return log; +} + +/** + * @file log.js + * @module log + */ +var log = createLogger('VIDEOJS'); +var createLogger$1 = log.createLogger; + +/** + * @file obj.js + * @module obj + */ + +/** + * @callback obj:EachCallback + * + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over + */ + +/** + * @callback obj:ReduceCallback + * + * @param {Mixed} accum + * The value that is accumulating over the reduce loop. + * + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over + * + * @return {Mixed} + * The new accumulated value. + */ +var toString$1 = Object.prototype.toString; +/** + * Get the keys of an Object + * + * @param {Object} + * The Object to get the keys from + * + * @return {string[]} + * An array of the keys from the object. Returns an empty array if the + * object passed in was invalid or had no keys. + * + * @private + */ + +var keys = function keys(object) { + return isObject$1(object) ? Object.keys(object) : []; +}; +/** + * Array-like iteration for objects. + * + * @param {Object} object + * The object to iterate over + * + * @param {obj:EachCallback} fn + * The callback function which is called for each key in the object. + */ + + +function each(object, fn) { + keys(object).forEach(function (key) { + return fn(object[key], key); + }); +} +/** + * Array-like reduce for objects. + * + * @param {Object} object + * The Object that you want to reduce. + * + * @param {Function} fn + * A callback function which is called for each key in the object. It + * receives the accumulated value and the per-iteration value and key + * as arguments. + * + * @param {Mixed} [initial = 0] + * Starting value + * + * @return {Mixed} + * The final accumulated value. + */ + +function reduce(object, fn, initial) { + if (initial === void 0) { + initial = 0; + } + + return keys(object).reduce(function (accum, key) { + return fn(accum, object[key], key); + }, initial); +} +/** + * Object.assign-style object shallow merge/extend. + * + * @param {Object} target + * @param {Object} ...sources + * @return {Object} + */ + +function assign(target) { + for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + sources[_key - 1] = arguments[_key]; + } + + if (Object.assign) { + return _extends_1.apply(void 0, [target].concat(sources)); + } + + sources.forEach(function (source) { + if (!source) { + return; + } + + each(source, function (value, key) { + target[key] = value; + }); + }); + return target; +} +/** + * Returns whether a value is an object of any kind - including DOM nodes, + * arrays, regular expressions, etc. Not functions, though. + * + * This avoids the gotcha where using `typeof` on a `null` value + * results in `'object'`. + * + * @param {Object} value + * @return {boolean} + */ + +function isObject$1(value) { + return !!value && typeof value === 'object'; +} +/** + * Returns whether an object appears to be a "plain" object - that is, a + * direct instance of `Object`. + * + * @param {Object} value + * @return {boolean} + */ + +function isPlain(value) { + return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object; +} + +/** + * @file computed-style.js + * @module computed-style + */ +/** + * A safe getComputedStyle. + * + * This is needed because in Firefox, if the player is loaded in an iframe with + * `display:none`, then `getComputedStyle` returns `null`, so, we do a + * null-check to make sure that the player doesn't break in these cases. + * + * @function + * @param {Element} el + * The element you want the computed style of + * + * @param {string} prop + * The property name you want + * + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + */ + +function computedStyle(el, prop) { + if (!el || !prop) { + return ''; + } + + if (typeof window_1$1.getComputedStyle === 'function') { + var computedStyleValue = window_1$1.getComputedStyle(el); + return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; + } + + return ''; +} + +/** + * @file dom.js + * @module dom + */ +/** + * Detect if a value is a string with any non-whitespace characters. + * + * @private + * @param {string} str + * The string to check + * + * @return {boolean} + * Will be `true` if the string is non-blank, `false` otherwise. + * + */ + +function isNonBlankString(str) { + // we use str.trim as it will trim any whitespace characters + // from the front or back of non-whitespace characters. aka + // Any string that contains non-whitespace characters will + // still contain them after `trim` but whitespace only strings + // will have a length of 0, failing this check. + return typeof str === 'string' && Boolean(str.trim()); +} +/** + * Throws an error if the passed string has whitespace. This is used by + * class methods to be relatively consistent with the classList API. + * + * @private + * @param {string} str + * The string to check for whitespace. + * + * @throws {Error} + * Throws an error if there is whitespace in the string. + */ + + +function throwIfWhitespace(str) { + // str.indexOf instead of regex because str.indexOf is faster performance wise. + if (str.indexOf(' ') >= 0) { + throw new Error('class has illegal whitespace characters'); + } +} +/** + * Produce a regular expression for matching a className within an elements className. + * + * @private + * @param {string} className + * The className to generate the RegExp for. + * + * @return {RegExp} + * The RegExp that will check for a specific `className` in an elements + * className. + */ + + +function classRegExp(className) { + return new RegExp('(^|\\s)' + className + '($|\\s)'); +} +/** + * Whether the current DOM interface appears to be real (i.e. not simulated). + * + * @return {boolean} + * Will be `true` if the DOM appears to be real, `false` otherwise. + */ + + +function isReal() { + // Both document and window will never be undefined thanks to `global`. + return document_1 === window_1$1.document; +} +/** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @param {Mixed} value + * The value to check. + * + * @return {boolean} + * Will be `true` if the value is a DOM element, `false` otherwise. + */ + +function isEl(value) { + return isObject$1(value) && value.nodeType === 1; +} +/** + * Determines if the current DOM is embedded in an iframe. + * + * @return {boolean} + * Will be `true` if the DOM is embedded in an iframe, `false` + * otherwise. + */ + +function isInFrame() { + // We need a try/catch here because Safari will throw errors when attempting + // to get either `parent` or `self` + try { + return window_1$1.parent !== window_1$1.self; + } catch (x) { + return true; + } +} +/** + * Creates functions to query the DOM using a given method. + * + * @private + * @param {string} method + * The method to create the query with. + * + * @return {Function} + * The query method + */ + +function createQuerier(method) { + return function (selector, context) { + if (!isNonBlankString(selector)) { + return document_1[method](null); + } + + if (isNonBlankString(context)) { + context = document_1.querySelector(context); + } + + var ctx = isEl(context) ? context : document_1; + return ctx[method] && ctx[method](selector); + }; +} +/** + * Creates an element and applies properties, attributes, and inserts content. + * + * @param {string} [tagName='div'] + * Name of tag to be created. + * + * @param {Object} [properties={}] + * Element properties to be applied. + * + * @param {Object} [attributes={}] + * Element attributes to be applied. + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor object. + * + * @return {Element} + * The element that was created. + */ + + +function createEl(tagName, properties, attributes, content) { + if (tagName === void 0) { + tagName = 'div'; + } + + if (properties === void 0) { + properties = {}; + } + + if (attributes === void 0) { + attributes = {}; + } + + var el = document_1.createElement(tagName); + Object.getOwnPropertyNames(properties).forEach(function (propName) { + var val = properties[propName]; // See #2176 + // We originally were accepting both properties and attributes in the + // same object, but that doesn't work so well. + + if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { + log.warn('Setting attributes in the second argument of createEl()\n' + 'has been deprecated. Use the third argument instead.\n' + ("createEl(type, properties, attributes). Attempting to set " + propName + " to " + val + ".")); + el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a + // method for it. + } else if (propName === 'textContent') { + textContent(el, val); + } else if (el[propName] !== val) { + el[propName] = val; + } + }); + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + el.setAttribute(attrName, attributes[attrName]); + }); + + if (content) { + appendContent(el, content); + } + + return el; +} +/** + * Injects text into an element, replacing any existing contents entirely. + * + * @param {Element} el + * The element to add text content into + * + * @param {string} text + * The text content to add. + * + * @return {Element} + * The element with added text content. + */ + +function textContent(el, text) { + if (typeof el.textContent === 'undefined') { + el.innerText = text; + } else { + el.textContent = text; + } + + return el; +} +/** + * Insert an element as the first child node of another + * + * @param {Element} child + * Element to insert + * + * @param {Element} parent + * Element to insert child into + */ + +function prependTo(child, parent) { + if (parent.firstChild) { + parent.insertBefore(child, parent.firstChild); + } else { + parent.appendChild(child); + } +} +/** + * Check if an element has a class name. + * + * @param {Element} element + * Element to check + * + * @param {string} classToCheck + * Class name to check for + * + * @return {boolean} + * Will be `true` if the element has a class, `false` otherwise. + * + * @throws {Error} + * Throws an error if `classToCheck` has white space. + */ + +function hasClass(element, classToCheck) { + throwIfWhitespace(classToCheck); + + if (element.classList) { + return element.classList.contains(classToCheck); + } + + return classRegExp(classToCheck).test(element.className); +} +/** + * Add a class name to an element. + * + * @param {Element} element + * Element to add class name to. + * + * @param {string} classToAdd + * Class name to add. + * + * @return {Element} + * The DOM element with the added class name. + */ + +function addClass(element, classToAdd) { + if (element.classList) { + element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it + // in the case of classList not being supported. + } else if (!hasClass(element, classToAdd)) { + element.className = (element.className + ' ' + classToAdd).trim(); + } + + return element; +} +/** + * Remove a class name from an element. + * + * @param {Element} element + * Element to remove a class name from. + * + * @param {string} classToRemove + * Class name to remove + * + * @return {Element} + * The DOM element with class name removed. + */ + +function removeClass(element, classToRemove) { + if (element.classList) { + element.classList.remove(classToRemove); + } else { + throwIfWhitespace(classToRemove); + element.className = element.className.split(/\s+/).filter(function (c) { + return c !== classToRemove; + }).join(' '); + } + + return element; +} +/** + * The callback definition for toggleClass. + * + * @callback module:dom~PredicateCallback + * @param {Element} element + * The DOM element of the Component. + * + * @param {string} classToToggle + * The `className` that wants to be toggled + * + * @return {boolean|undefined} + * If `true` is returned, the `classToToggle` will be added to the + * `element`. If `false`, the `classToToggle` will be removed from + * the `element`. If `undefined`, the callback will be ignored. + */ + +/** + * Adds or removes a class name to/from an element depending on an optional + * condition or the presence/absence of the class name. + * + * @param {Element} element + * The element to toggle a class name on. + * + * @param {string} classToToggle + * The class that should be toggled. + * + * @param {boolean|module:dom~PredicateCallback} [predicate] + * See the return value for {@link module:dom~PredicateCallback} + * + * @return {Element} + * The element with a class that has been toggled. + */ + +function toggleClass(element, classToToggle, predicate) { + // This CANNOT use `classList` internally because IE11 does not support the + // second parameter to the `classList.toggle()` method! Which is fine because + // `classList` will be used by the add/remove functions. + var has = hasClass(element, classToToggle); + + if (typeof predicate === 'function') { + predicate = predicate(element, classToToggle); + } + + if (typeof predicate !== 'boolean') { + predicate = !has; + } // If the necessary class operation matches the current state of the + // element, no action is required. + + + if (predicate === has) { + return; + } + + if (predicate) { + addClass(element, classToToggle); + } else { + removeClass(element, classToToggle); + } + + return element; +} +/** + * Apply attributes to an HTML element. + * + * @param {Element} el + * Element to add attributes to. + * + * @param {Object} [attributes] + * Attributes to be applied. + */ + +function setAttributes(el, attributes) { + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + var attrValue = attributes[attrName]; + + if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { + el.removeAttribute(attrName); + } else { + el.setAttribute(attrName, attrValue === true ? '' : attrValue); + } + }); +} +/** + * Get an element's attribute values, as defined on the HTML tag. + * + * Attributes are not the same as properties. They're defined on the tag + * or with setAttribute. + * + * @param {Element} tag + * Element from which to get tag attributes. + * + * @return {Object} + * All attributes of the element. Boolean attributes will be `true` or + * `false`, others will be strings. + */ + +function getAttributes(tag) { + var obj = {}; // known boolean attributes + // we can check for matching boolean properties, but not all browsers + // and not all tags know about these attributes, so, we still want to check them manually + + var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; + + if (tag && tag.attributes && tag.attributes.length > 0) { + var attrs = tag.attributes; + + for (var i = attrs.length - 1; i >= 0; i--) { + var attrName = attrs[i].name; + var attrVal = attrs[i].value; // check for known booleans + // the matching element property will return a value for typeof + + if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { + // the value of an included boolean attribute is typically an empty + // string ('') which would equal false if we just check for a false value. + // we also don't want support bad code like autoplay='false' + attrVal = attrVal !== null ? true : false; + } + + obj[attrName] = attrVal; + } + } + + return obj; +} +/** + * Get the value of an element's attribute. + * + * @param {Element} el + * A DOM element. + * + * @param {string} attribute + * Attribute to get the value of. + * + * @return {string} + * The value of the attribute. + */ + +function getAttribute(el, attribute) { + return el.getAttribute(attribute); +} +/** + * Set the value of an element's attribute. + * + * @param {Element} el + * A DOM element. + * + * @param {string} attribute + * Attribute to set. + * + * @param {string} value + * Value to set the attribute to. + */ + +function setAttribute(el, attribute, value) { + el.setAttribute(attribute, value); +} +/** + * Remove an element's attribute. + * + * @param {Element} el + * A DOM element. + * + * @param {string} attribute + * Attribute to remove. + */ + +function removeAttribute(el, attribute) { + el.removeAttribute(attribute); +} +/** + * Attempt to block the ability to select text. + */ + +function blockTextSelection() { + document_1.body.focus(); + + document_1.onselectstart = function () { + return false; + }; +} +/** + * Turn off text selection blocking. + */ + +function unblockTextSelection() { + document_1.onselectstart = function () { + return true; + }; +} +/** + * Identical to the native `getBoundingClientRect` function, but ensures that + * the method is supported at all (it is in all browsers we claim to support) + * and that the element is in the DOM before continuing. + * + * This wrapper function also shims properties which are not provided by some + * older browsers (namely, IE8). + * + * Additionally, some browsers do not support adding properties to a + * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard + * properties (except `x` and `y` which are not widely supported). This helps + * avoid implementations where keys are non-enumerable. + * + * @param {Element} el + * Element whose `ClientRect` we want to calculate. + * + * @return {Object|undefined} + * Always returns a plain object - or `undefined` if it cannot. + */ + +function getBoundingClientRect(el) { + if (el && el.getBoundingClientRect && el.parentNode) { + var rect = el.getBoundingClientRect(); + var result = {}; + ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { + if (rect[k] !== undefined) { + result[k] = rect[k]; + } + }); + + if (!result.height) { + result.height = parseFloat(computedStyle(el, 'height')); + } + + if (!result.width) { + result.width = parseFloat(computedStyle(el, 'width')); + } + + return result; + } +} +/** + * Represents the position of a DOM element on the page. + * + * @typedef {Object} module:dom~Position + * + * @property {number} left + * Pixels to the left. + * + * @property {number} top + * Pixels from the top. + */ + +/** + * Get the position of an element in the DOM. + * + * Uses `getBoundingClientRect` technique from John Resig. + * + * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ + * + * @param {Element} el + * Element from which to get offset. + * + * @return {module:dom~Position} + * The position of the element that was passed in. + */ + +function findPosition(el) { + if (!el || el && !el.offsetParent) { + return { + left: 0, + top: 0, + width: 0, + height: 0 + }; + } + + var width = el.offsetWidth; + var height = el.offsetHeight; + var left = 0; + var top = 0; + + do { + left += el.offsetLeft; + top += el.offsetTop; + el = el.offsetParent; + } while (el); + + return { + left: left, + top: top, + width: width, + height: height + }; +} +/** + * Represents x and y coordinates for a DOM element or mouse pointer. + * + * @typedef {Object} module:dom~Coordinates + * + * @property {number} x + * x coordinate in pixels + * + * @property {number} y + * y coordinate in pixels + */ + +/** + * Get the pointer position within an element. + * + * The base on the coordinates are the bottom left of the element. + * + * @param {Element} el + * Element on which to get the pointer position on. + * + * @param {EventTarget~Event} event + * Event object. + * + * @return {module:dom~Coordinates} + * A coordinates object corresponding to the mouse position. + * + */ + +function getPointerPosition(el, event) { + var position = {}; + var boxTarget = findPosition(event.target); + var box = findPosition(el); + var boxW = box.width; + var boxH = box.height; + var offsetY = event.offsetY - (box.top - boxTarget.top); + var offsetX = event.offsetX - (box.left - boxTarget.left); + + if (event.changedTouches) { + offsetX = event.changedTouches[0].pageX - box.left; + offsetY = event.changedTouches[0].pageY + box.top; + } + + position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH)); + position.x = Math.max(0, Math.min(1, offsetX / boxW)); + return position; +} +/** + * Determines, via duck typing, whether or not a value is a text node. + * + * @param {Mixed} value + * Check if this value is a text node. + * + * @return {boolean} + * Will be `true` if the value is a text node, `false` otherwise. + */ + +function isTextNode(value) { + return isObject$1(value) && value.nodeType === 3; +} +/** + * Empties the contents of an element. + * + * @param {Element} el + * The element to empty children from + * + * @return {Element} + * The element with no children + */ + +function emptyEl(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + + return el; +} +/** + * This is a mixed value that describes content to be injected into the DOM + * via some method. It can be of the following types: + * + * Type | Description + * -----------|------------- + * `string` | The value will be normalized into a text node. + * `Element` | The value will be accepted as-is. + * `TextNode` | The value will be accepted as-is. + * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored). + * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes. + * + * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor + */ + +/** + * Normalizes content for eventual insertion into the DOM. + * + * This allows a wide range of content definition methods, but helps protect + * from falling into the trap of simply writing to `innerHTML`, which could + * be an XSS concern. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @return {Array} + * All of the content that was passed in, normalized to an array of + * elements or text nodes. + */ + +function normalizeContent(content) { + // First, invoke content if it is a function. If it produces an array, + // that needs to happen before normalization. + if (typeof content === 'function') { + content = content(); + } // Next up, normalize to an array, so one or many items can be normalized, + // filtered, and returned. + + + return (Array.isArray(content) ? content : [content]).map(function (value) { + // First, invoke value if it is a function to produce a new value, + // which will be subsequently normalized to a Node of some kind. + if (typeof value === 'function') { + value = value(); + } + + if (isEl(value) || isTextNode(value)) { + return value; + } + + if (typeof value === 'string' && /\S/.test(value)) { + return document_1.createTextNode(value); + } + }).filter(function (value) { + return value; + }); +} +/** + * Normalizes and appends content to an element. + * + * @param {Element} el + * Element to append normalized content to. + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @return {Element} + * The element with appended normalized content. + */ + +function appendContent(el, content) { + normalizeContent(content).forEach(function (node) { + return el.appendChild(node); + }); + return el; +} +/** + * Normalizes and inserts content into an element; this is identical to + * `appendContent()`, except it empties the element first. + * + * @param {Element} el + * Element to insert normalized content into. + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @return {Element} + * The element with inserted normalized content. + */ + +function insertContent(el, content) { + return appendContent(emptyEl(el), content); +} +/** + * Check if an event was a single left click. + * + * @param {EventTarget~Event} event + * Event object. + * + * @return {boolean} + * Will be `true` if a single left click, `false` otherwise. + */ + +function isSingleLeftClick(event) { + // Note: if you create something draggable, be sure to + // call it on both `mousedown` and `mousemove` event, + // otherwise `mousedown` should be enough for a button + if (event.button === undefined && event.buttons === undefined) { + // Why do we need `buttons` ? + // Because, middle mouse sometimes have this: + // e.button === 0 and e.buttons === 4 + // Furthermore, we want to prevent combination click, something like + // HOLD middlemouse then left click, that would be + // e.button === 0, e.buttons === 5 + // just `button` is not gonna work + // Alright, then what this block does ? + // this is for chrome `simulate mobile devices` + // I want to support this as well + return true; + } + + if (event.button === 0 && event.buttons === undefined) { + // Touch screen, sometimes on some specific device, `buttons` + // doesn't have anything (safari on ios, blackberry...) + return true; + } // `mouseup` event on a single left click has + // `button` and `buttons` equal to 0 + + + if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { + return true; + } + + if (event.button !== 0 || event.buttons !== 1) { + // This is the reason we have those if else block above + // if any special case we can catch and let it slide + // we do it above, when get to here, this definitely + // is-not-left-click + return false; + } + + return true; +} +/** + * Finds a single DOM element matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {Element|null} + * The element that was found or null. + */ + +var $ = createQuerier('querySelector'); +/** + * Finds a all DOM elements matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {NodeList} + * A element list of elements that were found. Will be empty if none + * were found. + * + */ + +var $$ = createQuerier('querySelectorAll'); + +var Dom = /*#__PURE__*/Object.freeze({ + __proto__: null, + isReal: isReal, + isEl: isEl, + isInFrame: isInFrame, + createEl: createEl, + textContent: textContent, + prependTo: prependTo, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + toggleClass: toggleClass, + setAttributes: setAttributes, + getAttributes: getAttributes, + getAttribute: getAttribute, + setAttribute: setAttribute, + removeAttribute: removeAttribute, + blockTextSelection: blockTextSelection, + unblockTextSelection: unblockTextSelection, + getBoundingClientRect: getBoundingClientRect, + findPosition: findPosition, + getPointerPosition: getPointerPosition, + isTextNode: isTextNode, + emptyEl: emptyEl, + normalizeContent: normalizeContent, + appendContent: appendContent, + insertContent: insertContent, + isSingleLeftClick: isSingleLeftClick, + $: $, + $$: $$ +}); + +/** + * @file setup.js - Functions for setting up a player without + * user interaction based on the data-setup `attribute` of the video tag. + * + * @module setup + */ +var _windowLoaded = false; +var videojs; +/** + * Set up any tags that have a data-setup `attribute` when the player is started. + */ + +var autoSetup = function autoSetup() { + // Protect against breakage in non-browser environments and check global autoSetup option. + if (!isReal() || videojs.options.autoSetup === false) { + return; + } + + var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video')); + var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio')); + var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js')); + var mediaEls = vids.concat(audios, divs); // Check if any media elements exist + + if (mediaEls && mediaEls.length > 0) { + for (var i = 0, e = mediaEls.length; i < e; i++) { + var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. + + if (mediaEl && mediaEl.getAttribute) { + // Make sure this player hasn't already been set up. + if (mediaEl.player === undefined) { + var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. + // We only auto-setup if they've added the data-setup attr. + + if (options !== null) { + // Create new video.js instance. + videojs(mediaEl); + } + } // If getAttribute isn't defined, we need to wait for the DOM. + + } else { + autoSetupTimeout(1); + break; + } + } // No videos were found, so keep looping unless page is finished loading. + + } else if (!_windowLoaded) { + autoSetupTimeout(1); + } +}; +/** + * Wait until the page is loaded before running autoSetup. This will be called in + * autoSetup if `hasLoaded` returns false. + * + * @param {number} wait + * How long to wait in ms + * + * @param {module:videojs} [vjs] + * The videojs library function + */ + + +function autoSetupTimeout(wait, vjs) { + if (vjs) { + videojs = vjs; + } + + window_1$1.setTimeout(autoSetup, wait); +} +/** + * Used to set the internal tracking of window loaded state to true. + * + * @private + */ + + +function setWindowLoaded() { + _windowLoaded = true; + window_1$1.removeEventListener('load', setWindowLoaded); +} + +if (isReal()) { + if (document_1.readyState === 'complete') { + setWindowLoaded(); + } else { + /** + * Listen for the load event on window, and set _windowLoaded to true. + * + * We use a standard event listener here to avoid incrementing the GUID + * before any players are created. + * + * @listens load + */ + window_1$1.addEventListener('load', setWindowLoaded); + } +} + +/** + * @file stylesheet.js + * @module stylesheet + */ +/** + * Create a DOM syle element given a className for it. + * + * @param {string} className + * The className to add to the created style element. + * + * @return {Element} + * The element that was created. + */ + +var createStyleElement = function createStyleElement(className) { + var style = document_1.createElement('style'); + style.className = className; + return style; +}; +/** + * Add text to a DOM element. + * + * @param {Element} el + * The Element to add text content to. + * + * @param {string} content + * The text to add to the element. + */ + +var setTextContent = function setTextContent(el, content) { + if (el.styleSheet) { + el.styleSheet.cssText = content; + } else { + el.textContent = content; + } +}; + +/** + * @file guid.js + * @module guid + */ +// Default value for GUIDs. This allows us to reset the GUID counter in tests. +// +// The initial GUID is 3 because some users have come to rely on the first +// default player ID ending up as `vjs_video_3`. +// +// See: https://github.com/videojs/video.js/pull/6216 +var _initialGuid = 3; +/** + * Unique ID for an element or function + * + * @type {Number} + */ + +var _guid = _initialGuid; +/** + * Get a unique auto-incrementing ID by number that has not been returned before. + * + * @return {number} + * A new unique ID. + */ + +function newGUID() { + return _guid++; +} + +/** + * @file dom-data.js + * @module dom-data + */ +var FakeWeakMap; + +if (!window_1$1.WeakMap) { + FakeWeakMap = /*#__PURE__*/function () { + function FakeWeakMap() { + this.vdata = 'vdata' + Math.floor(window_1$1.performance && window_1$1.performance.now() || Date.now()); + this.data = {}; + } + + var _proto = FakeWeakMap.prototype; + + _proto.set = function set(key, value) { + var access = key[this.vdata] || newGUID(); + + if (!key[this.vdata]) { + key[this.vdata] = access; + } + + this.data[access] = value; + return this; + }; + + _proto.get = function get(key) { + var access = key[this.vdata]; // we have data, return it + + if (access) { + return this.data[access]; + } // we don't have data, return nothing. + // return undefined explicitly as that's the contract for this method + + + log('We have no data for this element', key); + return undefined; + }; + + _proto.has = function has(key) { + var access = key[this.vdata]; + return access in this.data; + }; + + _proto["delete"] = function _delete(key) { + var access = key[this.vdata]; + + if (access) { + delete this.data[access]; + delete key[this.vdata]; + } + }; + + return FakeWeakMap; + }(); +} +/** + * Element Data Store. + * + * Allows for binding data to an element without putting it directly on the + * element. Ex. Event listeners are stored here. + * (also from jsninja.com, slightly modified and updated for closure compiler) + * + * @type {Object} + * @private + */ + + +var DomData = window_1$1.WeakMap ? new WeakMap() : new FakeWeakMap(); + +/** + * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) + * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) + * This should work very similarly to jQuery's events, however it's based off the book version which isn't as + * robust as jquery's, so there's probably some differences. + * + * @file events.js + * @module events + */ +/** + * Clean up the listener cache and dispatchers + * + * @param {Element|Object} elem + * Element to clean up + * + * @param {string} type + * Type of event to clean up + */ + +function _cleanUpEvents(elem, type) { + if (!DomData.has(elem)) { + return; + } + + var data = DomData.get(elem); // Remove the events of a particular type if there are none left + + if (data.handlers[type].length === 0) { + delete data.handlers[type]; // data.handlers[type] = null; + // Setting to null was causing an error with data.handlers + // Remove the meta-handler from the element + + if (elem.removeEventListener) { + elem.removeEventListener(type, data.dispatcher, false); + } else if (elem.detachEvent) { + elem.detachEvent('on' + type, data.dispatcher); + } + } // Remove the events object if there are no types left + + + if (Object.getOwnPropertyNames(data.handlers).length <= 0) { + delete data.handlers; + delete data.dispatcher; + delete data.disabled; + } // Finally remove the element data if there is no data left + + + if (Object.getOwnPropertyNames(data).length === 0) { + DomData["delete"](elem); + } +} +/** + * Loops through an array of event types and calls the requested method for each type. + * + * @param {Function} fn + * The event method we want to use. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} callback + * Event listener. + */ + + +function _handleMultipleEvents(fn, elem, types, callback) { + types.forEach(function (type) { + // Call the event method for each one of the types + fn(elem, type, callback); + }); +} +/** + * Fix a native event to have standard property values + * + * @param {Object} event + * Event object to fix. + * + * @return {Object} + * Fixed event object. + */ + + +function fixEvent(event) { + if (event.fixed_) { + return event; + } + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } // Test if fixing up is needed + // Used to check if !event.stopPropagation instead of isPropagationStopped + // But native events return true for stopPropagation, but don't have + // other expected methods like isPropagationStopped. Seems to be a problem + // with the Javascript Ninja code. So we're just overriding all events now. + + + if (!event || !event.isPropagationStopped) { + var old = event || window_1$1.event; + event = {}; // Clone the old object so that we can modify the values event = {}; + // IE8 Doesn't like when you mess with native event properties + // Firefox returns false for event.hasOwnProperty('type') and other props + // which makes copying more difficult. + // TODO: Probably best to create a whitelist of event props + + for (var key in old) { + // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y + // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation + // and webkitMovementX/Y + if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { + // Chrome 32+ warns if you try to copy deprecated returnValue, but + // we still want to if preventDefault isn't supported (IE8). + if (!(key === 'returnValue' && old.preventDefault)) { + event[key] = old[key]; + } + } + } // The event occurred on this element + + + if (!event.target) { + event.target = event.srcElement || document_1; + } // Handle which other element the event is related to + + + if (!event.relatedTarget) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } // Stop the default browser action + + + event.preventDefault = function () { + if (old.preventDefault) { + old.preventDefault(); + } + + event.returnValue = false; + old.returnValue = false; + event.defaultPrevented = true; + }; + + event.defaultPrevented = false; // Stop the event from bubbling + + event.stopPropagation = function () { + if (old.stopPropagation) { + old.stopPropagation(); + } + + event.cancelBubble = true; + old.cancelBubble = true; + event.isPropagationStopped = returnTrue; + }; + + event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers + + event.stopImmediatePropagation = function () { + if (old.stopImmediatePropagation) { + old.stopImmediatePropagation(); + } + + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; + + event.isImmediatePropagationStopped = returnFalse; // Handle mouse position + + if (event.clientX !== null && event.clientX !== undefined) { + var doc = document_1.documentElement; + var body = document_1.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } // Handle key presses + + + event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: + // 0 == left; 1 == middle; 2 == right + + if (event.button !== null && event.button !== undefined) { + // The following is disabled because it does not pass videojs-standard + // and... yikes. + + /* eslint-disable */ + event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; + /* eslint-enable */ + } + } + + event.fixed_ = true; // Returns fixed-up instance + + return event; +} +/** + * Whether passive event listeners are supported + */ + +var _supportsPassive; + +var supportsPassive = function supportsPassive() { + if (typeof _supportsPassive !== 'boolean') { + _supportsPassive = false; + + try { + var opts = Object.defineProperty({}, 'passive', { + get: function get() { + _supportsPassive = true; + } + }); + window_1$1.addEventListener('test', null, opts); + window_1$1.removeEventListener('test', null, opts); + } catch (e) {// disregard + } + } + + return _supportsPassive; +}; +/** + * Touch events Chrome expects to be passive + */ + + +var passiveEvents = ['touchstart', 'touchmove']; +/** + * Add an event listener to element + * It stores the handler function in a separate cache object + * and adds a generic handler to the element's event, + * along with a unique id (guid) to the element. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string|string[]} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} fn + * Event listener. + */ + +function on(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(on, elem, type, fn); + } + + if (!DomData.has(elem)) { + DomData.set(elem, {}); + } + + var data = DomData.get(elem); // We need a place to store all our handler data + + if (!data.handlers) { + data.handlers = {}; + } + + if (!data.handlers[type]) { + data.handlers[type] = []; + } + + if (!fn.guid) { + fn.guid = newGUID(); + } + + data.handlers[type].push(fn); + + if (!data.dispatcher) { + data.disabled = false; + + data.dispatcher = function (event, hash) { + if (data.disabled) { + return; + } + + event = fixEvent(event); + var handlers = data.handlers[event.type]; + + if (handlers) { + // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. + var handlersCopy = handlers.slice(0); + + for (var m = 0, n = handlersCopy.length; m < n; m++) { + if (event.isImmediatePropagationStopped()) { + break; + } else { + try { + handlersCopy[m].call(elem, event, hash); + } catch (e) { + log.error(e); + } + } + } + } + }; + } + + if (data.handlers[type].length === 1) { + if (elem.addEventListener) { + var options = false; + + if (supportsPassive() && passiveEvents.indexOf(type) > -1) { + options = { + passive: true + }; + } + + elem.addEventListener(type, data.dispatcher, options); + } else if (elem.attachEvent) { + elem.attachEvent('on' + type, data.dispatcher); + } + } +} +/** + * Removes event listeners from an element + * + * @param {Element|Object} elem + * Object to remove listeners from. + * + * @param {string|string[]} [type] + * Type of listener to remove. Don't include to remove all events from element. + * + * @param {EventTarget~EventListener} [fn] + * Specific listener to remove. Don't include to remove listeners for an event + * type. + */ + +function off(elem, type, fn) { + // Don't want to add a cache object through getElData if not needed + if (!DomData.has(elem)) { + return; + } + + var data = DomData.get(elem); // If no events exist, nothing to unbind + + if (!data.handlers) { + return; + } + + if (Array.isArray(type)) { + return _handleMultipleEvents(off, elem, type, fn); + } // Utility function + + + var removeType = function removeType(el, t) { + data.handlers[t] = []; + + _cleanUpEvents(el, t); + }; // Are we removing all bound events? + + + if (type === undefined) { + for (var t in data.handlers) { + if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { + removeType(elem, t); + } + } + + return; + } + + var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind + + if (!handlers) { + return; + } // If no listener was provided, remove all listeners for type + + + if (!fn) { + removeType(elem, type); + return; + } // We're only removing a single handler + + + if (fn.guid) { + for (var n = 0; n < handlers.length; n++) { + if (handlers[n].guid === fn.guid) { + handlers.splice(n--, 1); + } + } + } + + _cleanUpEvents(elem, type); +} +/** + * Trigger an event for an element + * + * @param {Element|Object} elem + * Element to trigger an event on + * + * @param {EventTarget~Event|string} event + * A string (the type) or an event object with a type attribute + * + * @param {Object} [hash] + * data hash to pass along with the event + * + * @return {boolean|undefined} + * Returns the opposite of `defaultPrevented` if default was + * prevented. Otherwise, returns `undefined` + */ + +function trigger(elem, event, hash) { + // Fetches element data and a reference to the parent (for bubbling). + // Don't want to add a data object to cache for every parent, + // so checking hasElData first. + var elemData = DomData.has(elem) ? DomData.get(elem) : {}; + var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, + // handler; + // If an event name was passed as a string, creates an event out of it + + if (typeof event === 'string') { + event = { + type: event, + target: elem + }; + } else if (!event.target) { + event.target = elem; + } // Normalizes the event properties. + + + event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. + + if (elemData.dispatcher) { + elemData.dispatcher.call(elem, event, hash); + } // Unless explicitly stopped or the event does not bubble (e.g. media events) + // recursively calls this function to bubble the event up the DOM. + + + if (parent && !event.isPropagationStopped() && event.bubbles === true) { + trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. + } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { + if (!DomData.has(event.target)) { + DomData.set(event.target, {}); + } + + var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. + + if (event.target[event.type]) { + // Temporarily disables event dispatching on the target as we have already executed the handler. + targetData.disabled = true; // Executes the default action. + + if (typeof event.target[event.type] === 'function') { + event.target[event.type](); + } // Re-enables event dispatching. + + + targetData.disabled = false; + } + } // Inform the triggerer if the default was prevented by returning false + + + return !event.defaultPrevented; +} +/** + * Trigger a listener only once for an event. + * + * @param {Element|Object} elem + * Element or object to bind to. + * + * @param {string|string[]} type + * Name/type of event + * + * @param {Event~EventListener} fn + * Event listener function + */ + +function one(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(one, elem, type, fn); + } + + var func = function func() { + off(elem, type, func); + fn.apply(this, arguments); + }; // copy the guid to the new function so it can removed using the original function's ID + + + func.guid = fn.guid = fn.guid || newGUID(); + on(elem, type, func); +} +/** + * Trigger a listener only once and then turn if off for all + * configured events + * + * @param {Element|Object} elem + * Element or object to bind to. + * + * @param {string|string[]} type + * Name/type of event + * + * @param {Event~EventListener} fn + * Event listener function + */ + +function any(elem, type, fn) { + var func = function func() { + off(elem, type, func); + fn.apply(this, arguments); + }; // copy the guid to the new function so it can removed using the original function's ID + + + func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything + + on(elem, type, func); +} + +var Events = /*#__PURE__*/Object.freeze({ + __proto__: null, + fixEvent: fixEvent, + on: on, + off: off, + trigger: trigger, + one: one, + any: any +}); + +/** + * @file fn.js + * @module fn + */ +var UPDATE_REFRESH_INTERVAL = 30; +/** + * Bind (a.k.a proxy or context). A simple method for changing the context of + * a function. + * + * It also stores a unique id on the function so it can be easily removed from + * events. + * + * @function + * @param {Mixed} context + * The object to bind as scope. + * + * @param {Function} fn + * The function to be bound to a scope. + * + * @param {number} [uid] + * An optional unique ID for the function to be set + * + * @return {Function} + * The new function that will be bound into the context given + */ + +var bind = function bind(context, fn, uid) { + // Make sure the function has a unique ID + if (!fn.guid) { + fn.guid = newGUID(); + } // Create the new function that changes the context + + + var bound = fn.bind(context); // Allow for the ability to individualize this function + // Needed in the case where multiple objects might share the same prototype + // IF both items add an event listener with the same function, then you try to remove just one + // it will remove both because they both have the same guid. + // when using this, you need to use the bind method when you remove the listener as well. + // currently used in text tracks + + bound.guid = uid ? uid + '_' + fn.guid : fn.guid; + return bound; +}; +/** + * Wraps the given function, `fn`, with a new function that only invokes `fn` + * at most once per every `wait` milliseconds. + * + * @function + * @param {Function} fn + * The function to be throttled. + * + * @param {number} wait + * The number of milliseconds by which to throttle. + * + * @return {Function} + */ + +var throttle = function throttle(fn, wait) { + var last = window_1$1.performance.now(); + + var throttled = function throttled() { + var now = window_1$1.performance.now(); + + if (now - last >= wait) { + fn.apply(void 0, arguments); + last = now; + } + }; + + return throttled; +}; +/** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. + * + * Inspired by lodash and underscore implementations. + * + * @function + * @param {Function} func + * The function to wrap with debounce behavior. + * + * @param {number} wait + * The number of milliseconds to wait after the last invocation. + * + * @param {boolean} [immediate] + * Whether or not to invoke the function immediately upon creation. + * + * @param {Object} [context=window] + * The "context" in which the debounced function should debounce. For + * example, if this function should be tied to a Video.js player, + * the player can be passed here. Alternatively, defaults to the + * global `window` object. + * + * @return {Function} + * A debounced function. + */ + +var debounce = function debounce(func, wait, immediate, context) { + if (context === void 0) { + context = window_1$1; + } + + var timeout; + + var cancel = function cancel() { + context.clearTimeout(timeout); + timeout = null; + }; + /* eslint-disable consistent-this */ + + + var debounced = function debounced() { + var self = this; + var args = arguments; + + var _later = function later() { + timeout = null; + _later = null; + + if (!immediate) { + func.apply(self, args); + } + }; + + if (!timeout && immediate) { + func.apply(self, args); + } + + context.clearTimeout(timeout); + timeout = context.setTimeout(_later, wait); + }; + /* eslint-enable consistent-this */ + + + debounced.cancel = cancel; + return debounced; +}; + +/** + * @file src/js/event-target.js + */ +/** + * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It + * adds shorthand functions that wrap around lengthy functions. For example: + * the `on` function is a wrapper around `addEventListener`. + * + * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} + * @class EventTarget + */ + +var EventTarget = function EventTarget() {}; +/** + * A Custom DOM event. + * + * @typedef {Object} EventTarget~Event + * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} + */ + +/** + * All event listeners should follow the following format. + * + * @callback EventTarget~EventListener + * @this {EventTarget} + * + * @param {EventTarget~Event} event + * the event that triggered this function + * + * @param {Object} [hash] + * hash of data sent during the event + */ + +/** + * An object containing event names as keys and booleans as values. + * + * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} + * will have extra functionality. See that function for more information. + * + * @property EventTarget.prototype.allowedEvents_ + * @private + */ + + +EventTarget.prototype.allowedEvents_ = {}; +/** + * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a + * function that will get called when an event with a certain name gets triggered. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to call with `EventTarget`s + */ + +EventTarget.prototype.on = function (type, fn) { + // Remove the addEventListener alias before calling Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + + on(this, type, fn); + this.addEventListener = ael; +}; +/** + * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#on} + */ + + +EventTarget.prototype.addEventListener = EventTarget.prototype.on; +/** + * Removes an `event listener` for a specific event from an instance of `EventTarget`. + * This makes it so that the `event listener` will no longer get called when the + * named event happens. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to remove. + */ + +EventTarget.prototype.off = function (type, fn) { + off(this, type, fn); +}; +/** + * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#off} + */ + + +EventTarget.prototype.removeEventListener = EventTarget.prototype.off; +/** + * This function will add an `event listener` that gets triggered only once. After the + * first trigger it will get removed. This is like adding an `event listener` + * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to be called once for each event name. + */ + +EventTarget.prototype.one = function (type, fn) { + // Remove the addEventListener aliasing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + + one(this, type, fn); + this.addEventListener = ael; +}; + +EventTarget.prototype.any = function (type, fn) { + // Remove the addEventListener aliasing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + + any(this, type, fn); + this.addEventListener = ael; +}; +/** + * This function causes an event to happen. This will then cause any `event listeners` + * that are waiting for that event, to get called. If there are no `event listeners` + * for an event then nothing will happen. + * + * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. + * Trigger will also call the `on` + `uppercaseEventName` function. + * + * Example: + * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call + * `onClick` if it exists. + * + * @param {string|EventTarget~Event|Object} event + * The name of the event, an `Event`, or an object with a key of type set to + * an event name. + */ + + +EventTarget.prototype.trigger = function (event) { + var type = event.type || event; // deprecation + // In a future version we should default target to `this` + // similar to how we default the target to `elem` in + // `Events.trigger`. Right now the default `target` will be + // `document` due to the `Event.fixEvent` call. + + if (typeof event === 'string') { + event = { + type: type + }; + } + + event = fixEvent(event); + + if (this.allowedEvents_[type] && this['on' + type]) { + this['on' + type](event); + } + + trigger(this, event); +}; +/** + * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#trigger} + */ + + +EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; +var EVENT_MAP; + +EventTarget.prototype.queueTrigger = function (event) { + var _this = this; + + // only set up EVENT_MAP if it'll be used + if (!EVENT_MAP) { + EVENT_MAP = new Map(); + } + + var type = event.type || event; + var map = EVENT_MAP.get(this); + + if (!map) { + map = new Map(); + EVENT_MAP.set(this, map); + } + + var oldTimeout = map.get(type); + map["delete"](type); + window_1$1.clearTimeout(oldTimeout); + var timeout = window_1$1.setTimeout(function () { + // if we cleared out all timeouts for the current target, delete its map + if (map.size === 0) { + map = null; + EVENT_MAP["delete"](_this); + } + + _this.trigger(event); + }, 0); + map.set(type, timeout); +}; + +/** + * @file mixins/evented.js + * @module evented + */ +/** + * Returns whether or not an object has had the evented mixin applied. + * + * @param {Object} object + * An object to test. + * + * @return {boolean} + * Whether or not the object appears to be evented. + */ + +var isEvented = function isEvented(object) { + return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { + return typeof object[k] === 'function'; + }); +}; +/** + * Adds a callback to run after the evented mixin applied. + * + * @param {Object} object + * An object to Add + * @param {Function} callback + * The callback to run. + */ + + +var addEventedCallback = function addEventedCallback(target, callback) { + if (isEvented(target)) { + callback(); + } else { + if (!target.eventedCallbacks) { + target.eventedCallbacks = []; + } + + target.eventedCallbacks.push(callback); + } +}; +/** + * Whether a value is a valid event type - non-empty string or array. + * + * @private + * @param {string|Array} type + * The type value to test. + * + * @return {boolean} + * Whether or not the type is a valid event type. + */ + + +var isValidEventType = function isValidEventType(type) { + return (// The regex here verifies that the `type` contains at least one non- + // whitespace character. + typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length + ); +}; +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the target does not appear to be a valid event target. + * + * @param {Object} target + * The object to test. + */ + + +var validateTarget = function validateTarget(target) { + if (!target.nodeName && !isEvented(target)) { + throw new Error('Invalid target; must be a DOM node or evented object.'); + } +}; +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the type does not appear to be a valid event type. + * + * @param {string|Array} type + * The type to test. + */ + + +var validateEventType = function validateEventType(type) { + if (!isValidEventType(type)) { + throw new Error('Invalid event type; must be a non-empty string or array.'); + } +}; +/** + * Validates a value to determine if it is a valid listener. Throws if not. + * + * @private + * @throws {Error} + * If the listener is not a function. + * + * @param {Function} listener + * The listener to test. + */ + + +var validateListener = function validateListener(listener) { + if (typeof listener !== 'function') { + throw new Error('Invalid listener; must be a function.'); + } +}; +/** + * Takes an array of arguments given to `on()` or `one()`, validates them, and + * normalizes them into an object. + * + * @private + * @param {Object} self + * The evented object on which `on()` or `one()` was called. This + * object will be bound as the `this` value for the listener. + * + * @param {Array} args + * An array of arguments passed to `on()` or `one()`. + * + * @return {Object} + * An object containing useful values for `on()` or `one()` calls. + */ + + +var normalizeListenArgs = function normalizeListenArgs(self, args) { + // If the number of arguments is less than 3, the target is always the + // evented object itself. + var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; + var target; + var type; + var listener; + + if (isTargetingSelf) { + target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to + // the evented object itself. + + if (args.length >= 3) { + args.shift(); + } + + type = args[0]; + listener = args[1]; + } else { + target = args[0]; + type = args[1]; + listener = args[2]; + } + + validateTarget(target); + validateEventType(type); + validateListener(listener); + listener = bind(self, listener); + return { + isTargetingSelf: isTargetingSelf, + target: target, + type: type, + listener: listener + }; +}; +/** + * Adds the listener to the event type(s) on the target, normalizing for + * the type of target. + * + * @private + * @param {Element|Object} target + * A DOM node or evented object. + * + * @param {string} method + * The event binding method to use ("on" or "one"). + * + * @param {string|Array} type + * One or more event type(s). + * + * @param {Function} listener + * A listener function. + */ + + +var listen = function listen(target, method, type, listener) { + validateTarget(target); + + if (target.nodeName) { + Events[method](target, type, listener); + } else { + target[method](type, listener); + } +}; +/** + * Contains methods that provide event capabilities to an object which is passed + * to {@link module:evented|evented}. + * + * @mixin EventedMixin + */ + + +var EventedMixin = { + /** + * Add a listener to an event (or events) on this object or another evented + * object. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + on: function on() { + var _this = this; + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var _normalizeListenArgs = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs.isTargetingSelf, + target = _normalizeListenArgs.target, + type = _normalizeListenArgs.type, + listener = _normalizeListenArgs.listener; + + listen(target, 'on', type, listener); // If this object is listening to another evented object. + + if (!isTargetingSelf) { + // If this object is disposed, remove the listener. + var removeListenerOnDispose = function removeListenerOnDispose() { + return _this.off(target, type, listener); + }; // Use the same function ID as the listener so we can remove it later it + // using the ID of the original listener. + + + removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures + // that if the target is disposed BEFORE this object, we remove the + // removal listener that was just added. Otherwise, we create a memory leak. + + var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { + return _this.off('dispose', removeListenerOnDispose); + }; // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + + + removeRemoverOnTargetDispose.guid = listener.guid; + listen(this, 'on', 'dispose', removeListenerOnDispose); + listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); + } + }, + + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will be called once per event and then removed. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + one: function one() { + var _this2 = this; + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + var _normalizeListenArgs2 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, + target = _normalizeListenArgs2.target, + type = _normalizeListenArgs2.type, + listener = _normalizeListenArgs2.listener; // Targeting this evented object. + + + if (isTargetingSelf) { + listen(target, 'one', type, listener); // Targeting another evented object. + } else { + // TODO: This wrapper is incorrect! It should only + // remove the wrapper for the event type that called it. + // Instead all listners are removed on the first trigger! + // see https://github.com/videojs/video.js/issues/5962 + var wrapper = function wrapper() { + _this2.off(target, type, wrapper); + + for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + largs[_key3] = arguments[_key3]; + } + + listener.apply(null, largs); + }; // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + + + wrapper.guid = listener.guid; + listen(target, 'one', type, wrapper); + } + }, + + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will only be called once for the first event that is triggered + * then removed. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + any: function any() { + var _this3 = this; + + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; + } + + var _normalizeListenArgs3 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, + target = _normalizeListenArgs3.target, + type = _normalizeListenArgs3.type, + listener = _normalizeListenArgs3.listener; // Targeting this evented object. + + + if (isTargetingSelf) { + listen(target, 'any', type, listener); // Targeting another evented object. + } else { + var wrapper = function wrapper() { + _this3.off(target, type, wrapper); + + for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { + largs[_key5] = arguments[_key5]; + } + + listener.apply(null, largs); + }; // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + + + wrapper.guid = listener.guid; + listen(target, 'any', type, wrapper); + } + }, + + /** + * Removes listener(s) from event(s) on an evented object. + * + * @param {string|Array|Element|Object} [targetOrType] + * If this is a string or array, it represents the event type(s). + * + * Another evented object can be passed here instead, in which case + * ALL 3 arguments are _required_. + * + * @param {string|Array|Function} [typeOrListener] + * If the first argument was a string or array, this may be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function; otherwise, _all_ listeners bound to the + * event type(s) will be removed. + */ + off: function off$1(targetOrType, typeOrListener, listener) { + // Targeting this evented object. + if (!targetOrType || isValidEventType(targetOrType)) { + off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. + } else { + var target = targetOrType; + var type = typeOrListener; // Fail fast and in a meaningful way! + + validateTarget(target); + validateEventType(type); + validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used + + listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given + // the same guid as the event listener in on(). + + this.off('dispose', listener); + + if (target.nodeName) { + off(target, type, listener); + off(target, 'dispose', listener); + } else if (isEvented(target)) { + target.off(type, listener); + target.off('dispose', listener); + } + } + }, + + /** + * Fire an event on this evented object, causing its listeners to be called. + * + * @param {string|Object} event + * An event type or an object with a type property. + * + * @param {Object} [hash] + * An additional object to pass along to listeners. + * + * @return {boolean} + * Whether or not the default behavior was prevented. + */ + trigger: function trigger$1(event, hash) { + return trigger(this.eventBusEl_, event, hash); + } +}; +/** + * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. + * + * @param {Object} target + * The object to which to add event methods. + * + * @param {Object} [options={}] + * Options for customizing the mixin behavior. + * + * @param {string} [options.eventBusKey] + * By default, adds a `eventBusEl_` DOM element to the target object, + * which is used as an event bus. If the target object already has a + * DOM element that should be used, pass its key here. + * + * @return {Object} + * The target object. + */ + +function evented(target, options) { + if (options === void 0) { + options = {}; + } + + var _options = options, + eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. + + if (eventBusKey) { + if (!target[eventBusKey].nodeName) { + throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); + } + + target.eventBusEl_ = target[eventBusKey]; + } else { + target.eventBusEl_ = createEl('span', { + className: 'vjs-event-bus' + }); + } + + assign(target, EventedMixin); + + if (target.eventedCallbacks) { + target.eventedCallbacks.forEach(function (callback) { + callback(); + }); + } // When any evented object is disposed, it removes all its listeners. + + + target.on('dispose', function () { + target.off(); + window_1$1.setTimeout(function () { + target.eventBusEl_ = null; + }, 0); + }); + return target; +} + +/** + * @file mixins/stateful.js + * @module stateful + */ +/** + * Contains methods that provide statefulness to an object which is passed + * to {@link module:stateful}. + * + * @mixin StatefulMixin + */ + +var StatefulMixin = { + /** + * A hash containing arbitrary keys and values representing the state of + * the object. + * + * @type {Object} + */ + state: {}, + + /** + * Set the state of an object by mutating its + * {@link module:stateful~StatefulMixin.state|state} object in place. + * + * @fires module:stateful~StatefulMixin#statechanged + * @param {Object|Function} stateUpdates + * A new set of properties to shallow-merge into the plugin state. + * Can be a plain object or a function returning a plain object. + * + * @return {Object|undefined} + * An object containing changes that occurred. If no changes + * occurred, returns `undefined`. + */ + setState: function setState(stateUpdates) { + var _this = this; + + // Support providing the `stateUpdates` state as a function. + if (typeof stateUpdates === 'function') { + stateUpdates = stateUpdates(); + } + + var changes; + each(stateUpdates, function (value, key) { + // Record the change if the value is different from what's in the + // current state. + if (_this.state[key] !== value) { + changes = changes || {}; + changes[key] = { + from: _this.state[key], + to: value + }; + } + + _this.state[key] = value; + }); // Only trigger "statechange" if there were changes AND we have a trigger + // function. This allows us to not require that the target object be an + // evented object. + + if (changes && isEvented(this)) { + /** + * An event triggered on an object that is both + * {@link module:stateful|stateful} and {@link module:evented|evented} + * indicating that its state has changed. + * + * @event module:stateful~StatefulMixin#statechanged + * @type {Object} + * @property {Object} changes + * A hash containing the properties that were changed and + * the values they were changed `from` and `to`. + */ + this.trigger({ + changes: changes, + type: 'statechanged' + }); + } + + return changes; + } +}; +/** + * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target + * object. + * + * If the target object is {@link module:evented|evented} and has a + * `handleStateChanged` method, that method will be automatically bound to the + * `statechanged` event on itself. + * + * @param {Object} target + * The object to be made stateful. + * + * @param {Object} [defaultState] + * A default set of properties to populate the newly-stateful object's + * `state` property. + * + * @return {Object} + * Returns the `target`. + */ + +function stateful(target, defaultState) { + assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` + // added in that step. + + target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. + + if (typeof target.handleStateChanged === 'function' && isEvented(target)) { + target.on('statechanged', target.handleStateChanged); + } + + return target; +} + +/** + * @file string-cases.js + * @module to-lower-case + */ + +/** + * Lowercase the first letter of a string. + * + * @param {string} string + * String to be lowercased + * + * @return {string} + * The string with a lowercased first letter + */ +var toLowerCase = function toLowerCase(string) { + if (typeof string !== 'string') { + return string; + } + + return string.replace(/./, function (w) { + return w.toLowerCase(); + }); +}; +/** + * Uppercase the first letter of a string. + * + * @param {string} string + * String to be uppercased + * + * @return {string} + * The string with an uppercased first letter + */ + +var toTitleCase = function toTitleCase(string) { + if (typeof string !== 'string') { + return string; + } + + return string.replace(/./, function (w) { + return w.toUpperCase(); + }); +}; +/** + * Compares the TitleCase versions of the two strings for equality. + * + * @param {string} str1 + * The first string to compare + * + * @param {string} str2 + * The second string to compare + * + * @return {boolean} + * Whether the TitleCase versions of the strings are equal + */ + +var titleCaseEquals = function titleCaseEquals(str1, str2) { + return toTitleCase(str1) === toTitleCase(str2); +}; + +/** + * @file merge-options.js + * @module merge-options + */ +/** + * Merge two objects recursively. + * + * Performs a deep merge like + * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges + * plain objects (not arrays, elements, or anything else). + * + * Non-plain object values will be copied directly from the right-most + * argument. + * + * @static + * @param {Object[]} sources + * One or more objects to merge into a new object. + * + * @return {Object} + * A new object that is the merged result of all sources. + */ + +function mergeOptions() { + var result = {}; + + for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { + sources[_key] = arguments[_key]; + } + + sources.forEach(function (source) { + if (!source) { + return; + } + + each(source, function (value, key) { + if (!isPlain(value)) { + result[key] = value; + return; + } + + if (!isPlain(result[key])) { + result[key] = {}; + } + + result[key] = mergeOptions(result[key], value); + }); + }); + return result; +} + +var MapSham = /*#__PURE__*/function () { + function MapSham() { + this.map_ = {}; + } + + var _proto = MapSham.prototype; + + _proto.has = function has(key) { + return key in this.map_; + }; + + _proto["delete"] = function _delete(key) { + var has = this.has(key); + delete this.map_[key]; + return has; + }; + + _proto.set = function set(key, value) { + this.set_[key] = value; + return this; + }; + + _proto.forEach = function forEach(callback, thisArg) { + for (var key in this.map_) { + callback.call(thisArg, this.map_[key], key, this); + } + }; + + return MapSham; +}(); + +var Map$1 = window_1$1.Map ? window_1$1.Map : MapSham; + +var SetSham = /*#__PURE__*/function () { + function SetSham() { + this.set_ = {}; + } + + var _proto = SetSham.prototype; + + _proto.has = function has(key) { + return key in this.set_; + }; + + _proto["delete"] = function _delete(key) { + var has = this.has(key); + delete this.set_[key]; + return has; + }; + + _proto.add = function add(key) { + this.set_[key] = 1; + return this; + }; + + _proto.forEach = function forEach(callback, thisArg) { + for (var key in this.set_) { + callback.call(thisArg, key, key, this); + } + }; + + return SetSham; +}(); + +var Set = window_1$1.Set ? window_1$1.Set : SetSham; + +/** + * Player Component - Base class for all UI objects + * + * @file component.js + */ +/** + * Base class for all UI Components. + * Components are UI objects which represent both a javascript object and an element + * in the DOM. They can be children of other components, and can have + * children themselves. + * + * Components can also use methods from {@link EventTarget} + */ + +var Component = /*#__PURE__*/function () { + /** + * A callback that is called when a component is ready. Does not have any + * paramters and any callback value will be ignored. + * + * @callback Component~ReadyCallback + * @this Component + */ + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Object[]} [options.children] + * An array of children objects to intialize this component with. Children objects have + * a name property that will be used if more than one component of the same type needs to be + * added. + * + * @param {Component~ReadyCallback} [ready] + * Function that gets called when the `Component` is ready. + */ + function Component(player, options, ready) { + // The component might be the player itself and we can't pass `this` to super + if (!player && this.play) { + this.player_ = player = this; // eslint-disable-line + } else { + this.player_ = player; + } + + this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method + + this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults + + this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options + + options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied + + this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one + + if (!this.id_) { + // Don't require the player ID function in the case of mock players + var id = player && player.id && player.id() || 'no_player'; + this.id_ = id + "_component_" + newGUID(); + } + + this.name_ = options.name || null; // Create element if one wasn't provided in options + + if (options.el) { + this.el_ = options.el; + } else if (options.createEl !== false) { + this.el_ = this.createEl(); + } // if evented is anything except false, we want to mixin in evented + + + if (options.evented !== false) { + // Make this an evented object and use `el_`, if available, as its event bus + evented(this, { + eventBusKey: this.el_ ? 'el_' : null + }); + } + + stateful(this, this.constructor.defaultState); + this.children_ = []; + this.childIndex_ = {}; + this.childNameIndex_ = {}; + this.setTimeoutIds_ = new Set(); + this.setIntervalIds_ = new Set(); + this.rafIds_ = new Set(); + this.namedRafs_ = new Map$1(); + this.clearingTimersOnDispose_ = false; // Add any child components in options + + if (options.initChildren !== false) { + this.initChildren(); + } + + this.ready(ready); // Don't want to trigger ready here or it will before init is actually + // finished for all children that run this constructor + + if (options.reportTouchActivity !== false) { + this.enableTouchActivity(); + } + } + /** + * Dispose of the `Component` and all child components. + * + * @fires Component#dispose + */ + + + var _proto = Component.prototype; + + _proto.dispose = function dispose() { + // Bail out if the component has already been disposed. + if (this.isDisposed_) { + return; + } + /** + * Triggered when a `Component` is disposed. + * + * @event Component#dispose + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the dispose event does not + * bubble up + */ + + + this.trigger({ + type: 'dispose', + bubbles: false + }); + this.isDisposed_ = true; // Dispose all children. + + if (this.children_) { + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i].dispose) { + this.children_[i].dispose(); + } + } + } // Delete child references + + + this.children_ = null; + this.childIndex_ = null; + this.childNameIndex_ = null; + this.parentComponent_ = null; + + if (this.el_) { + // Remove element from DOM + if (this.el_.parentNode) { + this.el_.parentNode.removeChild(this.el_); + } + + if (DomData.has(this.el_)) { + DomData["delete"](this.el_); + } + + this.el_ = null; + } // remove reference to the player after disposing of the element + + + this.player_ = null; + } + /** + * Determine whether or not this component has been disposed. + * + * @return {boolean} + * If the component has been disposed, will be `true`. Otherwise, `false`. + */ + ; + + _proto.isDisposed = function isDisposed() { + return Boolean(this.isDisposed_); + } + /** + * Return the {@link Player} that the `Component` has attached to. + * + * @return {Player} + * The player that this `Component` has attached to. + */ + ; + + _proto.player = function player() { + return this.player_; + } + /** + * Deep merge of options objects with new options. + * > Note: When both `obj` and `options` contain properties whose values are objects. + * The two properties get merged using {@link module:mergeOptions} + * + * @param {Object} obj + * The object that contains new options. + * + * @return {Object} + * A new object of `this.options_` and `obj` merged together. + */ + ; + + _proto.options = function options(obj) { + if (!obj) { + return this.options_; + } + + this.options_ = mergeOptions(this.options_, obj); + return this.options_; + } + /** + * Get the `Component`s DOM element + * + * @return {Element} + * The DOM element for this `Component`. + */ + ; + + _proto.el = function el() { + return this.el_; + } + /** + * Create the `Component`s DOM element. + * + * @param {string} [tagName] + * Element's DOM node type. e.g. 'div' + * + * @param {Object} [properties] + * An object of properties that should be set. + * + * @param {Object} [attributes] + * An object of attributes that should be set. + * + * @return {Element} + * The element that gets created. + */ + ; + + _proto.createEl = function createEl$1(tagName, properties, attributes) { + return createEl(tagName, properties, attributes); + } + /** + * Localize a string given the string in english. + * + * If tokens are provided, it'll try and run a simple token replacement on the provided string. + * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. + * + * If a `defaultValue` is provided, it'll use that over `string`, + * if a value isn't found in provided language files. + * This is useful if you want to have a descriptive key for token replacement + * but have a succinct localized string and not require `en.json` to be included. + * + * Currently, it is used for the progress bar timing. + * ```js + * { + * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" + * } + * ``` + * It is then used like so: + * ```js + * this.localize('progress bar timing: currentTime={1} duration{2}', + * [this.player_.currentTime(), this.player_.duration()], + * '{1} of {2}'); + * ``` + * + * Which outputs something like: `01:23 of 24:56`. + * + * + * @param {string} string + * The string to localize and the key to lookup in the language files. + * @param {string[]} [tokens] + * If the current item has token replacements, provide the tokens here. + * @param {string} [defaultValue] + * Defaults to `string`. Can be a default value to use for token replacement + * if the lookup key is needed to be separate. + * + * @return {string} + * The localized string or if no localization exists the english string. + */ + ; + + _proto.localize = function localize(string, tokens, defaultValue) { + if (defaultValue === void 0) { + defaultValue = string; + } + + var code = this.player_.language && this.player_.language(); + var languages = this.player_.languages && this.player_.languages(); + var language = languages && languages[code]; + var primaryCode = code && code.split('-')[0]; + var primaryLang = languages && languages[primaryCode]; + var localizedString = defaultValue; + + if (language && language[string]) { + localizedString = language[string]; + } else if (primaryLang && primaryLang[string]) { + localizedString = primaryLang[string]; + } + + if (tokens) { + localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { + var value = tokens[index - 1]; + var ret = value; + + if (typeof value === 'undefined') { + ret = match; + } + + return ret; + }); + } + + return localizedString; + } + /** + * Return the `Component`s DOM element. This is where children get inserted. + * This will usually be the the same as the element returned in {@link Component#el}. + * + * @return {Element} + * The content element for this `Component`. + */ + ; + + _proto.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + } + /** + * Get this `Component`s ID + * + * @return {string} + * The id of this `Component` + */ + ; + + _proto.id = function id() { + return this.id_; + } + /** + * Get the `Component`s name. The name gets used to reference the `Component` + * and is set during registration. + * + * @return {string} + * The name of this `Component`. + */ + ; + + _proto.name = function name() { + return this.name_; + } + /** + * Get an array of all child components + * + * @return {Array} + * The children + */ + ; + + _proto.children = function children() { + return this.children_; + } + /** + * Returns the child `Component` with the given `id`. + * + * @param {string} id + * The id of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `id` or undefined. + */ + ; + + _proto.getChildById = function getChildById(id) { + return this.childIndex_[id]; + } + /** + * Returns the child `Component` with the given `name`. + * + * @param {string} name + * The name of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `name` or undefined. + */ + ; + + _proto.getChild = function getChild(name) { + if (!name) { + return; + } + + return this.childNameIndex_[name]; + } + /** + * Returns the descendant `Component` following the givent + * descendant `names`. For instance ['foo', 'bar', 'baz'] would + * try to get 'foo' on the current component, 'bar' on the 'foo' + * component and 'baz' on the 'bar' component and return undefined + * if any of those don't exist. + * + * @param {...string[]|...string} names + * The name of the child `Component` to get. + * + * @return {Component|undefined} + * The descendant `Component` following the given descendant + * `names` or undefined. + */ + ; + + _proto.getDescendant = function getDescendant() { + for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) { + names[_key] = arguments[_key]; + } + + // flatten array argument into the main array + names = names.reduce(function (acc, n) { + return acc.concat(n); + }, []); + var currentChild = this; + + for (var i = 0; i < names.length; i++) { + currentChild = currentChild.getChild(names[i]); + + if (!currentChild || !currentChild.getChild) { + return; + } + } + + return currentChild; + } + /** + * Add a child `Component` inside the current `Component`. + * + * + * @param {string|Component} child + * The name or instance of a child to add. + * + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @param {number} [index=this.children_.length] + * The index to attempt to add a child into. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + */ + ; + + _proto.addChild = function addChild(child, options, index) { + if (options === void 0) { + options = {}; + } + + if (index === void 0) { + index = this.children_.length; + } + + var component; + var componentName; // If child is a string, create component with options + + if (typeof child === 'string') { + componentName = toTitleCase(child); + var componentClassName = options.componentClass || componentName; // Set name through options + + options.name = componentName; // Create a new object & element for this controls set + // If there's no .player_, this is a player + + var ComponentClass = Component.getComponent(componentClassName); + + if (!ComponentClass) { + throw new Error("Component " + componentClassName + " does not exist"); + } // data stored directly on the videojs object may be + // misidentified as a component to retain + // backwards-compatibility with 4.x. check to make sure the + // component class can be instantiated. + + + if (typeof ComponentClass !== 'function') { + return null; + } + + component = new ComponentClass(this.player_ || this, options); // child is a component instance + } else { + component = child; + } + + if (component.parentComponent_) { + component.parentComponent_.removeChild(component); + } + + this.children_.splice(index, 0, component); + component.parentComponent_ = this; + + if (typeof component.id === 'function') { + this.childIndex_[component.id()] = component; + } // If a name wasn't used to create the component, check if we can use the + // name function of the component + + + componentName = componentName || component.name && toTitleCase(component.name()); + + if (componentName) { + this.childNameIndex_[componentName] = component; + this.childNameIndex_[toLowerCase(componentName)] = component; + } // Add the UI object's element to the container div (box) + // Having an element is not required + + + if (typeof component.el === 'function' && component.el()) { + // If inserting before a component, insert before that component's element + var refNode = null; + + if (this.children_[index + 1]) { + // Most children are components, but the video tech is an HTML element + if (this.children_[index + 1].el_) { + refNode = this.children_[index + 1].el_; + } else if (isEl(this.children_[index + 1])) { + refNode = this.children_[index + 1]; + } + } + + this.contentEl().insertBefore(component.el(), refNode); + } // Return so it can stored on parent object if desired. + + + return component; + } + /** + * Remove a child `Component` from this `Component`s list of children. Also removes + * the child `Component`s element from this `Component`s element. + * + * @param {Component} component + * The child `Component` to remove. + */ + ; + + _proto.removeChild = function removeChild(component) { + if (typeof component === 'string') { + component = this.getChild(component); + } + + if (!component || !this.children_) { + return; + } + + var childFound = false; + + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i] === component) { + childFound = true; + this.children_.splice(i, 1); + break; + } + } + + if (!childFound) { + return; + } + + component.parentComponent_ = null; + this.childIndex_[component.id()] = null; + this.childNameIndex_[toTitleCase(component.name())] = null; + this.childNameIndex_[toLowerCase(component.name())] = null; + var compEl = component.el(); + + if (compEl && compEl.parentNode === this.contentEl()) { + this.contentEl().removeChild(component.el()); + } + } + /** + * Add and initialize default child `Component`s based upon options. + */ + ; + + _proto.initChildren = function initChildren() { + var _this = this; + + var children = this.options_.children; + + if (children) { + // `this` is `parent` + var parentOptions = this.options_; + + var handleAdd = function handleAdd(child) { + var name = child.name; + var opts = child.opts; // Allow options for children to be set at the parent options + // e.g. videojs(id, { controlBar: false }); + // instead of videojs(id, { children: { controlBar: false }); + + if (parentOptions[name] !== undefined) { + opts = parentOptions[name]; + } // Allow for disabling default components + // e.g. options['children']['posterImage'] = false + + + if (opts === false) { + return; + } // Allow options to be passed as a simple boolean if no configuration + // is necessary. + + + if (opts === true) { + opts = {}; + } // We also want to pass the original player options + // to each component as well so they don't need to + // reach back into the player for options later. + + + opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. + // Add a direct reference to the child by name on the parent instance. + // If two of the same component are used, different names should be supplied + // for each + + var newChild = _this.addChild(name, opts); + + if (newChild) { + _this[name] = newChild; + } + }; // Allow for an array of children details to passed in the options + + + var workingChildren; + var Tech = Component.getComponent('Tech'); + + if (Array.isArray(children)) { + workingChildren = children; + } else { + workingChildren = Object.keys(children); + } + + workingChildren // children that are in this.options_ but also in workingChildren would + // give us extra children we do not want. So, we want to filter them out. + .concat(Object.keys(this.options_).filter(function (child) { + return !workingChildren.some(function (wchild) { + if (typeof wchild === 'string') { + return child === wchild; + } + + return child === wchild.name; + }); + })).map(function (child) { + var name; + var opts; + + if (typeof child === 'string') { + name = child; + opts = children[name] || _this.options_[name] || {}; + } else { + name = child.name; + opts = child; + } + + return { + name: name, + opts: opts + }; + }).filter(function (child) { + // we have to make sure that child.name isn't in the techOrder since + // techs are registerd as Components but can't aren't compatible + // See https://github.com/videojs/video.js/issues/2772 + var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); + return c && !Tech.isTech(c); + }).forEach(handleAdd); + } + } + /** + * Builds the default DOM class name. Should be overriden by sub-components. + * + * @return {string} + * The DOM class name for this object. + * + * @abstract + */ + ; + + _proto.buildCSSClass = function buildCSSClass() { + // Child classes can include a function that does: + // return 'CLASS NAME' + this._super(); + return ''; + } + /** + * Bind a listener to the component's ready state. + * Different from event listeners in that if the ready event has already happened + * it will trigger the function immediately. + * + * @return {Component} + * Returns itself; method can be chained. + */ + ; + + _proto.ready = function ready(fn, sync) { + if (sync === void 0) { + sync = false; + } + + if (!fn) { + return; + } + + if (!this.isReady_) { + this.readyQueue_ = this.readyQueue_ || []; + this.readyQueue_.push(fn); + return; + } + + if (sync) { + fn.call(this); + } else { + // Call the function asynchronously by default for consistency + this.setTimeout(fn, 1); + } + } + /** + * Trigger all the ready listeners for this `Component`. + * + * @fires Component#ready + */ + ; + + _proto.triggerReady = function triggerReady() { + this.isReady_ = true; // Ensure ready is triggered asynchronously + + this.setTimeout(function () { + var readyQueue = this.readyQueue_; // Reset Ready Queue + + this.readyQueue_ = []; + + if (readyQueue && readyQueue.length > 0) { + readyQueue.forEach(function (fn) { + fn.call(this); + }, this); + } // Allow for using event listeners also + + /** + * Triggered when a `Component` is ready. + * + * @event Component#ready + * @type {EventTarget~Event} + */ + + + this.trigger('ready'); + }, 1); + } + /** + * Find a single DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {Element|null} + * the dom element that was found, or null + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + ; + + _proto.$ = function $$1(selector, context) { + return $(selector, context || this.contentEl()); + } + /** + * Finds all DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {NodeList} + * a list of dom elements that were found + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + ; + + _proto.$$ = function $$$1(selector, context) { + return $$(selector, context || this.contentEl()); + } + /** + * Check if a component's element has a CSS class name. + * + * @param {string} classToCheck + * CSS class name to check. + * + * @return {boolean} + * - True if the `Component` has the class. + * - False if the `Component` does not have the class` + */ + ; + + _proto.hasClass = function hasClass$1(classToCheck) { + return hasClass(this.el_, classToCheck); + } + /** + * Add a CSS class name to the `Component`s element. + * + * @param {string} classToAdd + * CSS class name to add + */ + ; + + _proto.addClass = function addClass$1(classToAdd) { + addClass(this.el_, classToAdd); + } + /** + * Remove a CSS class name from the `Component`s element. + * + * @param {string} classToRemove + * CSS class name to remove + */ + ; + + _proto.removeClass = function removeClass$1(classToRemove) { + removeClass(this.el_, classToRemove); + } + /** + * Add or remove a CSS class name from the component's element. + * - `classToToggle` gets added when {@link Component#hasClass} would return false. + * - `classToToggle` gets removed when {@link Component#hasClass} would return true. + * + * @param {string} classToToggle + * The class to add or remove based on (@link Component#hasClass} + * + * @param {boolean|Dom~predicate} [predicate] + * An {@link Dom~predicate} function or a boolean + */ + ; + + _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { + toggleClass(this.el_, classToToggle, predicate); + } + /** + * Show the `Component`s element if it is hidden by removing the + * 'vjs-hidden' class name from it. + */ + ; + + _proto.show = function show() { + this.removeClass('vjs-hidden'); + } + /** + * Hide the `Component`s element if it is currently showing by adding the + * 'vjs-hidden` class name to it. + */ + ; + + _proto.hide = function hide() { + this.addClass('vjs-hidden'); + } + /** + * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' + * class name to it. Used during fadeIn/fadeOut. + * + * @private + */ + ; + + _proto.lockShowing = function lockShowing() { + this.addClass('vjs-lock-showing'); + } + /** + * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' + * class name from it. Used during fadeIn/fadeOut. + * + * @private + */ + ; + + _proto.unlockShowing = function unlockShowing() { + this.removeClass('vjs-lock-showing'); + } + /** + * Get the value of an attribute on the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to get the value from. + * + * @return {string|null} + * - The value of the attribute that was asked for. + * - Can be an empty string on some browsers if the attribute does not exist + * or has no value + * - Most browsers will return null if the attibute does not exist or has + * no value. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} + */ + ; + + _proto.getAttribute = function getAttribute$1(attribute) { + return getAttribute(this.el_, attribute); + } + /** + * Set the value of an attribute on the `Component`'s element + * + * @param {string} attribute + * Name of the attribute to set. + * + * @param {string} value + * Value to set the attribute to. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} + */ + ; + + _proto.setAttribute = function setAttribute$1(attribute, value) { + setAttribute(this.el_, attribute, value); + } + /** + * Remove an attribute from the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to remove. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} + */ + ; + + _proto.removeAttribute = function removeAttribute$1(attribute) { + removeAttribute(this.el_, attribute); + } + /** + * Get or set the width of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The width that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + ; + + _proto.width = function width(num, skipListeners) { + return this.dimension('width', num, skipListeners); + } + /** + * Get or set the height of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The height that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + ; + + _proto.height = function height(num, skipListeners) { + return this.dimension('height', num, skipListeners); + } + /** + * Set both the width and height of the `Component` element at the same time. + * + * @param {number|string} width + * Width to set the `Component`s element to. + * + * @param {number|string} height + * Height to set the `Component`s element to. + */ + ; + + _proto.dimensions = function dimensions(width, height) { + // Skip componentresize listeners on width for optimization + this.width(width, true); + this.height(height); + } + /** + * Get or set width or height of the `Component` element. This is the shared code + * for the {@link Component#width} and {@link Component#height}. + * + * Things to know: + * - If the width or height in an number this will return the number postfixed with 'px'. + * - If the width/height is a percent this will return the percent postfixed with '%' + * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function + * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. + * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} + * for more information + * - If you want the computed style of the component, use {@link Component#currentWidth} + * and {@link {Component#currentHeight} + * + * @fires Component#componentresize + * + * @param {string} widthOrHeight + 8 'width' or 'height' + * + * @param {number|string} [num] + 8 New dimension + * + * @param {boolean} [skipListeners] + * Skip componentresize event trigger + * + * @return {number} + * The dimension when getting or 0 if unset + */ + ; + + _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { + if (num !== undefined) { + // Set to zero if null or literally NaN (NaN !== NaN) + if (num === null || num !== num) { + num = 0; + } // Check if using css width/height (% or px) and adjust + + + if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { + this.el_.style[widthOrHeight] = num; + } else if (num === 'auto') { + this.el_.style[widthOrHeight] = ''; + } else { + this.el_.style[widthOrHeight] = num + 'px'; + } // skipListeners allows us to avoid triggering the resize event when setting both width and height + + + if (!skipListeners) { + /** + * Triggered when a component is resized. + * + * @event Component#componentresize + * @type {EventTarget~Event} + */ + this.trigger('componentresize'); + } + + return; + } // Not setting a value, so getting it + // Make sure element exists + + + if (!this.el_) { + return 0; + } // Get dimension value from style + + + var val = this.el_.style[widthOrHeight]; + var pxIndex = val.indexOf('px'); + + if (pxIndex !== -1) { + // Return the pixel value with no 'px' + return parseInt(val.slice(0, pxIndex), 10); + } // No px so using % or no style was set, so falling back to offsetWidth/height + // If component has display:none, offset will return 0 + // TODO: handle display:none and no dimension style using px + + + return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); + } + /** + * Get the computed width or the height of the component's element. + * + * Uses `window.getComputedStyle`. + * + * @param {string} widthOrHeight + * A string containing 'width' or 'height'. Whichever one you want to get. + * + * @return {number} + * The dimension that gets asked for or 0 if nothing was set + * for that dimension. + */ + ; + + _proto.currentDimension = function currentDimension(widthOrHeight) { + var computedWidthOrHeight = 0; + + if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { + throw new Error('currentDimension only accepts width or height value'); + } + + computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer + + computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying + // and we want to check the offset values. + // This code also runs wherever getComputedStyle doesn't exist. + + if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { + var rule = "offset" + toTitleCase(widthOrHeight); + computedWidthOrHeight = this.el_[rule]; + } + + return computedWidthOrHeight; + } + /** + * An object that contains width and height values of the `Component`s + * computed style. Uses `window.getComputedStyle`. + * + * @typedef {Object} Component~DimensionObject + * + * @property {number} width + * The width of the `Component`s computed style. + * + * @property {number} height + * The height of the `Component`s computed style. + */ + + /** + * Get an object that contains computed width and height values of the + * component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {Component~DimensionObject} + * The computed dimensions of the component's element. + */ + ; + + _proto.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; + } + /** + * Get the computed width of the component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {number} + * The computed width of the component's element. + */ + ; + + _proto.currentWidth = function currentWidth() { + return this.currentDimension('width'); + } + /** + * Get the computed height of the component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {number} + * The computed height of the component's element. + */ + ; + + _proto.currentHeight = function currentHeight() { + return this.currentDimension('height'); + } + /** + * Set the focus to this component + */ + ; + + _proto.focus = function focus() { + this.el_.focus(); + } + /** + * Remove the focus from this component + */ + ; + + _proto.blur = function blur() { + this.el_.blur(); + } + /** + * When this Component receives a `keydown` event which it does not process, + * it passes the event to the Player for handling. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + */ + ; + + _proto.handleKeyDown = function handleKeyDown(event) { + if (this.player_) { + // We only stop propagation here because we want unhandled events to fall + // back to the browser. + event.stopPropagation(); + this.player_.handleKeyDown(event); + } + } + /** + * Many components used to have a `handleKeyPress` method, which was poorly + * named because it listened to a `keydown` event. This method name now + * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` + * will not see their method calls stop working. + * + * @param {EventTarget~Event} event + * The event that caused this function to be called. + */ + ; + + _proto.handleKeyPress = function handleKeyPress(event) { + this.handleKeyDown(event); + } + /** + * Emit a 'tap' events when touch event support gets detected. This gets used to + * support toggling the controls through a tap on the video. They get enabled + * because every sub-component would have extra overhead otherwise. + * + * @private + * @fires Component#tap + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchleave + * @listens Component#touchcancel + * @listens Component#touchend + */ + ; + + _proto.emitTapEvents = function emitTapEvents() { + // Track the start time so we can determine how long the touch lasted + var touchStart = 0; + var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap + // Other popular libs use anywhere from 2 (hammer.js) to 15, + // so 10 seems like a nice, round number. + + var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap + + var touchTimeThreshold = 200; + var couldBeTap; + this.on('touchstart', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length === 1) { + // Copy pageX/pageY from the object + firstTouch = { + pageX: event.touches[0].pageX, + pageY: event.touches[0].pageY + }; // Record start time so we can detect a tap vs. "touch and hold" + + touchStart = window_1$1.performance.now(); // Reset couldBeTap tracking + + couldBeTap = true; + } + }); + this.on('touchmove', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length > 1) { + couldBeTap = false; + } else if (firstTouch) { + // Some devices will throw touchmoves for all but the slightest of taps. + // So, if we moved only a small distance, this could still be a tap + var xdiff = event.touches[0].pageX - firstTouch.pageX; + var ydiff = event.touches[0].pageY - firstTouch.pageY; + var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + + if (touchDistance > tapMovementThreshold) { + couldBeTap = false; + } + } + }); + + var noTap = function noTap() { + couldBeTap = false; + }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s + + + this.on('touchleave', noTap); + this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate + // event + + this.on('touchend', function (event) { + firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen + + if (couldBeTap === true) { + // Measure how long the touch lasted + var touchTime = window_1$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap + + if (touchTime < touchTimeThreshold) { + // Don't let browser turn this into a click + event.preventDefault(); + /** + * Triggered when a `Component` is tapped. + * + * @event Component#tap + * @type {EventTarget~Event} + */ + + this.trigger('tap'); // It may be good to copy the touchend event object and change the + // type to tap, if the other event properties aren't exact after + // Events.fixEvent runs (e.g. event.target) + } + } + }); + } + /** + * This function reports user activity whenever touch events happen. This can get + * turned off by any sub-components that wants touch events to act another way. + * + * Report user touch activity when touch events occur. User activity gets used to + * determine when controls should show/hide. It is simple when it comes to mouse + * events, because any mouse event should show the controls. So we capture mouse + * events that bubble up to the player and report activity when that happens. + * With touch events it isn't as easy as `touchstart` and `touchend` toggle player + * controls. So touch events can't help us at the player level either. + * + * User activity gets checked asynchronously. So what could happen is a tap event + * on the video turns the controls off. Then the `touchend` event bubbles up to + * the player. Which, if it reported user activity, would turn the controls right + * back on. We also don't want to completely block touch events from bubbling up. + * Furthermore a `touchmove` event and anything other than a tap, should not turn + * controls back on. + * + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchend + * @listens Component#touchcancel + */ + ; + + _proto.enableTouchActivity = function enableTouchActivity() { + // Don't continue if the root player doesn't support reporting user activity + if (!this.player() || !this.player().reportUserActivity) { + return; + } // listener for reporting that the user is active + + + var report = bind(this.player(), this.player().reportUserActivity); + var touchHolding; + this.on('touchstart', function () { + report(); // For as long as the they are touching the device or have their mouse down, + // we consider them active even if they're not moving their finger or mouse. + // So we want to continue to update that they are active + + this.clearInterval(touchHolding); // report at the same interval as activityCheck + + touchHolding = this.setInterval(report, 250); + }); + + var touchEnd = function touchEnd(event) { + report(); // stop the interval that maintains activity if the touch is holding + + this.clearInterval(touchHolding); + }; + + this.on('touchmove', report); + this.on('touchend', touchEnd); + this.on('touchcancel', touchEnd); + } + /** + * A callback that has no parameters and is bound into `Component`s context. + * + * @callback Component~GenericCallback + * @this Component + */ + + /** + * Creates a function that runs after an `x` millisecond timeout. This function is a + * wrapper around `window.setTimeout`. There are a few reasons to use this one + * instead though: + * 1. It gets cleared via {@link Component#clearTimeout} when + * {@link Component#dispose} gets called. + * 2. The function callback will gets turned into a {@link Component~GenericCallback} + * + * > Note: You can't use `window.clearTimeout` on the id returned by this function. This + * will cause its dispose listener not to get cleaned up! Please use + * {@link Component#clearTimeout} or {@link Component#dispose} instead. + * + * @param {Component~GenericCallback} fn + * The function that will be run after `timeout`. + * + * @param {number} timeout + * Timeout in milliseconds to delay before executing the specified function. + * + * @return {number} + * Returns a timeout ID that gets used to identify the timeout. It can also + * get used in {@link Component#clearTimeout} to clear the timeout that + * was set. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} + */ + ; + + _proto.setTimeout = function setTimeout(fn, timeout) { + var _this2 = this; + + // declare as variables so they are properly available in timeout function + // eslint-disable-next-line + var timeoutId; + fn = bind(this, fn); + this.clearTimersOnDispose_(); + timeoutId = window_1$1.setTimeout(function () { + if (_this2.setTimeoutIds_.has(timeoutId)) { + _this2.setTimeoutIds_["delete"](timeoutId); + } + + fn(); + }, timeout); + this.setTimeoutIds_.add(timeoutId); + return timeoutId; + } + /** + * Clears a timeout that gets created via `window.setTimeout` or + * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} + * use this function instead of `window.clearTimout`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} timeoutId + * The id of the timeout to clear. The return value of + * {@link Component#setTimeout} or `window.setTimeout`. + * + * @return {number} + * Returns the timeout id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} + */ + ; + + _proto.clearTimeout = function clearTimeout(timeoutId) { + if (this.setTimeoutIds_.has(timeoutId)) { + this.setTimeoutIds_["delete"](timeoutId); + window_1$1.clearTimeout(timeoutId); + } + + return timeoutId; + } + /** + * Creates a function that gets run every `x` milliseconds. This function is a wrapper + * around `window.setInterval`. There are a few reasons to use this one instead though. + * 1. It gets cleared via {@link Component#clearInterval} when + * {@link Component#dispose} gets called. + * 2. The function callback will be a {@link Component~GenericCallback} + * + * @param {Component~GenericCallback} fn + * The function to run every `x` seconds. + * + * @param {number} interval + * Execute the specified function every `x` milliseconds. + * + * @return {number} + * Returns an id that can be used to identify the interval. It can also be be used in + * {@link Component#clearInterval} to clear the interval. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} + */ + ; + + _proto.setInterval = function setInterval(fn, interval) { + fn = bind(this, fn); + this.clearTimersOnDispose_(); + var intervalId = window_1$1.setInterval(fn, interval); + this.setIntervalIds_.add(intervalId); + return intervalId; + } + /** + * Clears an interval that gets created via `window.setInterval` or + * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} + * use this function instead of `window.clearInterval`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} intervalId + * The id of the interval to clear. The return value of + * {@link Component#setInterval} or `window.setInterval`. + * + * @return {number} + * Returns the interval id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} + */ + ; + + _proto.clearInterval = function clearInterval(intervalId) { + if (this.setIntervalIds_.has(intervalId)) { + this.setIntervalIds_["delete"](intervalId); + window_1$1.clearInterval(intervalId); + } + + return intervalId; + } + /** + * Queues up a callback to be passed to requestAnimationFrame (rAF), but + * with a few extra bonuses: + * + * - Supports browsers that do not support rAF by falling back to + * {@link Component#setTimeout}. + * + * - The callback is turned into a {@link Component~GenericCallback} (i.e. + * bound to the component). + * + * - Automatic cancellation of the rAF callback is handled if the component + * is disposed before it is called. + * + * @param {Component~GenericCallback} fn + * A function that will be bound to this component and executed just + * before the browser's next repaint. + * + * @return {number} + * Returns an rAF ID that gets used to identify the timeout. It can + * also be used in {@link Component#cancelAnimationFrame} to cancel + * the animation frame callback. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} + */ + ; + + _proto.requestAnimationFrame = function requestAnimationFrame(fn) { + var _this3 = this; + + // Fall back to using a timer. + if (!this.supportsRaf_) { + return this.setTimeout(fn, 1000 / 60); + } + + this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function + // eslint-disable-next-line + + var id; + fn = bind(this, fn); + id = window_1$1.requestAnimationFrame(function () { + if (_this3.rafIds_.has(id)) { + _this3.rafIds_["delete"](id); + } + + fn(); + }); + this.rafIds_.add(id); + return id; + } + /** + * Request an animation frame, but only one named animation + * frame will be queued. Another will never be added until + * the previous one finishes. + * + * @param {string} name + * The name to give this requestAnimationFrame + * + * @param {Component~GenericCallback} fn + * A function that will be bound to this component and executed just + * before the browser's next repaint. + */ + ; + + _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) { + var _this4 = this; + + if (this.namedRafs_.has(name)) { + return; + } + + this.clearTimersOnDispose_(); + fn = bind(this, fn); + var id = this.requestAnimationFrame(function () { + fn(); + + if (_this4.namedRafs_.has(name)) { + _this4.namedRafs_["delete"](name); + } + }); + this.namedRafs_.set(name, id); + return name; + } + /** + * Cancels a current named animation frame if it exists. + * + * @param {string} name + * The name of the requestAnimationFrame to cancel. + */ + ; + + _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) { + if (!this.namedRafs_.has(name)) { + return; + } + + this.cancelAnimationFrame(this.namedRafs_.get(name)); + this.namedRafs_["delete"](name); + } + /** + * Cancels a queued callback passed to {@link Component#requestAnimationFrame} + * (rAF). + * + * If you queue an rAF callback via {@link Component#requestAnimationFrame}, + * use this function instead of `window.cancelAnimationFrame`. If you don't, + * your dispose listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} id + * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. + * + * @return {number} + * Returns the rAF ID that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} + */ + ; + + _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { + // Fall back to using a timer. + if (!this.supportsRaf_) { + return this.clearTimeout(id); + } + + if (this.rafIds_.has(id)) { + this.rafIds_["delete"](id); + window_1$1.cancelAnimationFrame(id); + } + + return id; + } + /** + * A function to setup `requestAnimationFrame`, `setTimeout`, + * and `setInterval`, clearing on dispose. + * + * > Previously each timer added and removed dispose listeners on it's own. + * For better performance it was decided to batch them all, and use `Set`s + * to track outstanding timer ids. + * + * @private + */ + ; + + _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { + var _this5 = this; + + if (this.clearingTimersOnDispose_) { + return; + } + + this.clearingTimersOnDispose_ = true; + this.one('dispose', function () { + [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(function (_ref) { + var idName = _ref[0], + cancelName = _ref[1]; + + // for a `Set` key will actually be the value again + // so forEach((val, val) =>` but for maps we want to use + // the key. + _this5[idName].forEach(function (val, key) { + return _this5[cancelName](key); + }); + }); + _this5.clearingTimersOnDispose_ = false; + }); + } + /** + * Register a `Component` with `videojs` given the name and the component. + * + * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s + * should be registered using {@link Tech.registerTech} or + * {@link videojs:videojs.registerTech}. + * + * > NOTE: This function can also be seen on videojs as + * {@link videojs:videojs.registerComponent}. + * + * @param {string} name + * The name of the `Component` to register. + * + * @param {Component} ComponentToRegister + * The `Component` class to register. + * + * @return {Component} + * The `Component` that was registered. + */ + ; + + Component.registerComponent = function registerComponent(name, ComponentToRegister) { + if (typeof name !== 'string' || !name) { + throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); + } + + var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. + + var isTech = Tech && Tech.isTech(ComponentToRegister); + var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); + + if (isTech || !isComp) { + var reason; + + if (isTech) { + reason = 'techs must be registered using Tech.registerTech()'; + } else { + reason = 'must be a Component subclass'; + } + + throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); + } + + name = toTitleCase(name); + + if (!Component.components_) { + Component.components_ = {}; + } + + var Player = Component.getComponent('Player'); + + if (name === 'Player' && Player && Player.players) { + var players = Player.players; + var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be + // in Players.players. So, we must loop through and verify that the value + // for each item is not null. This allows registration of the Player component + // after all players have been disposed or before any were created. + + if (players && playerNames.length > 0 && playerNames.map(function (pname) { + return players[pname]; + }).every(Boolean)) { + throw new Error('Can not register Player component after player has been created.'); + } + } + + Component.components_[name] = ComponentToRegister; + Component.components_[toLowerCase(name)] = ComponentToRegister; + return ComponentToRegister; + } + /** + * Get a `Component` based on the name it was registered with. + * + * @param {string} name + * The Name of the component to get. + * + * @return {Component} + * The `Component` that got registered under the given name. + * + * @deprecated In `videojs` 6 this will not return `Component`s that were not + * registered using {@link Component.registerComponent}. Currently we + * check the global `videojs` object for a `Component` name and + * return that if it exists. + */ + ; + + Component.getComponent = function getComponent(name) { + if (!name || !Component.components_) { + return; + } + + return Component.components_[name]; + }; + + return Component; +}(); +/** + * Whether or not this component supports `requestAnimationFrame`. + * + * This is exposed primarily for testing purposes. + * + * @private + * @type {Boolean} + */ + + +Component.prototype.supportsRaf_ = typeof window_1$1.requestAnimationFrame === 'function' && typeof window_1$1.cancelAnimationFrame === 'function'; +Component.registerComponent('Component', Component); + +/** + * @file browser.js + * @module browser + */ +var USER_AGENT = window_1$1.navigator && window_1$1.navigator.userAgent || ''; +var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); +var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; +/** + * Whether or not this device is an iPod. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_IPOD = /iPod/i.test(USER_AGENT); +/** + * The detected iOS version - or `null`. + * + * @static + * @const + * @type {string|null} + */ + +var IOS_VERSION = function () { + var match = USER_AGENT.match(/OS (\d+)_/i); + + if (match && match[1]) { + return match[1]; + } + + return null; +}(); +/** + * Whether or not this is an Android device. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_ANDROID = /Android/i.test(USER_AGENT); +/** + * The detected Android version - or `null`. + * + * @static + * @const + * @type {number|string|null} + */ + +var ANDROID_VERSION = function () { + // This matches Android Major.Minor.Patch versions + // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned + var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); + + if (!match) { + return null; + } + + var major = match[1] && parseFloat(match[1]); + var minor = match[2] && parseFloat(match[2]); + + if (major && minor) { + return parseFloat(match[1] + '.' + match[2]); + } else if (major) { + return major; + } + + return null; +}(); +/** + * Whether or not this is a native Android browser. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; +/** + * Whether or not this is Mozilla Firefox. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_FIREFOX = /Firefox/i.test(USER_AGENT); +/** + * Whether or not this is Microsoft Edge. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_EDGE = /Edg/i.test(USER_AGENT); +/** + * Whether or not this is Google Chrome. + * + * This will also be `true` for Chrome on iOS, which will have different support + * as it is actually Safari under the hood. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); +/** + * The detected Google Chrome version - or `null`. + * + * @static + * @const + * @type {number|null} + */ + +var CHROME_VERSION = function () { + var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); + + if (match && match[2]) { + return parseFloat(match[2]); + } + + return null; +}(); +/** + * The detected Internet Explorer version - or `null`. + * + * @static + * @const + * @type {number|null} + */ + +var IE_VERSION = function () { + var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); + var version = result && parseFloat(result[1]); + + if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { + // IE 11 has a different user agent string than other IE versions + version = 11.0; + } + + return version; +}(); +/** + * Whether or not this is desktop Safari. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; +/** + * Whether or not this is a Windows machine. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_WINDOWS = /Windows/i.test(USER_AGENT); +/** + * Whether or not this device is touch-enabled. + * + * @static + * @const + * @type {Boolean} + */ + +var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1$1 || window_1$1.navigator.maxTouchPoints || window_1$1.DocumentTouch && window_1$1.document instanceof window_1$1.DocumentTouch); +/** + * Whether or not this device is an iPad. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); +/** + * Whether or not this device is an iPhone. + * + * @static + * @const + * @type {Boolean} + */ +// The Facebook app's UIWebView identifies as both an iPhone and iPad, so +// to identify iPhones, we need to exclude iPads. +// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ + +var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; +/** + * Whether or not this is an iOS device. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; +/** + * Whether or not this is any flavor of Safari - including iOS. + * + * @static + * @const + * @type {Boolean} + */ + +var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; + +var browser = /*#__PURE__*/Object.freeze({ + __proto__: null, + IS_IPOD: IS_IPOD, + IOS_VERSION: IOS_VERSION, + IS_ANDROID: IS_ANDROID, + ANDROID_VERSION: ANDROID_VERSION, + IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, + IS_FIREFOX: IS_FIREFOX, + IS_EDGE: IS_EDGE, + IS_CHROME: IS_CHROME, + CHROME_VERSION: CHROME_VERSION, + IE_VERSION: IE_VERSION, + IS_SAFARI: IS_SAFARI, + IS_WINDOWS: IS_WINDOWS, + TOUCH_ENABLED: TOUCH_ENABLED, + IS_IPAD: IS_IPAD, + IS_IPHONE: IS_IPHONE, + IS_IOS: IS_IOS, + IS_ANY_SAFARI: IS_ANY_SAFARI +}); + +/** + * @file time-ranges.js + * @module time-ranges + */ + +/** + * Returns the time for the specified index at the start or end + * of a TimeRange object. + * + * @typedef {Function} TimeRangeIndex + * + * @param {number} [index=0] + * The range number to return the time for. + * + * @return {number} + * The time offset at the specified index. + * + * @deprecated The index argument must be provided. + * In the future, leaving it out will throw an error. + */ + +/** + * An object that contains ranges of time. + * + * @typedef {Object} TimeRange + * + * @property {number} length + * The number of time ranges represented by this object. + * + * @property {module:time-ranges~TimeRangeIndex} start + * Returns the time offset at which a specified time range begins. + * + * @property {module:time-ranges~TimeRangeIndex} end + * Returns the time offset at which a specified time range ends. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges + */ + +/** + * Check if any of the time ranges are over the maximum index. + * + * @private + * @param {string} fnName + * The function name to use for logging + * + * @param {number} index + * The index to check + * + * @param {number} maxIndex + * The maximum possible index + * + * @throws {Error} if the timeRanges provided are over the maxIndex + */ +function rangeCheck(fnName, index, maxIndex) { + if (typeof index !== 'number' || index < 0 || index > maxIndex) { + throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); + } +} +/** + * Get the time for the specified index at the start or end + * of a TimeRange object. + * + * @private + * @param {string} fnName + * The function name to use for logging + * + * @param {string} valueIndex + * The property that should be used to get the time. should be + * 'start' or 'end' + * + * @param {Array} ranges + * An array of time ranges + * + * @param {Array} [rangeIndex=0] + * The index to start the search at + * + * @return {number} + * The time that offset at the specified index. + * + * @deprecated rangeIndex must be set to a value, in the future this will throw an error. + * @throws {Error} if rangeIndex is more than the length of ranges + */ + + +function getRange(fnName, valueIndex, ranges, rangeIndex) { + rangeCheck(fnName, rangeIndex, ranges.length - 1); + return ranges[rangeIndex][valueIndex]; +} +/** + * Create a time range object given ranges of time. + * + * @private + * @param {Array} [ranges] + * An array of time ranges. + */ + + +function createTimeRangesObj(ranges) { + if (ranges === undefined || ranges.length === 0) { + return { + length: 0, + start: function start() { + throw new Error('This TimeRanges object is empty'); + }, + end: function end() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + + return { + length: ranges.length, + start: getRange.bind(null, 'start', 0, ranges), + end: getRange.bind(null, 'end', 1, ranges) + }; +} +/** + * Create a `TimeRange` object which mimics an + * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. + * + * @param {number|Array[]} start + * The start of a single range (a number) or an array of ranges (an + * array of arrays of two numbers each). + * + * @param {number} end + * The end of a single range. Cannot be used with the array form of + * the `start` argument. + */ + + +function createTimeRanges(start, end) { + if (Array.isArray(start)) { + return createTimeRangesObj(start); + } else if (start === undefined || end === undefined) { + return createTimeRangesObj(); + } + + return createTimeRangesObj([[start, end]]); +} + +/** + * @file buffer.js + * @module buffer + */ +/** + * Compute the percentage of the media that has been buffered. + * + * @param {TimeRange} buffered + * The current `TimeRange` object representing buffered time ranges + * + * @param {number} duration + * Total duration of the media + * + * @return {number} + * Percent buffered of the total duration in decimal form. + */ + +function bufferedPercent(buffered, duration) { + var bufferedDuration = 0; + var start; + var end; + + if (!duration) { + return 0; + } + + if (!buffered || !buffered.length) { + buffered = createTimeRanges(0, 0); + } + + for (var i = 0; i < buffered.length; i++) { + start = buffered.start(i); + end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction + + if (end > duration) { + end = duration; + } + + bufferedDuration += end - start; + } + + return bufferedDuration / duration; +} + +/** + * @file fullscreen-api.js + * @module fullscreen-api + * @private + */ +/** + * Store the browser-specific methods for the fullscreen API. + * + * @type {Object} + * @see [Specification]{@link https://fullscreen.spec.whatwg.org} + * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} + */ + +var FullscreenApi = { + prefixed: true +}; // browser API methods + +var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit +['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla +['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft +['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; +var specApi = apiMap[0]; +var browserApi; // determine the supported set of functions + +for (var i = 0; i < apiMap.length; i++) { + // check for exitFullscreen function + if (apiMap[i][1] in document_1) { + browserApi = apiMap[i]; + break; + } +} // map the browser API names to the spec API names + + +if (browserApi) { + for (var _i = 0; _i < browserApi.length; _i++) { + FullscreenApi[specApi[_i]] = browserApi[_i]; + } + + FullscreenApi.prefixed = browserApi[0] !== specApi[0]; +} + +/** + * @file media-error.js + */ +/** + * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. + * + * @param {number|string|Object|MediaError} value + * This can be of multiple types: + * - number: should be a standard error code + * - string: an error message (the code will be 0) + * - Object: arbitrary properties + * - `MediaError` (native): used to populate a video.js `MediaError` object + * - `MediaError` (video.js): will return itself if it's already a + * video.js `MediaError` object. + * + * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} + * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} + * + * @class MediaError + */ + +function MediaError(value) { + // Allow redundant calls to this constructor to avoid having `instanceof` + // checks peppered around the code. + if (value instanceof MediaError) { + return value; + } + + if (typeof value === 'number') { + this.code = value; + } else if (typeof value === 'string') { + // default code is zero, so this is a custom error + this.message = value; + } else if (isObject$1(value)) { + // We assign the `code` property manually because native `MediaError` objects + // do not expose it as an own/enumerable property of the object. + if (typeof value.code === 'number') { + this.code = value.code; + } + + assign(this, value); + } + + if (!this.message) { + this.message = MediaError.defaultMessages[this.code] || ''; + } +} +/** + * The error code that refers two one of the defined `MediaError` types + * + * @type {Number} + */ + + +MediaError.prototype.code = 0; +/** + * An optional message that to show with the error. Message is not part of the HTML5 + * video spec but allows for more informative custom errors. + * + * @type {String} + */ + +MediaError.prototype.message = ''; +/** + * An optional status code that can be set by plugins to allow even more detail about + * the error. For example a plugin might provide a specific HTTP status code and an + * error message for that code. Then when the plugin gets that error this class will + * know how to display an error message for it. This allows a custom message to show + * up on the `Player` error overlay. + * + * @type {Array} + */ + +MediaError.prototype.status = null; +/** + * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the + * specification listed under {@link MediaError} for more information. + * + * @enum {array} + * @readonly + * @property {string} 0 - MEDIA_ERR_CUSTOM + * @property {string} 1 - MEDIA_ERR_ABORTED + * @property {string} 2 - MEDIA_ERR_NETWORK + * @property {string} 3 - MEDIA_ERR_DECODE + * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED + * @property {string} 5 - MEDIA_ERR_ENCRYPTED + */ + +MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; +/** + * The default `MediaError` messages based on the {@link MediaError.errorTypes}. + * + * @type {Array} + * @constant + */ + +MediaError.defaultMessages = { + 1: 'You aborted the media playback', + 2: 'A network error caused the media download to fail part-way.', + 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', + 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', + 5: 'The media is encrypted and we do not have the keys to decrypt it.' +}; // Add types as properties on MediaError +// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; + +for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { + MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance + + MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; +} // jsdocs for instance/static members added above + +/** + * Returns whether an object is `Promise`-like (i.e. has a `then` method). + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + * + * @return {boolean} + * Whether or not the object is `Promise`-like. + */ +function isPromise(value) { + return value !== undefined && value !== null && typeof value.then === 'function'; +} +/** + * Silence a Promise-like object. + * + * This is useful for avoiding non-harmful, but potentially confusing "uncaught + * play promise" rejection error messages. + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + */ + +function silencePromise(value) { + if (isPromise(value)) { + value.then(null, function (e) {}); + } +} + +/** + * @file text-track-list-converter.js Utilities for capturing text track state and + * re-creating tracks based on a capture. + * + * @module text-track-list-converter + */ + +/** + * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that + * represents the {@link TextTrack}'s state. + * + * @param {TextTrack} track + * The text track to query. + * + * @return {Object} + * A serializable javascript representation of the TextTrack. + * @private + */ +var trackToJson_ = function trackToJson_(track) { + var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { + if (track[prop]) { + acc[prop] = track[prop]; + } + + return acc; + }, { + cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { + return { + startTime: cue.startTime, + endTime: cue.endTime, + text: cue.text, + id: cue.id + }; + }) + }); + return ret; +}; +/** + * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the + * state of all {@link TextTrack}s currently configured. The return array is compatible with + * {@link text-track-list-converter:jsonToTextTracks}. + * + * @param {Tech} tech + * The tech object to query + * + * @return {Array} + * A serializable javascript representation of the {@link Tech}s + * {@link TextTrackList}. + */ + + +var textTracksToJson = function textTracksToJson(tech) { + var trackEls = tech.$$('track'); + var trackObjs = Array.prototype.map.call(trackEls, function (t) { + return t.track; + }); + var tracks = Array.prototype.map.call(trackEls, function (trackEl) { + var json = trackToJson_(trackEl.track); + + if (trackEl.src) { + json.src = trackEl.src; + } + + return json; + }); + return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { + return trackObjs.indexOf(track) === -1; + }).map(trackToJson_)); +}; +/** + * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript + * object {@link TextTrack} representations. + * + * @param {Array} json + * An array of `TextTrack` representation objects, like those that would be + * produced by `textTracksToJson`. + * + * @param {Tech} tech + * The `Tech` to create the `TextTrack`s on. + */ + + +var jsonToTextTracks = function jsonToTextTracks(json, tech) { + json.forEach(function (track) { + var addedTrack = tech.addRemoteTextTrack(track).track; + + if (!track.src && track.cues) { + track.cues.forEach(function (cue) { + return addedTrack.addCue(cue); + }); + } + }); + return tech.textTracks(); +}; + +var textTrackConverter = { + textTracksToJson: textTracksToJson, + jsonToTextTracks: jsonToTextTracks, + trackToJson_: trackToJson_ +}; + +var MODAL_CLASS_NAME = 'vjs-modal-dialog'; +/** + * The `ModalDialog` displays over the video and its controls, which blocks + * interaction with the player until it is closed. + * + * Modal dialogs include a "Close" button and will close when that button + * is activated - or when ESC is pressed anywhere. + * + * @extends Component + */ + +var ModalDialog = /*#__PURE__*/function (_Component) { + inheritsLoose(ModalDialog, _Component); + + /** + * Create an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Mixed} [options.content=undefined] + * Provide customized content for this modal. + * + * @param {string} [options.description] + * A text description for the modal, primarily for accessibility. + * + * @param {boolean} [options.fillAlways=false] + * Normally, modals are automatically filled only the first time + * they open. This tells the modal to refresh its content + * every time it opens. + * + * @param {string} [options.label] + * A text label for the modal, primarily for accessibility. + * + * @param {boolean} [options.pauseOnOpen=true] + * If `true`, playback will will be paused if playing when + * the modal opens, and resumed when it closes. + * + * @param {boolean} [options.temporary=true] + * If `true`, the modal can only be opened once; it will be + * disposed as soon as it's closed. + * + * @param {boolean} [options.uncloseable=false] + * If `true`, the user will not be able to close the modal + * through the UI in the normal ways. Programmatic closing is + * still possible. + */ + function ModalDialog(player, options) { + var _this; + + _this = _Component.call(this, player, options) || this; + _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; + + _this.closeable(!_this.options_.uncloseable); + + _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized + // because we only want the contents of the modal in the contentEl + // (not the UI elements like the close button). + + + _this.contentEl_ = createEl('div', { + className: MODAL_CLASS_NAME + "-content" + }, { + role: 'document' + }); + _this.descEl_ = createEl('p', { + className: MODAL_CLASS_NAME + "-description vjs-control-text", + id: _this.el().getAttribute('aria-describedby') + }); + textContent(_this.descEl_, _this.description()); + + _this.el_.appendChild(_this.descEl_); + + _this.el_.appendChild(_this.contentEl_); + + return _this; + } + /** + * Create the `ModalDialog`'s DOM element + * + * @return {Element} + * The DOM element that gets created. + */ + + + var _proto = ModalDialog.prototype; + + _proto.createEl = function createEl() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass(), + tabIndex: -1 + }, { + 'aria-describedby': this.id() + "_description", + 'aria-hidden': 'true', + 'aria-label': this.label(), + 'role': 'dialog' + }); + }; + + _proto.dispose = function dispose() { + this.contentEl_ = null; + this.descEl_ = null; + this.previouslyActiveEl_ = null; + + _Component.prototype.dispose.call(this); + } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + ; + + _proto.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); + } + /** + * Returns the label string for this modal. Primarily used for accessibility. + * + * @return {string} + * the localized or raw label of this modal. + */ + ; + + _proto.label = function label() { + return this.localize(this.options_.label || 'Modal Window'); + } + /** + * Returns the description string for this modal. Primarily used for + * accessibility. + * + * @return {string} + * The localized or raw description of this modal. + */ + ; + + _proto.description = function description() { + var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. + + if (this.closeable()) { + desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); + } + + return desc; + } + /** + * Opens the modal. + * + * @fires ModalDialog#beforemodalopen + * @fires ModalDialog#modalopen + */ + ; + + _proto.open = function open() { + if (!this.opened_) { + var player = this.player(); + /** + * Fired just before a `ModalDialog` is opened. + * + * @event ModalDialog#beforemodalopen + * @type {EventTarget~Event} + */ + + this.trigger('beforemodalopen'); + this.opened_ = true; // Fill content if the modal has never opened before and + // never been filled. + + if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { + this.fill(); + } // If the player was playing, pause it and take note of its previously + // playing state. + + + this.wasPlaying_ = !player.paused(); + + if (this.options_.pauseOnOpen && this.wasPlaying_) { + player.pause(); + } + + this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. + + this.hadControls_ = player.controls(); + player.controls(false); + this.show(); + this.conditionalFocus_(); + this.el().setAttribute('aria-hidden', 'false'); + /** + * Fired just after a `ModalDialog` is opened. + * + * @event ModalDialog#modalopen + * @type {EventTarget~Event} + */ + + this.trigger('modalopen'); + this.hasBeenOpened_ = true; + } + } + /** + * If the `ModalDialog` is currently open or closed. + * + * @param {boolean} [value] + * If given, it will open (`true`) or close (`false`) the modal. + * + * @return {boolean} + * the current open state of the modaldialog + */ + ; + + _proto.opened = function opened(value) { + if (typeof value === 'boolean') { + this[value ? 'open' : 'close'](); + } + + return this.opened_; + } + /** + * Closes the modal, does nothing if the `ModalDialog` is + * not open. + * + * @fires ModalDialog#beforemodalclose + * @fires ModalDialog#modalclose + */ + ; + + _proto.close = function close() { + if (!this.opened_) { + return; + } + + var player = this.player(); + /** + * Fired just before a `ModalDialog` is closed. + * + * @event ModalDialog#beforemodalclose + * @type {EventTarget~Event} + */ + + this.trigger('beforemodalclose'); + this.opened_ = false; + + if (this.wasPlaying_ && this.options_.pauseOnOpen) { + player.play(); + } + + this.off('keydown', this.handleKeyDown); + + if (this.hadControls_) { + player.controls(true); + } + + this.hide(); + this.el().setAttribute('aria-hidden', 'true'); + /** + * Fired just after a `ModalDialog` is closed. + * + * @event ModalDialog#modalclose + * @type {EventTarget~Event} + */ + + this.trigger('modalclose'); + this.conditionalBlur_(); + + if (this.options_.temporary) { + this.dispose(); + } + } + /** + * Check to see if the `ModalDialog` is closeable via the UI. + * + * @param {boolean} [value] + * If given as a boolean, it will set the `closeable` option. + * + * @return {boolean} + * Returns the final value of the closable option. + */ + ; + + _proto.closeable = function closeable(value) { + if (typeof value === 'boolean') { + var closeable = this.closeable_ = !!value; + var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. + + if (closeable && !close) { + // The close button should be a child of the modal - not its + // content element, so temporarily change the content element. + var temp = this.contentEl_; + this.contentEl_ = this.el_; + close = this.addChild('closeButton', { + controlText: 'Close Modal Dialog' + }); + this.contentEl_ = temp; + this.on(close, 'close', this.close); + } // If this is being made uncloseable and has a close button, remove it. + + + if (!closeable && close) { + this.off(close, 'close', this.close); + this.removeChild(close); + close.dispose(); + } + } + + return this.closeable_; + } + /** + * Fill the modal's content element with the modal's "content" option. + * The content element will be emptied before this change takes place. + */ + ; + + _proto.fill = function fill() { + this.fillWith(this.content()); + } + /** + * Fill the modal's content element with arbitrary content. + * The content element will be emptied before this change takes place. + * + * @fires ModalDialog#beforemodalfill + * @fires ModalDialog#modalfill + * + * @param {Mixed} [content] + * The same rules apply to this as apply to the `content` option. + */ + ; + + _proto.fillWith = function fillWith(content) { + var contentEl = this.contentEl(); + var parentEl = contentEl.parentNode; + var nextSiblingEl = contentEl.nextSibling; + /** + * Fired just before a `ModalDialog` is filled with content. + * + * @event ModalDialog#beforemodalfill + * @type {EventTarget~Event} + */ + + this.trigger('beforemodalfill'); + this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing + // manipulation to avoid modifying the live DOM multiple times. + + parentEl.removeChild(contentEl); + this.empty(); + insertContent(contentEl, content); + /** + * Fired just after a `ModalDialog` is filled with content. + * + * @event ModalDialog#modalfill + * @type {EventTarget~Event} + */ + + this.trigger('modalfill'); // Re-inject the re-filled content element. + + if (nextSiblingEl) { + parentEl.insertBefore(contentEl, nextSiblingEl); + } else { + parentEl.appendChild(contentEl); + } // make sure that the close button is last in the dialog DOM + + + var closeButton = this.getChild('closeButton'); + + if (closeButton) { + parentEl.appendChild(closeButton.el_); + } + } + /** + * Empties the content element. This happens anytime the modal is filled. + * + * @fires ModalDialog#beforemodalempty + * @fires ModalDialog#modalempty + */ + ; + + _proto.empty = function empty() { + /** + * Fired just before a `ModalDialog` is emptied. + * + * @event ModalDialog#beforemodalempty + * @type {EventTarget~Event} + */ + this.trigger('beforemodalempty'); + emptyEl(this.contentEl()); + /** + * Fired just after a `ModalDialog` is emptied. + * + * @event ModalDialog#modalempty + * @type {EventTarget~Event} + */ + + this.trigger('modalempty'); + } + /** + * Gets or sets the modal content, which gets normalized before being + * rendered into the DOM. + * + * This does not update the DOM or fill the modal, but it is called during + * that process. + * + * @param {Mixed} [value] + * If defined, sets the internal content value to be used on the + * next call(s) to `fill`. This value is normalized before being + * inserted. To "clear" the internal content value, pass `null`. + * + * @return {Mixed} + * The current content of the modal dialog + */ + ; + + _proto.content = function content(value) { + if (typeof value !== 'undefined') { + this.content_ = value; + } + + return this.content_; + } + /** + * conditionally focus the modal dialog if focus was previously on the player. + * + * @private + */ + ; + + _proto.conditionalFocus_ = function conditionalFocus_() { + var activeEl = document_1.activeElement; + var playerEl = this.player_.el_; + this.previouslyActiveEl_ = null; + + if (playerEl.contains(activeEl) || playerEl === activeEl) { + this.previouslyActiveEl_ = activeEl; + this.focus(); + } + } + /** + * conditionally blur the element and refocus the last focused element + * + * @private + */ + ; + + _proto.conditionalBlur_ = function conditionalBlur_() { + if (this.previouslyActiveEl_) { + this.previouslyActiveEl_.focus(); + this.previouslyActiveEl_ = null; + } + } + /** + * Keydown handler. Attached when modal is focused. + * + * @listens keydown + */ + ; + + _proto.handleKeyDown = function handleKeyDown(event) { + // Do not allow keydowns to reach out of the modal dialog. + event.stopPropagation(); + + if (keycode.isEventKey(event, 'Escape') && this.closeable()) { + event.preventDefault(); + this.close(); + return; + } // exit early if it isn't a tab key + + + if (!keycode.isEventKey(event, 'Tab')) { + return; + } + + var focusableEls = this.focusableEls_(); + var activeEl = this.el_.querySelector(':focus'); + var focusIndex; + + for (var i = 0; i < focusableEls.length; i++) { + if (activeEl === focusableEls[i]) { + focusIndex = i; + break; + } + } + + if (document_1.activeElement === this.el_) { + focusIndex = 0; + } + + if (event.shiftKey && focusIndex === 0) { + focusableEls[focusableEls.length - 1].focus(); + event.preventDefault(); + } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { + focusableEls[0].focus(); + event.preventDefault(); + } + } + /** + * get all focusable elements + * + * @private + */ + ; + + _proto.focusableEls_ = function focusableEls_() { + var allChildren = this.el_.querySelectorAll('*'); + return Array.prototype.filter.call(allChildren, function (child) { + return (child instanceof window_1$1.HTMLAnchorElement || child instanceof window_1$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window_1$1.HTMLInputElement || child instanceof window_1$1.HTMLSelectElement || child instanceof window_1$1.HTMLTextAreaElement || child instanceof window_1$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window_1$1.HTMLIFrameElement || child instanceof window_1$1.HTMLObjectElement || child instanceof window_1$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); + }); + }; + + return ModalDialog; +}(Component); +/** + * Default options for `ModalDialog` default options. + * + * @type {Object} + * @private + */ + + +ModalDialog.prototype.options_ = { + pauseOnOpen: true, + temporary: true +}; +Component.registerComponent('ModalDialog', ModalDialog); + +/** + * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and + * {@link VideoTrackList} + * + * @extends EventTarget + */ + +var TrackList = /*#__PURE__*/function (_EventTarget) { + inheritsLoose(TrackList, _EventTarget); + + /** + * Create an instance of this class + * + * @param {Track[]} tracks + * A list of tracks to initialize the list with. + * + * @abstract + */ + function TrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + _this = _EventTarget.call(this) || this; + _this.tracks_ = []; + /** + * @memberof TrackList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + + Object.defineProperty(assertThisInitialized(_this), 'length', { + get: function get() { + return this.tracks_.length; + } + }); + + for (var i = 0; i < tracks.length; i++) { + _this.addTrack(tracks[i]); + } + + return _this; + } + /** + * Add a {@link Track} to the `TrackList` + * + * @param {Track} track + * The audio, video, or text track to add to the list. + * + * @fires TrackList#addtrack + */ + + + var _proto = TrackList.prototype; + + _proto.addTrack = function addTrack(track) { + var index = this.tracks_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get() { + return this.tracks_[index]; + } + }); + } // Do not add duplicate tracks + + + if (this.tracks_.indexOf(track) === -1) { + this.tracks_.push(track); + /** + * Triggered when a track is added to a track list. + * + * @event TrackList#addtrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was added. + */ + + this.trigger({ + track: track, + type: 'addtrack', + target: this + }); + } + } + /** + * Remove a {@link Track} from the `TrackList` + * + * @param {Track} rtrack + * The audio, video, or text track to remove from the list. + * + * @fires TrackList#removetrack + */ + ; + + _proto.removeTrack = function removeTrack(rtrack) { + var track; + + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + + if (track.off) { + track.off(); + } + + this.tracks_.splice(i, 1); + break; + } + } + + if (!track) { + return; + } + /** + * Triggered when a track is removed from track list. + * + * @event TrackList#removetrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was removed. + */ + + + this.trigger({ + track: track, + type: 'removetrack', + target: this + }); + } + /** + * Get a Track from the TrackList by a tracks id + * + * @param {string} id - the id of the track to get + * @method getTrackById + * @return {Track} + * @private + */ + ; + + _proto.getTrackById = function getTrackById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var track = this[i]; + + if (track.id === id) { + result = track; + break; + } + } + + return result; + }; + + return TrackList; +}(EventTarget); +/** + * Triggered when a different track is selected/enabled. + * + * @event TrackList#change + * @type {EventTarget~Event} + */ + +/** + * Events that can be called with on + eventName. See {@link EventHandler}. + * + * @property {Object} TrackList#allowedEvents_ + * @private + */ + + +TrackList.prototype.allowedEvents_ = { + change: 'change', + addtrack: 'addtrack', + removetrack: 'removetrack' +}; // emulate attribute EventHandler support to allow for feature detection + +for (var event in TrackList.prototype.allowedEvents_) { + TrackList.prototype['on' + event] = null; +} + +/** + * Anywhere we call this function we diverge from the spec + * as we only support one enabled audiotrack at a time + * + * @param {AudioTrackList} list + * list to work on + * + * @param {AudioTrack} track + * The track to skip + * + * @private + */ + +var disableOthers = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (!Object.keys(list[i]).length || track.id === list[i].id) { + continue; + } // another audio track is enabled, disable it + + + list[i].enabled = false; + } +}; +/** + * The current list of {@link AudioTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} + * @extends TrackList + */ + + +var AudioTrackList = /*#__PURE__*/function (_TrackList) { + inheritsLoose(AudioTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {AudioTrack[]} [tracks=[]] + * A list of `AudioTrack` to instantiate the list with. + */ + function AudioTrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].enabled) { + disableOthers(tracks, tracks[i]); + break; + } + } + + _this = _TrackList.call(this, tracks) || this; + _this.changing_ = false; + return _this; + } + /** + * Add an {@link AudioTrack} to the `AudioTrackList`. + * + * @param {AudioTrack} track + * The AudioTrack to add to the list + * + * @fires TrackList#addtrack + */ + + + var _proto = AudioTrackList.prototype; + + _proto.addTrack = function addTrack(track) { + var _this2 = this; + + if (track.enabled) { + disableOthers(this, track); + } + + _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this + + + if (!track.addEventListener) { + return; + } + + track.enabledChange_ = function () { + // when we are disabling other tracks (since we don't support + // more than one track at a time) we will set changing_ + // to true so that we don't trigger additional change events + if (_this2.changing_) { + return; + } + + _this2.changing_ = true; + disableOthers(_this2, track); + _this2.changing_ = false; + + _this2.trigger('change'); + }; + /** + * @listens AudioTrack#enabledchange + * @fires TrackList#change + */ + + + track.addEventListener('enabledchange', track.enabledChange_); + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); + + if (rtrack.removeEventListener && rtrack.enabledChange_) { + rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); + rtrack.enabledChange_ = null; + } + }; + + return AudioTrackList; +}(TrackList); + +/** + * Un-select all other {@link VideoTrack}s that are selected. + * + * @param {VideoTrackList} list + * list to work on + * + * @param {VideoTrack} track + * The track to skip + * + * @private + */ + +var disableOthers$1 = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (!Object.keys(list[i]).length || track.id === list[i].id) { + continue; + } // another video track is enabled, disable it + + + list[i].selected = false; + } +}; +/** + * The current list of {@link VideoTrack} for a video. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} + * @extends TrackList + */ + + +var VideoTrackList = /*#__PURE__*/function (_TrackList) { + inheritsLoose(VideoTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {VideoTrack[]} [tracks=[]] + * A list of `VideoTrack` to instantiate the list with. + */ + function VideoTrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].selected) { + disableOthers$1(tracks, tracks[i]); + break; + } + } + + _this = _TrackList.call(this, tracks) || this; + _this.changing_ = false; + /** + * @member {number} VideoTrackList#selectedIndex + * The current index of the selected {@link VideoTrack`}. + */ + + Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', { + get: function get() { + for (var _i = 0; _i < this.length; _i++) { + if (this[_i].selected) { + return _i; + } + } + + return -1; + }, + set: function set() {} + }); + return _this; + } + /** + * Add a {@link VideoTrack} to the `VideoTrackList`. + * + * @param {VideoTrack} track + * The VideoTrack to add to the list + * + * @fires TrackList#addtrack + */ + + + var _proto = VideoTrackList.prototype; + + _proto.addTrack = function addTrack(track) { + var _this2 = this; + + if (track.selected) { + disableOthers$1(this, track); + } + + _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this + + + if (!track.addEventListener) { + return; + } + + track.selectedChange_ = function () { + if (_this2.changing_) { + return; + } + + _this2.changing_ = true; + disableOthers$1(_this2, track); + _this2.changing_ = false; + + _this2.trigger('change'); + }; + /** + * @listens VideoTrack#selectedchange + * @fires TrackList#change + */ + + + track.addEventListener('selectedchange', track.selectedChange_); + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); + + if (rtrack.removeEventListener && rtrack.selectedChange_) { + rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); + rtrack.selectedChange_ = null; + } + }; + + return VideoTrackList; +}(TrackList); + +/** + * The current list of {@link TextTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} + * @extends TrackList + */ + +var TextTrackList = /*#__PURE__*/function (_TrackList) { + inheritsLoose(TextTrackList, _TrackList); + + function TextTrackList() { + return _TrackList.apply(this, arguments) || this; + } + + var _proto = TextTrackList.prototype; + + /** + * Add a {@link TextTrack} to the `TextTrackList` + * + * @param {TextTrack} track + * The text track to add to the list. + * + * @fires TrackList#addtrack + */ + _proto.addTrack = function addTrack(track) { + var _this = this; + + _TrackList.prototype.addTrack.call(this, track); + + if (!this.queueChange_) { + this.queueChange_ = function () { + return _this.queueTrigger('change'); + }; + } + + if (!this.triggerSelectedlanguagechange) { + this.triggerSelectedlanguagechange_ = function () { + return _this.trigger('selectedlanguagechange'); + }; + } + /** + * @listens TextTrack#modechange + * @fires TrackList#change + */ + + + track.addEventListener('modechange', this.queueChange_); + var nonLanguageTextTrackKind = ['metadata', 'chapters']; + + if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { + track.addEventListener('modechange', this.triggerSelectedlanguagechange_); + } + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added + + + if (rtrack.removeEventListener) { + if (this.queueChange_) { + rtrack.removeEventListener('modechange', this.queueChange_); + } + + if (this.selectedlanguagechange_) { + rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); + } + } + }; + + return TextTrackList; +}(TrackList); + +/** + * @file html-track-element-list.js + */ + +/** + * The current list of {@link HtmlTrackElement}s. + */ +var HtmlTrackElementList = /*#__PURE__*/function () { + /** + * Create an instance of this class. + * + * @param {HtmlTrackElement[]} [tracks=[]] + * A list of `HtmlTrackElement` to instantiate the list with. + */ + function HtmlTrackElementList(trackElements) { + if (trackElements === void 0) { + trackElements = []; + } + + this.trackElements_ = []; + /** + * @memberof HtmlTrackElementList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + + Object.defineProperty(this, 'length', { + get: function get() { + return this.trackElements_.length; + } + }); + + for (var i = 0, length = trackElements.length; i < length; i++) { + this.addTrackElement_(trackElements[i]); + } + } + /** + * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` + * + * @param {HtmlTrackElement} trackElement + * The track element to add to the list. + * + * @private + */ + + + var _proto = HtmlTrackElementList.prototype; + + _proto.addTrackElement_ = function addTrackElement_(trackElement) { + var index = this.trackElements_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get() { + return this.trackElements_[index]; + } + }); + } // Do not add duplicate elements + + + if (this.trackElements_.indexOf(trackElement) === -1) { + this.trackElements_.push(trackElement); + } + } + /** + * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an + * {@link TextTrack}. + * + * @param {TextTrack} track + * The track associated with a track element. + * + * @return {HtmlTrackElement|undefined} + * The track element that was found or undefined. + * + * @private + */ + ; + + _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { + var trackElement_; + + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (track === this.trackElements_[i].track) { + trackElement_ = this.trackElements_[i]; + break; + } + } + + return trackElement_; + } + /** + * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` + * + * @param {HtmlTrackElement} trackElement + * The track element to remove from the list. + * + * @private + */ + ; + + _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (trackElement === this.trackElements_[i]) { + if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { + this.trackElements_[i].track.off(); + } + + if (typeof this.trackElements_[i].off === 'function') { + this.trackElements_[i].off(); + } + + this.trackElements_.splice(i, 1); + break; + } + } + }; + + return HtmlTrackElementList; +}(); + +/** + * @file text-track-cue-list.js + */ + +/** + * @typedef {Object} TextTrackCueList~TextTrackCue + * + * @property {string} id + * The unique id for this text track cue + * + * @property {number} startTime + * The start time for this text track cue + * + * @property {number} endTime + * The end time for this text track cue + * + * @property {boolean} pauseOnExit + * Pause when the end time is reached if true. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} + */ + +/** + * A List of TextTrackCues. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} + */ +var TextTrackCueList = /*#__PURE__*/function () { + /** + * Create an instance of this class.. + * + * @param {Array} cues + * A list of cues to be initialized with + */ + function TextTrackCueList(cues) { + TextTrackCueList.prototype.setCues_.call(this, cues); + /** + * @memberof TextTrackCueList + * @member {number} length + * The current number of `TextTrackCue`s in the TextTrackCueList. + * @instance + */ + + Object.defineProperty(this, 'length', { + get: function get() { + return this.length_; + } + }); + } + /** + * A setter for cues in this list. Creates getters + * an an index for the cues. + * + * @param {Array} cues + * An array of cues to set + * + * @private + */ + + + var _proto = TextTrackCueList.prototype; + + _proto.setCues_ = function setCues_(cues) { + var oldLength = this.length || 0; + var i = 0; + var l = cues.length; + this.cues_ = cues; + this.length_ = cues.length; + + var defineProp = function defineProp(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get: function get() { + return this.cues_[index]; + } + }); + } + }; + + if (oldLength < l) { + i = oldLength; + + for (; i < l; i++) { + defineProp.call(this, i); + } + } + } + /** + * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. + * + * @param {string} id + * The id of the cue that should be searched for. + * + * @return {TextTrackCueList~TextTrackCue|null} + * A single cue or null if none was found. + */ + ; + + _proto.getCueById = function getCueById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var cue = this[i]; + + if (cue.id === id) { + result = cue; + break; + } + } + + return result; + }; + + return TextTrackCueList; +}(); + +/** + * @file track-kinds.js + */ + +/** + * All possible `VideoTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind + * @typedef VideoTrack~Kind + * @enum + */ +var VideoTrackKind = { + alternative: 'alternative', + captions: 'captions', + main: 'main', + sign: 'sign', + subtitles: 'subtitles', + commentary: 'commentary' +}; +/** + * All possible `AudioTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind + * @typedef AudioTrack~Kind + * @enum + */ + +var AudioTrackKind = { + 'alternative': 'alternative', + 'descriptions': 'descriptions', + 'main': 'main', + 'main-desc': 'main-desc', + 'translation': 'translation', + 'commentary': 'commentary' +}; +/** + * All possible `TextTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind + * @typedef TextTrack~Kind + * @enum + */ + +var TextTrackKind = { + subtitles: 'subtitles', + captions: 'captions', + descriptions: 'descriptions', + chapters: 'chapters', + metadata: 'metadata' +}; +/** + * All possible `TextTrackMode`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode + * @typedef TextTrack~Mode + * @enum + */ + +var TextTrackMode = { + disabled: 'disabled', + hidden: 'hidden', + showing: 'showing' +}; + +/** + * A Track class that contains all of the common functionality for {@link AudioTrack}, + * {@link VideoTrack}, and {@link TextTrack}. + * + * > Note: This class should not be used directly + * + * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} + * @extends EventTarget + * @abstract + */ + +var Track = /*#__PURE__*/function (_EventTarget) { + inheritsLoose(Track, _EventTarget); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {string} [options.kind=''] + * A valid kind for the track type you are creating. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @abstract + */ + function Track(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + _this = _EventTarget.call(this) || this; + var trackProps = { + id: options.id || 'vjs_track_' + newGUID(), + kind: options.kind || '', + label: options.label || '', + language: options.language || '' + }; + /** + * @memberof Track + * @member {string} id + * The id of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} kind + * The kind of track that this is. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} label + * The label of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} language + * The two letter language code for this track. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ + + var _loop = function _loop(key) { + Object.defineProperty(assertThisInitialized(_this), key, { + get: function get() { + return trackProps[key]; + }, + set: function set() {} + }); + }; + + for (var key in trackProps) { + _loop(key); + } + + return _this; + } + + return Track; +}(EventTarget); + +/** + * @file url.js + * @module url + */ +/** + * @typedef {Object} url:URLObject + * + * @property {string} protocol + * The protocol of the url that was parsed. + * + * @property {string} hostname + * The hostname of the url that was parsed. + * + * @property {string} port + * The port of the url that was parsed. + * + * @property {string} pathname + * The pathname of the url that was parsed. + * + * @property {string} search + * The search query of the url that was parsed. + * + * @property {string} hash + * The hash of the url that was parsed. + * + * @property {string} host + * The host of the url that was parsed. + */ + +/** + * Resolve and parse the elements of a URL. + * + * @function + * @param {String} url + * The url to parse + * + * @return {url:URLObject} + * An object of url details + */ + +var parseUrl = function parseUrl(url) { + var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL + + var a = document_1.createElement('a'); + a.href = url; // IE8 (and 9?) Fix + // ie8 doesn't parse the URL correctly until the anchor is actually + // added to the body, and an innerHTML is needed to trigger the parsing + + var addToBody = a.host === '' && a.protocol !== 'file:'; + var div; + + if (addToBody) { + div = document_1.createElement('div'); + div.innerHTML = ""; + a = div.firstChild; // prevent the div from affecting layout + + div.setAttribute('style', 'display:none; position:absolute;'); + document_1.body.appendChild(div); + } // Copy the specific URL properties to a new object + // This is also needed for IE8 because the anchor loses its + // properties when it's removed from the dom + + + var details = {}; + + for (var i = 0; i < props.length; i++) { + details[props[i]] = a[props[i]]; + } // IE9 adds the port to the host property unlike everyone else. If + // a port identifier is added for standard ports, strip it. + + + if (details.protocol === 'http:') { + details.host = details.host.replace(/:80$/, ''); + } + + if (details.protocol === 'https:') { + details.host = details.host.replace(/:443$/, ''); + } + + if (!details.protocol) { + details.protocol = window_1$1.location.protocol; + } + + if (addToBody) { + document_1.body.removeChild(div); + } + + return details; +}; +/** + * Get absolute version of relative URL. Used to tell Flash the correct URL. + * + * @function + * @param {string} url + * URL to make absolute + * + * @return {string} + * Absolute URL + * + * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue + */ + +var getAbsoluteURL = function getAbsoluteURL(url) { + // Check if absolute URL + if (!url.match(/^https?:\/\//)) { + // Convert to absolute URL. Flash hosted off-site needs an absolute URL. + var div = document_1.createElement('div'); + div.innerHTML = "x"; + url = div.firstChild.href; + } + + return url; +}; +/** + * Returns the extension of the passed file name. It will return an empty string + * if passed an invalid path. + * + * @function + * @param {string} path + * The fileName path like '/path/to/file.mp4' + * + * @return {string} + * The extension in lower case or an empty string if no + * extension could be found. + */ + +var getFileExtension = function getFileExtension(path) { + if (typeof path === 'string') { + var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; + var pathParts = splitPathRe.exec(path); + + if (pathParts) { + return pathParts.pop().toLowerCase(); + } + } + + return ''; +}; +/** + * Returns whether the url passed is a cross domain request or not. + * + * @function + * @param {string} url + * The url to check. + * + * @param {Object} [winLoc] + * the domain to check the url against, defaults to window.location + * + * @param {string} [winLoc.protocol] + * The window location protocol defaults to window.location.protocol + * + * @param {string} [winLoc.host] + * The window location host defaults to window.location.host + * + * @return {boolean} + * Whether it is a cross domain request or not. + */ + +var isCrossOrigin = function isCrossOrigin(url, winLoc) { + if (winLoc === void 0) { + winLoc = window_1$1.location; + } + + var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol + + var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin + // IE8 doesn't know location.origin, so we won't rely on it here + + var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; + return crossOrigin; +}; + +var Url = /*#__PURE__*/Object.freeze({ + __proto__: null, + parseUrl: parseUrl, + getAbsoluteURL: getAbsoluteURL, + getFileExtension: getFileExtension, + isCrossOrigin: isCrossOrigin +}); + +/** + * Takes a webvtt file contents and parses it into cues + * + * @param {string} srcContent + * webVTT file contents + * + * @param {TextTrack} track + * TextTrack to add cues to. Cues come from the srcContent. + * + * @private + */ + +var parseCues = function parseCues(srcContent, track) { + var parser = new window_1$1.WebVTT.Parser(window_1$1, window_1$1.vttjs, window_1$1.WebVTT.StringDecoder()); + var errors = []; + + parser.oncue = function (cue) { + track.addCue(cue); + }; + + parser.onparsingerror = function (error) { + errors.push(error); + }; + + parser.onflush = function () { + track.trigger({ + type: 'loadeddata', + target: track + }); + }; + + parser.parse(srcContent); + + if (errors.length > 0) { + if (window_1$1.console && window_1$1.console.groupCollapsed) { + window_1$1.console.groupCollapsed("Text Track parsing errors for " + track.src); + } + + errors.forEach(function (error) { + return log.error(error); + }); + + if (window_1$1.console && window_1$1.console.groupEnd) { + window_1$1.console.groupEnd(); + } + } + + parser.flush(); +}; +/** + * Load a `TextTrack` from a specified url. + * + * @param {string} src + * Url to load track from. + * + * @param {TextTrack} track + * Track to add cues to. Comes from the content at the end of `url`. + * + * @private + */ + + +var loadTrack = function loadTrack(src, track) { + var opts = { + uri: src + }; + var crossOrigin = isCrossOrigin(src); + + if (crossOrigin) { + opts.cors = crossOrigin; + } + + var withCredentials = track.tech_.crossOrigin() === 'use-credentials'; + + if (withCredentials) { + opts.withCredentials = withCredentials; + } + + xhr(opts, bind(this, function (err, response, responseBody) { + if (err) { + return log.error(err, response); + } + + track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading + // NOTE: this is only used for the alt/video.novtt.js build + + if (typeof window_1$1.WebVTT !== 'function') { + if (track.tech_) { + // to prevent use before define eslint error, we define loadHandler + // as a let here + track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { + if (event.type === 'vttjserror') { + log.error("vttjs failed to load, stopping trying to process " + track.src); + return; + } + + return parseCues(responseBody, track); + }); + } + } else { + parseCues(responseBody, track); + } + })); +}; +/** + * A representation of a single `TextTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} + * @extends Track + */ + + +var TextTrack = /*#__PURE__*/function (_Track) { + inheritsLoose(TextTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} options={} + * Object of option names and values + * + * @param {Tech} options.tech + * A reference to the tech that owns this TextTrack. + * + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. + * + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * version of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. + */ + function TextTrack(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + if (!options.tech) { + throw new Error('A tech was not provided.'); + } + + var settings = mergeOptions(options, { + kind: TextTrackKind[options.kind] || 'subtitles', + language: options.language || options.srclang || '' + }); + var mode = TextTrackMode[settings.mode] || 'disabled'; + var default_ = settings["default"]; + + if (settings.kind === 'metadata' || settings.kind === 'chapters') { + mode = 'hidden'; + } + + _this = _Track.call(this, settings) || this; + _this.tech_ = settings.tech; + _this.cues_ = []; + _this.activeCues_ = []; + _this.preload_ = _this.tech_.preloadTextTracks !== false; + var cues = new TextTrackCueList(_this.cues_); + var activeCues = new TextTrackCueList(_this.activeCues_); + var changed = false; + var timeupdateHandler = bind(assertThisInitialized(_this), function () { + // Accessing this.activeCues for the side-effects of updating itself + // due to its nature as a getter function. Do not remove or cues will + // stop updating! + // Use the setter to prevent deletion from uglify (pure_getters rule) + this.activeCues = this.activeCues; + + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); + + if (mode !== 'disabled') { + _this.tech_.ready(function () { + _this.tech_.on('timeupdate', timeupdateHandler); + }, true); + } + + Object.defineProperties(assertThisInitialized(_this), { + /** + * @memberof TextTrack + * @member {boolean} default + * If this track was set to be on or off by default. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ + "default": { + get: function get() { + return default_; + }, + set: function set() {} + }, + + /** + * @memberof TextTrack + * @member {string} mode + * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will + * not be set if setting to an invalid mode. + * @instance + * + * @fires TextTrack#modechange + */ + mode: { + get: function get() { + return mode; + }, + set: function set(newMode) { + var _this2 = this; + + if (!TextTrackMode[newMode]) { + return; + } + + mode = newMode; + + if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { + // On-demand load. + loadTrack(this.src, this); + } + + if (mode !== 'disabled') { + this.tech_.ready(function () { + _this2.tech_.on('timeupdate', timeupdateHandler); + }, true); + } else { + this.tech_.off('timeupdate', timeupdateHandler); + } + /** + * An event that fires when mode changes on this track. This allows + * the TextTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! + * + * @event TextTrack#modechange + * @type {EventTarget~Event} + */ + + + this.trigger('modechange'); + } + }, + + /** + * @memberof TextTrack + * @member {TextTrackCueList} cues + * The text track cue list for this TextTrack. + * @instance + */ + cues: { + get: function get() { + if (!this.loaded_) { + return null; + } + + return cues; + }, + set: function set() {} + }, + + /** + * @memberof TextTrack + * @member {TextTrackCueList} activeCues + * The list text track cues that are currently active for this TextTrack. + * @instance + */ + activeCues: { + get: function get() { + if (!this.loaded_) { + return null; + } // nothing to do + + + if (this.cues.length === 0) { + return activeCues; + } + + var ct = this.tech_.currentTime(); + var active = []; + + for (var i = 0, l = this.cues.length; i < l; i++) { + var cue = this.cues[i]; + + if (cue.startTime <= ct && cue.endTime >= ct) { + active.push(cue); + } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { + active.push(cue); + } + } + + changed = false; + + if (active.length !== this.activeCues_.length) { + changed = true; + } else { + for (var _i = 0; _i < active.length; _i++) { + if (this.activeCues_.indexOf(active[_i]) === -1) { + changed = true; + } + } + } + + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); + return activeCues; + }, + // /!\ Keep this setter empty (see the timeupdate handler above) + set: function set() {} + } + }); + + if (settings.src) { + _this.src = settings.src; + + if (!_this.preload_) { + // Tracks will load on-demand. + // Act like we're loaded for other purposes. + _this.loaded_ = true; + } + + if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { + loadTrack(_this.src, assertThisInitialized(_this)); + } + } else { + _this.loaded_ = true; + } + + return _this; + } + /** + * Add a cue to the internal list of cues. + * + * @param {TextTrack~Cue} cue + * The cue to add to our internal list + */ + + + var _proto = TextTrack.prototype; + + _proto.addCue = function addCue(originalCue) { + var cue = originalCue; + + if (window_1$1.vttjs && !(originalCue instanceof window_1$1.vttjs.VTTCue)) { + cue = new window_1$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); + + for (var prop in originalCue) { + if (!(prop in cue)) { + cue[prop] = originalCue[prop]; + } + } // make sure that `id` is copied over + + + cue.id = originalCue.id; + cue.originalCue_ = originalCue; + } + + var tracks = this.tech_.textTracks(); + + for (var i = 0; i < tracks.length; i++) { + if (tracks[i] !== this) { + tracks[i].removeCue(cue); + } + } + + this.cues_.push(cue); + this.cues.setCues_(this.cues_); + } + /** + * Remove a cue from our internal list + * + * @param {TextTrack~Cue} removeCue + * The cue to remove from our internal list + */ + ; + + _proto.removeCue = function removeCue(_removeCue) { + var i = this.cues_.length; + + while (i--) { + var cue = this.cues_[i]; + + if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { + this.cues_.splice(i, 1); + this.cues.setCues_(this.cues_); + break; + } + } + }; + + return TextTrack; +}(Track); +/** + * cuechange - One or more cues in the track have become active or stopped being active. + */ + + +TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' +}; + +/** + * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} + * only one `AudioTrack` in the list will be enabled at a time. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} + * @extends Track + */ + +var AudioTrack = /*#__PURE__*/function (_Track) { + inheritsLoose(AudioTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {AudioTrack~Kind} [options.kind=''] + * A valid audio track kind + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.enabled] + * If this track is the one that is currently playing. If this track is part of + * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. + */ + function AudioTrack(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + var settings = mergeOptions(options, { + kind: AudioTrackKind[options.kind] || '' + }); + _this = _Track.call(this, settings) || this; + var enabled = false; + /** + * @memberof AudioTrack + * @member {boolean} enabled + * If this `AudioTrack` is enabled or not. When setting this will + * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + + Object.defineProperty(assertThisInitialized(_this), 'enabled', { + get: function get() { + return enabled; + }, + set: function set(newEnabled) { + // an invalid or unchanged value + if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { + return; + } + + enabled = newEnabled; + /** + * An event that fires when enabled changes on this track. This allows + * the AudioTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event AudioTrack#enabledchange + * @type {EventTarget~Event} + */ + + this.trigger('enabledchange'); + } + }); // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + + if (settings.enabled) { + _this.enabled = settings.enabled; + } + + _this.loaded_ = true; + return _this; + } + + return AudioTrack; +}(Track); + +/** + * A representation of a single `VideoTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} + * @extends Track + */ + +var VideoTrack = /*#__PURE__*/function (_Track) { + inheritsLoose(VideoTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {string} [options.kind=''] + * A valid {@link VideoTrack~Kind} + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.selected] + * If this track is the one that is currently playing. + */ + function VideoTrack(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + var settings = mergeOptions(options, { + kind: VideoTrackKind[options.kind] || '' + }); + _this = _Track.call(this, settings) || this; + var selected = false; + /** + * @memberof VideoTrack + * @member {boolean} selected + * If this `VideoTrack` is selected or not. When setting this will + * fire {@link VideoTrack#selectedchange} if the state of selected changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + + Object.defineProperty(assertThisInitialized(_this), 'selected', { + get: function get() { + return selected; + }, + set: function set(newSelected) { + // an invalid or unchanged value + if (typeof newSelected !== 'boolean' || newSelected === selected) { + return; + } + + selected = newSelected; + /** + * An event that fires when selected changes on this track. This allows + * the VideoTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event VideoTrack#selectedchange + * @type {EventTarget~Event} + */ + + this.trigger('selectedchange'); + } + }); // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + + if (settings.selected) { + _this.selected = settings.selected; + } + + return _this; + } + + return VideoTrack; +}(Track); + +/** + * @memberof HTMLTrackElement + * @typedef {HTMLTrackElement~ReadyState} + * @enum {number} + */ + +var NONE = 0; +var LOADING = 1; +var LOADED = 2; +var ERROR = 3; +/** + * A single track represented in the DOM. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} + * @extends EventTarget + */ + +var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) { + inheritsLoose(HTMLTrackElement, _EventTarget); + + /** + * Create an instance of this class. + * + * @param {Object} options={} + * Object of option names and values + * + * @param {Tech} options.tech + * A reference to the tech that owns this HTMLTrackElement. + * + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. + * + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * vesion of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. + */ + function HTMLTrackElement(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + _this = _EventTarget.call(this) || this; + var readyState; + var track = new TextTrack(options); + _this.kind = track.kind; + _this.src = track.src; + _this.srclang = track.language; + _this.label = track.label; + _this["default"] = track["default"]; + Object.defineProperties(assertThisInitialized(_this), { + /** + * @memberof HTMLTrackElement + * @member {HTMLTrackElement~ReadyState} readyState + * The current ready state of the track element. + * @instance + */ + readyState: { + get: function get() { + return readyState; + } + }, + + /** + * @memberof HTMLTrackElement + * @member {TextTrack} track + * The underlying TextTrack object. + * @instance + * + */ + track: { + get: function get() { + return track; + } + } + }); + readyState = NONE; + /** + * @listens TextTrack#loadeddata + * @fires HTMLTrackElement#load + */ + + track.addEventListener('loadeddata', function () { + readyState = LOADED; + + _this.trigger({ + type: 'load', + target: assertThisInitialized(_this) + }); + }); + return _this; + } + + return HTMLTrackElement; +}(EventTarget); + +HTMLTrackElement.prototype.allowedEvents_ = { + load: 'load' +}; +HTMLTrackElement.NONE = NONE; +HTMLTrackElement.LOADING = LOADING; +HTMLTrackElement.LOADED = LOADED; +HTMLTrackElement.ERROR = ERROR; + +/* + * This file contains all track properties that are used in + * player.js, tech.js, html5.js and possibly other techs in the future. + */ + +var NORMAL = { + audio: { + ListClass: AudioTrackList, + TrackClass: AudioTrack, + capitalName: 'Audio' + }, + video: { + ListClass: VideoTrackList, + TrackClass: VideoTrack, + capitalName: 'Video' + }, + text: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'Text' + } +}; +Object.keys(NORMAL).forEach(function (type) { + NORMAL[type].getterName = type + "Tracks"; + NORMAL[type].privateName = type + "Tracks_"; +}); +var REMOTE = { + remoteText: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'RemoteText', + getterName: 'remoteTextTracks', + privateName: 'remoteTextTracks_' + }, + remoteTextEl: { + ListClass: HtmlTrackElementList, + TrackClass: HTMLTrackElement, + capitalName: 'RemoteTextTrackEls', + getterName: 'remoteTextTrackEls', + privateName: 'remoteTextTrackEls_' + } +}; + +var ALL = _extends_1({}, NORMAL, REMOTE); + +REMOTE.names = Object.keys(REMOTE); +NORMAL.names = Object.keys(NORMAL); +ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); + +/** + * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string + * that just contains the src url alone. + * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` + * `var SourceString = 'http://example.com/some-video.mp4';` + * + * @typedef {Object|string} Tech~SourceObject + * + * @property {string} src + * The url to the source + * + * @property {string} type + * The mime type of the source + */ + +/** + * A function used by {@link Tech} to create a new {@link TextTrack}. + * + * @private + * + * @param {Tech} self + * An instance of the Tech class. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @param {Object} [options={}] + * An object with additional text track options + * + * @return {TextTrack} + * The text track that was created. + */ + +function createTrackHelper(self, kind, label, language, options) { + if (options === void 0) { + options = {}; + } + + var tracks = self.textTracks(); + options.kind = kind; + + if (label) { + options.label = label; + } + + if (language) { + options.language = language; + } + + options.tech = self; + var track = new ALL.text.TrackClass(options); + tracks.addTrack(track); + return track; +} +/** + * This is the base class for media playback technology controllers, such as + * {@link Flash} and {@link HTML5} + * + * @extends Component + */ + + +var Tech = /*#__PURE__*/function (_Component) { + inheritsLoose(Tech, _Component); + + /** + * Create an instance of this Tech. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} ready + * Callback function to call when the `HTML5` Tech is ready. + */ + function Tech(options, ready) { + var _this; + + if (options === void 0) { + options = {}; + } + + if (ready === void 0) { + ready = function ready() {}; + } + + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; + _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to + // implement a very limited played() + + _this.hasStarted_ = false; + + _this.on('playing', function () { + this.hasStarted_ = true; + }); + + _this.on('loadstart', function () { + this.hasStarted_ = false; + }); + + ALL.names.forEach(function (name) { + var props = ALL[name]; + + if (options && options[props.getterName]) { + _this[props.privateName] = options[props.getterName]; + } + }); // Manually track progress in cases where the browser/flash player doesn't report it. + + if (!_this.featuresProgressEvents) { + _this.manualProgressOn(); + } // Manually track timeupdates in cases where the browser/flash player doesn't report it. + + + if (!_this.featuresTimeupdateEvents) { + _this.manualTimeUpdatesOn(); + } + + ['Text', 'Audio', 'Video'].forEach(function (track) { + if (options["native" + track + "Tracks"] === false) { + _this["featuresNative" + track + "Tracks"] = false; + } + }); + + if (options.nativeCaptions === false || options.nativeTextTracks === false) { + _this.featuresNativeTextTracks = false; + } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { + _this.featuresNativeTextTracks = true; + } + + if (!_this.featuresNativeTextTracks) { + _this.emulateTextTracks(); + } + + _this.preloadTextTracks = options.preloadTextTracks !== false; + _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); + + _this.initTrackListeners(); // Turn on component tap events only if not using native controls + + + if (!options.nativeControlsForTouch) { + _this.emitTapEvents(); + } + + if (_this.constructor) { + _this.name_ = _this.constructor.name || 'Unknown Tech'; + } + + return _this; + } + /** + * A special function to trigger source set in a way that will allow player + * to re-trigger if the player or tech are not ready yet. + * + * @fires Tech#sourceset + * @param {string} src The source string at the time of the source changing. + */ + + + var _proto = Tech.prototype; + + _proto.triggerSourceset = function triggerSourceset(src) { + var _this2 = this; + + if (!this.isReady_) { + // on initial ready we have to trigger source set + // 1ms after ready so that player can watch for it. + this.one('ready', function () { + return _this2.setTimeout(function () { + return _this2.triggerSourceset(src); + }, 1); + }); + } + /** + * Fired when the source is set on the tech causing the media element + * to reload. + * + * @see {@link Player#event:sourceset} + * @event Tech#sourceset + * @type {EventTarget~Event} + */ + + + this.trigger({ + src: src, + type: 'sourceset' + }); + } + /* Fallbacks for unsupported event types + ================================================================================ */ + + /** + * Polyfill the `progress` event for browsers that don't support it natively. + * + * @see {@link Tech#trackProgress} + */ + ; + + _proto.manualProgressOn = function manualProgressOn() { + this.on('durationchange', this.onDurationChange); + this.manualProgress = true; // Trigger progress watching when a source begins loading + + this.one('ready', this.trackProgress); + } + /** + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + */ + ; + + _proto.manualProgressOff = function manualProgressOff() { + this.manualProgress = false; + this.stopTrackingProgress(); + this.off('durationchange', this.onDurationChange); + } + /** + * This is used to trigger a `progress` event when the buffered percent changes. It + * sets an interval function that will be called every 500 milliseconds to check if the + * buffer end percent has changed. + * + * > This function is called by {@link Tech#manualProgressOn} + * + * @param {EventTarget~Event} event + * The `ready` event that caused this to run. + * + * @listens Tech#ready + * @fires Tech#progress + */ + ; + + _proto.trackProgress = function trackProgress(event) { + this.stopTrackingProgress(); + this.progressInterval = this.setInterval(bind(this, function () { + // Don't trigger unless buffered amount is greater than last time + var numBufferedPercent = this.bufferedPercent(); + + if (this.bufferedPercent_ !== numBufferedPercent) { + /** + * See {@link Player#progress} + * + * @event Tech#progress + * @type {EventTarget~Event} + */ + this.trigger('progress'); + } + + this.bufferedPercent_ = numBufferedPercent; + + if (numBufferedPercent === 1) { + this.stopTrackingProgress(); + } + }), 500); + } + /** + * Update our internal duration on a `durationchange` event by calling + * {@link Tech#duration}. + * + * @param {EventTarget~Event} event + * The `durationchange` event that caused this to run. + * + * @listens Tech#durationchange + */ + ; + + _proto.onDurationChange = function onDurationChange(event) { + this.duration_ = this.duration(); + } + /** + * Get and create a `TimeRange` object for buffering. + * + * @return {TimeRange} + * The time range object that was created. + */ + ; + + _proto.buffered = function buffered() { + return createTimeRanges(0, 0); + } + /** + * Get the percentage of the current video that is currently buffered. + * + * @return {number} + * A number from 0 to 1 that represents the decimal percentage of the + * video that is buffered. + * + */ + ; + + _proto.bufferedPercent = function bufferedPercent$1() { + return bufferedPercent(this.buffered(), this.duration_); + } + /** + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + * Stop manually tracking progress events by clearing the interval that was set in + * {@link Tech#trackProgress}. + */ + ; + + _proto.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); + } + /** + * Polyfill the `timeupdate` event for browsers that don't support it. + * + * @see {@link Tech#trackCurrentTime} + */ + ; + + _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { + this.manualTimeUpdates = true; + this.on('play', this.trackCurrentTime); + this.on('pause', this.stopTrackingCurrentTime); + } + /** + * Turn off the polyfill for `timeupdate` events that was created in + * {@link Tech#manualTimeUpdatesOn} + */ + ; + + _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { + this.manualTimeUpdates = false; + this.stopTrackingCurrentTime(); + this.off('play', this.trackCurrentTime); + this.off('pause', this.stopTrackingCurrentTime); + } + /** + * Sets up an interval function to track current time and trigger `timeupdate` every + * 250 milliseconds. + * + * @listens Tech#play + * @triggers Tech#timeupdate + */ + ; + + _proto.trackCurrentTime = function trackCurrentTime() { + if (this.currentTimeInterval) { + this.stopTrackingCurrentTime(); + } + + this.currentTimeInterval = this.setInterval(function () { + /** + * Triggered at an interval of 250ms to indicated that time is passing in the video. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ + type: 'timeupdate', + target: this, + manuallyTriggered: true + }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 + }, 250); + } + /** + * Stop the interval function created in {@link Tech#trackCurrentTime} so that the + * `timeupdate` event is no longer triggered. + * + * @listens {Tech#pause} + */ + ; + + _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { + this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, + // the progress bar won't make it all the way to the end + + this.trigger({ + type: 'timeupdate', + target: this, + manuallyTriggered: true + }); + } + /** + * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, + * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. + * + * @fires Component#dispose + */ + ; + + _proto.dispose = function dispose() { + // clear out all tracks because we can't reuse them between techs + this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking + + if (this.manualProgress) { + this.manualProgressOff(); + } + + if (this.manualTimeUpdates) { + this.manualTimeUpdatesOff(); + } + + _Component.prototype.dispose.call(this); + } + /** + * Clear out a single `TrackList` or an array of `TrackLists` given their names. + * + * > Note: Techs without source handlers should call this between sources for `video` + * & `audio` tracks. You don't want to use them between tracks! + * + * @param {string[]|string} types + * TrackList names to clear, valid names are `video`, `audio`, and + * `text`. + */ + ; + + _proto.clearTracks = function clearTracks(types) { + var _this3 = this; + + types = [].concat(types); // clear out all tracks because we can't reuse them between techs + + types.forEach(function (type) { + var list = _this3[type + "Tracks"]() || []; + var i = list.length; + + while (i--) { + var track = list[i]; + + if (type === 'text') { + _this3.removeRemoteTextTrack(track); + } + + list.removeTrack(track); + } + }); + } + /** + * Remove any TextTracks added via addRemoteTextTrack that are + * flagged for automatic garbage collection + */ + ; + + _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { + var list = this.autoRemoteTextTracks_ || []; + var i = list.length; + + while (i--) { + var track = list[i]; + this.removeRemoteTextTrack(track); + } + } + /** + * Reset the tech, which will removes all sources and reset the internal readyState. + * + * @abstract + */ + ; + + _proto.reset = function reset() {} + /** + * Get the value of `crossOrigin` from the tech. + * + * @abstract + * + * @see {Html5#crossOrigin} + */ + ; + + _proto.crossOrigin = function crossOrigin() {} + /** + * Set the value of `crossOrigin` on the tech. + * + * @abstract + * + * @param {string} crossOrigin the crossOrigin value + * @see {Html5#setCrossOrigin} + */ + ; + + _proto.setCrossOrigin = function setCrossOrigin() {} + /** + * Get or set an error on the Tech. + * + * @param {MediaError} [err] + * Error to set on the Tech + * + * @return {MediaError|null} + * The current error object on the tech, or null if there isn't one. + */ + ; + + _proto.error = function error(err) { + if (err !== undefined) { + this.error_ = new MediaError(err); + this.trigger('error'); + } + + return this.error_; + } + /** + * Returns the `TimeRange`s that have been played through for the current source. + * + * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. + * It only checks whether the source has played at all or not. + * + * @return {TimeRange} + * - A single time range if this video has played + * - An empty set of ranges if not. + */ + ; + + _proto.played = function played() { + if (this.hasStarted_) { + return createTimeRanges(0, 0); + } + + return createTimeRanges(); + } + /** + * Set whether we are scrubbing or not + * + * @abstract + * + * @see {Html5#setScrubbing} + */ + ; + + _proto.setScrubbing = function setScrubbing() {} + /** + * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was + * previously called. + * + * @fires Tech#timeupdate + */ + ; + + _proto.setCurrentTime = function setCurrentTime() { + // improve the accuracy of manual timeupdates + if (this.manualTimeUpdates) { + /** + * A manual `timeupdate` event. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ + type: 'timeupdate', + target: this, + manuallyTriggered: true + }); + } + } + /** + * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and + * {@link TextTrackList} events. + * + * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. + * + * @fires Tech#audiotrackchange + * @fires Tech#videotrackchange + * @fires Tech#texttrackchange + */ + ; + + _proto.initTrackListeners = function initTrackListeners() { + var _this4 = this; + + /** + * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} + * + * @event Tech#audiotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} + * + * @event Tech#videotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link TextTrackList} + * + * @event Tech#texttrackchange + * @type {EventTarget~Event} + */ + NORMAL.names.forEach(function (name) { + var props = NORMAL[name]; + + var trackListChanges = function trackListChanges() { + _this4.trigger(name + "trackchange"); + }; + + var tracks = _this4[props.getterName](); + + tracks.addEventListener('removetrack', trackListChanges); + tracks.addEventListener('addtrack', trackListChanges); + + _this4.on('dispose', function () { + tracks.removeEventListener('removetrack', trackListChanges); + tracks.removeEventListener('addtrack', trackListChanges); + }); + }); + } + /** + * Emulate TextTracks using vtt.js if necessary + * + * @fires Tech#vttjsloaded + * @fires Tech#vttjserror + */ + ; + + _proto.addWebVttScript_ = function addWebVttScript_() { + var _this5 = this; + + if (window_1$1.WebVTT) { + return; + } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system + // signals that the Tech is ready at which point Tech.el_ is part of the DOM + // before inserting the WebVTT script + + + if (document_1.body.contains(this.el())) { + // load via require if available and vtt.js script location was not passed in + // as an option. novtt builds will turn the above require call into an empty object + // which will cause this if check to always fail. + if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { + this.trigger('vttjsloaded'); + return; + } // load vtt.js via the script location option or the cdn of no location was + // passed in + + + var script = document_1.createElement('script'); + script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; + + script.onload = function () { + /** + * Fired when vtt.js is loaded. + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this5.trigger('vttjsloaded'); + }; + + script.onerror = function () { + /** + * Fired when vtt.js was not loaded due to an error + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this5.trigger('vttjserror'); + }; + + this.on('dispose', function () { + script.onload = null; + script.onerror = null; + }); // but have not loaded yet and we set it to true before the inject so that + // we don't overwrite the injected window.WebVTT if it loads right away + + window_1$1.WebVTT = true; + this.el().parentNode.appendChild(script); + } else { + this.ready(this.addWebVttScript_); + } + } + /** + * Emulate texttracks + * + */ + ; + + _proto.emulateTextTracks = function emulateTextTracks() { + var _this6 = this; + + var tracks = this.textTracks(); + var remoteTracks = this.remoteTextTracks(); + + var handleAddTrack = function handleAddTrack(e) { + return tracks.addTrack(e.track); + }; + + var handleRemoveTrack = function handleRemoveTrack(e) { + return tracks.removeTrack(e.track); + }; + + remoteTracks.on('addtrack', handleAddTrack); + remoteTracks.on('removetrack', handleRemoveTrack); + this.addWebVttScript_(); + + var updateDisplay = function updateDisplay() { + return _this6.trigger('texttrackchange'); + }; + + var textTracksChanges = function textTracksChanges() { + updateDisplay(); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + track.removeEventListener('cuechange', updateDisplay); + + if (track.mode === 'showing') { + track.addEventListener('cuechange', updateDisplay); + } + } + }; + + textTracksChanges(); + tracks.addEventListener('change', textTracksChanges); + tracks.addEventListener('addtrack', textTracksChanges); + tracks.addEventListener('removetrack', textTracksChanges); + this.on('dispose', function () { + remoteTracks.off('addtrack', handleAddTrack); + remoteTracks.off('removetrack', handleRemoveTrack); + tracks.removeEventListener('change', textTracksChanges); + tracks.removeEventListener('addtrack', textTracksChanges); + tracks.removeEventListener('removetrack', textTracksChanges); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + track.removeEventListener('cuechange', updateDisplay); + } + }); + } + /** + * Create and returns a remote {@link TextTrack} object. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @return {TextTrack} + * The TextTrack that gets created. + */ + ; + + _proto.addTextTrack = function addTextTrack(kind, label, language) { + if (!kind) { + throw new Error('TextTrack kind is required but was not provided'); + } + + return createTrackHelper(this, kind, label, language); + } + /** + * Create an emulated TextTrack for use by addRemoteTextTrack + * + * This is intended to be overridden by classes that inherit from + * Tech in order to create native or custom TextTracks. + * + * @param {Object} options + * The object should contain the options to initialize the TextTrack with. + * + * @param {string} [options.kind] + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). + * + * @param {string} [options.label]. + * Label to identify the text track + * + * @param {string} [options.language] + * Two letter language abbreviation. + * + * @return {HTMLTrackElement} + * The track element that gets created. + */ + ; + + _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { + var track = mergeOptions(options, { + tech: this + }); + return new REMOTE.remoteTextEl.TrackClass(track); + } + /** + * Creates a remote text track object and returns an html track element. + * + * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. + * + * @param {Object} options + * See {@link Tech#createRemoteTextTrack} for more detailed properties. + * + * @param {boolean} [manualCleanup=true] + * - When false: the TextTrack will be automatically removed from the video + * element whenever the source changes + * - When True: The TextTrack will have to be cleaned up manually + * + * @return {HTMLTrackElement} + * An Html Track Element. + * + * @deprecated The default functionality for this function will be equivalent + * to "manualCleanup=false" in the future. The manualCleanup parameter will + * also be removed. + */ + ; + + _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { + var _this7 = this; + + if (options === void 0) { + options = {}; + } + + var htmlTrackElement = this.createRemoteTextTrack(options); + + if (manualCleanup !== true && manualCleanup !== false) { + // deprecation warning + log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); + manualCleanup = true; + } // store HTMLTrackElement and TextTrack to remote list + + + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack(htmlTrackElement.track); + + if (manualCleanup !== true) { + // create the TextTrackList if it doesn't exist + this.ready(function () { + return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); + }); + } + + return htmlTrackElement; + } + /** + * Remove a remote text track from the remote `TextTrackList`. + * + * @param {TextTrack} track + * `TextTrack` to remove from the `TextTrackList` + */ + ; + + _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list + + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack(track); + this.autoRemoteTextTracks_.removeTrack(track); + } + /** + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object} + * An object with supported media playback quality metrics + * + * @abstract + */ + ; + + _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return {}; + } + /** + * Attempt to create a floating video window always on top of other windows + * so that users may continue consuming media while they interact with other + * content sites, or applications on their device. + * + * @see [Spec]{@link https://wicg.github.io/picture-in-picture} + * + * @return {Promise|undefined} + * A promise with a Picture-in-Picture window if the browser supports + * Promises (or one was passed in as an option). It returns undefined + * otherwise. + * + * @abstract + */ + ; + + _proto.requestPictureInPicture = function requestPictureInPicture() { + var PromiseClass = this.options_.Promise || window_1$1.Promise; + + if (PromiseClass) { + return PromiseClass.reject(); + } + } + /** + * A method to check for the value of the 'disablePictureInPicture'