commit ae3224c18bd336e320dd71f9d96c72ee97117bf9 Author: creations Date: Sun Jun 8 17:17:18 2025 -0400 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..980ef21 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c03b3f2 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# NODE_ENV=development +HOST=0.0.0.0 +PORT=8080 + +SRS_URL= diff --git a/.forgejo/workflows/biomejs.yml b/.forgejo/workflows/biomejs.yml new file mode 100644 index 0000000..15c990c --- /dev/null +++ b/.forgejo/workflows/biomejs.yml @@ -0,0 +1,24 @@ +name: Code quality checks + +on: + push: + pull_request: + +jobs: + biome: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Bun + run: | + curl -fsSL https://bun.sh/install | bash + export BUN_INSTALL="$HOME/.bun" + echo "$BUN_INSTALL/bin" >> $GITHUB_PATH + + - name: Install Dependencies + run: bun install + + - name: Run Biome with verbose output + run: bunx biome ci . --verbose diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ca3be7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/node_modules +bun.lock +logs +public/custom +.env diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d93a942 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2025, creations.works + +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. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..279ed14 --- /dev/null +++ b/biome.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": true, + "ignore": ["dist", "public/js/flv.min.js", "public/js/flv.min.js.map"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineEnding": "lf" + }, + "organizeImports": { + "enabled": true + }, + "css": { + "formatter": { + "indentStyle": "tab", + "lineEnding": "lf" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "error", + "noUnusedVariables": "error" + }, + "style": { + "useConst": "error", + "noVar": "error" + } + }, + "ignore": ["types"] + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "indentStyle": "tab", + "lineEnding": "lf", + "jsxQuoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..cb3f09b --- /dev/null +++ b/config/index.ts @@ -0,0 +1,30 @@ +import { echo } from "@atums/echo"; + +const environment: Environment = { + port: Number.parseInt(process.env.PORT || "8080", 10), + host: process.env.HOST || "0.0.0.0", + development: + process.env.NODE_ENV === "development" || process.argv.includes("--dev"), +}; + +const srsUrl = process.env.SRS_URL; + +function verifyRequiredVariables(): void { + const requiredVariables = ["HOST", "PORT", "SRS_URL"]; + + let hasError = false; + + for (const key of requiredVariables) { + const value = process.env[key]; + if (value === undefined || value.trim() === "") { + echo.error(`Missing or empty environment variable: ${key}`); + hasError = true; + } + } + + if (hasError) { + process.exit(1); + } +} + +export { environment, verifyRequiredVariables, srsUrl }; diff --git a/logger.json b/logger.json new file mode 100644 index 0000000..521b3bc --- /dev/null +++ b/logger.json @@ -0,0 +1,39 @@ +{ + "directory": "logs", + "level": "debug", + "disableFile": false, + + "rotate": true, + "maxFiles": 3, + + "console": true, + "consoleColor": true, + + "dateFormat": "yyyy-MM-dd HH:mm:ss.SSS", + "timezone": "local", + + "silent": false, + + "pattern": "{color:gray}{pretty-timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{color:blue}{line}{reset}:{color:blue}{column}{color:gray}){reset} {data}", + "levelColor": { + "debug": "blue", + "info": "green", + "warn": "yellow", + "error": "red", + "fatal": "red" + }, + + "customPattern": "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}", + "customColors": { + "GET": "green", + "POST": "blue", + "PUT": "yellow", + "DELETE": "red", + "PATCH": "cyan", + "HEAD": "magenta", + "OPTIONS": "white", + "TRACE": "gray" + }, + + "prettyPrint": true +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fd0cd43 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "bun_frontend_template", + "module": "src/index.ts", + "type": "module", + "scripts": { + "start": "bun run src/index.ts", + "dev": "bun run --hot src/index.ts --dev", + "lint": "bunx biome check", + "lint:fix": "bunx biome check --fix", + "cleanup": "rm -rf logs node_modules bun.lockdb" + }, + "devDependencies": { + "@types/bun": "latest", + "@biomejs/biome": "latest" + }, + "peerDependencies": { + "typescript": "latest" + }, + "dependencies": { + "@atums/echo": "latest" + } +} diff --git a/public/assets/favicon.ico b/public/assets/favicon.ico new file mode 100644 index 0000000..69ec50d Binary files /dev/null and b/public/assets/favicon.ico differ diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..993233a --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,327 @@ +:root[data-theme="dark"] { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #1f2937; + --bg-card: rgba(22, 27, 34, 0.95); + --bg-card-hover: rgba(31, 38, 49, 0.95); + --bg-input: rgba(22, 27, 34, 0.85); + --bg-stat: rgba(31, 38, 49, 0.75); + + --border-primary: #30363d; + --border-secondary: #444c56; + --border-accent: #58a6ff; + + --text-primary: #c9d1d9; + --text-secondary: #8b949e; + --text-muted: #6e7681; + --text-accent: #58a6ff; + + --accent-blue: #58a6ff; + --accent-purple: #a371f7; + --accent-green: #3fb950; + --accent-red: #f85149; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3); +} + + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +header { + text-align: center; + margin-bottom: 2rem; +} + +h1 { + font-size: 3rem; + font-weight: 700; + color: var(--accent-blue); + margin-bottom: 1rem; +} + +.search-container { + max-width: 400px; + margin: 0 auto; +} + +.search-input { + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 2px solid var(--border-primary); + border-radius: 0.5rem; + background: var(--bg-input); + color: var(--text-primary); + backdrop-filter: blur(10px); + transition: all 0.3s ease; +} + +.search-input:focus { + outline: none; + border-color: var(--accent-blue); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.search-input::placeholder { + color: var(--text-secondary); +} + +.stats { + display: flex; + justify-content: center; + gap: 2rem; + margin-bottom: 2rem; +} + +.stat-item { + background: var(--bg-stat); + padding: 1rem 2rem; + border-radius: 0.5rem; + border: 1px solid var(--border-primary); + backdrop-filter: blur(10px); +} + +.stat-label { + color: var(--text-secondary); + font-size: 0.875rem; +} + +.stat-value { + color: var(--text-accent); + font-size: 1.25rem; + font-weight: 600; + margin-left: 0.5rem; +} + +.loading { + text-align: center; + padding: 3rem; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid var(--border-primary); + border-top: 4px solid var(--accent-blue); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 1rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.error-state { + text-align: center; + padding: 3rem; +} + +.error-state p { + color: var(--accent-red); + margin-bottom: 1rem; +} + +.retry-btn { + background: var(--accent-blue); + color: var(--text-primary); + border: none; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.retry-btn:hover { + background: #2563eb; + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.streams-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.stream-card { + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 0.75rem; + padding: 1.5rem; + backdrop-filter: blur(15px); + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.stream-card:hover { + transform: translateY(-4px); + background: var(--bg-card-hover); + box-shadow: var(--shadow-xl); + border-color: var(--border-accent); +} + +.stream-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.stream-header-actions { + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; + margin-left: 1rem; +} + +.watch-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 24px; + background: var(--accent-green); + border: 1px solid rgba(34, 197, 94, 0.3); + border-radius: 0.375rem; + color: white; + text-decoration: none; + transition: all 0.2s ease; + opacity: 0.9; +} + +.watch-btn:hover { + opacity: 1; + background: #16a34a; + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} + +.stream-name { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; + word-break: break-word; +} + +.stream-info { + display: grid; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.info-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +.info-label { + color: var(--text-secondary); + font-size: 0.875rem; +} + +.info-value { + color: var(--text-primary); + font-size: 0.875rem; + font-weight: 500; + text-align: right; + word-break: break-all; +} + +.stream-url { + background: rgba(59, 130, 246, 0.08); + border: 1px solid rgba(59, 130, 246, 0.2); + border-radius: 0.375rem; + padding: 0.5rem; + font-family: "SF Mono", "Monaco", "Menlo", "Ubuntu Mono", monospace; + font-size: 0.75rem; + color: #93c5fd; + word-break: break-all; + cursor: pointer; + transition: all 0.2s ease; + position: relative; +} + +.stream-watch-link { + text-decoration: none; + color: inherit; + text-align: center; +} + +.stream-url:hover { + background: rgba(59, 130, 246, 0.12); + border-color: rgba(59, 130, 246, 0.4); + transform: translateY(-1px); +} + +.stream-url:active { + transform: translateY(0); +} + +.no-results { + text-align: center; + padding: 3rem; + color: var(--text-secondary); +} + +@media (max-width: 768px) { + .container { + padding: 1rem; + } + + h1 { + font-size: 2rem; + } + + .stats { + flex-direction: column; + align-items: center; + gap: 1rem; + } + + .streams-grid { + grid-template-columns: 1fr; + } + + .stream-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .stream-header-actions { + margin-left: 0; + align-self: flex-end; + } +} diff --git a/public/css/watch.css b/public/css/watch.css new file mode 100644 index 0000000..30aff68 --- /dev/null +++ b/public/css/watch.css @@ -0,0 +1,346 @@ +.watch-container { + max-width: 1400px; + margin: 0 auto; + padding: 1rem; +} + +.watch-header { + margin-bottom: 1.5rem; +} + +.header-content { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; +} + +.back-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 0.5rem; + color: var(--text-primary); + text-decoration: none; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} + +.back-btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-accent); + transform: translateY(-1px); +} + +.watch-header h1 { + font-size: 1.75rem; + margin: 0; + text-align: center; +} + +.stream-controls { + display: flex; + gap: 0.5rem; +} + +.control-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 0.5rem; + color: var(--text-primary); + cursor: pointer; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} + +.control-btn:hover { + background: var(--bg-card-hover); + border-color: var(--border-accent); + transform: translateY(-1px); +} + +.video-container { + position: relative; + background: var(--bg-card); + overflow: hidden; + margin-bottom: 1rem; + aspect-ratio: 16 / 9; + padding: 0; +} + +.video-player { + width: 100%; + height: 100%; + object-fit: contain; + background: #000; +} + +.loading-overlay, +.error-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--bg-card); + backdrop-filter: blur(15px); + z-index: 10; +} + +.error-content { + text-align: center; + max-width: 400px; + padding: 2rem; +} + +.error-content svg { + color: var(--accent-red); + margin-bottom: 1rem; +} + +.error-content h3 { + color: var(--accent-red); + margin-bottom: 0.5rem; +} + +.error-content p { + color: var(--text-secondary); + margin-bottom: 1.5rem; +} + +.stream-status-container { + display: flex; + justify-content: space-between; + align-items: center; + justify-content: flex-end; + margin-bottom: 2rem; + gap: 1rem; + flex-wrap: wrap; +} + +.stream-status { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 9999px; + backdrop-filter: blur(10px); +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.status-text { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-primary); +} + +.stream-stats { + display: flex; + gap: 1rem; + padding: 0.75rem 1rem; + background: var(--bg-card); + border: .5px solid var(--border-primary); + border-radius: 1rem; + backdrop-filter: blur(10px); +} + +.stat { + display: flex; + flex-direction: column; + align-items: center; + min-width: 60px; +} + +.stat-label { + font-size: 0.75rem; + color: var(--text-secondary); +} + +.stat-value { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-primary); +} + +.stream-info-panel { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; +} + +.info-section { + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 1rem; + padding: 1.5rem; + backdrop-filter: blur(15px); +} + +.info-section h3 { + color: var(--text-primary); + margin-bottom: 1rem; + font-size: 1.125rem; +} + +.info-grid { + display: grid; + gap: 1rem; +} + +.info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border-primary); +} + +.info-item:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.info-label { + color: var(--text-secondary); + font-size: 0.875rem; + font-weight: 500; +} + +.info-value { + color: var(--text-primary); + font-size: 0.875rem; + font-weight: 600; + text-align: right; +} + +.url-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.url-item { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.url-label { + color: var(--text-secondary); + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.url-value { + background: rgba(59, 130, 246, 0.08); + border: 1px solid rgba(59, 130, 246, 0.2); + border-radius: 0.375rem; + padding: 0.5rem; + font-family: "SF Mono", "Monaco", "Menlo", "Ubuntu Mono", monospace; + font-size: 0.75rem; + color: #93c5fd; + word-break: break-all; + cursor: pointer; + transition: all 0.2s ease; +} + +.url-value:hover { + background: rgba(59, 130, 246, 0.12); + border-color: rgba(59, 130, 246, 0.4); + transform: translateY(-1px); +} + +.url-value:active { + transform: translateY(0); +} + +@media (max-width: 768px) { + .watch-container { + padding: 0.5rem; + } + + .header-content { + flex-direction: column; + align-items: stretch; + text-align: center; + } + + .watch-header h1 { + font-size: 1.5rem; + order: -1; + } + + .video-container { + aspect-ratio: 16 / 10; + margin-bottom: 1rem; + } + + .stream-status-container { + flex-direction: column; + align-items: stretch; + } + + .stream-stats { + justify-content: center; + } + + .stat { + flex-direction: row; + gap: 0.5rem; + min-width: auto; + } + + .stream-info-panel { + grid-template-columns: 1fr; + gap: 1rem; + } + + .info-section { + padding: 1rem; + } +} + +@media (max-width: 480px) { + .stream-controls { + width: 100%; + justify-content: center; + } + + .stream-stats { + flex-direction: column; + gap: 0.5rem; + align-items: flex-start; + } + + .stat { + flex-direction: row; + gap: 0.5rem; + min-width: auto; + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..6b01da1 --- /dev/null +++ b/public/index.html @@ -0,0 +1,51 @@ + + + + + + + Stream Dashboard + + + + + + +
+
+

Live Streams

+
+ +
+
+ +
+
+ Total Streams: + 0 +
+
+ Online: + 0 +
+
+ +
+
+

Loading streams...

+
+ + + + + + +
+ + + \ No newline at end of file diff --git a/public/js/flv.min.js b/public/js/flv.min.js new file mode 100644 index 0000000..c5010fb --- /dev/null +++ b/public/js/flv.min.js @@ -0,0 +1,10 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.flvjs=t():e.flvjs=t()}(self,(function(){return function(){var e={264:function(e,t,i){ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE + * @version v4.2.8+1e68dce6 + */ +e.exports=function(){"use strict";function e(e){var t=typeof e;return null!==e&&("object"===t||"function"===t)}function t(e){return"function"==typeof e}var n=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},r=0,s=void 0,o=void 0,a=function(e,t){b[r]=e,b[r+1]=t,2===(r+=2)&&(o?o(E):A())};function h(e){o=e}function u(e){a=e}var l="undefined"!=typeof window?window:void 0,d=l||{},c=d.MutationObserver||d.WebKitMutationObserver,f="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),_="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel;function p(){return function(){return process.nextTick(E)}}function m(){return void 0!==s?function(){s(E)}:y()}function g(){var e=0,t=new c(E),i=document.createTextNode("");return t.observe(i,{characterData:!0}),function(){i.data=e=++e%2}}function v(){var e=new MessageChannel;return e.port1.onmessage=E,function(){return e.port2.postMessage(0)}}function y(){var e=setTimeout;return function(){return e(E,1)}}var b=new Array(1e3);function E(){for(var e=0;e0&&o.length>r&&!o.warned){o.warned=!0;var l=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");l.name="MaxListenersExceededWarning",l.emitter=e,l.type=t,l.count=o.length,u=l,console&&console.warn&&console.warn(u)}return e}function l(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function d(e,t,i){var n={fired:!1,wrapFn:void 0,target:e,type:t,listener:i},r=l.bind(n);return r.listener=i,n.wrapFn=r,r}function c(e,t,i){var n=e._events;if(void 0===n)return[];var r=n[t];return void 0===r?[]:"function"==typeof r?i?[r.listener||r]:[r]:i?function(e){for(var t=new Array(e.length),i=0;i0&&(o=t[0]),o instanceof Error)throw o;var a=new Error("Unhandled error."+(o?" ("+o.message+")":""));throw a.context=o,a}var h=s[e];if(void 0===h)return!1;if("function"==typeof h)n(h,this,t);else{var u=h.length,l=_(h,u);for(i=0;i=0;s--)if(i[s]===t||i[s].listener===t){o=i[s].listener,r=s;break}if(r<0)return this;0===r?i.shift():function(e,t){for(;t+1=0;n--)this.removeListener(e,t[n]);return this},s.prototype.listeners=function(e){return c(this,e,!0)},s.prototype.rawListeners=function(e){return c(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):f.call(e,t)},s.prototype.listenerCount=f,s.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},397:function(e,t,i){function n(e){var t={};function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=t,i.i=function(e){return e},i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/",i.oe=function(e){throw console.error(e),e};var n=i(i.s=ENTRY_MODULE);return n.default||n}var r="[\\.|\\-|\\+|\\w|/|@]+",s="\\(\\s*(/\\*.*?\\*/)?\\s*.*?([\\.|\\-|\\+|\\w|/|@]+).*?\\)";function o(e){return(e+"").replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}function a(e,t,n){var a={};a[n]=[];var h=t.toString(),u=h.match(/^function\s?\w*\(\w+,\s*\w+,\s*(\w+)\)/);if(!u)return a;for(var l,d=u[1],c=new RegExp("(\\\\n|\\W)"+o(d)+s,"g");l=c.exec(h);)"dll-reference"!==l[3]&&a[n].push(l[3]);for(c=new RegExp("\\("+o(d)+'\\("(dll-reference\\s('+r+'))"\\)\\)'+s,"g");l=c.exec(h);)e[l[2]]||(a[n].push(l[1]),e[l[2]]=i(l[1]).m),a[l[2]]=a[l[2]]||[],a[l[2]].push(l[4]);for(var f,_=Object.keys(a),p=0;p<_.length;p++)for(var m=0;m0}),!1)}e.exports=function(e,t){t=t||{};var r={main:i.m},s=t.all?{main:Object.keys(r.main)}:function(e,t){for(var i={main:[t]},n={main:[]},r={main:{}};h(i);)for(var s=Object.keys(i),o=0;o=e[r]&&t0&&e[0].originalDts=t[r].dts&&et[n].lastSample.originalDts&&e=t[n].lastSample.originalDts&&(n===t.length-1||n0&&(r=this._searchNearestSegmentBefore(i.originalBeginDts)+1),this._lastAppendLocation=r,this._list.splice(r,0,i)},e.prototype.getLastSegmentBefore=function(e){var t=this._searchNearestSegmentBefore(e);return t>=0?this._list[t]:null},e.prototype.getLastSampleBefore=function(e){var t=this.getLastSegmentBefore(e);return null!=t?t.lastSample:null},e.prototype.getLastSyncPointBefore=function(e){for(var t=this._searchNearestSegmentBefore(e),i=this._list[t].syncPoints;0===i.length&&t>0;)t--,i=this._list[t].syncPoints;return i.length>0?i[i.length-1]:null},e}()},949:function(e,t,i){"use strict";i.d(t,{Z:function(){return R}});var n=i(716),r=i.n(n),s=i(300),o=i(538),a=i(118);function h(e,t,i){var n=e;if(t+i=128){t.push(String.fromCharCode(65535&s)),n+=2;continue}}else if(i[n]<240){if(h(i,n,2))if((s=(15&i[n])<<12|(63&i[n+1])<<6|63&i[n+2])>=2048&&55296!=(63488&s)){t.push(String.fromCharCode(65535&s)),n+=3;continue}}else if(i[n]<248){var s;if(h(i,n,3))if((s=(7&i[n])<<18|(63&i[n+1])<<12|(63&i[n+2])<<6|63&i[n+3])>65536&&s<1114112){s-=65536,t.push(String.fromCharCode(s>>>10|55296)),t.push(String.fromCharCode(1023&s|56320)),n+=4;continue}}t.push(String.fromCharCode(65533)),++n}return t.join("")},d=i(29),c=(u=new ArrayBuffer(2),new DataView(u).setInt16(0,256,!0),256===new Int16Array(u)[0]),f=function(){function e(){}return e.parseScriptData=function(t,i,n){var r={};try{var o=e.parseValue(t,i,n),a=e.parseValue(t,i+o.size,n-o.size);r[o.data]=a.data}catch(e){s.Z.e("AMF",e.toString())}return r},e.parseObject=function(t,i,n){if(n<3)throw new d.rT("Data not enough when parse ScriptDataObject");var r=e.parseString(t,i,n),s=e.parseValue(t,i+r.size,n-r.size),o=s.objectEnd;return{data:{name:r.data,value:s.data},size:r.size+s.size,objectEnd:o}},e.parseVariable=function(t,i,n){return e.parseObject(t,i,n)},e.parseString=function(e,t,i){if(i<2)throw new d.rT("Data not enough when parse String");var n=new DataView(e,t,i).getUint16(0,!c);return{data:n>0?l(new Uint8Array(e,t+2,n)):"",size:2+n}},e.parseLongString=function(e,t,i){if(i<4)throw new d.rT("Data not enough when parse LongString");var n=new DataView(e,t,i).getUint32(0,!c);return{data:n>0?l(new Uint8Array(e,t+4,n)):"",size:4+n}},e.parseDate=function(e,t,i){if(i<10)throw new d.rT("Data size invalid when parse Date");var n=new DataView(e,t,i),r=n.getFloat64(0,!c),s=n.getInt16(8,!c);return{data:new Date(r+=60*s*1e3),size:10}},e.parseValue=function(t,i,n){if(n<1)throw new d.rT("Data not enough when parse Value");var r,o=new DataView(t,i,n),a=1,h=o.getUint8(0),u=!1;try{switch(h){case 0:r=o.getFloat64(1,!c),a+=8;break;case 1:r=!!o.getUint8(1),a+=1;break;case 2:var l=e.parseString(t,i+1,n-1);r=l.data,a+=l.size;break;case 3:r={};var f=0;for(9==(16777215&o.getUint32(n-4,!c))&&(f=3);a32)throw new d.OC("ExpGolomb: readBits() bits exceeded max 32bits!");if(e<=this._current_word_bits_left){var t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}var i=this._current_word_bits_left?this._current_word:0;i>>>=32-this._current_word_bits_left;var n=e-this._current_word_bits_left;this._fillCurrentWord();var r=Math.min(n,this._current_word_bits_left),s=this._current_word>>>32-r;return this._current_word<<=r,this._current_word_bits_left-=r,i=i<>>e))return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()},e.prototype.readUEG=function(){var e=this._skipLeadingZero();return this.readBits(e+1)-1},e.prototype.readSEG=function(){var e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)},e}(),p=function(){function e(){}return e._ebsp2rbsp=function(e){for(var t=e,i=t.byteLength,n=new Uint8Array(i),r=0,s=0;s=2&&3===t[s]&&0===t[s-1]&&0===t[s-2]||(n[r]=t[s],r++);return new Uint8Array(n.buffer,0,r)},e.parseSPS=function(t){var i=e._ebsp2rbsp(t),n=new _(i);n.readByte();var r=n.readByte();n.readByte();var s=n.readByte();n.readUEG();var o=e.getProfileString(r),a=e.getLevelString(s),h=1,u=420,l=8;if((100===r||110===r||122===r||244===r||44===r||83===r||86===r||118===r||128===r||138===r||144===r)&&(3===(h=n.readUEG())&&n.readBits(1),h<=3&&(u=[0,420,422,444][h]),l=n.readUEG()+8,n.readUEG(),n.readBits(1),n.readBool()))for(var d=3!==h?8:12,c=0;c0&&k<16?(L=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2][k-1],R=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1][k-1]):255===k&&(L=n.readByte()<<8|n.readByte(),R=n.readByte()<<8|n.readByte())}if(n.readBool()&&n.readBool(),n.readBool()&&(n.readBits(4),n.readBool()&&n.readBits(24)),n.readBool()&&(n.readUEG(),n.readUEG()),n.readBool()){var D=n.readBits(32),I=n.readBits(32);O=n.readBool(),w=(T=I)/(C=2*D)}}var M=1;1===L&&1===R||(M=L/R);var B=0,x=0;0===h?(B=1,x=2-y):(B=3===h?1:2,x=(1===h?2:1)*(2-y));var P=16*(g+1),U=16*(v+1)*(2-y);P-=(b+E)*B,U-=(S+A)*x;var N=Math.ceil(P*M);return n.destroy(),n=null,{profile_string:o,level_string:a,bit_depth:l,ref_frames:m,chroma_format:u,chroma_format_string:e.getChromaFormatString(u),frame_rate:{fixed:O,fps:w,fps_den:C,fps_num:T},sar_ratio:{width:L,height:R},codec_size:{width:P,height:U},present_size:{width:N,height:U}}},e._skipScalingList=function(e,t){for(var i=8,n=8,r=0;r>>2!=0,o=0!=(1&t[4]),a=(n=t)[r=5]<<24|n[r+1]<<16|n[r+2]<<8|n[r+3];return a<9?i:{match:!0,consumed:a,dataOffset:a,hasAudioTrack:s,hasVideoTrack:o}},e.prototype.bindDataSource=function(e){return e.onDataArrival=this.parseChunks.bind(this),this},Object.defineProperty(e.prototype,"onTrackMetadata",{get:function(){return this._onTrackMetadata},set:function(e){this._onTrackMetadata=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaInfo",{get:function(){return this._onMediaInfo},set:function(e){this._onMediaInfo=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMetaDataArrived",{get:function(){return this._onMetaDataArrived},set:function(e){this._onMetaDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onScriptDataArrived",{get:function(){return this._onScriptDataArrived},set:function(e){this._onScriptDataArrived=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataAvailable",{get:function(){return this._onDataAvailable},set:function(e){this._onDataAvailable=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"timestampBase",{get:function(){return this._timestampBase},set:function(e){this._timestampBase=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedDuration",{get:function(){return this._duration},set:function(e){this._durationOverrided=!0,this._duration=e,this._mediaInfo.duration=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasAudio",{set:function(e){this._hasAudioFlagOverrided=!0,this._hasAudio=e,this._mediaInfo.hasAudio=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"overridedHasVideo",{set:function(e){this._hasVideoFlagOverrided=!0,this._hasVideo=e,this._mediaInfo.hasVideo=e},enumerable:!1,configurable:!0}),e.prototype.resetMediaInfo=function(){this._mediaInfo=new a.Z},e.prototype._isInitialMetadataDispatched=function(){return this._hasAudio&&this._hasVideo?this._audioInitialMetadataDispatched&&this._videoInitialMetadataDispatched:this._hasAudio&&!this._hasVideo?this._audioInitialMetadataDispatched:!(this._hasAudio||!this._hasVideo)&&this._videoInitialMetadataDispatched},e.prototype.parseChunks=function(t,i){if(!(this._onError&&this._onMediaInfo&&this._onTrackMetadata&&this._onDataAvailable))throw new d.rT("Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified");var n=0,r=this._littleEndian;if(0===i){if(!(t.byteLength>13))return 0;n=e.probe(t).dataOffset}this._firstParse&&(this._firstParse=!1,i+n!==this._dataOffset&&s.Z.w(this.TAG,"First time parsing but chunk byteStart invalid!"),0!==(o=new DataView(t,n)).getUint32(0,!r)&&s.Z.w(this.TAG,"PrevTagSize0 !== 0 !!!"),n+=4);for(;nt.byteLength)break;var a=o.getUint8(0),h=16777215&o.getUint32(0,!r);if(n+11+h+4>t.byteLength)break;if(8===a||9===a||18===a){var u=o.getUint8(4),l=o.getUint8(5),c=o.getUint8(6)|l<<8|u<<16|o.getUint8(7)<<24;0!==(16777215&o.getUint32(7,!r))&&s.Z.w(this.TAG,"Meet tag which has StreamID != 0!");var f=n+11;switch(a){case 8:this._parseAudioData(t,f,h,c);break;case 9:this._parseVideoData(t,f,h,c,i+n);break;case 18:this._parseScriptData(t,f,h)}var _=o.getUint32(11+h,!r);_!==11+h&&s.Z.w(this.TAG,"Invalid PrevTagSize "+_),n+=11+h+4}else s.Z.w(this.TAG,"Unsupported tag type "+a+", skipped"),n+=11+h+4}return this._isInitialMetadataDispatched()&&this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack),n},e.prototype._parseScriptData=function(e,t,i){var n=f.parseScriptData(e,t,i);if(n.hasOwnProperty("onMetaData")){if(null==n.onMetaData||"object"!=typeof n.onMetaData)return void s.Z.w(this.TAG,"Invalid onMetaData structure!");this._metadata&&s.Z.w(this.TAG,"Found another onMetaData tag!"),this._metadata=n;var r=this._metadata.onMetaData;if(this._onMetaDataArrived&&this._onMetaDataArrived(Object.assign({},r)),"boolean"==typeof r.hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=r.hasAudio,this._mediaInfo.hasAudio=this._hasAudio),"boolean"==typeof r.hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=r.hasVideo,this._mediaInfo.hasVideo=this._hasVideo),"number"==typeof r.audiodatarate&&(this._mediaInfo.audioDataRate=r.audiodatarate),"number"==typeof r.videodatarate&&(this._mediaInfo.videoDataRate=r.videodatarate),"number"==typeof r.width&&(this._mediaInfo.width=r.width),"number"==typeof r.height&&(this._mediaInfo.height=r.height),"number"==typeof r.duration){if(!this._durationOverrided){var o=Math.floor(r.duration*this._timescale);this._duration=o,this._mediaInfo.duration=o}}else this._mediaInfo.duration=0;if("number"==typeof r.framerate){var a=Math.floor(1e3*r.framerate);if(a>0){var h=a/1e3;this._referenceFrameRate.fixed=!0,this._referenceFrameRate.fps=h,this._referenceFrameRate.fps_num=a,this._referenceFrameRate.fps_den=1e3,this._mediaInfo.fps=h}}if("object"==typeof r.keyframes){this._mediaInfo.hasKeyframesIndex=!0;var u=r.keyframes;this._mediaInfo.keyframesIndex=this._parseKeyframesIndex(u),r.keyframes=null}else this._mediaInfo.hasKeyframesIndex=!1;this._dispatch=!1,this._mediaInfo.metadata=r,s.Z.v(this.TAG,"Parsed onMetaData"),this._mediaInfo.isComplete()&&this._onMediaInfo(this._mediaInfo)}Object.keys(n).length>0&&this._onScriptDataArrived&&this._onScriptDataArrived(Object.assign({},n))},e.prototype._parseKeyframesIndex=function(e){for(var t=[],i=[],n=1;n>>4;if(2===o||10===o){var a=0,h=(12&r)>>>2;if(h>=0&&h<=4){a=this._flvSoundRateTable[h];var u=1&r,l=this._audioMetadata,d=this._audioTrack;if(l||(!1===this._hasAudio&&!1===this._hasAudioFlagOverrided&&(this._hasAudio=!0,this._mediaInfo.hasAudio=!0),(l=this._audioMetadata={}).type="audio",l.id=d.id,l.timescale=this._timescale,l.duration=this._duration,l.audioSampleRate=a,l.channelCount=0===u?1:2),10===o){var c=this._parseAACAudioData(e,t+1,i-1);if(null==c)return;if(0===c.packetType){l.config&&s.Z.w(this.TAG,"Found another AudioSpecificConfig!");var f=c.data;l.audioSampleRate=f.samplingRate,l.channelCount=f.channelCount,l.codec=f.codec,l.originalCodec=f.originalCodec,l.config=f.config,l.refSampleDuration=1024/l.audioSampleRate*l.timescale,s.Z.v(this.TAG,"Parsed AudioSpecificConfig"),this._isInitialMetadataDispatched()?this._dispatch&&(this._audioTrack.length||this._videoTrack.length)&&this._onDataAvailable(this._audioTrack,this._videoTrack):this._audioInitialMetadataDispatched=!0,this._dispatch=!1,this._onTrackMetadata("audio",l),(g=this._mediaInfo).audioCodec=l.originalCodec,g.audioSampleRate=l.audioSampleRate,g.audioChannelCount=l.channelCount,g.hasVideo?null!=g.videoCodec&&(g.mimeType='video/x-flv; codecs="'+g.videoCodec+","+g.audioCodec+'"'):g.mimeType='video/x-flv; codecs="'+g.audioCodec+'"',g.isComplete()&&this._onMediaInfo(g)}else if(1===c.packetType){var _=this._timestampBase+n,p={unit:c.data,length:c.data.byteLength,dts:_,pts:_};d.samples.push(p),d.length+=c.data.length}else s.Z.e(this.TAG,"Flv: Unsupported AAC data type "+c.packetType)}else if(2===o){if(!l.codec){var g;if(null==(f=this._parseMP3AudioData(e,t+1,i-1,!0)))return;l.audioSampleRate=f.samplingRate,l.channelCount=f.channelCount,l.codec=f.codec,l.originalCodec=f.originalCodec,l.refSampleDuration=1152/l.audioSampleRate*l.timescale,s.Z.v(this.TAG,"Parsed MPEG Audio Frame Header"),this._audioInitialMetadataDispatched=!0,this._onTrackMetadata("audio",l),(g=this._mediaInfo).audioCodec=l.codec,g.audioSampleRate=l.audioSampleRate,g.audioChannelCount=l.channelCount,g.audioDataRate=f.bitRate,g.hasVideo?null!=g.videoCodec&&(g.mimeType='video/x-flv; codecs="'+g.videoCodec+","+g.audioCodec+'"'):g.mimeType='video/x-flv; codecs="'+g.audioCodec+'"',g.isComplete()&&this._onMediaInfo(g)}var v=this._parseMP3AudioData(e,t+1,i-1,!1);if(null==v)return;_=this._timestampBase+n;var y={unit:v,length:v.byteLength,dts:_,pts:_};d.samples.push(y),d.length+=v.length}}else this._onError(m.Z.FORMAT_ERROR,"Flv: Invalid audio sample rate idx: "+h)}else this._onError(m.Z.CODEC_UNSUPPORTED,"Flv: Unsupported audio codec idx: "+o)}},e.prototype._parseAACAudioData=function(e,t,i){if(!(i<=1)){var n={},r=new Uint8Array(e,t,i);return n.packetType=r[0],0===r[0]?n.data=this._parseAACAudioSpecificConfig(e,t+1,i-1):n.data=r.subarray(1),n}s.Z.w(this.TAG,"Flv: Invalid AAC packet, missing AACPacketType or/and Data!")},e.prototype._parseAACAudioSpecificConfig=function(e,t,i){var n,r,s=new Uint8Array(e,t,i),o=null,a=0,h=null;if(a=n=s[0]>>>3,(r=(7&s[0])<<1|s[1]>>>7)<0||r>=this._mpegSamplingRates.length)this._onError(m.Z.FORMAT_ERROR,"Flv: AAC invalid sampling frequency index!");else{var u=this._mpegSamplingRates[r],l=(120&s[1])>>>3;if(!(l<0||l>=8)){5===a&&(h=(7&s[1])<<1|s[2]>>>7,(124&s[2])>>>2);var d=self.navigator.userAgent.toLowerCase();return-1!==d.indexOf("firefox")?r>=6?(a=5,o=new Array(4),h=r-3):(a=2,o=new Array(2),h=r):-1!==d.indexOf("android")?(a=2,o=new Array(2),h=r):(a=5,h=r,o=new Array(4),r>=6?h=r-3:1===l&&(a=2,o=new Array(2),h=r)),o[0]=a<<3,o[0]|=(15&r)>>>1,o[1]=(15&r)<<7,o[1]|=(15&l)<<3,5===a&&(o[1]|=(15&h)>>>1,o[2]=(1&h)<<7,o[2]|=8,o[3]=0),{config:o,samplingRate:u,channelCount:l,codec:"mp4a.40."+a,originalCodec:"mp4a.40."+n}}this._onError(m.Z.FORMAT_ERROR,"Flv: AAC invalid channel configuration")}},e.prototype._parseMP3AudioData=function(e,t,i,n){if(!(i<4)){this._littleEndian;var r=new Uint8Array(e,t,i),o=null;if(n){if(255!==r[0])return;var a=r[1]>>>3&3,h=(6&r[1])>>1,u=(240&r[2])>>>4,l=(12&r[2])>>>2,d=3!==(r[3]>>>6&3)?2:1,c=0,f=0;switch(a){case 0:c=this._mpegAudioV25SampleRateTable[l];break;case 2:c=this._mpegAudioV20SampleRateTable[l];break;case 3:c=this._mpegAudioV10SampleRateTable[l]}switch(h){case 1:34,u>>4,h=15&o;7===h?this._parseAVCVideoPacket(e,t+1,i-1,n,r,a):this._onError(m.Z.CODEC_UNSUPPORTED,"Flv: Unsupported codec in video frame: "+h)}},e.prototype._parseAVCVideoPacket=function(e,t,i,n,r,o){if(i<4)s.Z.w(this.TAG,"Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime");else{var a=this._littleEndian,h=new DataView(e,t,i),u=h.getUint8(0),l=(16777215&h.getUint32(0,!a))<<8>>8;if(0===u)this._parseAVCDecoderConfigurationRecord(e,t+4,i-4);else if(1===u)this._parseAVCVideoData(e,t+4,i-4,n,r,o,l);else if(2!==u)return void this._onError(m.Z.FORMAT_ERROR,"Flv: Invalid video packet type "+u)}},e.prototype._parseAVCDecoderConfigurationRecord=function(e,t,i){if(i<7)s.Z.w(this.TAG,"Flv: Invalid AVCDecoderConfigurationRecord, lack of data!");else{var n=this._videoMetadata,r=this._videoTrack,o=this._littleEndian,a=new DataView(e,t,i);n?void 0!==n.avcc&&s.Z.w(this.TAG,"Found another AVCDecoderConfigurationRecord!"):(!1===this._hasVideo&&!1===this._hasVideoFlagOverrided&&(this._hasVideo=!0,this._mediaInfo.hasVideo=!0),(n=this._videoMetadata={}).type="video",n.id=r.id,n.timescale=this._timescale,n.duration=this._duration);var h=a.getUint8(0),u=a.getUint8(1);a.getUint8(2),a.getUint8(3);if(1===h&&0!==u)if(this._naluLengthSize=1+(3&a.getUint8(4)),3===this._naluLengthSize||4===this._naluLengthSize){var l=31&a.getUint8(5);if(0!==l){l>1&&s.Z.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: SPS Count = "+l);for(var d=6,c=0;c1&&s.Z.w(this.TAG,"Flv: Strange AVCDecoderConfigurationRecord: PPS Count = "+R),d++;for(c=0;c=i){s.Z.w(this.TAG,"Malformed Nalu near timestamp "+_+", offset = "+c+", dataSize = "+i);break}var m=u.getUint32(c,!h);if(3===f&&(m>>>=8),m>i-f)return void s.Z.w(this.TAG,"Malformed Nalus near timestamp "+_+", NaluSize > DataSize!");var g=31&u.getUint8(c+f);5===g&&(p=!0);var v=new Uint8Array(e,t+c,f+m),y={type:g,data:v};l.push(y),d+=v.byteLength,c+=f+m}if(l.length){var b=this._videoTrack,E={units:l,length:d,isKeyframe:p,dts:_,cts:a,pts:_+a};p&&(E.fileposition=r),b.samples.push(E),b.length+=d}},e}(),v=function(){function e(){}return e.init=function(){for(var t in e.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[],".mp3":[]},e.types)e.types.hasOwnProperty(t)&&(e.types[t]=[t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2),t.charCodeAt(3)]);var i=e.constants={};i.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),i.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),i.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),i.STSC=i.STCO=i.STTS,i.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),i.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),i.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),i.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),i.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])},e.box=function(e){for(var t=8,i=null,n=Array.prototype.slice.call(arguments,1),r=n.length,s=0;s>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);var o=8;for(s=0;s>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))},e.trak=function(t){return e.box(e.types.trak,e.tkhd(t),e.mdia(t))},e.tkhd=function(t){var i=t.id,n=t.duration,r=t.presentWidth,s=t.presentHeight;return e.box(e.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,r>>>8&255,255&r,0,0,s>>>8&255,255&s,0,0]))},e.mdia=function(t){return e.box(e.types.mdia,e.mdhd(t),e.hdlr(t),e.minf(t))},e.mdhd=function(t){var i=t.timescale,n=t.duration;return e.box(e.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,n>>>24&255,n>>>16&255,n>>>8&255,255&n,85,196,0,0]))},e.hdlr=function(t){var i=null;return i="audio"===t.type?e.constants.HDLR_AUDIO:e.constants.HDLR_VIDEO,e.box(e.types.hdlr,i)},e.minf=function(t){var i=null;return i="audio"===t.type?e.box(e.types.smhd,e.constants.SMHD):e.box(e.types.vmhd,e.constants.VMHD),e.box(e.types.minf,i,e.dinf(),e.stbl(t))},e.dinf=function(){return e.box(e.types.dinf,e.box(e.types.dref,e.constants.DREF))},e.stbl=function(t){return e.box(e.types.stbl,e.stsd(t),e.box(e.types.stts,e.constants.STTS),e.box(e.types.stsc,e.constants.STSC),e.box(e.types.stsz,e.constants.STSZ),e.box(e.types.stco,e.constants.STCO))},e.stsd=function(t){return"audio"===t.type?"mp3"===t.codec?e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp3(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.mp4a(t)):e.box(e.types.stsd,e.constants.STSD_PREFIX,e.avc1(t))},e.mp3=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types[".mp3"],r)},e.mp4a=function(t){var i=t.channelCount,n=t.audioSampleRate,r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,i,0,16,0,0,0,0,n>>>8&255,255&n,0,0]);return e.box(e.types.mp4a,r,e.esds(t))},e.esds=function(t){var i=t.config||[],n=i.length,r=new Uint8Array([0,0,0,0,3,23+n,0,1,0,4,15+n,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([n]).concat(i).concat([6,1,2]));return e.box(e.types.esds,r)},e.avc1=function(t){var i=t.avcc,n=t.codecWidth,r=t.codecHeight,s=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,n>>>8&255,255&n,r>>>8&255,255&r,0,72,0,0,0,72,0,0,0,0,0,0,0,1,10,120,113,113,47,102,108,118,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return e.box(e.types.avc1,s,e.box(e.types.avcC,i))},e.mvex=function(t){return e.box(e.types.mvex,e.trex(t))},e.trex=function(t){var i=t.id,n=new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return e.box(e.types.trex,n)},e.moof=function(t,i){return e.box(e.types.moof,e.mfhd(t.sequenceNumber),e.traf(t,i))},e.mfhd=function(t){var i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t]);return e.box(e.types.mfhd,i)},e.traf=function(t,i){var n=t.id,r=e.box(e.types.tfhd,new Uint8Array([0,0,0,0,n>>>24&255,n>>>16&255,n>>>8&255,255&n])),s=e.box(e.types.tfdt,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),o=e.sdtp(t),a=e.trun(t,o.byteLength+16+16+8+16+8+8);return e.box(e.types.traf,r,s,a,o)},e.sdtp=function(t){for(var i=t.samples||[],n=i.length,r=new Uint8Array(4+n),s=0;s>>24&255,r>>>16&255,r>>>8&255,255&r,i>>>24&255,i>>>16&255,i>>>8&255,255&i],0);for(var a=0;a>>24&255,h>>>16&255,h>>>8&255,255&h,u>>>24&255,u>>>16&255,u>>>8&255,255&u,l.isLeading<<2|l.dependsOn,l.isDependedOn<<6|l.hasRedundancy<<4|l.isNonSync,0,0,d>>>24&255,d>>>16&255,d>>>8&255,255&d],12+16*a)}return e.box(e.types.trun,o)},e.mdat=function(t){return e.box(e.types.mdat,t)},e}();v.init();var y=v,b=function(){function e(){}return e.getSilentFrame=function(e,t){if("mp4a.40.2"===e){if(1===t)return new Uint8Array([0,200,0,128,35,128]);if(2===t)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===t)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===t)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===t)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}return null},e}(),E=i(51),S=function(){function e(e){this.TAG="MP4Remuxer",this._config=e,this._isLive=!0===e.isLive,this._dtsBase=-1,this._dtsBaseInited=!1,this._audioDtsBase=1/0,this._videoDtsBase=1/0,this._audioNextDts=void 0,this._videoNextDts=void 0,this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList=new E.J1("audio"),this._videoSegmentInfoList=new E.J1("video"),this._onInitSegment=null,this._onMediaSegment=null,this._forceFirstIDR=!(!o.Z.chrome||!(o.Z.version.major<50||50===o.Z.version.major&&o.Z.version.build<2661)),this._fillSilentAfterSeek=o.Z.msedge||o.Z.msie,this._mp3UseMpegAudio=!o.Z.firefox,this._fillAudioTimestampGap=this._config.fixAudioTimestampGap}return e.prototype.destroy=function(){this._dtsBase=-1,this._dtsBaseInited=!1,this._audioMeta=null,this._videoMeta=null,this._audioSegmentInfoList.clear(),this._audioSegmentInfoList=null,this._videoSegmentInfoList.clear(),this._videoSegmentInfoList=null,this._onInitSegment=null,this._onMediaSegment=null},e.prototype.bindDataSource=function(e){return e.onDataAvailable=this.remux.bind(this),e.onTrackMetadata=this._onTrackMetadataReceived.bind(this),this},Object.defineProperty(e.prototype,"onInitSegment",{get:function(){return this._onInitSegment},set:function(e){this._onInitSegment=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onMediaSegment",{get:function(){return this._onMediaSegment},set:function(e){this._onMediaSegment=e},enumerable:!1,configurable:!0}),e.prototype.insertDiscontinuity=function(){this._audioNextDts=this._videoNextDts=void 0},e.prototype.seek=function(e){this._audioStashedLastSample=null,this._videoStashedLastSample=null,this._videoSegmentInfoList.clear(),this._audioSegmentInfoList.clear()},e.prototype.remux=function(e,t){if(!this._onMediaSegment)throw new d.rT("MP4Remuxer: onMediaSegment callback must be specificed!");this._dtsBaseInited||this._calculateDtsBase(e,t),this._remuxVideo(t),this._remuxAudio(e)},e.prototype._onTrackMetadataReceived=function(e,t){var i=null,n="mp4",r=t.codec;if("audio"===e)this._audioMeta=t,"mp3"===t.codec&&this._mp3UseMpegAudio?(n="mpeg",r="",i=new Uint8Array):i=y.generateInitSegment(t);else{if("video"!==e)return;this._videoMeta=t,i=y.generateInitSegment(t)}if(!this._onInitSegment)throw new d.rT("MP4Remuxer: onInitSegment callback must be specified!");this._onInitSegment(e,{type:e,data:i.buffer,codec:r,container:e+"/"+n,mediaDuration:t.duration})},e.prototype._calculateDtsBase=function(e,t){this._dtsBaseInited||(e.samples&&e.samples.length&&(this._audioDtsBase=e.samples[0].dts),t.samples&&t.samples.length&&(this._videoDtsBase=t.samples[0].dts),this._dtsBase=Math.min(this._audioDtsBase,this._videoDtsBase),this._dtsBaseInited=!0)},e.prototype.flushStashedSamples=function(){var e=this._videoStashedLastSample,t=this._audioStashedLastSample,i={type:"video",id:1,sequenceNumber:0,samples:[],length:0};null!=e&&(i.samples.push(e),i.length=e.length);var n={type:"audio",id:2,sequenceNumber:0,samples:[],length:0};null!=t&&(n.samples.push(t),n.length=t.length),this._videoStashedLastSample=null,this._audioStashedLastSample=null,this._remuxVideo(i,!0),this._remuxAudio(n,!0)},e.prototype._remuxAudio=function(e,t){if(null!=this._audioMeta){var i,n=e,r=n.samples,a=void 0,h=-1,u=this._audioMeta.refSampleDuration,l="mp3"===this._audioMeta.codec&&this._mp3UseMpegAudio,d=this._dtsBaseInited&&void 0===this._audioNextDts,c=!1;if(r&&0!==r.length&&(1!==r.length||t)){var f=0,_=null,p=0;l?(f=0,p=n.length):(f=8,p=8+n.length);var m=null;if(r.length>1&&(p-=(m=r.pop()).length),null!=this._audioStashedLastSample){var g=this._audioStashedLastSample;this._audioStashedLastSample=null,r.unshift(g),p+=g.length}null!=m&&(this._audioStashedLastSample=m);var v=r[0].dts-this._dtsBase;if(this._audioNextDts)a=v-this._audioNextDts;else if(this._audioSegmentInfoList.isEmpty())a=0,this._fillSilentAfterSeek&&!this._videoSegmentInfoList.isEmpty()&&"mp3"!==this._audioMeta.originalCodec&&(c=!0);else{var S=this._audioSegmentInfoList.getLastSampleBefore(v);if(null!=S){var A=v-(S.originalDts+S.duration);A<=3&&(A=0),a=v-(S.dts+S.duration+A)}else a=0}if(c){var L=v-a,R=this._videoSegmentInfoList.getLastSegmentBefore(v);if(null!=R&&R.beginDts=3*u&&this._fillAudioTimestampGap&&!o.Z.safari){I=!0;var P,U=Math.floor(a/u);s.Z.w(this.TAG,"Large audio timestamp gap detected, may cause AV sync to drift. Silent frames will be generated to avoid unsync.\noriginalDts: "+D+" ms, curRefDts: "+x+" ms, dtsCorrection: "+Math.round(a)+" ms, generate: "+U+" frames"),w=Math.floor(x),B=Math.floor(x+u)-w,null==(P=b.getSilentFrame(this._audioMeta.originalCodec,this._audioMeta.channelCount))&&(s.Z.w(this.TAG,"Unable to generate silent frame for "+this._audioMeta.originalCodec+" with "+this._audioMeta.channelCount+" channels, repeat last frame"),P=k),M=[];for(var N=0;N=1?T[T.length-1].duration:Math.floor(u);this._audioNextDts=w+B}-1===h&&(h=w),T.push({dts:w,pts:w,cts:0,unit:g.unit,size:g.unit.byteLength,duration:B,originalDts:D,flags:{isLeading:0,dependsOn:1,isDependedOn:0,hasRedundancy:0}}),I&&T.push.apply(T,M)}}if(0===T.length)return n.samples=[],void(n.length=0);l?_=new Uint8Array(p):((_=new Uint8Array(p))[0]=p>>>24&255,_[1]=p>>>16&255,_[2]=p>>>8&255,_[3]=255&p,_.set(y.types.mdat,4));for(C=0;C1&&(d-=(c=s.pop()).length),null!=this._videoStashedLastSample){var f=this._videoStashedLastSample;this._videoStashedLastSample=null,s.unshift(f),d+=f.length}null!=c&&(this._videoStashedLastSample=c);var _=s[0].dts-this._dtsBase;if(this._videoNextDts)o=_-this._videoNextDts;else if(this._videoSegmentInfoList.isEmpty())o=0;else{var p=this._videoSegmentInfoList.getLastSampleBefore(_);if(null!=p){var m=_-(p.originalDts+p.duration);m<=3&&(m=0),o=_-(p.dts+p.duration+m)}else o=0}for(var g=new E.Yy,v=[],b=0;b=1?v[v.length-1].duration:Math.floor(this._videoMeta.refSampleDuration);if(A){var T=new E.Wk(L,w,O,f.dts,!0);T.fileposition=f.fileposition,g.appendSyncPoint(T)}v.push({dts:L,pts:w,cts:R,units:f.units,size:f.length,isKeyframe:A,duration:O,originalDts:S,flags:{isLeading:0,dependsOn:A?2:1,isDependedOn:A?1:0,hasRedundancy:0,isNonSync:A?0:1}})}(l=new Uint8Array(d))[0]=d>>>24&255,l[1]=d>>>16&255,l[2]=d>>>8&255,l[3]=255&d,l.set(y.types.mdat,4);for(b=0;b0)this._demuxer.bindDataSource(this._ioctl),this._demuxer.timestampBase=this._mediaDataSource.segments[this._currentSegmentIndex].timestampBase,r=this._demuxer.parseChunks(e,t);else if((n=g.probe(e)).match){this._demuxer=new g(n,this._config),this._remuxer||(this._remuxer=new S(this._config));var o=this._mediaDataSource;null==o.duration||isNaN(o.duration)||(this._demuxer.overridedDuration=o.duration),"boolean"==typeof o.hasAudio&&(this._demuxer.overridedHasAudio=o.hasAudio),"boolean"==typeof o.hasVideo&&(this._demuxer.overridedHasVideo=o.hasVideo),this._demuxer.timestampBase=o.segments[this._currentSegmentIndex].timestampBase,this._demuxer.onError=this._onDemuxException.bind(this),this._demuxer.onMediaInfo=this._onMediaInfo.bind(this),this._demuxer.onMetaDataArrived=this._onMetaDataArrived.bind(this),this._demuxer.onScriptDataArrived=this._onScriptDataArrived.bind(this),this._remuxer.bindDataSource(this._demuxer.bindDataSource(this._ioctl)),this._remuxer.onInitSegment=this._onRemuxerInitSegmentArrival.bind(this),this._remuxer.onMediaSegment=this._onRemuxerMediaSegmentArrival.bind(this),r=this._demuxer.parseChunks(e,t)}else n=null,s.Z.e(this.TAG,"Non-FLV, Unsupported media type!"),Promise.resolve().then((function(){i._internalAbort()})),this._emitter.emit(L.Z.DEMUX_ERROR,m.Z.FORMAT_UNSUPPORTED,"Non-FLV, Unsupported media type"),r=0;return r},e.prototype._onMediaInfo=function(e){var t=this;null==this._mediaInfo&&(this._mediaInfo=Object.assign({},e),this._mediaInfo.keyframesIndex=null,this._mediaInfo.segments=[],this._mediaInfo.segmentCount=this._mediaDataSource.segments.length,Object.setPrototypeOf(this._mediaInfo,a.Z.prototype));var i=Object.assign({},e);Object.setPrototypeOf(i,a.Z.prototype),this._mediaInfo.segments[this._currentSegmentIndex]=i,this._reportSegmentMediaInfo(this._currentSegmentIndex),null!=this._pendingSeekTime&&Promise.resolve().then((function(){var e=t._pendingSeekTime;t._pendingSeekTime=null,t.seek(e)}))},e.prototype._onMetaDataArrived=function(e){this._emitter.emit(L.Z.METADATA_ARRIVED,e)},e.prototype._onScriptDataArrived=function(e){this._emitter.emit(L.Z.SCRIPTDATA_ARRIVED,e)},e.prototype._onIOSeeked=function(){this._remuxer.insertDiscontinuity()},e.prototype._onIOComplete=function(e){var t=e+1;t0&&i[0].originalDts===n&&(n=i[0].pts),this._emitter.emit(L.Z.RECOMMEND_SEEKPOINT,n)}},e.prototype._enableStatisticsReporter=function(){null==this._statisticsReporter&&(this._statisticsReporter=self.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval))},e.prototype._disableStatisticsReporter=function(){this._statisticsReporter&&(self.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype._reportSegmentMediaInfo=function(e){var t=this._mediaInfo.segments[e],i=Object.assign({},t);i.duration=this._mediaInfo.duration,i.segmentCount=this._mediaInfo.segmentCount,delete i.segments,delete i.keyframesIndex,this._emitter.emit(L.Z.MEDIA_INFO,i)},e.prototype._reportStatisticsInfo=function(){var e={};e.url=this._ioctl.currentURL,e.hasRedirect=this._ioctl.hasRedirect,e.hasRedirect&&(e.redirectedURL=this._ioctl.currentRedirectedURL),e.speed=this._ioctl.currentSpeed,e.loaderType=this._ioctl.loaderType,e.currentSegmentIndex=this._currentSegmentIndex,e.totalSegmentCount=this._mediaDataSource.segments.length,this._emitter.emit(L.Z.STATISTICS_INFO,e)},e}()},257:function(e,t){"use strict";t.Z={IO_ERROR:"io_error",DEMUX_ERROR:"demux_error",INIT_SEGMENT:"init_segment",MEDIA_SEGMENT:"media_segment",LOADING_COMPLETE:"loading_complete",RECOVERED_EARLY_EOF:"recovered_early_eof",MEDIA_INFO:"media_info",METADATA_ARRIVED:"metadata_arrived",SCRIPTDATA_ARRIVED:"scriptdata_arrived",STATISTICS_INFO:"statistics_info",RECOMMEND_SEEKPOINT:"recommend_seekpoint"}},82:function(e,t,i){"use strict";i(846),i(219),i(949),i(257)},600:function(e,t){"use strict";t.Z={OK:"OK",FORMAT_ERROR:"FormatError",FORMAT_UNSUPPORTED:"FormatUnsupported",CODEC_UNSUPPORTED:"CodecUnsupported"}},60:function(e,t,i){"use strict";i.d(t,{default:function(){return D}});var n=i(219),r=i(191),s={enableWorker:!1,enableStashBuffer:!0,stashInitialSize:void 0,isLive:!1,lazyLoad:!0,lazyLoadMaxDuration:180,lazyLoadRecoverDuration:30,deferLoadAfterSourceOpen:!0,autoCleanupMaxBackwardDuration:180,autoCleanupMinBackwardDuration:120,statisticsInfoReportInterval:600,fixAudioTimestampGap:!0,accurateSeek:!1,seekType:"range",seekParamStart:"bstart",seekParamEnd:"bend",rangeLoadZeroStart:!1,customSeekHandler:void 0,reuseRedirectedURL:!1,headers:void 0,customLoader:void 0};function o(){return Object.assign({},s)}var a=function(){function e(){}return e.supportMSEH264Playback=function(){return window.MediaSource&&window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')},e.supportNetworkStreamIO=function(){var e=new r.Z({},o()),t=e.loaderType;return e.destroy(),"fetch-stream-loader"==t||"xhr-moz-chunked-loader"==t},e.getNetworkLoaderTypeName=function(){var e=new r.Z({},o()),t=e.loaderType;return e.destroy(),t},e.supportNativeMediaPlayback=function(t){null==e.videoElement&&(e.videoElement=window.document.createElement("video"));var i=e.videoElement.canPlayType(t);return"probably"===i||"maybe"==i},e.getFeatureList=function(){var t={mseFlvPlayback:!1,mseLiveFlvPlayback:!1,networkStreamIO:!1,networkLoaderName:"",nativeMP4H264Playback:!1,nativeWebmVP8Playback:!1,nativeWebmVP9Playback:!1};return t.mseFlvPlayback=e.supportMSEH264Playback(),t.networkStreamIO=e.supportNetworkStreamIO(),t.networkLoaderName=e.getNetworkLoaderTypeName(),t.mseLiveFlvPlayback=t.mseFlvPlayback&&t.networkStreamIO,t.nativeMP4H264Playback=e.supportNativeMediaPlayback('video/mp4; codecs="avc1.42001E, mp4a.40.2"'),t.nativeWebmVP8Playback=e.supportNativeMediaPlayback('video/webm; codecs="vp8.0, vorbis"'),t.nativeWebmVP9Playback=e.supportNativeMediaPlayback('video/webm; codecs="vp9"'),t},e}(),h=i(939),u=i(716),l=i.n(u),d=i(300),c=i(538),f={ERROR:"error",LOADING_COMPLETE:"loading_complete",RECOVERED_EARLY_EOF:"recovered_early_eof",MEDIA_INFO:"media_info",METADATA_ARRIVED:"metadata_arrived",SCRIPTDATA_ARRIVED:"scriptdata_arrived",STATISTICS_INFO:"statistics_info"},_=i(397),p=i.n(_),m=i(846),g=i(949),v=i(257),y=i(118),b=function(){function e(e,t){if(this.TAG="Transmuxer",this._emitter=new(l()),t.enableWorker&&"undefined"!=typeof Worker)try{this._worker=p()(82),this._workerDestroying=!1,this._worker.addEventListener("message",this._onWorkerMessage.bind(this)),this._worker.postMessage({cmd:"init",param:[e,t]}),this.e={onLoggingConfigChanged:this._onLoggingConfigChanged.bind(this)},m.Z.registerListener(this.e.onLoggingConfigChanged),this._worker.postMessage({cmd:"logging_config",param:m.Z.getConfig()})}catch(i){d.Z.e(this.TAG,"Error while initialize transmuxing worker, fallback to inline transmuxing"),this._worker=null,this._controller=new g.Z(e,t)}else this._controller=new g.Z(e,t);if(this._controller){var i=this._controller;i.on(v.Z.IO_ERROR,this._onIOError.bind(this)),i.on(v.Z.DEMUX_ERROR,this._onDemuxError.bind(this)),i.on(v.Z.INIT_SEGMENT,this._onInitSegment.bind(this)),i.on(v.Z.MEDIA_SEGMENT,this._onMediaSegment.bind(this)),i.on(v.Z.LOADING_COMPLETE,this._onLoadingComplete.bind(this)),i.on(v.Z.RECOVERED_EARLY_EOF,this._onRecoveredEarlyEof.bind(this)),i.on(v.Z.MEDIA_INFO,this._onMediaInfo.bind(this)),i.on(v.Z.METADATA_ARRIVED,this._onMetaDataArrived.bind(this)),i.on(v.Z.SCRIPTDATA_ARRIVED,this._onScriptDataArrived.bind(this)),i.on(v.Z.STATISTICS_INFO,this._onStatisticsInfo.bind(this)),i.on(v.Z.RECOMMEND_SEEKPOINT,this._onRecommendSeekpoint.bind(this))}}return e.prototype.destroy=function(){this._worker?this._workerDestroying||(this._workerDestroying=!0,this._worker.postMessage({cmd:"destroy"}),m.Z.removeListener(this.e.onLoggingConfigChanged),this.e=null):(this._controller.destroy(),this._controller=null),this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.hasWorker=function(){return null!=this._worker},e.prototype.open=function(){this._worker?this._worker.postMessage({cmd:"start"}):this._controller.start()},e.prototype.close=function(){this._worker?this._worker.postMessage({cmd:"stop"}):this._controller.stop()},e.prototype.seek=function(e){this._worker?this._worker.postMessage({cmd:"seek",param:e}):this._controller.seek(e)},e.prototype.pause=function(){this._worker?this._worker.postMessage({cmd:"pause"}):this._controller.pause()},e.prototype.resume=function(){this._worker?this._worker.postMessage({cmd:"resume"}):this._controller.resume()},e.prototype._onInitSegment=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(v.Z.INIT_SEGMENT,e,t)}))},e.prototype._onMediaSegment=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(v.Z.MEDIA_SEGMENT,e,t)}))},e.prototype._onLoadingComplete=function(){var e=this;Promise.resolve().then((function(){e._emitter.emit(v.Z.LOADING_COMPLETE)}))},e.prototype._onRecoveredEarlyEof=function(){var e=this;Promise.resolve().then((function(){e._emitter.emit(v.Z.RECOVERED_EARLY_EOF)}))},e.prototype._onMediaInfo=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(v.Z.MEDIA_INFO,e)}))},e.prototype._onMetaDataArrived=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(v.Z.METADATA_ARRIVED,e)}))},e.prototype._onScriptDataArrived=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(v.Z.SCRIPTDATA_ARRIVED,e)}))},e.prototype._onStatisticsInfo=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(v.Z.STATISTICS_INFO,e)}))},e.prototype._onIOError=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(v.Z.IO_ERROR,e,t)}))},e.prototype._onDemuxError=function(e,t){var i=this;Promise.resolve().then((function(){i._emitter.emit(v.Z.DEMUX_ERROR,e,t)}))},e.prototype._onRecommendSeekpoint=function(e){var t=this;Promise.resolve().then((function(){t._emitter.emit(v.Z.RECOMMEND_SEEKPOINT,e)}))},e.prototype._onLoggingConfigChanged=function(e){this._worker&&this._worker.postMessage({cmd:"logging_config",param:e})},e.prototype._onWorkerMessage=function(e){var t=e.data,i=t.data;if("destroyed"===t.msg||this._workerDestroying)return this._workerDestroying=!1,this._worker.terminate(),void(this._worker=null);switch(t.msg){case v.Z.INIT_SEGMENT:case v.Z.MEDIA_SEGMENT:this._emitter.emit(t.msg,i.type,i.data);break;case v.Z.LOADING_COMPLETE:case v.Z.RECOVERED_EARLY_EOF:this._emitter.emit(t.msg);break;case v.Z.MEDIA_INFO:Object.setPrototypeOf(i,y.Z.prototype),this._emitter.emit(t.msg,i);break;case v.Z.METADATA_ARRIVED:case v.Z.SCRIPTDATA_ARRIVED:case v.Z.STATISTICS_INFO:this._emitter.emit(t.msg,i);break;case v.Z.IO_ERROR:case v.Z.DEMUX_ERROR:this._emitter.emit(t.msg,i.type,i.info);break;case v.Z.RECOMMEND_SEEKPOINT:this._emitter.emit(t.msg,i);break;case"logcat_callback":d.Z.emitter.emit("log",i.type,i.logcat)}},e}(),E={ERROR:"error",SOURCE_OPEN:"source_open",UPDATE_END:"update_end",BUFFER_FULL:"buffer_full"},S=i(51),A=i(29),L=function(){function e(e){this.TAG="MSEController",this._config=e,this._emitter=new(l()),this._config.isLive&&null==this._config.autoCleanupSourceBuffer&&(this._config.autoCleanupSourceBuffer=!0),this.e={onSourceOpen:this._onSourceOpen.bind(this),onSourceEnded:this._onSourceEnded.bind(this),onSourceClose:this._onSourceClose.bind(this),onSourceBufferError:this._onSourceBufferError.bind(this),onSourceBufferUpdateEnd:this._onSourceBufferUpdateEnd.bind(this)},this._mediaSource=null,this._mediaSourceObjectURL=null,this._mediaElement=null,this._isBufferFull=!1,this._hasPendingEos=!1,this._requireSetMediaDuration=!1,this._pendingMediaDuration=0,this._pendingSourceBufferInit=[],this._mimeTypes={video:null,audio:null},this._sourceBuffers={video:null,audio:null},this._lastInitSegments={video:null,audio:null},this._pendingSegments={video:[],audio:[]},this._pendingRemoveRanges={video:[],audio:[]},this._idrList=new S.Vn}return e.prototype.destroy=function(){(this._mediaElement||this._mediaSource)&&this.detachMediaElement(),this.e=null,this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.attachMediaElement=function(e){if(this._mediaSource)throw new A.rT("MediaSource has been attached to an HTMLMediaElement!");var t=this._mediaSource=new window.MediaSource;t.addEventListener("sourceopen",this.e.onSourceOpen),t.addEventListener("sourceended",this.e.onSourceEnded),t.addEventListener("sourceclose",this.e.onSourceClose),this._mediaElement=e,this._mediaSourceObjectURL=window.URL.createObjectURL(this._mediaSource),e.src=this._mediaSourceObjectURL},e.prototype.detachMediaElement=function(){if(this._mediaSource){var e=this._mediaSource;for(var t in this._sourceBuffers){var i=this._pendingSegments[t];i.splice(0,i.length),this._pendingSegments[t]=null,this._pendingRemoveRanges[t]=null,this._lastInitSegments[t]=null;var n=this._sourceBuffers[t];if(n){if("closed"!==e.readyState){try{e.removeSourceBuffer(n)}catch(e){d.Z.e(this.TAG,e.message)}n.removeEventListener("error",this.e.onSourceBufferError),n.removeEventListener("updateend",this.e.onSourceBufferUpdateEnd)}this._mimeTypes[t]=null,this._sourceBuffers[t]=null}}if("open"===e.readyState)try{e.endOfStream()}catch(e){d.Z.e(this.TAG,e.message)}e.removeEventListener("sourceopen",this.e.onSourceOpen),e.removeEventListener("sourceended",this.e.onSourceEnded),e.removeEventListener("sourceclose",this.e.onSourceClose),this._pendingSourceBufferInit=[],this._isBufferFull=!1,this._idrList.clear(),this._mediaSource=null}this._mediaElement&&(this._mediaElement.src="",this._mediaElement.removeAttribute("src"),this._mediaElement=null),this._mediaSourceObjectURL&&(window.URL.revokeObjectURL(this._mediaSourceObjectURL),this._mediaSourceObjectURL=null)},e.prototype.appendInitSegment=function(e,t){if(!this._mediaSource||"open"!==this._mediaSource.readyState)return this._pendingSourceBufferInit.push(e),void this._pendingSegments[e.type].push(e);var i=e,n=""+i.container;i.codec&&i.codec.length>0&&(n+=";codecs="+i.codec);var r=!1;if(d.Z.v(this.TAG,"Received Initialization Segment, mimeType: "+n),this._lastInitSegments[i.type]=i,n!==this._mimeTypes[i.type]){if(this._mimeTypes[i.type])d.Z.v(this.TAG,"Notice: "+i.type+" mimeType changed, origin: "+this._mimeTypes[i.type]+", target: "+n);else{r=!0;try{var s=this._sourceBuffers[i.type]=this._mediaSource.addSourceBuffer(n);s.addEventListener("error",this.e.onSourceBufferError),s.addEventListener("updateend",this.e.onSourceBufferUpdateEnd)}catch(e){return d.Z.e(this.TAG,e.message),void this._emitter.emit(E.ERROR,{code:e.code,msg:e.message})}}this._mimeTypes[i.type]=n}t||this._pendingSegments[i.type].push(i),r||this._sourceBuffers[i.type]&&!this._sourceBuffers[i.type].updating&&this._doAppendSegments(),c.Z.safari&&"audio/mpeg"===i.container&&i.mediaDuration>0&&(this._requireSetMediaDuration=!0,this._pendingMediaDuration=i.mediaDuration/1e3,this._updateMediaSourceDuration())},e.prototype.appendMediaSegment=function(e){var t=e;this._pendingSegments[t.type].push(t),this._config.autoCleanupSourceBuffer&&this._needCleanupSourceBuffer()&&this._doCleanupSourceBuffer();var i=this._sourceBuffers[t.type];!i||i.updating||this._hasPendingRemoveRanges()||this._doAppendSegments()},e.prototype.seek=function(e){for(var t in this._sourceBuffers)if(this._sourceBuffers[t]){var i=this._sourceBuffers[t];if("open"===this._mediaSource.readyState)try{i.abort()}catch(e){d.Z.e(this.TAG,e.message)}this._idrList.clear();var n=this._pendingSegments[t];if(n.splice(0,n.length),"closed"!==this._mediaSource.readyState){for(var r=0;r=1&&e-n.start(0)>=this._config.autoCleanupMaxBackwardDuration)return!0}}return!1},e.prototype._doCleanupSourceBuffer=function(){var e=this._mediaElement.currentTime;for(var t in this._sourceBuffers){var i=this._sourceBuffers[t];if(i){for(var n=i.buffered,r=!1,s=0;s=this._config.autoCleanupMaxBackwardDuration){r=!0;var h=e-this._config.autoCleanupMinBackwardDuration;this._pendingRemoveRanges[t].push({start:o,end:h})}}else a0&&(isNaN(t)||i>t)&&(d.Z.v(this.TAG,"Update MediaSource duration from "+t+" to "+i),this._mediaSource.duration=i),this._requireSetMediaDuration=!1,this._pendingMediaDuration=0}},e.prototype._doRemoveRanges=function(){for(var e in this._pendingRemoveRanges)if(this._sourceBuffers[e]&&!this._sourceBuffers[e].updating)for(var t=this._sourceBuffers[e],i=this._pendingRemoveRanges[e];i.length&&!t.updating;){var n=i.shift();t.remove(n.start,n.end)}},e.prototype._doAppendSegments=function(){var e=this._pendingSegments;for(var t in e)if(this._sourceBuffers[t]&&!this._sourceBuffers[t].updating&&e[t].length>0){var i=e[t].shift();if(i.timestampOffset){var n=this._sourceBuffers[t].timestampOffset,r=i.timestampOffset/1e3;Math.abs(n-r)>.1&&(d.Z.v(this.TAG,"Update MPEG audio timestampOffset from "+n+" to "+r),this._sourceBuffers[t].timestampOffset=r),delete i.timestampOffset}if(!i.data||0===i.data.byteLength)continue;try{this._sourceBuffers[t].appendBuffer(i.data),this._isBufferFull=!1,"video"===t&&i.hasOwnProperty("info")&&this._idrList.appendArray(i.info.syncPoints)}catch(e){this._pendingSegments[t].unshift(i),22===e.code?(this._isBufferFull||this._emitter.emit(E.BUFFER_FULL),this._isBufferFull=!0):(d.Z.e(this.TAG,e.message),this._emitter.emit(E.ERROR,{code:e.code,msg:e.message}))}}},e.prototype._onSourceOpen=function(){if(d.Z.v(this.TAG,"MediaSource onSourceOpen"),this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._pendingSourceBufferInit.length>0)for(var e=this._pendingSourceBufferInit;e.length;){var t=e.shift();this.appendInitSegment(t,!0)}this._hasPendingSegments()&&this._doAppendSegments(),this._emitter.emit(E.SOURCE_OPEN)},e.prototype._onSourceEnded=function(){d.Z.v(this.TAG,"MediaSource onSourceEnded")},e.prototype._onSourceClose=function(){d.Z.v(this.TAG,"MediaSource onSourceClose"),this._mediaSource&&null!=this.e&&(this._mediaSource.removeEventListener("sourceopen",this.e.onSourceOpen),this._mediaSource.removeEventListener("sourceended",this.e.onSourceEnded),this._mediaSource.removeEventListener("sourceclose",this.e.onSourceClose))},e.prototype._hasPendingSegments=function(){var e=this._pendingSegments;return e.video.length>0||e.audio.length>0},e.prototype._hasPendingRemoveRanges=function(){var e=this._pendingRemoveRanges;return e.video.length>0||e.audio.length>0},e.prototype._onSourceBufferUpdateEnd=function(){this._requireSetMediaDuration?this._updateMediaSourceDuration():this._hasPendingRemoveRanges()?this._doRemoveRanges():this._hasPendingSegments()?this._doAppendSegments():this._hasPendingEos&&this.endOfStream(),this._emitter.emit(E.UPDATE_END)},e.prototype._onSourceBufferError=function(e){d.Z.e(this.TAG,"SourceBuffer Error: "+e)},e}(),R=i(600),w={NETWORK_ERROR:"NetworkError",MEDIA_ERROR:"MediaError",OTHER_ERROR:"OtherError"},O={NETWORK_EXCEPTION:h.nm.EXCEPTION,NETWORK_STATUS_CODE_INVALID:h.nm.HTTP_STATUS_CODE_INVALID,NETWORK_TIMEOUT:h.nm.CONNECTING_TIMEOUT,NETWORK_UNRECOVERABLE_EARLY_EOF:h.nm.UNRECOVERABLE_EARLY_EOF,MEDIA_MSE_ERROR:"MediaMSEError",MEDIA_FORMAT_ERROR:R.Z.FORMAT_ERROR,MEDIA_FORMAT_UNSUPPORTED:R.Z.FORMAT_UNSUPPORTED,MEDIA_CODEC_UNSUPPORTED:R.Z.CODEC_UNSUPPORTED},T=function(){function e(e,t){if(this.TAG="FlvPlayer",this._type="FlvPlayer",this._emitter=new(l()),this._config=o(),"object"==typeof t&&Object.assign(this._config,t),"flv"!==e.type.toLowerCase())throw new A.OC("FlvPlayer requires an flv MediaDataSource input!");!0===e.isLive&&(this._config.isLive=!0),this.e={onvLoadedMetadata:this._onvLoadedMetadata.bind(this),onvSeeking:this._onvSeeking.bind(this),onvCanPlay:this._onvCanPlay.bind(this),onvStalled:this._onvStalled.bind(this),onvProgress:this._onvProgress.bind(this)},self.performance&&self.performance.now?this._now=self.performance.now.bind(self.performance):this._now=Date.now,this._pendingSeekTime=null,this._requestSetTime=!1,this._seekpointRecord=null,this._progressChecker=null,this._mediaDataSource=e,this._mediaElement=null,this._msectl=null,this._transmuxer=null,this._mseSourceOpened=!1,this._hasPendingLoad=!1,this._receivedCanPlay=!1,this._mediaInfo=null,this._statisticsInfo=null;var i=c.Z.chrome&&(c.Z.version.major<50||50===c.Z.version.major&&c.Z.version.build<2661);this._alwaysSeekKeyframe=!!(i||c.Z.msedge||c.Z.msie),this._alwaysSeekKeyframe&&(this._config.accurateSeek=!1)}return e.prototype.destroy=function(){null!=this._progressChecker&&(window.clearInterval(this._progressChecker),this._progressChecker=null),this._transmuxer&&this.unload(),this._mediaElement&&this.detachMediaElement(),this.e=null,this._mediaDataSource=null,this._emitter.removeAllListeners(),this._emitter=null},e.prototype.on=function(e,t){var i=this;e===f.MEDIA_INFO?null!=this._mediaInfo&&Promise.resolve().then((function(){i._emitter.emit(f.MEDIA_INFO,i.mediaInfo)})):e===f.STATISTICS_INFO&&null!=this._statisticsInfo&&Promise.resolve().then((function(){i._emitter.emit(f.STATISTICS_INFO,i.statisticsInfo)})),this._emitter.addListener(e,t)},e.prototype.off=function(e,t){this._emitter.removeListener(e,t)},e.prototype.attachMediaElement=function(e){var t=this;if(this._mediaElement=e,e.addEventListener("loadedmetadata",this.e.onvLoadedMetadata),e.addEventListener("seeking",this.e.onvSeeking),e.addEventListener("canplay",this.e.onvCanPlay),e.addEventListener("stalled",this.e.onvStalled),e.addEventListener("progress",this.e.onvProgress),this._msectl=new L(this._config),this._msectl.on(E.UPDATE_END,this._onmseUpdateEnd.bind(this)),this._msectl.on(E.BUFFER_FULL,this._onmseBufferFull.bind(this)),this._msectl.on(E.SOURCE_OPEN,(function(){t._mseSourceOpened=!0,t._hasPendingLoad&&(t._hasPendingLoad=!1,t.load())})),this._msectl.on(E.ERROR,(function(e){t._emitter.emit(f.ERROR,w.MEDIA_ERROR,O.MEDIA_MSE_ERROR,e)})),this._msectl.attachMediaElement(e),null!=this._pendingSeekTime)try{e.currentTime=this._pendingSeekTime,this._pendingSeekTime=null}catch(e){}},e.prototype.detachMediaElement=function(){this._mediaElement&&(this._msectl.detachMediaElement(),this._mediaElement.removeEventListener("loadedmetadata",this.e.onvLoadedMetadata),this._mediaElement.removeEventListener("seeking",this.e.onvSeeking),this._mediaElement.removeEventListener("canplay",this.e.onvCanPlay),this._mediaElement.removeEventListener("stalled",this.e.onvStalled),this._mediaElement.removeEventListener("progress",this.e.onvProgress),this._mediaElement=null),this._msectl&&(this._msectl.destroy(),this._msectl=null)},e.prototype.load=function(){var e=this;if(!this._mediaElement)throw new A.rT("HTMLMediaElement must be attached before load()!");if(this._transmuxer)throw new A.rT("FlvPlayer.load() has been called, please call unload() first!");this._hasPendingLoad||(this._config.deferLoadAfterSourceOpen&&!1===this._mseSourceOpened?this._hasPendingLoad=!0:(this._mediaElement.readyState>0&&(this._requestSetTime=!0,this._mediaElement.currentTime=0),this._transmuxer=new b(this._mediaDataSource,this._config),this._transmuxer.on(v.Z.INIT_SEGMENT,(function(t,i){e._msectl.appendInitSegment(i)})),this._transmuxer.on(v.Z.MEDIA_SEGMENT,(function(t,i){if(e._msectl.appendMediaSegment(i),e._config.lazyLoad&&!e._config.isLive){var n=e._mediaElement.currentTime;i.info.endDts>=1e3*(n+e._config.lazyLoadMaxDuration)&&null==e._progressChecker&&(d.Z.v(e.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),e._suspendTransmuxer())}})),this._transmuxer.on(v.Z.LOADING_COMPLETE,(function(){e._msectl.endOfStream(),e._emitter.emit(f.LOADING_COMPLETE)})),this._transmuxer.on(v.Z.RECOVERED_EARLY_EOF,(function(){e._emitter.emit(f.RECOVERED_EARLY_EOF)})),this._transmuxer.on(v.Z.IO_ERROR,(function(t,i){e._emitter.emit(f.ERROR,w.NETWORK_ERROR,t,i)})),this._transmuxer.on(v.Z.DEMUX_ERROR,(function(t,i){e._emitter.emit(f.ERROR,w.MEDIA_ERROR,t,{code:-1,msg:i})})),this._transmuxer.on(v.Z.MEDIA_INFO,(function(t){e._mediaInfo=t,e._emitter.emit(f.MEDIA_INFO,Object.assign({},t))})),this._transmuxer.on(v.Z.METADATA_ARRIVED,(function(t){e._emitter.emit(f.METADATA_ARRIVED,t)})),this._transmuxer.on(v.Z.SCRIPTDATA_ARRIVED,(function(t){e._emitter.emit(f.SCRIPTDATA_ARRIVED,t)})),this._transmuxer.on(v.Z.STATISTICS_INFO,(function(t){e._statisticsInfo=e._fillStatisticsInfo(t),e._emitter.emit(f.STATISTICS_INFO,Object.assign({},e._statisticsInfo))})),this._transmuxer.on(v.Z.RECOMMEND_SEEKPOINT,(function(t){e._mediaElement&&!e._config.accurateSeek&&(e._requestSetTime=!0,e._mediaElement.currentTime=t/1e3)})),this._transmuxer.open()))},e.prototype.unload=function(){this._mediaElement&&this._mediaElement.pause(),this._msectl&&this._msectl.seek(0),this._transmuxer&&(this._transmuxer.close(),this._transmuxer.destroy(),this._transmuxer=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._internalSeek(e):this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){return Object.assign({},this._mediaInfo)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){return null==this._statisticsInfo&&(this._statisticsInfo={}),this._statisticsInfo=this._fillStatisticsInfo(this._statisticsInfo),Object.assign({},this._statisticsInfo)},enumerable:!1,configurable:!0}),e.prototype._fillStatisticsInfo=function(e){if(e.playerType=this._type,!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},e.prototype._onmseUpdateEnd=function(){if(this._config.lazyLoad&&!this._config.isLive){for(var e=this._mediaElement.buffered,t=this._mediaElement.currentTime,i=0,n=0;n=t+this._config.lazyLoadMaxDuration&&null==this._progressChecker&&(d.Z.v(this.TAG,"Maximum buffering duration exceeded, suspend transmuxing task"),this._suspendTransmuxer())}},e.prototype._onmseBufferFull=function(){d.Z.v(this.TAG,"MSE SourceBuffer is full, suspend transmuxing task"),null==this._progressChecker&&this._suspendTransmuxer()},e.prototype._suspendTransmuxer=function(){this._transmuxer&&(this._transmuxer.pause(),null==this._progressChecker&&(this._progressChecker=window.setInterval(this._checkProgressAndResume.bind(this),1e3)))},e.prototype._checkProgressAndResume=function(){for(var e=this._mediaElement.currentTime,t=this._mediaElement.buffered,i=!1,n=0;n=r&&e=s-this._config.lazyLoadRecoverDuration&&(i=!0);break}}i&&(window.clearInterval(this._progressChecker),this._progressChecker=null,i&&(d.Z.v(this.TAG,"Continue loading from paused position"),this._transmuxer.resume()))},e.prototype._isTimepointBuffered=function(e){for(var t=this._mediaElement.buffered,i=0;i=n&&e0){var r=this._mediaElement.buffered.start(0);(r<1&&e0&&t.currentTime0){var n=i.start(0);if(n<1&&t0&&(this._mediaElement.currentTime=0),this._mediaElement.preload="auto",this._mediaElement.load(),this._statisticsReporter=window.setInterval(this._reportStatisticsInfo.bind(this),this._config.statisticsInfoReportInterval)},e.prototype.unload=function(){this._mediaElement&&(this._mediaElement.src="",this._mediaElement.removeAttribute("src")),null!=this._statisticsReporter&&(window.clearInterval(this._statisticsReporter),this._statisticsReporter=null)},e.prototype.play=function(){return this._mediaElement.play()},e.prototype.pause=function(){this._mediaElement.pause()},Object.defineProperty(e.prototype,"type",{get:function(){return this._type},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"buffered",{get:function(){return this._mediaElement.buffered},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"duration",{get:function(){return this._mediaElement.duration},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"volume",{get:function(){return this._mediaElement.volume},set:function(e){this._mediaElement.volume=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"muted",{get:function(){return this._mediaElement.muted},set:function(e){this._mediaElement.muted=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentTime",{get:function(){return this._mediaElement?this._mediaElement.currentTime:0},set:function(e){this._mediaElement?this._mediaElement.currentTime=e:this._pendingSeekTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"mediaInfo",{get:function(){var e={mimeType:(this._mediaElement instanceof HTMLAudioElement?"audio/":"video/")+this._mediaDataSource.type};return this._mediaElement&&(e.duration=Math.floor(1e3*this._mediaElement.duration),this._mediaElement instanceof HTMLVideoElement&&(e.width=this._mediaElement.videoWidth,e.height=this._mediaElement.videoHeight)),e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"statisticsInfo",{get:function(){var e={playerType:this._type,url:this._mediaDataSource.url};if(!(this._mediaElement instanceof HTMLVideoElement))return e;var t=!0,i=0,n=0;if(this._mediaElement.getVideoPlaybackQuality){var r=this._mediaElement.getVideoPlaybackQuality();i=r.totalVideoFrames,n=r.droppedVideoFrames}else null!=this._mediaElement.webkitDecodedFrameCount?(i=this._mediaElement.webkitDecodedFrameCount,n=this._mediaElement.webkitDroppedFrameCount):t=!1;return t&&(e.decodedFrames=i,e.droppedFrames=n),e},enumerable:!1,configurable:!0}),e.prototype._onvLoadedMetadata=function(e){null!=this._pendingSeekTime&&(this._mediaElement.currentTime=this._pendingSeekTime,this._pendingSeekTime=null),this._emitter.emit(f.MEDIA_INFO,this.mediaInfo)},e.prototype._reportStatisticsInfo=function(){this._emitter.emit(f.STATISTICS_INFO,this.statisticsInfo)},e}();n.Z.install();var k={createPlayer:function(e,t){var i=e;if(null==i||"object"!=typeof i)throw new A.OC("MediaDataSource must be an javascript object!");if(!i.hasOwnProperty("type"))throw new A.OC("MediaDataSource must has type field to indicate video file type!");switch(i.type){case"flv":return new T(i,t);default:return new C(i,t)}},isSupported:function(){return a.supportMSEH264Playback()},getFeatureList:function(){return a.getFeatureList()}};k.BaseLoader=h.fp,k.LoaderStatus=h.GM,k.LoaderErrors=h.nm,k.Events=f,k.ErrorTypes=w,k.ErrorDetails=O,k.FlvPlayer=T,k.NativePlayer=C,k.LoggingControl=m.Z,Object.defineProperty(k,"version",{enumerable:!0,get:function(){return"1.6.2"}});var D=k},324:function(e,t,i){e.exports=i(60).default},191:function(e,t,i){"use strict";i.d(t,{Z:function(){return y}});var n,r=i(300),s=function(){function e(){this._firstCheckpoint=0,this._lastCheckpoint=0,this._intervalBytes=0,this._totalBytes=0,this._lastSecondBytes=0,self.performance&&self.performance.now?this._now=self.performance.now.bind(self.performance):this._now=Date.now}return e.prototype.reset=function(){this._firstCheckpoint=this._lastCheckpoint=0,this._totalBytes=this._intervalBytes=0,this._lastSecondBytes=0},e.prototype.addBytes=function(e){0===this._firstCheckpoint?(this._firstCheckpoint=this._now(),this._lastCheckpoint=this._firstCheckpoint,this._intervalBytes+=e,this._totalBytes+=e):this._now()-this._lastCheckpoint<1e3?(this._intervalBytes+=e,this._totalBytes+=e):(this._lastSecondBytes=this._intervalBytes,this._intervalBytes=e,this._totalBytes+=e,this._lastCheckpoint=this._now())},Object.defineProperty(e.prototype,"currentKBps",{get:function(){this.addBytes(0);var e=(this._now()-this._lastCheckpoint)/1e3;return 0==e&&(e=1),this._intervalBytes/e/1024},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"lastSecondKBps",{get:function(){return this.addBytes(0),0!==this._lastSecondBytes?this._lastSecondBytes/1024:this._now()-this._lastCheckpoint>=500?this.currentKBps:0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"averageKBps",{get:function(){var e=(this._now()-this._firstCheckpoint)/1e3;return this._totalBytes/e/1024},enumerable:!1,configurable:!0}),e}(),o=i(939),a=i(538),h=i(29),u=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),l=function(e){function t(t,i){var n=e.call(this,"fetch-stream-loader")||this;return n.TAG="FetchStreamLoader",n._seekHandler=t,n._config=i,n._needStash=!0,n._requestAbort=!1,n._contentLength=null,n._receivedLength=0,n}return u(t,e),t.isSupported=function(){try{var e=a.Z.msedge&&a.Z.version.minor>=15048,t=!a.Z.msedge||e;return self.fetch&&self.ReadableStream&&t}catch(e){return!1}},t.prototype.destroy=function(){this.isWorking()&&this.abort(),e.prototype.destroy.call(this)},t.prototype.open=function(e,t){var i=this;this._dataSource=e,this._range=t;var n=e.url;this._config.reuseRedirectedURL&&null!=e.redirectedURL&&(n=e.redirectedURL);var r=this._seekHandler.getConfig(n,t),s=new self.Headers;if("object"==typeof r.headers){var a=r.headers;for(var u in a)a.hasOwnProperty(u)&&s.append(u,a[u])}var l={method:"GET",headers:s,mode:"cors",cache:"default",referrerPolicy:"no-referrer-when-downgrade"};if("object"==typeof this._config.headers)for(var u in this._config.headers)s.append(u,this._config.headers[u]);!1===e.cors&&(l.mode="same-origin"),e.withCredentials&&(l.credentials="include"),e.referrerPolicy&&(l.referrerPolicy=e.referrerPolicy),self.AbortController&&(this._abortController=new self.AbortController,l.signal=this._abortController.signal),this._status=o.GM.kConnecting,self.fetch(r.url,l).then((function(e){if(i._requestAbort)return i._status=o.GM.kIdle,void e.body.cancel();if(e.ok&&e.status>=200&&e.status<=299){if(e.url!==r.url&&i._onURLRedirect){var t=i._seekHandler.removeURLParameters(e.url);i._onURLRedirect(t)}var n=e.headers.get("Content-Length");return null!=n&&(i._contentLength=parseInt(n),0!==i._contentLength&&i._onContentLengthKnown&&i._onContentLengthKnown(i._contentLength)),i._pump.call(i,e.body.getReader())}if(i._status=o.GM.kError,!i._onError)throw new h.OZ("FetchStreamLoader: Http code invalid, "+e.status+" "+e.statusText);i._onError(o.nm.HTTP_STATUS_CODE_INVALID,{code:e.status,msg:e.statusText})})).catch((function(e){if(!i._abortController||!i._abortController.signal.aborted){if(i._status=o.GM.kError,!i._onError)throw e;i._onError(o.nm.EXCEPTION,{code:-1,msg:e.message})}}))},t.prototype.abort=function(){if(this._requestAbort=!0,(this._status!==o.GM.kBuffering||!a.Z.chrome)&&this._abortController)try{this._abortController.abort()}catch(e){}},t.prototype._pump=function(e){var t=this;return e.read().then((function(i){if(i.done)if(null!==t._contentLength&&t._receivedLength299)){if(this._status=o.GM.kError,!this._onError)throw new h.OZ("MozChunkedLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(o.nm.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}else this._status=o.GM.kBuffering}},t.prototype._onProgress=function(e){if(this._status!==o.GM.kError){null===this._contentLength&&null!==e.total&&0!==e.total&&(this._contentLength=e.total,this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength));var t=e.target.response,i=this._range.from+this._receivedLength;this._receivedLength+=t.byteLength,this._onDataArrival&&this._onDataArrival(t,i,this._receivedLength)}},t.prototype._onLoadEnd=function(e){!0!==this._requestAbort?this._status!==o.GM.kError&&(this._status=o.GM.kComplete,this._onComplete&&this._onComplete(this._range.from,this._range.from+this._receivedLength-1)):this._requestAbort=!1},t.prototype._onXhrError=function(e){this._status=o.GM.kError;var t=0,i=null;if(this._contentLength&&e.loaded=this._contentLength&&(i=this._range.from+this._contentLength-1),this._currentRequestRange={from:t,to:i},this._internalOpen(this._dataSource,this._currentRequestRange)},t.prototype._internalOpen=function(e,t){this._lastTimeLoaded=0;var i=e.url;this._config.reuseRedirectedURL&&(null!=this._currentRedirectedURL?i=this._currentRedirectedURL:null!=e.redirectedURL&&(i=e.redirectedURL));var n=this._seekHandler.getConfig(i,t);this._currentRequestURL=n.url;var r=this._xhr=new XMLHttpRequest;if(r.open("GET",n.url,!0),r.responseType="arraybuffer",r.onreadystatechange=this._onReadyStateChange.bind(this),r.onprogress=this._onProgress.bind(this),r.onload=this._onLoad.bind(this),r.onerror=this._onXhrError.bind(this),e.withCredentials&&(r.withCredentials=!0),"object"==typeof n.headers){var s=n.headers;for(var o in s)s.hasOwnProperty(o)&&r.setRequestHeader(o,s[o])}if("object"==typeof this._config.headers){s=this._config.headers;for(var o in s)s.hasOwnProperty(o)&&r.setRequestHeader(o,s[o])}r.send()},t.prototype.abort=function(){this._requestAbort=!0,this._internalAbort(),this._status=o.GM.kComplete},t.prototype._internalAbort=function(){this._xhr&&(this._xhr.onreadystatechange=null,this._xhr.onprogress=null,this._xhr.onload=null,this._xhr.onerror=null,this._xhr.abort(),this._xhr=null)},t.prototype._onReadyStateChange=function(e){var t=e.target;if(2===t.readyState){if(null!=t.responseURL){var i=this._seekHandler.removeURLParameters(t.responseURL);t.responseURL!==this._currentRequestURL&&i!==this._currentRedirectedURL&&(this._currentRedirectedURL=i,this._onURLRedirect&&this._onURLRedirect(i))}if(t.status>=200&&t.status<=299){if(this._waitForTotalLength)return;this._status=o.GM.kBuffering}else{if(this._status=o.GM.kError,!this._onError)throw new h.OZ("RangeLoader: Http code invalid, "+t.status+" "+t.statusText);this._onError(o.nm.HTTP_STATUS_CODE_INVALID,{code:t.status,msg:t.statusText})}}},t.prototype._onProgress=function(e){if(this._status!==o.GM.kError){if(null===this._contentLength){var t=!1;if(this._waitForTotalLength){this._waitForTotalLength=!1,this._totalLengthReceived=!0,t=!0;var i=e.total;this._internalAbort(),null!=i&0!==i&&(this._totalLength=i)}if(-1===this._range.to?this._contentLength=this._totalLength-this._range.from:this._contentLength=this._range.to-this._range.from+1,t)return void this._openSubRange();this._onContentLengthKnown&&this._onContentLengthKnown(this._contentLength)}var n=e.loaded-this._lastTimeLoaded;this._lastTimeLoaded=e.loaded,this._speedSampler.addBytes(n)}},t.prototype._normalizeSpeed=function(e){var t=this._chunkSizeKBList,i=t.length-1,n=0,r=0,s=i;if(e=t[n]&&e=3&&(t=this._speedSampler.currentKBps)),0!==t){var i=this._normalizeSpeed(t);this._currentSpeedNormalized!==i&&(this._currentSpeedNormalized=i,this._currentChunkSizeKB=i)}var n=e.target.response,r=this._range.from+this._receivedLength;this._receivedLength+=n.byteLength;var s=!1;null!=this._contentLength&&this._receivedLength0&&this._receivedLength0)for(var s=i.split("&"),o=0;o0;a[0]!==this._startName&&a[0]!==this._endName&&(h&&(r+="&"),r+=s[o])}return 0===r.length?t:t+"?"+r},e}(),y=function(){function e(e,t,i){this.TAG="IOController",this._config=t,this._extraData=i,this._stashInitialSize=393216,null!=t.stashInitialSize&&t.stashInitialSize>0&&(this._stashInitialSize=t.stashInitialSize),this._stashUsed=0,this._stashSize=this._stashInitialSize,this._bufferSize=3145728,this._stashBuffer=new ArrayBuffer(this._bufferSize),this._stashByteStart=0,this._enableStash=!0,!1===t.enableStashBuffer&&(this._enableStash=!1),this._loader=null,this._loaderClass=null,this._seekHandler=null,this._dataSource=e,this._isWebSocketURL=/wss?:\/\/(.+?)/.test(e.url),this._refTotalLength=e.filesize?e.filesize:null,this._totalLength=this._refTotalLength,this._fullRequestFlag=!1,this._currentRange=null,this._redirectedURL=null,this._speedNormalized=0,this._speedSampler=new s,this._speedNormalizeList=[64,128,256,384,512,768,1024,1536,2048,3072,4096],this._isEarlyEofReconnecting=!1,this._paused=!1,this._resumeFrom=0,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._selectSeekHandler(),this._selectLoader(),this._createLoader()}return e.prototype.destroy=function(){this._loader.isWorking()&&this._loader.abort(),this._loader.destroy(),this._loader=null,this._loaderClass=null,this._dataSource=null,this._stashBuffer=null,this._stashUsed=this._stashSize=this._bufferSize=this._stashByteStart=0,this._currentRange=null,this._speedSampler=null,this._isEarlyEofReconnecting=!1,this._onDataArrival=null,this._onSeeked=null,this._onError=null,this._onComplete=null,this._onRedirect=null,this._onRecoveredEarlyEof=null,this._extraData=null},e.prototype.isWorking=function(){return this._loader&&this._loader.isWorking()&&!this._paused},e.prototype.isPaused=function(){return this._paused},Object.defineProperty(e.prototype,"status",{get:function(){return this._loader.status},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"extraData",{get:function(){return this._extraData},set:function(e){this._extraData=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onDataArrival",{get:function(){return this._onDataArrival},set:function(e){this._onDataArrival=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onSeeked",{get:function(){return this._onSeeked},set:function(e){this._onSeeked=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onError",{get:function(){return this._onError},set:function(e){this._onError=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onComplete",{get:function(){return this._onComplete},set:function(e){this._onComplete=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRedirect",{get:function(){return this._onRedirect},set:function(e){this._onRedirect=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onRecoveredEarlyEof",{get:function(){return this._onRecoveredEarlyEof},set:function(e){this._onRecoveredEarlyEof=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentURL",{get:function(){return this._dataSource.url},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"hasRedirect",{get:function(){return null!=this._redirectedURL||null!=this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentRedirectedURL",{get:function(){return this._redirectedURL||this._dataSource.redirectedURL},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"currentSpeed",{get:function(){return this._loaderClass===_?this._loader.currentSpeed:this._speedSampler.lastSecondKBps},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"loaderType",{get:function(){return this._loader.type},enumerable:!1,configurable:!0}),e.prototype._selectSeekHandler=function(){var e=this._config;if("range"===e.seekType)this._seekHandler=new g(this._config.rangeLoadZeroStart);else if("param"===e.seekType){var t=e.seekParamStart||"bstart",i=e.seekParamEnd||"bend";this._seekHandler=new v(t,i)}else{if("custom"!==e.seekType)throw new h.OC("Invalid seekType in config: "+e.seekType);if("function"!=typeof e.customSeekHandler)throw new h.OC("Custom seekType specified in config but invalid customSeekHandler!");this._seekHandler=new e.customSeekHandler}},e.prototype._selectLoader=function(){if(null!=this._config.customLoader)this._loaderClass=this._config.customLoader;else if(this._isWebSocketURL)this._loaderClass=m;else if(l.isSupported())this._loaderClass=l;else if(c.isSupported())this._loaderClass=c;else{if(!_.isSupported())throw new h.OZ("Your browser doesn't support xhr with arraybuffer responseType!");this._loaderClass=_}},e.prototype._createLoader=function(){this._loader=new this._loaderClass(this._seekHandler,this._config),!1===this._loader.needStashBuffer&&(this._enableStash=!1),this._loader.onContentLengthKnown=this._onContentLengthKnown.bind(this),this._loader.onURLRedirect=this._onURLRedirect.bind(this),this._loader.onDataArrival=this._onLoaderChunkArrival.bind(this),this._loader.onComplete=this._onLoaderComplete.bind(this),this._loader.onError=this._onLoaderError.bind(this)},e.prototype.open=function(e){this._currentRange={from:0,to:-1},e&&(this._currentRange.from=e),this._speedSampler.reset(),e||(this._fullRequestFlag=!0),this._loader.open(this._dataSource,Object.assign({},this._currentRange))},e.prototype.abort=function(){this._loader.abort(),this._paused&&(this._paused=!1,this._resumeFrom=0)},e.prototype.pause=function(){this.isWorking()&&(this._loader.abort(),0!==this._stashUsed?(this._resumeFrom=this._stashByteStart,this._currentRange.to=this._stashByteStart-1):this._resumeFrom=this._currentRange.to+1,this._stashUsed=0,this._stashByteStart=0,this._paused=!0)},e.prototype.resume=function(){if(this._paused){this._paused=!1;var e=this._resumeFrom;this._resumeFrom=0,this._internalSeek(e,!0)}},e.prototype.seek=function(e){this._paused=!1,this._stashUsed=0,this._stashByteStart=0,this._internalSeek(e,!0)},e.prototype._internalSeek=function(e,t){this._loader.isWorking()&&this._loader.abort(),this._flushStashBuffer(t),this._loader.destroy(),this._loader=null;var i={from:e,to:-1};this._currentRange={from:i.from,to:-1},this._speedSampler.reset(),this._stashSize=this._stashInitialSize,this._createLoader(),this._loader.open(this._dataSource,i),this._onSeeked&&this._onSeeked()},e.prototype.updateUrl=function(e){if(!e||"string"!=typeof e||0===e.length)throw new h.OC("Url must be a non-empty string!");this._dataSource.url=e},e.prototype._expandBuffer=function(e){for(var t=this._stashSize;t+10485760){var n=new Uint8Array(this._stashBuffer,0,this._stashUsed);new Uint8Array(i,0,t).set(n,0)}this._stashBuffer=i,this._bufferSize=t}},e.prototype._normalizeSpeed=function(e){var t=this._speedNormalizeList,i=t.length-1,n=0,r=0,s=i;if(e=t[n]&&e=512&&e<=1024?Math.floor(1.5*e):2*e)>8192&&(t=8192);var i=1024*t+1048576;this._bufferSize0){var s=this._stashBuffer.slice(0,this._stashUsed);if((u=this._dispatchChunks(s,this._stashByteStart))0){l=new Uint8Array(s,u);a.set(l,0),this._stashUsed=l.byteLength,this._stashByteStart+=u}}else this._stashUsed=0,this._stashByteStart+=u;this._stashUsed+e.byteLength>this._bufferSize&&(this._expandBuffer(this._stashUsed+e.byteLength),a=new Uint8Array(this._stashBuffer,0,this._bufferSize)),a.set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength}else{if((u=this._dispatchChunks(e,t))this._bufferSize&&(this._expandBuffer(o),a=new Uint8Array(this._stashBuffer,0,this._bufferSize)),a.set(new Uint8Array(e,u),0),this._stashUsed+=o,this._stashByteStart=t+u}}else if(0===this._stashUsed){var o;if((u=this._dispatchChunks(e,t))this._bufferSize&&this._expandBuffer(o),(a=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e,u),0),this._stashUsed+=o,this._stashByteStart=t+u}else{var a,u;if(this._stashUsed+e.byteLength>this._bufferSize&&this._expandBuffer(this._stashUsed+e.byteLength),(a=new Uint8Array(this._stashBuffer,0,this._bufferSize)).set(new Uint8Array(e),this._stashUsed),this._stashUsed+=e.byteLength,(u=this._dispatchChunks(this._stashBuffer.slice(0,this._stashUsed),this._stashByteStart))0){var l=new Uint8Array(this._stashBuffer,u);a.set(l,0)}this._stashUsed-=u,this._stashByteStart+=u}}},e.prototype._flushStashBuffer=function(e){if(this._stashUsed>0){var t=this._stashBuffer.slice(0,this._stashUsed),i=this._dispatchChunks(t,this._stashByteStart),n=t.byteLength-i;if(i0){var s=new Uint8Array(this._stashBuffer,0,this._bufferSize),o=new Uint8Array(t,i);s.set(o,0),this._stashUsed=o.byteLength,this._stashByteStart+=i}return 0}r.Z.w(this.TAG,n+" bytes unconsumed data remain when flush buffer, dropped")}return this._stashUsed=0,this._stashByteStart=0,n}return 0},e.prototype._onLoaderComplete=function(e,t){this._flushStashBuffer(!0),this._onComplete&&this._onComplete(this._extraData)},e.prototype._onLoaderError=function(e,t){switch(r.Z.e(this.TAG,"Loader error, code = "+t.code+", msg = "+t.msg),this._flushStashBuffer(!1),this._isEarlyEofReconnecting&&(this._isEarlyEofReconnecting=!1,e=o.nm.UNRECOVERABLE_EARLY_EOF),e){case o.nm.EARLY_EOF:if(!this._config.isLive&&this._totalLength){var i=this._currentRange.to+1;return void(i=0&&/(rv)(?::| )([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(firefox)[ \/]([\w.]+)/.exec(e)||[],n=/(ipad)/.exec(e)||/(ipod)/.exec(e)||/(windows phone)/.exec(e)||/(iphone)/.exec(e)||/(kindle)/.exec(e)||/(android)/.exec(e)||/(windows)/.exec(e)||/(mac)/.exec(e)||/(linux)/.exec(e)||/(cros)/.exec(e)||[],r={browser:t[5]||t[3]||t[1]||"",version:t[2]||t[4]||"0",majorVersion:t[4]||t[2]||"0",platform:n[0]||""},s={};if(r.browser){s[r.browser]=!0;var o=r.majorVersion.split(".");s.version={major:parseInt(r.majorVersion,10),string:r.version},o.length>1&&(s.version.minor=parseInt(o[1],10)),o.length>2&&(s.version.build=parseInt(o[2],10))}if(r.platform&&(s[r.platform]=!0),(s.chrome||s.opr||s.safari)&&(s.webkit=!0),s.rv||s.iemobile){s.rv&&delete s.rv;var a="msie";r.browser=a,s.msie=!0}if(s.edge){delete s.edge;var h="msedge";r.browser=h,s.msedge=!0}if(s.opr){var u="opera";r.browser=u,s.opera=!0}if(s.safari&&s.android){var l="android";r.browser=l,s.android=!0}for(var d in s.name=r.browser,s.platform=r.platform,i)i.hasOwnProperty(d)&&delete i[d];Object.assign(i,s)}(),t.Z=i},29:function(e,t,i){"use strict";i.d(t,{OZ:function(){return s},rT:function(){return o},OC:function(){return a},do:function(){return h}});var n,r=(n=function(e,t){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])})(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function i(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)}),s=function(){function e(e){this._message=e}return Object.defineProperty(e.prototype,"name",{get:function(){return"RuntimeException"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"message",{get:function(){return this._message},enumerable:!1,configurable:!0}),e.prototype.toString=function(){return this.name+": "+this.message},e}(),o=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"IllegalStateException"},enumerable:!1,configurable:!0}),t}(s),a=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"InvalidArgumentException"},enumerable:!1,configurable:!0}),t}(s),h=function(e){function t(t){return e.call(this,t)||this}return r(t,e),Object.defineProperty(t.prototype,"name",{get:function(){return"NotImplementedException"},enumerable:!1,configurable:!0}),t}(s)},300:function(e,t,i){"use strict";var n=i(716),r=i.n(n),s=function(){function e(){}return e.e=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","error",n),e.ENABLE_ERROR&&(console.error?console.error(n):console.warn?console.warn(n):console.log(n))},e.i=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","info",n),e.ENABLE_INFO&&(console.info?console.info(n):console.log(n))},e.w=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","warn",n),e.ENABLE_WARN&&(console.warn?console.warn(n):console.log(n))},e.d=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","debug",n),e.ENABLE_DEBUG&&(console.debug?console.debug(n):console.log(n))},e.v=function(t,i){t&&!e.FORCE_GLOBAL_TAG||(t=e.GLOBAL_TAG);var n="["+t+"] > "+i;e.ENABLE_CALLBACK&&e.emitter.emit("log","verbose",n),e.ENABLE_VERBOSE&&console.log(n)},e}();s.GLOBAL_TAG="flv.js",s.FORCE_GLOBAL_TAG=!1,s.ENABLE_ERROR=!0,s.ENABLE_INFO=!0,s.ENABLE_WARN=!0,s.ENABLE_DEBUG=!0,s.ENABLE_VERBOSE=!0,s.ENABLE_CALLBACK=!1,s.emitter=new(r()),t.Z=s},846:function(e,t,i){"use strict";var n=i(716),r=i.n(n),s=i(300),o=function(){function e(){}return Object.defineProperty(e,"forceGlobalTag",{get:function(){return s.Z.FORCE_GLOBAL_TAG},set:function(t){s.Z.FORCE_GLOBAL_TAG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"globalTag",{get:function(){return s.Z.GLOBAL_TAG},set:function(t){s.Z.GLOBAL_TAG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableAll",{get:function(){return s.Z.ENABLE_VERBOSE&&s.Z.ENABLE_DEBUG&&s.Z.ENABLE_INFO&&s.Z.ENABLE_WARN&&s.Z.ENABLE_ERROR},set:function(t){s.Z.ENABLE_VERBOSE=t,s.Z.ENABLE_DEBUG=t,s.Z.ENABLE_INFO=t,s.Z.ENABLE_WARN=t,s.Z.ENABLE_ERROR=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableDebug",{get:function(){return s.Z.ENABLE_DEBUG},set:function(t){s.Z.ENABLE_DEBUG=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableVerbose",{get:function(){return s.Z.ENABLE_VERBOSE},set:function(t){s.Z.ENABLE_VERBOSE=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableInfo",{get:function(){return s.Z.ENABLE_INFO},set:function(t){s.Z.ENABLE_INFO=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableWarn",{get:function(){return s.Z.ENABLE_WARN},set:function(t){s.Z.ENABLE_WARN=t,e._notifyChange()},enumerable:!1,configurable:!0}),Object.defineProperty(e,"enableError",{get:function(){return s.Z.ENABLE_ERROR},set:function(t){s.Z.ENABLE_ERROR=t,e._notifyChange()},enumerable:!1,configurable:!0}),e.getConfig=function(){return{globalTag:s.Z.GLOBAL_TAG,forceGlobalTag:s.Z.FORCE_GLOBAL_TAG,enableVerbose:s.Z.ENABLE_VERBOSE,enableDebug:s.Z.ENABLE_DEBUG,enableInfo:s.Z.ENABLE_INFO,enableWarn:s.Z.ENABLE_WARN,enableError:s.Z.ENABLE_ERROR,enableCallback:s.Z.ENABLE_CALLBACK}},e.applyConfig=function(e){s.Z.GLOBAL_TAG=e.globalTag,s.Z.FORCE_GLOBAL_TAG=e.forceGlobalTag,s.Z.ENABLE_VERBOSE=e.enableVerbose,s.Z.ENABLE_DEBUG=e.enableDebug,s.Z.ENABLE_INFO=e.enableInfo,s.Z.ENABLE_WARN=e.enableWarn,s.Z.ENABLE_ERROR=e.enableError,s.Z.ENABLE_CALLBACK=e.enableCallback},e._notifyChange=function(){var t=e.emitter;if(t.listenerCount("change")>0){var i=e.getConfig();t.emit("change",i)}},e.registerListener=function(t){e.emitter.addListener("change",t)},e.removeListener=function(t){e.emitter.removeListener("change",t)},e.addLogListener=function(t){s.Z.emitter.addListener("log",t),s.Z.emitter.listenerCount("log")>0&&(s.Z.ENABLE_CALLBACK=!0,e._notifyChange())},e.removeLogListener=function(t){s.Z.emitter.removeListener("log",t),0===s.Z.emitter.listenerCount("log")&&(s.Z.ENABLE_CALLBACK=!1,e._notifyChange())},e}();o.emitter=new(r()),t.Z=o},219:function(e,t,i){"use strict";var n=function(){function e(){}return e.install=function(){Object.setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},Object.assign=Object.assign||function(e){if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(e),i=1;i postsJSON\n values[1] // => commentsJSON\n\n return values;\n });\n ```\n\n @class Promise\n @param {Function} resolver\n Useful for tooling.\n @constructor\n*/\n\nvar Promise$1 = function () {\n function Promise(resolver) {\n this[PROMISE_ID] = nextId();\n this._result = this._state = undefined;\n this._subscribers = [];\n\n if (noop !== resolver) {\n typeof resolver !== 'function' && needsResolver();\n this instanceof Promise ? initializePromise(this, resolver) : needsNew();\n }\n }\n\n /**\n The primary way of interacting with a promise is through its `then` method,\n which registers callbacks to receive either a promise's eventual value or the\n reason why the promise cannot be fulfilled.\n ```js\n findUser().then(function(user){\n // user is available\n }, function(reason){\n // user is unavailable, and you are given the reason why\n });\n ```\n Chaining\n --------\n The return value of `then` is itself a promise. This second, 'downstream'\n promise is resolved with the return value of the first promise's fulfillment\n or rejection handler, or rejected if the handler throws an exception.\n ```js\n findUser().then(function (user) {\n return user.name;\n }, function (reason) {\n return 'default name';\n }).then(function (userName) {\n // If `findUser` fulfilled, `userName` will be the user's name, otherwise it\n // will be `'default name'`\n });\n findUser().then(function (user) {\n throw new Error('Found user, but still unhappy');\n }, function (reason) {\n throw new Error('`findUser` rejected and we're unhappy');\n }).then(function (value) {\n // never reached\n }, function (reason) {\n // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.\n // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.\n });\n ```\n If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.\n ```js\n findUser().then(function (user) {\n throw new PedagogicalException('Upstream error');\n }).then(function (value) {\n // never reached\n }).then(function (value) {\n // never reached\n }, function (reason) {\n // The `PedgagocialException` is propagated all the way down to here\n });\n ```\n Assimilation\n ------------\n Sometimes the value you want to propagate to a downstream promise can only be\n retrieved asynchronously. This can be achieved by returning a promise in the\n fulfillment or rejection handler. The downstream promise will then be pending\n until the returned promise is settled. This is called *assimilation*.\n ```js\n findUser().then(function (user) {\n return findCommentsByAuthor(user);\n }).then(function (comments) {\n // The user's comments are now available\n });\n ```\n If the assimliated promise rejects, then the downstream promise will also reject.\n ```js\n findUser().then(function (user) {\n return findCommentsByAuthor(user);\n }).then(function (comments) {\n // If `findCommentsByAuthor` fulfills, we'll have the value here\n }, function (reason) {\n // If `findCommentsByAuthor` rejects, we'll have the reason here\n });\n ```\n Simple Example\n --------------\n Synchronous Example\n ```javascript\n let result;\n try {\n result = findResult();\n // success\n } catch(reason) {\n // failure\n }\n ```\n Errback Example\n ```js\n findResult(function(result, err){\n if (err) {\n // failure\n } else {\n // success\n }\n });\n ```\n Promise Example;\n ```javascript\n findResult().then(function(result){\n // success\n }, function(reason){\n // failure\n });\n ```\n Advanced Example\n --------------\n Synchronous Example\n ```javascript\n let author, books;\n try {\n author = findAuthor();\n books = findBooksByAuthor(author);\n // success\n } catch(reason) {\n // failure\n }\n ```\n Errback Example\n ```js\n function foundBooks(books) {\n }\n function failure(reason) {\n }\n findAuthor(function(author, err){\n if (err) {\n failure(err);\n // failure\n } else {\n try {\n findBoooksByAuthor(author, function(books, err) {\n if (err) {\n failure(err);\n } else {\n try {\n foundBooks(books);\n } catch(reason) {\n failure(reason);\n }\n }\n });\n } catch(error) {\n failure(err);\n }\n // success\n }\n });\n ```\n Promise Example;\n ```javascript\n findAuthor().\n then(findBooksByAuthor).\n then(function(books){\n // found books\n }).catch(function(reason){\n // something went wrong\n });\n ```\n @method then\n @param {Function} onFulfilled\n @param {Function} onRejected\n Useful for tooling.\n @return {Promise}\n */\n\n /**\n `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same\n as the catch block of a try/catch statement.\n ```js\n function findAuthor(){\n throw new Error('couldn't find that author');\n }\n // synchronous\n try {\n findAuthor();\n } catch(reason) {\n // something went wrong\n }\n // async with promises\n findAuthor().catch(function(reason){\n // something went wrong\n });\n ```\n @method catch\n @param {Function} onRejection\n Useful for tooling.\n @return {Promise}\n */\n\n\n Promise.prototype.catch = function _catch(onRejection) {\n return this.then(null, onRejection);\n };\n\n /**\n `finally` will be invoked regardless of the promise's fate just as native\n try/catch/finally behaves\n \n Synchronous example:\n \n ```js\n findAuthor() {\n if (Math.random() > 0.5) {\n throw new Error();\n }\n return new Author();\n }\n \n try {\n return findAuthor(); // succeed or fail\n } catch(error) {\n return findOtherAuther();\n } finally {\n // always runs\n // doesn't affect the return value\n }\n ```\n \n Asynchronous example:\n \n ```js\n findAuthor().catch(function(reason){\n return findOtherAuther();\n }).finally(function(){\n // author was either found, or not\n });\n ```\n \n @method finally\n @param {Function} callback\n @return {Promise}\n */\n\n\n Promise.prototype.finally = function _finally(callback) {\n var promise = this;\n var constructor = promise.constructor;\n\n if (isFunction(callback)) {\n return promise.then(function (value) {\n return constructor.resolve(callback()).then(function () {\n return value;\n });\n }, function (reason) {\n return constructor.resolve(callback()).then(function () {\n throw reason;\n });\n });\n }\n\n return promise.then(callback, callback);\n };\n\n return Promise;\n}();\n\nPromise$1.prototype.then = then;\nPromise$1.all = all;\nPromise$1.race = race;\nPromise$1.resolve = resolve$1;\nPromise$1.reject = reject$1;\nPromise$1._setScheduler = setScheduler;\nPromise$1._setAsap = setAsap;\nPromise$1._asap = asap;\n\n/*global self*/\nfunction polyfill() {\n var local = void 0;\n\n if (typeof global !== 'undefined') {\n local = global;\n } else if (typeof self !== 'undefined') {\n local = self;\n } else {\n try {\n local = Function('return this')();\n } catch (e) {\n throw new Error('polyfill failed because global object is unavailable in this environment');\n }\n }\n\n var P = local.Promise;\n\n if (P) {\n var promiseToString = null;\n try {\n promiseToString = Object.prototype.toString.call(P.resolve());\n } catch (e) {\n // silently ignored\n }\n\n if (promiseToString === '[object Promise]' && !P.cast) {\n return;\n }\n }\n\n local.Promise = Promise$1;\n}\n\n// Strange compat..\nPromise$1.polyfill = polyfill;\nPromise$1.Promise = Promise$1;\n\nreturn Promise$1;\n\n})));\n\n\n\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nvar R = typeof Reflect === 'object' ? Reflect : null\nvar ReflectApply = R && typeof R.apply === 'function'\n ? R.apply\n : function ReflectApply(target, receiver, args) {\n return Function.prototype.apply.call(target, receiver, args);\n }\n\nvar ReflectOwnKeys\nif (R && typeof R.ownKeys === 'function') {\n ReflectOwnKeys = R.ownKeys\n} else if (Object.getOwnPropertySymbols) {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target)\n .concat(Object.getOwnPropertySymbols(target));\n };\n} else {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target);\n };\n}\n\nfunction ProcessEmitWarning(warning) {\n if (console && console.warn) console.warn(warning);\n}\n\nvar NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {\n return value !== value;\n}\n\nfunction EventEmitter() {\n EventEmitter.init.call(this);\n}\nmodule.exports = EventEmitter;\nmodule.exports.once = once;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._eventsCount = 0;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nvar defaultMaxListeners = 10;\n\nfunction checkListener(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('The \"listener\" argument must be of type Function. Received type ' + typeof listener);\n }\n}\n\nObject.defineProperty(EventEmitter, 'defaultMaxListeners', {\n enumerable: true,\n get: function() {\n return defaultMaxListeners;\n },\n set: function(arg) {\n if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {\n throw new RangeError('The value of \"defaultMaxListeners\" is out of range. It must be a non-negative number. Received ' + arg + '.');\n }\n defaultMaxListeners = arg;\n }\n});\n\nEventEmitter.init = function() {\n\n if (this._events === undefined ||\n this._events === Object.getPrototypeOf(this)._events) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n }\n\n this._maxListeners = this._maxListeners || undefined;\n};\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {\n if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {\n throw new RangeError('The value of \"n\" is out of range. It must be a non-negative number. Received ' + n + '.');\n }\n this._maxListeners = n;\n return this;\n};\n\nfunction _getMaxListeners(that) {\n if (that._maxListeners === undefined)\n return EventEmitter.defaultMaxListeners;\n return that._maxListeners;\n}\n\nEventEmitter.prototype.getMaxListeners = function getMaxListeners() {\n return _getMaxListeners(this);\n};\n\nEventEmitter.prototype.emit = function emit(type) {\n var args = [];\n for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);\n var doError = (type === 'error');\n\n var events = this._events;\n if (events !== undefined)\n doError = (doError && events.error === undefined);\n else if (!doError)\n return false;\n\n // If there is no 'error' event listener then throw.\n if (doError) {\n var er;\n if (args.length > 0)\n er = args[0];\n if (er instanceof Error) {\n // Note: The comments on the `throw` lines are intentional, they show\n // up in Node's output if this results in an unhandled exception.\n throw er; // Unhandled 'error' event\n }\n // At least give some kind of context to the user\n var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));\n err.context = er;\n throw err; // Unhandled 'error' event\n }\n\n var handler = events[type];\n\n if (handler === undefined)\n return false;\n\n if (typeof handler === 'function') {\n ReflectApply(handler, this, args);\n } else {\n var len = handler.length;\n var listeners = arrayClone(handler, len);\n for (var i = 0; i < len; ++i)\n ReflectApply(listeners[i], this, args);\n }\n\n return true;\n};\n\nfunction _addListener(target, type, listener, prepend) {\n var m;\n var events;\n var existing;\n\n checkListener(listener);\n\n events = target._events;\n if (events === undefined) {\n events = target._events = Object.create(null);\n target._eventsCount = 0;\n } else {\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (events.newListener !== undefined) {\n target.emit('newListener', type,\n listener.listener ? listener.listener : listener);\n\n // Re-assign `events` because a newListener handler could have caused the\n // this._events to be assigned to a new object\n events = target._events;\n }\n existing = events[type];\n }\n\n if (existing === undefined) {\n // Optimize the case of one listener. Don't need the extra array object.\n existing = events[type] = listener;\n ++target._eventsCount;\n } else {\n if (typeof existing === 'function') {\n // Adding the second element, need to change to array.\n existing = events[type] =\n prepend ? [listener, existing] : [existing, listener];\n // If we've already got an array, just append.\n } else if (prepend) {\n existing.unshift(listener);\n } else {\n existing.push(listener);\n }\n\n // Check for listener leak\n m = _getMaxListeners(target);\n if (m > 0 && existing.length > m && !existing.warned) {\n existing.warned = true;\n // No error code for this since it is a Warning\n // eslint-disable-next-line no-restricted-syntax\n var w = new Error('Possible EventEmitter memory leak detected. ' +\n existing.length + ' ' + String(type) + ' listeners ' +\n 'added. Use emitter.setMaxListeners() to ' +\n 'increase limit');\n w.name = 'MaxListenersExceededWarning';\n w.emitter = target;\n w.type = type;\n w.count = existing.length;\n ProcessEmitWarning(w);\n }\n }\n\n return target;\n}\n\nEventEmitter.prototype.addListener = function addListener(type, listener) {\n return _addListener(this, type, listener, false);\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.prependListener =\n function prependListener(type, listener) {\n return _addListener(this, type, listener, true);\n };\n\nfunction onceWrapper() {\n if (!this.fired) {\n this.target.removeListener(this.type, this.wrapFn);\n this.fired = true;\n if (arguments.length === 0)\n return this.listener.call(this.target);\n return this.listener.apply(this.target, arguments);\n }\n}\n\nfunction _onceWrap(target, type, listener) {\n var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };\n var wrapped = onceWrapper.bind(state);\n wrapped.listener = listener;\n state.wrapFn = wrapped;\n return wrapped;\n}\n\nEventEmitter.prototype.once = function once(type, listener) {\n checkListener(listener);\n this.on(type, _onceWrap(this, type, listener));\n return this;\n};\n\nEventEmitter.prototype.prependOnceListener =\n function prependOnceListener(type, listener) {\n checkListener(listener);\n this.prependListener(type, _onceWrap(this, type, listener));\n return this;\n };\n\n// Emits a 'removeListener' event if and only if the listener was removed.\nEventEmitter.prototype.removeListener =\n function removeListener(type, listener) {\n var list, events, position, i, originalListener;\n\n checkListener(listener);\n\n events = this._events;\n if (events === undefined)\n return this;\n\n list = events[type];\n if (list === undefined)\n return this;\n\n if (list === listener || list.listener === listener) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else {\n delete events[type];\n if (events.removeListener)\n this.emit('removeListener', type, list.listener || listener);\n }\n } else if (typeof list !== 'function') {\n position = -1;\n\n for (i = list.length - 1; i >= 0; i--) {\n if (list[i] === listener || list[i].listener === listener) {\n originalListener = list[i].listener;\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (position === 0)\n list.shift();\n else {\n spliceOne(list, position);\n }\n\n if (list.length === 1)\n events[type] = list[0];\n\n if (events.removeListener !== undefined)\n this.emit('removeListener', type, originalListener || listener);\n }\n\n return this;\n };\n\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\n\nEventEmitter.prototype.removeAllListeners =\n function removeAllListeners(type) {\n var listeners, events, i;\n\n events = this._events;\n if (events === undefined)\n return this;\n\n // not listening for removeListener, no need to emit\n if (events.removeListener === undefined) {\n if (arguments.length === 0) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n } else if (events[type] !== undefined) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else\n delete events[type];\n }\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n var keys = Object.keys(events);\n var key;\n for (i = 0; i < keys.length; ++i) {\n key = keys[i];\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = Object.create(null);\n this._eventsCount = 0;\n return this;\n }\n\n listeners = events[type];\n\n if (typeof listeners === 'function') {\n this.removeListener(type, listeners);\n } else if (listeners !== undefined) {\n // LIFO order\n for (i = listeners.length - 1; i >= 0; i--) {\n this.removeListener(type, listeners[i]);\n }\n }\n\n return this;\n };\n\nfunction _listeners(target, type, unwrap) {\n var events = target._events;\n\n if (events === undefined)\n return [];\n\n var evlistener = events[type];\n if (evlistener === undefined)\n return [];\n\n if (typeof evlistener === 'function')\n return unwrap ? [evlistener.listener || evlistener] : [evlistener];\n\n return unwrap ?\n unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);\n}\n\nEventEmitter.prototype.listeners = function listeners(type) {\n return _listeners(this, type, true);\n};\n\nEventEmitter.prototype.rawListeners = function rawListeners(type) {\n return _listeners(this, type, false);\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n if (typeof emitter.listenerCount === 'function') {\n return emitter.listenerCount(type);\n } else {\n return listenerCount.call(emitter, type);\n }\n};\n\nEventEmitter.prototype.listenerCount = listenerCount;\nfunction listenerCount(type) {\n var events = this._events;\n\n if (events !== undefined) {\n var evlistener = events[type];\n\n if (typeof evlistener === 'function') {\n return 1;\n } else if (evlistener !== undefined) {\n return evlistener.length;\n }\n }\n\n return 0;\n}\n\nEventEmitter.prototype.eventNames = function eventNames() {\n return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];\n};\n\nfunction arrayClone(arr, n) {\n var copy = new Array(n);\n for (var i = 0; i < n; ++i)\n copy[i] = arr[i];\n return copy;\n}\n\nfunction spliceOne(list, index) {\n for (; index + 1 < list.length; index++)\n list[index] = list[index + 1];\n list.pop();\n}\n\nfunction unwrapListeners(arr) {\n var ret = new Array(arr.length);\n for (var i = 0; i < ret.length; ++i) {\n ret[i] = arr[i].listener || arr[i];\n }\n return ret;\n}\n\nfunction once(emitter, name) {\n return new Promise(function (resolve, reject) {\n function errorListener(err) {\n emitter.removeListener(name, resolver);\n reject(err);\n }\n\n function resolver() {\n if (typeof emitter.removeListener === 'function') {\n emitter.removeListener('error', errorListener);\n }\n resolve([].slice.call(arguments));\n };\n\n eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });\n if (name !== 'error') {\n addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });\n }\n });\n}\n\nfunction addErrorHandlerIfEventEmitter(emitter, handler, flags) {\n if (typeof emitter.on === 'function') {\n eventTargetAgnosticAddListener(emitter, 'error', handler, flags);\n }\n}\n\nfunction eventTargetAgnosticAddListener(emitter, name, listener, flags) {\n if (typeof emitter.on === 'function') {\n if (flags.once) {\n emitter.once(name, listener);\n } else {\n emitter.on(name, listener);\n }\n } else if (typeof emitter.addEventListener === 'function') {\n // EventTarget does not have `error` event semantics like Node\n // EventEmitters, we do not listen for `error` events here.\n emitter.addEventListener(name, function wrapListener(arg) {\n // IE does not have builtin `{ once: true }` support so we\n // have to do it manually.\n if (flags.once) {\n emitter.removeEventListener(name, wrapListener);\n }\n listener(arg);\n });\n } else {\n throw new TypeError('The \"emitter\" argument must be of type EventEmitter. Received type ' + typeof emitter);\n }\n}\n","function webpackBootstrapFunc (modules) {\n/******/ // The module cache\n/******/ var installedModules = {};\n\n/******/ // The require function\n/******/ function __webpack_require__(moduleId) {\n\n/******/ // Check if module is in cache\n/******/ if(installedModules[moduleId])\n/******/ return installedModules[moduleId].exports;\n\n/******/ // Create a new module (and put it into the cache)\n/******/ var module = installedModules[moduleId] = {\n/******/ i: moduleId,\n/******/ l: false,\n/******/ exports: {}\n/******/ };\n\n/******/ // Execute the module function\n/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n/******/ // Flag the module as loaded\n/******/ module.l = true;\n\n/******/ // Return the exports of the module\n/******/ return module.exports;\n/******/ }\n\n/******/ // expose the modules object (__webpack_modules__)\n/******/ __webpack_require__.m = modules;\n\n/******/ // expose the module cache\n/******/ __webpack_require__.c = installedModules;\n\n/******/ // identity function for calling harmony imports with the correct context\n/******/ __webpack_require__.i = function(value) { return value; };\n\n/******/ // define getter function for harmony exports\n/******/ __webpack_require__.d = function(exports, name, getter) {\n/******/ if(!__webpack_require__.o(exports, name)) {\n/******/ Object.defineProperty(exports, name, {\n/******/ configurable: false,\n/******/ enumerable: true,\n/******/ get: getter\n/******/ });\n/******/ }\n/******/ };\n\n/******/ // define __esModule on exports\n/******/ __webpack_require__.r = function(exports) {\n/******/ Object.defineProperty(exports, '__esModule', { value: true });\n/******/ };\n\n/******/ // getDefaultExport function for compatibility with non-harmony modules\n/******/ __webpack_require__.n = function(module) {\n/******/ var getter = module && module.__esModule ?\n/******/ function getDefault() { return module['default']; } :\n/******/ function getModuleExports() { return module; };\n/******/ __webpack_require__.d(getter, 'a', getter);\n/******/ return getter;\n/******/ };\n\n/******/ // Object.prototype.hasOwnProperty.call\n/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n/******/ // __webpack_public_path__\n/******/ __webpack_require__.p = \"/\";\n\n/******/ // on error function for async loading\n/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n var f = __webpack_require__(__webpack_require__.s = ENTRY_MODULE)\n return f.default || f // try to call default if defined to also support babel esmodule exports\n}\n\nvar moduleNameReqExp = '[\\\\.|\\\\-|\\\\+|\\\\w|\\/|@]+'\nvar dependencyRegExp = '\\\\(\\\\s*(\\/\\\\*.*?\\\\*\\/)?\\\\s*.*?(' + moduleNameReqExp + ').*?\\\\)' // additional chars when output.pathinfo is true\n\n// http://stackoverflow.com/a/2593661/130442\nfunction quoteRegExp (str) {\n return (str + '').replace(/[.?*+^$[\\]\\\\(){}|-]/g, '\\\\$&')\n}\n\nfunction isNumeric(n) {\n return !isNaN(1 * n); // 1 * n converts integers, integers as string (\"123\"), 1e3 and \"1e3\" to integers and strings to NaN\n}\n\nfunction getModuleDependencies (sources, module, queueName) {\n var retval = {}\n retval[queueName] = []\n\n var fnString = module.toString()\n var wrapperSignature = fnString.match(/^function\\s?\\w*\\(\\w+,\\s*\\w+,\\s*(\\w+)\\)/)\n if (!wrapperSignature) return retval\n var webpackRequireName = wrapperSignature[1]\n\n // main bundle deps\n var re = new RegExp('(\\\\\\\\n|\\\\W)' + quoteRegExp(webpackRequireName) + dependencyRegExp, 'g')\n var match\n while ((match = re.exec(fnString))) {\n if (match[3] === 'dll-reference') continue\n retval[queueName].push(match[3])\n }\n\n // dll deps\n re = new RegExp('\\\\(' + quoteRegExp(webpackRequireName) + '\\\\(\"(dll-reference\\\\s(' + moduleNameReqExp + '))\"\\\\)\\\\)' + dependencyRegExp, 'g')\n while ((match = re.exec(fnString))) {\n if (!sources[match[2]]) {\n retval[queueName].push(match[1])\n sources[match[2]] = __webpack_require__(match[1]).m\n }\n retval[match[2]] = retval[match[2]] || []\n retval[match[2]].push(match[4])\n }\n\n // convert 1e3 back to 1000 - this can be important after uglify-js converted 1000 to 1e3\n var keys = Object.keys(retval);\n for (var i = 0; i < keys.length; i++) {\n for (var j = 0; j < retval[keys[i]].length; j++) {\n if (isNumeric(retval[keys[i]][j])) {\n retval[keys[i]][j] = 1 * retval[keys[i]][j];\n }\n }\n }\n\n return retval\n}\n\nfunction hasValuesInQueues (queues) {\n var keys = Object.keys(queues)\n return keys.reduce(function (hasValues, key) {\n return hasValues || queues[key].length > 0\n }, false)\n}\n\nfunction getRequiredModules (sources, moduleId) {\n var modulesQueue = {\n main: [moduleId]\n }\n var requiredModules = {\n main: []\n }\n var seenModules = {\n main: {}\n }\n\n while (hasValuesInQueues(modulesQueue)) {\n var queues = Object.keys(modulesQueue)\n for (var i = 0; i < queues.length; i++) {\n var queueName = queues[i]\n var queue = modulesQueue[queueName]\n var moduleToCheck = queue.pop()\n seenModules[queueName] = seenModules[queueName] || {}\n if (seenModules[queueName][moduleToCheck] || !sources[queueName][moduleToCheck]) continue\n seenModules[queueName][moduleToCheck] = true\n requiredModules[queueName] = requiredModules[queueName] || []\n requiredModules[queueName].push(moduleToCheck)\n var newModules = getModuleDependencies(sources, sources[queueName][moduleToCheck], queueName)\n var newModulesKeys = Object.keys(newModules)\n for (var j = 0; j < newModulesKeys.length; j++) {\n modulesQueue[newModulesKeys[j]] = modulesQueue[newModulesKeys[j]] || []\n modulesQueue[newModulesKeys[j]] = modulesQueue[newModulesKeys[j]].concat(newModules[newModulesKeys[j]])\n }\n }\n }\n\n return requiredModules\n}\n\nmodule.exports = function (moduleId, options) {\n options = options || {}\n var sources = {\n main: __webpack_modules__\n }\n\n var requiredModules = options.all ? { main: Object.keys(sources.main) } : getRequiredModules(sources, moduleId)\n\n var src = ''\n\n Object.keys(requiredModules).filter(function (m) { return m !== 'main' }).forEach(function (module) {\n var entryModule = 0\n while (requiredModules[module][entryModule]) {\n entryModule++\n }\n requiredModules[module].push(entryModule)\n sources[module][entryModule] = '(function(module, exports, __webpack_require__) { module.exports = __webpack_require__; })'\n src = src + 'var ' + module + ' = (' + webpackBootstrapFunc.toString().replace('ENTRY_MODULE', JSON.stringify(entryModule)) + ')({' + requiredModules[module].map(function (id) { return '' + JSON.stringify(id) + ': ' + sources[module][id].toString() }).join(',') + '});\\n'\n })\n\n src = src + 'new ((' + webpackBootstrapFunc.toString().replace('ENTRY_MODULE', JSON.stringify(moduleId)) + ')({' + requiredModules.main.map(function (id) { return '' + JSON.stringify(id) + ': ' + sources.main[id].toString() }).join(',') + '}))(self);'\n\n var blob = new window.Blob([src], { type: 'text/javascript' })\n if (options.bare) { return blob }\n\n var URL = window.URL || window.webkitURL || window.mozURL || window.msURL\n\n var workerUrl = URL.createObjectURL(blob)\n var worker = new window.Worker(workerUrl)\n worker.objectURL = workerUrl\n\n return worker\n}\n","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass MediaInfo {\n\n constructor() {\n this.mimeType = null;\n this.duration = null;\n\n this.hasAudio = null;\n this.hasVideo = null;\n this.audioCodec = null;\n this.videoCodec = null;\n this.audioDataRate = null;\n this.videoDataRate = null;\n\n this.audioSampleRate = null;\n this.audioChannelCount = null;\n\n this.width = null;\n this.height = null;\n this.fps = null;\n this.profile = null;\n this.level = null;\n this.refFrames = null;\n this.chromaFormat = null;\n this.sarNum = null;\n this.sarDen = null;\n\n this.metadata = null;\n this.segments = null; // MediaInfo[]\n this.segmentCount = null;\n this.hasKeyframesIndex = null;\n this.keyframesIndex = null;\n }\n\n isComplete() {\n let audioInfoComplete = (this.hasAudio === false) ||\n (this.hasAudio === true &&\n this.audioCodec != null &&\n this.audioSampleRate != null &&\n this.audioChannelCount != null);\n\n let videoInfoComplete = (this.hasVideo === false) ||\n (this.hasVideo === true &&\n this.videoCodec != null &&\n this.width != null &&\n this.height != null &&\n this.fps != null &&\n this.profile != null &&\n this.level != null &&\n this.refFrames != null &&\n this.chromaFormat != null &&\n this.sarNum != null &&\n this.sarDen != null);\n\n // keyframesIndex may not be present\n return this.mimeType != null &&\n this.duration != null &&\n this.metadata != null &&\n this.hasKeyframesIndex != null &&\n audioInfoComplete &&\n videoInfoComplete;\n }\n\n isSeekable() {\n return this.hasKeyframesIndex === true;\n }\n\n getNearestKeyframe(milliseconds) {\n if (this.keyframesIndex == null) {\n return null;\n }\n\n let table = this.keyframesIndex;\n let keyframeIdx = this._search(table.times, milliseconds);\n\n return {\n index: keyframeIdx,\n milliseconds: table.times[keyframeIdx],\n fileposition: table.filepositions[keyframeIdx]\n };\n }\n\n _search(list, value) {\n let idx = 0;\n\n let last = list.length - 1;\n let mid = 0;\n let lbound = 0;\n let ubound = last;\n\n if (value < list[0]) {\n idx = 0;\n lbound = ubound + 1; // skip search\n }\n\n while (lbound <= ubound) {\n mid = lbound + Math.floor((ubound - lbound) / 2);\n if (mid === last || (value >= list[mid] && value < list[mid + 1])) {\n idx = mid;\n break;\n } else if (list[mid] < value) {\n lbound = mid + 1;\n } else {\n ubound = mid - 1;\n }\n }\n\n return idx;\n }\n\n}\n\nexport default MediaInfo;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Represents an media sample (audio / video)\nexport class SampleInfo {\n\n constructor(dts, pts, duration, originalDts, isSync) {\n this.dts = dts;\n this.pts = pts;\n this.duration = duration;\n this.originalDts = originalDts;\n this.isSyncPoint = isSync;\n this.fileposition = null;\n }\n\n}\n\n// Media Segment concept is defined in Media Source Extensions spec.\n// Particularly in ISO BMFF format, an Media Segment contains a moof box followed by a mdat box.\nexport class MediaSegmentInfo {\n\n constructor() {\n this.beginDts = 0;\n this.endDts = 0;\n this.beginPts = 0;\n this.endPts = 0;\n this.originalBeginDts = 0;\n this.originalEndDts = 0;\n this.syncPoints = []; // SampleInfo[n], for video IDR frames only\n this.firstSample = null; // SampleInfo\n this.lastSample = null; // SampleInfo\n }\n\n appendSyncPoint(sampleInfo) { // also called Random Access Point\n sampleInfo.isSyncPoint = true;\n this.syncPoints.push(sampleInfo);\n }\n\n}\n\n// Ordered list for recording video IDR frames, sorted by originalDts\nexport class IDRSampleList {\n\n constructor() {\n this._list = [];\n }\n\n clear() {\n this._list = [];\n }\n\n appendArray(syncPoints) {\n let list = this._list;\n\n if (syncPoints.length === 0) {\n return;\n }\n\n if (list.length > 0 && syncPoints[0].originalDts < list[list.length - 1].originalDts) {\n this.clear();\n }\n\n Array.prototype.push.apply(list, syncPoints);\n }\n\n getLastSyncPointBeforeDts(dts) {\n if (this._list.length == 0) {\n return null;\n }\n\n let list = this._list;\n let idx = 0;\n let last = list.length - 1;\n let mid = 0;\n let lbound = 0;\n let ubound = last;\n\n if (dts < list[0].dts) {\n idx = 0;\n lbound = ubound + 1;\n }\n\n while (lbound <= ubound) {\n mid = lbound + Math.floor((ubound - lbound) / 2);\n if (mid === last || (dts >= list[mid].dts && dts < list[mid + 1].dts)) {\n idx = mid;\n break;\n } else if (list[mid].dts < dts) {\n lbound = mid + 1;\n } else {\n ubound = mid - 1;\n }\n }\n return this._list[idx];\n }\n\n}\n\n// Data structure for recording information of media segments in single track.\nexport class MediaSegmentInfoList {\n\n constructor(type) {\n this._type = type;\n this._list = [];\n this._lastAppendLocation = -1; // cached last insert location\n }\n\n get type() {\n return this._type;\n }\n\n get length() {\n return this._list.length;\n }\n\n isEmpty() {\n return this._list.length === 0;\n }\n\n clear() {\n this._list = [];\n this._lastAppendLocation = -1;\n }\n\n _searchNearestSegmentBefore(originalBeginDts) {\n let list = this._list;\n if (list.length === 0) {\n return -2;\n }\n let last = list.length - 1;\n let mid = 0;\n let lbound = 0;\n let ubound = last;\n\n let idx = 0;\n\n if (originalBeginDts < list[0].originalBeginDts) {\n idx = -1;\n return idx;\n }\n\n while (lbound <= ubound) {\n mid = lbound + Math.floor((ubound - lbound) / 2);\n if (mid === last || (originalBeginDts > list[mid].lastSample.originalDts &&\n (originalBeginDts < list[mid + 1].originalBeginDts))) {\n idx = mid;\n break;\n } else if (list[mid].originalBeginDts < originalBeginDts) {\n lbound = mid + 1;\n } else {\n ubound = mid - 1;\n }\n }\n return idx;\n }\n\n _searchNearestSegmentAfter(originalBeginDts) {\n return this._searchNearestSegmentBefore(originalBeginDts) + 1;\n }\n\n append(mediaSegmentInfo) {\n let list = this._list;\n let msi = mediaSegmentInfo;\n let lastAppendIdx = this._lastAppendLocation;\n let insertIdx = 0;\n\n if (lastAppendIdx !== -1 && lastAppendIdx < list.length &&\n msi.originalBeginDts >= list[lastAppendIdx].lastSample.originalDts &&\n ((lastAppendIdx === list.length - 1) ||\n (lastAppendIdx < list.length - 1 &&\n msi.originalBeginDts < list[lastAppendIdx + 1].originalBeginDts))) {\n insertIdx = lastAppendIdx + 1; // use cached location idx\n } else {\n if (list.length > 0) {\n insertIdx = this._searchNearestSegmentBefore(msi.originalBeginDts) + 1;\n }\n }\n\n this._lastAppendLocation = insertIdx;\n this._list.splice(insertIdx, 0, msi);\n }\n\n getLastSegmentBefore(originalBeginDts) {\n let idx = this._searchNearestSegmentBefore(originalBeginDts);\n if (idx >= 0) {\n return this._list[idx];\n } else { // -1\n return null;\n }\n }\n\n getLastSampleBefore(originalBeginDts) {\n let segment = this.getLastSegmentBefore(originalBeginDts);\n if (segment != null) {\n return segment.lastSample;\n } else {\n return null;\n }\n }\n\n getLastSyncPointBefore(originalBeginDts) {\n let segmentIdx = this._searchNearestSegmentBefore(originalBeginDts);\n let syncPoints = this._list[segmentIdx].syncPoints;\n while (syncPoints.length === 0 && segmentIdx > 0) {\n segmentIdx--;\n syncPoints = this._list[segmentIdx].syncPoints;\n }\n if (syncPoints.length > 0) {\n return syncPoints[syncPoints.length - 1];\n } else {\n return null;\n }\n }\n\n}","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * This file is derived from C++ project libWinTF8 (https://github.com/m13253/libWinTF8)\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nfunction checkContinuation(uint8array, start, checkLength) {\n let array = uint8array;\n if (start + checkLength < array.length) {\n while (checkLength--) {\n if ((array[++start] & 0xC0) !== 0x80)\n return false;\n }\n return true;\n } else {\n return false;\n }\n}\n\nfunction decodeUTF8(uint8array) {\n let out = [];\n let input = uint8array;\n let i = 0;\n let length = uint8array.length;\n\n while (i < length) {\n if (input[i] < 0x80) {\n out.push(String.fromCharCode(input[i]));\n ++i;\n continue;\n } else if (input[i] < 0xC0) {\n // fallthrough\n } else if (input[i] < 0xE0) {\n if (checkContinuation(input, i, 1)) {\n let ucs4 = (input[i] & 0x1F) << 6 | (input[i + 1] & 0x3F);\n if (ucs4 >= 0x80) {\n out.push(String.fromCharCode(ucs4 & 0xFFFF));\n i += 2;\n continue;\n }\n }\n } else if (input[i] < 0xF0) {\n if (checkContinuation(input, i, 2)) {\n let ucs4 = (input[i] & 0xF) << 12 | (input[i + 1] & 0x3F) << 6 | input[i + 2] & 0x3F;\n if (ucs4 >= 0x800 && (ucs4 & 0xF800) !== 0xD800) {\n out.push(String.fromCharCode(ucs4 & 0xFFFF));\n i += 3;\n continue;\n }\n }\n } else if (input[i] < 0xF8) {\n if (checkContinuation(input, i, 3)) {\n let ucs4 = (input[i] & 0x7) << 18 | (input[i + 1] & 0x3F) << 12\n | (input[i + 2] & 0x3F) << 6 | (input[i + 3] & 0x3F);\n if (ucs4 > 0x10000 && ucs4 < 0x110000) {\n ucs4 -= 0x10000;\n out.push(String.fromCharCode((ucs4 >>> 10) | 0xD800));\n out.push(String.fromCharCode((ucs4 & 0x3FF) | 0xDC00));\n i += 4;\n continue;\n }\n }\n }\n out.push(String.fromCharCode(0xFFFD));\n ++i;\n }\n\n return out.join('');\n}\n\nexport default decodeUTF8;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport decodeUTF8 from '../utils/utf8-conv.js';\nimport {IllegalStateException} from '../utils/exception.js';\n\nlet le = (function () {\n let buf = new ArrayBuffer(2);\n (new DataView(buf)).setInt16(0, 256, true); // little-endian write\n return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE\n})();\n\nclass AMF {\n\n static parseScriptData(arrayBuffer, dataOffset, dataSize) {\n let data = {};\n\n try {\n let name = AMF.parseValue(arrayBuffer, dataOffset, dataSize);\n let value = AMF.parseValue(arrayBuffer, dataOffset + name.size, dataSize - name.size);\n\n data[name.data] = value.data;\n } catch (e) {\n Log.e('AMF', e.toString());\n }\n\n return data;\n }\n\n static parseObject(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 3) {\n throw new IllegalStateException('Data not enough when parse ScriptDataObject');\n }\n let name = AMF.parseString(arrayBuffer, dataOffset, dataSize);\n let value = AMF.parseValue(arrayBuffer, dataOffset + name.size, dataSize - name.size);\n let isObjectEnd = value.objectEnd;\n\n return {\n data: {\n name: name.data,\n value: value.data\n },\n size: name.size + value.size,\n objectEnd: isObjectEnd\n };\n }\n\n static parseVariable(arrayBuffer, dataOffset, dataSize) {\n return AMF.parseObject(arrayBuffer, dataOffset, dataSize);\n }\n\n static parseString(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 2) {\n throw new IllegalStateException('Data not enough when parse String');\n }\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n let length = v.getUint16(0, !le);\n\n let str;\n if (length > 0) {\n str = decodeUTF8(new Uint8Array(arrayBuffer, dataOffset + 2, length));\n } else {\n str = '';\n }\n\n return {\n data: str,\n size: 2 + length\n };\n }\n\n static parseLongString(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 4) {\n throw new IllegalStateException('Data not enough when parse LongString');\n }\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n let length = v.getUint32(0, !le);\n\n let str;\n if (length > 0) {\n str = decodeUTF8(new Uint8Array(arrayBuffer, dataOffset + 4, length));\n } else {\n str = '';\n }\n\n return {\n data: str,\n size: 4 + length\n };\n }\n\n static parseDate(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 10) {\n throw new IllegalStateException('Data size invalid when parse Date');\n }\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n let timestamp = v.getFloat64(0, !le);\n let localTimeOffset = v.getInt16(8, !le);\n timestamp += localTimeOffset * 60 * 1000; // get UTC time\n\n return {\n data: new Date(timestamp),\n size: 8 + 2\n };\n }\n\n static parseValue(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 1) {\n throw new IllegalStateException('Data not enough when parse Value');\n }\n\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n\n let offset = 1;\n let type = v.getUint8(0);\n let value;\n let objectEnd = false;\n\n try {\n switch (type) {\n case 0: // Number(Double) type\n value = v.getFloat64(1, !le);\n offset += 8;\n break;\n case 1: { // Boolean type\n let b = v.getUint8(1);\n value = b ? true : false;\n offset += 1;\n break;\n }\n case 2: { // String type\n let amfstr = AMF.parseString(arrayBuffer, dataOffset + 1, dataSize - 1);\n value = amfstr.data;\n offset += amfstr.size;\n break;\n }\n case 3: { // Object(s) type\n value = {};\n let terminal = 0; // workaround for malformed Objects which has missing ScriptDataObjectEnd\n if ((v.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) {\n terminal = 3;\n }\n while (offset < dataSize - 4) { // 4 === type(UI8) + ScriptDataObjectEnd(UI24)\n let amfobj = AMF.parseObject(arrayBuffer, dataOffset + offset, dataSize - offset - terminal);\n if (amfobj.objectEnd)\n break;\n value[amfobj.data.name] = amfobj.data.value;\n offset += amfobj.size;\n }\n if (offset <= dataSize - 3) {\n let marker = v.getUint32(offset - 1, !le) & 0x00FFFFFF;\n if (marker === 9) {\n offset += 3;\n }\n }\n break;\n }\n case 8: { // ECMA array type (Mixed array)\n value = {};\n offset += 4; // ECMAArrayLength(UI32)\n let terminal = 0; // workaround for malformed MixedArrays which has missing ScriptDataObjectEnd\n if ((v.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) {\n terminal = 3;\n }\n while (offset < dataSize - 8) { // 8 === type(UI8) + ECMAArrayLength(UI32) + ScriptDataVariableEnd(UI24)\n let amfvar = AMF.parseVariable(arrayBuffer, dataOffset + offset, dataSize - offset - terminal);\n if (amfvar.objectEnd)\n break;\n value[amfvar.data.name] = amfvar.data.value;\n offset += amfvar.size;\n }\n if (offset <= dataSize - 3) {\n let marker = v.getUint32(offset - 1, !le) & 0x00FFFFFF;\n if (marker === 9) {\n offset += 3;\n }\n }\n break;\n }\n case 9: // ScriptDataObjectEnd\n value = undefined;\n offset = 1;\n objectEnd = true;\n break;\n case 10: { // Strict array type\n // ScriptDataValue[n]. NOTE: according to video_file_format_spec_v10_1.pdf\n value = [];\n let strictArrayLength = v.getUint32(1, !le);\n offset += 4;\n for (let i = 0; i < strictArrayLength; i++) {\n let val = AMF.parseValue(arrayBuffer, dataOffset + offset, dataSize - offset);\n value.push(val.data);\n offset += val.size;\n }\n break;\n }\n case 11: { // Date type\n let date = AMF.parseDate(arrayBuffer, dataOffset + 1, dataSize - 1);\n value = date.data;\n offset += date.size;\n break;\n }\n case 12: { // Long string type\n let amfLongStr = AMF.parseString(arrayBuffer, dataOffset + 1, dataSize - 1);\n value = amfLongStr.data;\n offset += amfLongStr.size;\n break;\n }\n default:\n // ignore and skip\n offset = dataSize;\n Log.w('AMF', 'Unsupported AMF value type ' + type);\n }\n } catch (e) {\n Log.e('AMF', e.toString());\n }\n\n return {\n data: value,\n size: offset,\n objectEnd: objectEnd\n };\n }\n\n}\n\nexport default AMF;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {IllegalStateException, InvalidArgumentException} from '../utils/exception.js';\n\n// Exponential-Golomb buffer decoder\nclass ExpGolomb {\n\n constructor(uint8array) {\n this.TAG = 'ExpGolomb';\n\n this._buffer = uint8array;\n this._buffer_index = 0;\n this._total_bytes = uint8array.byteLength;\n this._total_bits = uint8array.byteLength * 8;\n this._current_word = 0;\n this._current_word_bits_left = 0;\n }\n\n destroy() {\n this._buffer = null;\n }\n\n _fillCurrentWord() {\n let buffer_bytes_left = this._total_bytes - this._buffer_index;\n if (buffer_bytes_left <= 0)\n throw new IllegalStateException('ExpGolomb: _fillCurrentWord() but no bytes available');\n\n let bytes_read = Math.min(4, buffer_bytes_left);\n let word = new Uint8Array(4);\n word.set(this._buffer.subarray(this._buffer_index, this._buffer_index + bytes_read));\n this._current_word = new DataView(word.buffer).getUint32(0, false);\n\n this._buffer_index += bytes_read;\n this._current_word_bits_left = bytes_read * 8;\n }\n\n readBits(bits) {\n if (bits > 32)\n throw new InvalidArgumentException('ExpGolomb: readBits() bits exceeded max 32bits!');\n\n if (bits <= this._current_word_bits_left) {\n let result = this._current_word >>> (32 - bits);\n this._current_word <<= bits;\n this._current_word_bits_left -= bits;\n return result;\n }\n\n let result = this._current_word_bits_left ? this._current_word : 0;\n result = result >>> (32 - this._current_word_bits_left);\n let bits_need_left = bits - this._current_word_bits_left;\n\n this._fillCurrentWord();\n let bits_read_next = Math.min(bits_need_left, this._current_word_bits_left);\n\n let result2 = this._current_word >>> (32 - bits_read_next);\n this._current_word <<= bits_read_next;\n this._current_word_bits_left -= bits_read_next;\n\n result = (result << bits_read_next) | result2;\n return result;\n }\n\n readBool() {\n return this.readBits(1) === 1;\n }\n\n readByte() {\n return this.readBits(8);\n }\n\n _skipLeadingZero() {\n let zero_count;\n for (zero_count = 0; zero_count < this._current_word_bits_left; zero_count++) {\n if (0 !== (this._current_word & (0x80000000 >>> zero_count))) {\n this._current_word <<= zero_count;\n this._current_word_bits_left -= zero_count;\n return zero_count;\n }\n }\n this._fillCurrentWord();\n return zero_count + this._skipLeadingZero();\n }\n\n readUEG() { // unsigned exponential golomb\n let leading_zeros = this._skipLeadingZero();\n return this.readBits(leading_zeros + 1) - 1;\n }\n\n readSEG() { // signed exponential golomb\n let value = this.readUEG();\n if (value & 0x01) {\n return (value + 1) >>> 1;\n } else {\n return -1 * (value >>> 1);\n }\n }\n\n}\n\nexport default ExpGolomb;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport ExpGolomb from './exp-golomb.js';\n\nclass SPSParser {\n\n static _ebsp2rbsp(uint8array) {\n let src = uint8array;\n let src_length = src.byteLength;\n let dst = new Uint8Array(src_length);\n let dst_idx = 0;\n\n for (let i = 0; i < src_length; i++) {\n if (i >= 2) {\n // Unescape: Skip 0x03 after 00 00\n if (src[i] === 0x03 && src[i - 1] === 0x00 && src[i - 2] === 0x00) {\n continue;\n }\n }\n dst[dst_idx] = src[i];\n dst_idx++;\n }\n\n return new Uint8Array(dst.buffer, 0, dst_idx);\n }\n\n static parseSPS(uint8array) {\n let rbsp = SPSParser._ebsp2rbsp(uint8array);\n let gb = new ExpGolomb(rbsp);\n\n gb.readByte();\n let profile_idc = gb.readByte(); // profile_idc\n gb.readByte(); // constraint_set_flags[5] + reserved_zero[3]\n let level_idc = gb.readByte(); // level_idc\n gb.readUEG(); // seq_parameter_set_id\n\n let profile_string = SPSParser.getProfileString(profile_idc);\n let level_string = SPSParser.getLevelString(level_idc);\n let chroma_format_idc = 1;\n let chroma_format = 420;\n let chroma_format_table = [0, 420, 422, 444];\n let bit_depth = 8;\n\n if (profile_idc === 100 || profile_idc === 110 || profile_idc === 122 ||\n profile_idc === 244 || profile_idc === 44 || profile_idc === 83 ||\n profile_idc === 86 || profile_idc === 118 || profile_idc === 128 ||\n profile_idc === 138 || profile_idc === 144) {\n\n chroma_format_idc = gb.readUEG();\n if (chroma_format_idc === 3) {\n gb.readBits(1); // separate_colour_plane_flag\n }\n if (chroma_format_idc <= 3) {\n chroma_format = chroma_format_table[chroma_format_idc];\n }\n\n bit_depth = gb.readUEG() + 8; // bit_depth_luma_minus8\n gb.readUEG(); // bit_depth_chroma_minus8\n gb.readBits(1); // qpprime_y_zero_transform_bypass_flag\n if (gb.readBool()) { // seq_scaling_matrix_present_flag\n let scaling_list_count = (chroma_format_idc !== 3) ? 8 : 12;\n for (let i = 0; i < scaling_list_count; i++) {\n if (gb.readBool()) { // seq_scaling_list_present_flag\n if (i < 6) {\n SPSParser._skipScalingList(gb, 16);\n } else {\n SPSParser._skipScalingList(gb, 64);\n }\n }\n }\n }\n }\n gb.readUEG(); // log2_max_frame_num_minus4\n let pic_order_cnt_type = gb.readUEG();\n if (pic_order_cnt_type === 0) {\n gb.readUEG(); // log2_max_pic_order_cnt_lsb_minus_4\n } else if (pic_order_cnt_type === 1) {\n gb.readBits(1); // delta_pic_order_always_zero_flag\n gb.readSEG(); // offset_for_non_ref_pic\n gb.readSEG(); // offset_for_top_to_bottom_field\n let num_ref_frames_in_pic_order_cnt_cycle = gb.readUEG();\n for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {\n gb.readSEG(); // offset_for_ref_frame\n }\n }\n let ref_frames = gb.readUEG(); // max_num_ref_frames\n gb.readBits(1); // gaps_in_frame_num_value_allowed_flag\n\n let pic_width_in_mbs_minus1 = gb.readUEG();\n let pic_height_in_map_units_minus1 = gb.readUEG();\n\n let frame_mbs_only_flag = gb.readBits(1);\n if (frame_mbs_only_flag === 0) {\n gb.readBits(1); // mb_adaptive_frame_field_flag\n }\n gb.readBits(1); // direct_8x8_inference_flag\n\n let frame_crop_left_offset = 0;\n let frame_crop_right_offset = 0;\n let frame_crop_top_offset = 0;\n let frame_crop_bottom_offset = 0;\n\n let frame_cropping_flag = gb.readBool();\n if (frame_cropping_flag) {\n frame_crop_left_offset = gb.readUEG();\n frame_crop_right_offset = gb.readUEG();\n frame_crop_top_offset = gb.readUEG();\n frame_crop_bottom_offset = gb.readUEG();\n }\n\n let sar_width = 1, sar_height = 1;\n let fps = 0, fps_fixed = true, fps_num = 0, fps_den = 0;\n\n let vui_parameters_present_flag = gb.readBool();\n if (vui_parameters_present_flag) {\n if (gb.readBool()) { // aspect_ratio_info_present_flag\n let aspect_ratio_idc = gb.readByte();\n let sar_w_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];\n let sar_h_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];\n\n if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {\n sar_width = sar_w_table[aspect_ratio_idc - 1];\n sar_height = sar_h_table[aspect_ratio_idc - 1];\n } else if (aspect_ratio_idc === 255) {\n sar_width = gb.readByte() << 8 | gb.readByte();\n sar_height = gb.readByte() << 8 | gb.readByte();\n }\n }\n\n if (gb.readBool()) { // overscan_info_present_flag\n gb.readBool(); // overscan_appropriate_flag\n }\n if (gb.readBool()) { // video_signal_type_present_flag\n gb.readBits(4); // video_format & video_full_range_flag\n if (gb.readBool()) { // colour_description_present_flag\n gb.readBits(24); // colour_primaries & transfer_characteristics & matrix_coefficients\n }\n }\n if (gb.readBool()) { // chroma_loc_info_present_flag\n gb.readUEG(); // chroma_sample_loc_type_top_field\n gb.readUEG(); // chroma_sample_loc_type_bottom_field\n }\n if (gb.readBool()) { // timing_info_present_flag\n let num_units_in_tick = gb.readBits(32);\n let time_scale = gb.readBits(32);\n fps_fixed = gb.readBool(); // fixed_frame_rate_flag\n\n fps_num = time_scale;\n fps_den = num_units_in_tick * 2;\n fps = fps_num / fps_den;\n }\n }\n\n let sarScale = 1;\n if (sar_width !== 1 || sar_height !== 1) {\n sarScale = sar_width / sar_height;\n }\n\n let crop_unit_x = 0, crop_unit_y = 0;\n if (chroma_format_idc === 0) {\n crop_unit_x = 1;\n crop_unit_y = 2 - frame_mbs_only_flag;\n } else {\n let sub_wc = (chroma_format_idc === 3) ? 1 : 2;\n let sub_hc = (chroma_format_idc === 1) ? 2 : 1;\n crop_unit_x = sub_wc;\n crop_unit_y = sub_hc * (2 - frame_mbs_only_flag);\n }\n\n let codec_width = (pic_width_in_mbs_minus1 + 1) * 16;\n let codec_height = (2 - frame_mbs_only_flag) * ((pic_height_in_map_units_minus1 + 1) * 16);\n\n codec_width -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x;\n codec_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y;\n\n let present_width = Math.ceil(codec_width * sarScale);\n\n gb.destroy();\n gb = null;\n\n return {\n profile_string: profile_string, // baseline, high, high10, ...\n level_string: level_string, // 3, 3.1, 4, 4.1, 5, 5.1, ...\n bit_depth: bit_depth, // 8bit, 10bit, ...\n ref_frames: ref_frames,\n chroma_format: chroma_format, // 4:2:0, 4:2:2, ...\n chroma_format_string: SPSParser.getChromaFormatString(chroma_format),\n\n frame_rate: {\n fixed: fps_fixed,\n fps: fps,\n fps_den: fps_den,\n fps_num: fps_num\n },\n\n sar_ratio: {\n width: sar_width,\n height: sar_height\n },\n\n codec_size: {\n width: codec_width,\n height: codec_height\n },\n\n present_size: {\n width: present_width,\n height: codec_height\n }\n };\n }\n\n static _skipScalingList(gb, count) {\n let last_scale = 8, next_scale = 8;\n let delta_scale = 0;\n for (let i = 0; i < count; i++) {\n if (next_scale !== 0) {\n delta_scale = gb.readSEG();\n next_scale = (last_scale + delta_scale + 256) % 256;\n }\n last_scale = (next_scale === 0) ? last_scale : next_scale;\n }\n }\n\n static getProfileString(profile_idc) {\n switch (profile_idc) {\n case 66:\n return 'Baseline';\n case 77:\n return 'Main';\n case 88:\n return 'Extended';\n case 100:\n return 'High';\n case 110:\n return 'High10';\n case 122:\n return 'High422';\n case 244:\n return 'High444';\n default:\n return 'Unknown';\n }\n }\n\n static getLevelString(level_idc) {\n return (level_idc / 10).toFixed(1);\n }\n\n static getChromaFormatString(chroma) {\n switch (chroma) {\n case 420:\n return '4:2:0';\n case 422:\n return '4:2:2';\n case 444:\n return '4:4:4';\n default:\n return 'Unknown';\n }\n }\n\n}\n\nexport default SPSParser;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport AMF from './amf-parser.js';\nimport SPSParser from './sps-parser.js';\nimport DemuxErrors from './demux-errors.js';\nimport MediaInfo from '../core/media-info.js';\nimport {IllegalStateException} from '../utils/exception.js';\n\nfunction Swap16(src) {\n return (((src >>> 8) & 0xFF) |\n ((src & 0xFF) << 8));\n}\n\nfunction Swap32(src) {\n return (((src & 0xFF000000) >>> 24) |\n ((src & 0x00FF0000) >>> 8) |\n ((src & 0x0000FF00) << 8) |\n ((src & 0x000000FF) << 24));\n}\n\nfunction ReadBig32(array, index) {\n return ((array[index] << 24) |\n (array[index + 1] << 16) |\n (array[index + 2] << 8) |\n (array[index + 3]));\n}\n\n\nclass FLVDemuxer {\n\n constructor(probeData, config) {\n this.TAG = 'FLVDemuxer';\n\n this._config = config;\n\n this._onError = null;\n this._onMediaInfo = null;\n this._onMetaDataArrived = null;\n this._onScriptDataArrived = null;\n this._onTrackMetadata = null;\n this._onDataAvailable = null;\n\n this._dataOffset = probeData.dataOffset;\n this._firstParse = true;\n this._dispatch = false;\n\n this._hasAudio = probeData.hasAudioTrack;\n this._hasVideo = probeData.hasVideoTrack;\n\n this._hasAudioFlagOverrided = false;\n this._hasVideoFlagOverrided = false;\n\n this._audioInitialMetadataDispatched = false;\n this._videoInitialMetadataDispatched = false;\n\n this._mediaInfo = new MediaInfo();\n this._mediaInfo.hasAudio = this._hasAudio;\n this._mediaInfo.hasVideo = this._hasVideo;\n this._metadata = null;\n this._audioMetadata = null;\n this._videoMetadata = null;\n\n this._naluLengthSize = 4;\n this._timestampBase = 0; // int32, in milliseconds\n this._timescale = 1000;\n this._duration = 0; // int32, in milliseconds\n this._durationOverrided = false;\n this._referenceFrameRate = {\n fixed: true,\n fps: 23.976,\n fps_num: 23976,\n fps_den: 1000\n };\n\n this._flvSoundRateTable = [5500, 11025, 22050, 44100, 48000];\n\n this._mpegSamplingRates = [\n 96000, 88200, 64000, 48000, 44100, 32000,\n 24000, 22050, 16000, 12000, 11025, 8000, 7350\n ];\n\n this._mpegAudioV10SampleRateTable = [44100, 48000, 32000, 0];\n this._mpegAudioV20SampleRateTable = [22050, 24000, 16000, 0];\n this._mpegAudioV25SampleRateTable = [11025, 12000, 8000, 0];\n\n this._mpegAudioL1BitRateTable = [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1];\n this._mpegAudioL2BitRateTable = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1];\n this._mpegAudioL3BitRateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1];\n\n this._videoTrack = {type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0};\n this._audioTrack = {type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0};\n\n this._littleEndian = (function () {\n let buf = new ArrayBuffer(2);\n (new DataView(buf)).setInt16(0, 256, true); // little-endian write\n return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE\n })();\n }\n\n destroy() {\n this._mediaInfo = null;\n this._metadata = null;\n this._audioMetadata = null;\n this._videoMetadata = null;\n this._videoTrack = null;\n this._audioTrack = null;\n\n this._onError = null;\n this._onMediaInfo = null;\n this._onMetaDataArrived = null;\n this._onScriptDataArrived = null;\n this._onTrackMetadata = null;\n this._onDataAvailable = null;\n }\n\n static probe(buffer) {\n let data = new Uint8Array(buffer);\n let mismatch = {match: false};\n\n if (data[0] !== 0x46 || data[1] !== 0x4C || data[2] !== 0x56 || data[3] !== 0x01) {\n return mismatch;\n }\n\n let hasAudio = ((data[4] & 4) >>> 2) !== 0;\n let hasVideo = (data[4] & 1) !== 0;\n\n let offset = ReadBig32(data, 5);\n\n if (offset < 9) {\n return mismatch;\n }\n\n return {\n match: true,\n consumed: offset,\n dataOffset: offset,\n hasAudioTrack: hasAudio,\n hasVideoTrack: hasVideo\n };\n }\n\n bindDataSource(loader) {\n loader.onDataArrival = this.parseChunks.bind(this);\n return this;\n }\n\n // prototype: function(type: string, metadata: any): void\n get onTrackMetadata() {\n return this._onTrackMetadata;\n }\n\n set onTrackMetadata(callback) {\n this._onTrackMetadata = callback;\n }\n\n // prototype: function(mediaInfo: MediaInfo): void\n get onMediaInfo() {\n return this._onMediaInfo;\n }\n\n set onMediaInfo(callback) {\n this._onMediaInfo = callback;\n }\n\n get onMetaDataArrived() {\n return this._onMetaDataArrived;\n }\n\n set onMetaDataArrived(callback) {\n this._onMetaDataArrived = callback;\n }\n\n get onScriptDataArrived() {\n return this._onScriptDataArrived;\n }\n\n set onScriptDataArrived(callback) {\n this._onScriptDataArrived = callback;\n }\n\n // prototype: function(type: number, info: string): void\n get onError() {\n return this._onError;\n }\n\n set onError(callback) {\n this._onError = callback;\n }\n\n // prototype: function(videoTrack: any, audioTrack: any): void\n get onDataAvailable() {\n return this._onDataAvailable;\n }\n\n set onDataAvailable(callback) {\n this._onDataAvailable = callback;\n }\n\n // timestamp base for output samples, must be in milliseconds\n get timestampBase() {\n return this._timestampBase;\n }\n\n set timestampBase(base) {\n this._timestampBase = base;\n }\n\n get overridedDuration() {\n return this._duration;\n }\n\n // Force-override media duration. Must be in milliseconds, int32\n set overridedDuration(duration) {\n this._durationOverrided = true;\n this._duration = duration;\n this._mediaInfo.duration = duration;\n }\n\n // Force-override audio track present flag, boolean\n set overridedHasAudio(hasAudio) {\n this._hasAudioFlagOverrided = true;\n this._hasAudio = hasAudio;\n this._mediaInfo.hasAudio = hasAudio;\n }\n\n // Force-override video track present flag, boolean\n set overridedHasVideo(hasVideo) {\n this._hasVideoFlagOverrided = true;\n this._hasVideo = hasVideo;\n this._mediaInfo.hasVideo = hasVideo;\n }\n\n resetMediaInfo() {\n this._mediaInfo = new MediaInfo();\n }\n\n _isInitialMetadataDispatched() {\n if (this._hasAudio && this._hasVideo) { // both audio & video\n return this._audioInitialMetadataDispatched && this._videoInitialMetadataDispatched;\n }\n if (this._hasAudio && !this._hasVideo) { // audio only\n return this._audioInitialMetadataDispatched;\n }\n if (!this._hasAudio && this._hasVideo) { // video only\n return this._videoInitialMetadataDispatched;\n }\n return false;\n }\n\n // function parseChunks(chunk: ArrayBuffer, byteStart: number): number;\n parseChunks(chunk, byteStart) {\n if (!this._onError || !this._onMediaInfo || !this._onTrackMetadata || !this._onDataAvailable) {\n throw new IllegalStateException('Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified');\n }\n\n let offset = 0;\n let le = this._littleEndian;\n\n if (byteStart === 0) { // buffer with FLV header\n if (chunk.byteLength > 13) {\n let probeData = FLVDemuxer.probe(chunk);\n offset = probeData.dataOffset;\n } else {\n return 0;\n }\n }\n\n if (this._firstParse) { // handle PreviousTagSize0 before Tag1\n this._firstParse = false;\n if (byteStart + offset !== this._dataOffset) {\n Log.w(this.TAG, 'First time parsing but chunk byteStart invalid!');\n }\n\n let v = new DataView(chunk, offset);\n let prevTagSize0 = v.getUint32(0, !le);\n if (prevTagSize0 !== 0) {\n Log.w(this.TAG, 'PrevTagSize0 !== 0 !!!');\n }\n offset += 4;\n }\n\n while (offset < chunk.byteLength) {\n this._dispatch = true;\n\n let v = new DataView(chunk, offset);\n\n if (offset + 11 + 4 > chunk.byteLength) {\n // data not enough for parsing an flv tag\n break;\n }\n\n let tagType = v.getUint8(0);\n let dataSize = v.getUint32(0, !le) & 0x00FFFFFF;\n\n if (offset + 11 + dataSize + 4 > chunk.byteLength) {\n // data not enough for parsing actual data body\n break;\n }\n\n if (tagType !== 8 && tagType !== 9 && tagType !== 18) {\n Log.w(this.TAG, `Unsupported tag type ${tagType}, skipped`);\n // consume the whole tag (skip it)\n offset += 11 + dataSize + 4;\n continue;\n }\n\n let ts2 = v.getUint8(4);\n let ts1 = v.getUint8(5);\n let ts0 = v.getUint8(6);\n let ts3 = v.getUint8(7);\n\n let timestamp = ts0 | (ts1 << 8) | (ts2 << 16) | (ts3 << 24);\n\n let streamId = v.getUint32(7, !le) & 0x00FFFFFF;\n if (streamId !== 0) {\n Log.w(this.TAG, 'Meet tag which has StreamID != 0!');\n }\n\n let dataOffset = offset + 11;\n\n switch (tagType) {\n case 8: // Audio\n this._parseAudioData(chunk, dataOffset, dataSize, timestamp);\n break;\n case 9: // Video\n this._parseVideoData(chunk, dataOffset, dataSize, timestamp, byteStart + offset);\n break;\n case 18: // ScriptDataObject\n this._parseScriptData(chunk, dataOffset, dataSize);\n break;\n }\n\n let prevTagSize = v.getUint32(11 + dataSize, !le);\n if (prevTagSize !== 11 + dataSize) {\n Log.w(this.TAG, `Invalid PrevTagSize ${prevTagSize}`);\n }\n\n offset += 11 + dataSize + 4; // tagBody + dataSize + prevTagSize\n }\n\n // dispatch parsed frames to consumer (typically, the remuxer)\n if (this._isInitialMetadataDispatched()) {\n if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {\n this._onDataAvailable(this._audioTrack, this._videoTrack);\n }\n }\n\n return offset; // consumed bytes, just equals latest offset index\n }\n\n _parseScriptData(arrayBuffer, dataOffset, dataSize) {\n let scriptData = AMF.parseScriptData(arrayBuffer, dataOffset, dataSize);\n\n if (scriptData.hasOwnProperty('onMetaData')) {\n if (scriptData.onMetaData == null || typeof scriptData.onMetaData !== 'object') {\n Log.w(this.TAG, 'Invalid onMetaData structure!');\n return;\n }\n if (this._metadata) {\n Log.w(this.TAG, 'Found another onMetaData tag!');\n }\n this._metadata = scriptData;\n let onMetaData = this._metadata.onMetaData;\n\n if (this._onMetaDataArrived) {\n this._onMetaDataArrived(Object.assign({}, onMetaData));\n }\n\n if (typeof onMetaData.hasAudio === 'boolean') { // hasAudio\n if (this._hasAudioFlagOverrided === false) {\n this._hasAudio = onMetaData.hasAudio;\n this._mediaInfo.hasAudio = this._hasAudio;\n }\n }\n if (typeof onMetaData.hasVideo === 'boolean') { // hasVideo\n if (this._hasVideoFlagOverrided === false) {\n this._hasVideo = onMetaData.hasVideo;\n this._mediaInfo.hasVideo = this._hasVideo;\n }\n }\n if (typeof onMetaData.audiodatarate === 'number') { // audiodatarate\n this._mediaInfo.audioDataRate = onMetaData.audiodatarate;\n }\n if (typeof onMetaData.videodatarate === 'number') { // videodatarate\n this._mediaInfo.videoDataRate = onMetaData.videodatarate;\n }\n if (typeof onMetaData.width === 'number') { // width\n this._mediaInfo.width = onMetaData.width;\n }\n if (typeof onMetaData.height === 'number') { // height\n this._mediaInfo.height = onMetaData.height;\n }\n if (typeof onMetaData.duration === 'number') { // duration\n if (!this._durationOverrided) {\n let duration = Math.floor(onMetaData.duration * this._timescale);\n this._duration = duration;\n this._mediaInfo.duration = duration;\n }\n } else {\n this._mediaInfo.duration = 0;\n }\n if (typeof onMetaData.framerate === 'number') { // framerate\n let fps_num = Math.floor(onMetaData.framerate * 1000);\n if (fps_num > 0) {\n let fps = fps_num / 1000;\n this._referenceFrameRate.fixed = true;\n this._referenceFrameRate.fps = fps;\n this._referenceFrameRate.fps_num = fps_num;\n this._referenceFrameRate.fps_den = 1000;\n this._mediaInfo.fps = fps;\n }\n }\n if (typeof onMetaData.keyframes === 'object') { // keyframes\n this._mediaInfo.hasKeyframesIndex = true;\n let keyframes = onMetaData.keyframes;\n this._mediaInfo.keyframesIndex = this._parseKeyframesIndex(keyframes);\n onMetaData.keyframes = null; // keyframes has been extracted, remove it\n } else {\n this._mediaInfo.hasKeyframesIndex = false;\n }\n this._dispatch = false;\n this._mediaInfo.metadata = onMetaData;\n Log.v(this.TAG, 'Parsed onMetaData');\n if (this._mediaInfo.isComplete()) {\n this._onMediaInfo(this._mediaInfo);\n }\n }\n\n if (Object.keys(scriptData).length > 0) {\n if (this._onScriptDataArrived) {\n this._onScriptDataArrived(Object.assign({}, scriptData));\n }\n }\n }\n\n _parseKeyframesIndex(keyframes) {\n let times = [];\n let filepositions = [];\n\n // ignore first keyframe which is actually AVC Sequence Header (AVCDecoderConfigurationRecord)\n for (let i = 1; i < keyframes.times.length; i++) {\n let time = this._timestampBase + Math.floor(keyframes.times[i] * 1000);\n times.push(time);\n filepositions.push(keyframes.filepositions[i]);\n }\n\n return {\n times: times,\n filepositions: filepositions\n };\n }\n\n _parseAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) {\n if (dataSize <= 1) {\n Log.w(this.TAG, 'Flv: Invalid audio packet, missing SoundData payload!');\n return;\n }\n\n if (this._hasAudioFlagOverrided === true && this._hasAudio === false) {\n // If hasAudio: false indicated explicitly in MediaDataSource,\n // Ignore all the audio packets\n return;\n }\n\n let le = this._littleEndian;\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n\n let soundSpec = v.getUint8(0);\n\n let soundFormat = soundSpec >>> 4;\n if (soundFormat !== 2 && soundFormat !== 10) { // MP3 or AAC\n this._onError(DemuxErrors.CODEC_UNSUPPORTED, 'Flv: Unsupported audio codec idx: ' + soundFormat);\n return;\n }\n\n let soundRate = 0;\n let soundRateIndex = (soundSpec & 12) >>> 2;\n if (soundRateIndex >= 0 && soundRateIndex <= 4) {\n soundRate = this._flvSoundRateTable[soundRateIndex];\n } else {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid audio sample rate idx: ' + soundRateIndex);\n return;\n }\n\n let soundSize = (soundSpec & 2) >>> 1; // unused\n let soundType = (soundSpec & 1);\n\n\n let meta = this._audioMetadata;\n let track = this._audioTrack;\n\n if (!meta) {\n if (this._hasAudio === false && this._hasAudioFlagOverrided === false) {\n this._hasAudio = true;\n this._mediaInfo.hasAudio = true;\n }\n\n // initial metadata\n meta = this._audioMetadata = {};\n meta.type = 'audio';\n meta.id = track.id;\n meta.timescale = this._timescale;\n meta.duration = this._duration;\n meta.audioSampleRate = soundRate;\n meta.channelCount = (soundType === 0 ? 1 : 2);\n }\n\n if (soundFormat === 10) { // AAC\n let aacData = this._parseAACAudioData(arrayBuffer, dataOffset + 1, dataSize - 1);\n if (aacData == undefined) {\n return;\n }\n\n if (aacData.packetType === 0) { // AAC sequence header (AudioSpecificConfig)\n if (meta.config) {\n Log.w(this.TAG, 'Found another AudioSpecificConfig!');\n }\n let misc = aacData.data;\n meta.audioSampleRate = misc.samplingRate;\n meta.channelCount = misc.channelCount;\n meta.codec = misc.codec;\n meta.originalCodec = misc.originalCodec;\n meta.config = misc.config;\n // The decode result of an aac sample is 1024 PCM samples\n meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale;\n Log.v(this.TAG, 'Parsed AudioSpecificConfig');\n\n if (this._isInitialMetadataDispatched()) {\n // Non-initial metadata, force dispatch (or flush) parsed frames to remuxer\n if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {\n this._onDataAvailable(this._audioTrack, this._videoTrack);\n }\n } else {\n this._audioInitialMetadataDispatched = true;\n }\n // then notify new metadata\n this._dispatch = false;\n this._onTrackMetadata('audio', meta);\n\n let mi = this._mediaInfo;\n mi.audioCodec = meta.originalCodec;\n mi.audioSampleRate = meta.audioSampleRate;\n mi.audioChannelCount = meta.channelCount;\n if (mi.hasVideo) {\n if (mi.videoCodec != null) {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.videoCodec + ',' + mi.audioCodec + '\"';\n }\n } else {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.audioCodec + '\"';\n }\n if (mi.isComplete()) {\n this._onMediaInfo(mi);\n }\n } else if (aacData.packetType === 1) { // AAC raw frame data\n let dts = this._timestampBase + tagTimestamp;\n let aacSample = {unit: aacData.data, length: aacData.data.byteLength, dts: dts, pts: dts};\n track.samples.push(aacSample);\n track.length += aacData.data.length;\n } else {\n Log.e(this.TAG, `Flv: Unsupported AAC data type ${aacData.packetType}`);\n }\n } else if (soundFormat === 2) { // MP3\n if (!meta.codec) {\n // We need metadata for mp3 audio track, extract info from frame header\n let misc = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, true);\n if (misc == undefined) {\n return;\n }\n meta.audioSampleRate = misc.samplingRate;\n meta.channelCount = misc.channelCount;\n meta.codec = misc.codec;\n meta.originalCodec = misc.originalCodec;\n // The decode result of an mp3 sample is 1152 PCM samples\n meta.refSampleDuration = 1152 / meta.audioSampleRate * meta.timescale;\n Log.v(this.TAG, 'Parsed MPEG Audio Frame Header');\n\n this._audioInitialMetadataDispatched = true;\n this._onTrackMetadata('audio', meta);\n\n let mi = this._mediaInfo;\n mi.audioCodec = meta.codec;\n mi.audioSampleRate = meta.audioSampleRate;\n mi.audioChannelCount = meta.channelCount;\n mi.audioDataRate = misc.bitRate;\n if (mi.hasVideo) {\n if (mi.videoCodec != null) {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.videoCodec + ',' + mi.audioCodec + '\"';\n }\n } else {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.audioCodec + '\"';\n }\n if (mi.isComplete()) {\n this._onMediaInfo(mi);\n }\n }\n\n // This packet is always a valid audio packet, extract it\n let data = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, false);\n if (data == undefined) {\n return;\n }\n let dts = this._timestampBase + tagTimestamp;\n let mp3Sample = {unit: data, length: data.byteLength, dts: dts, pts: dts};\n track.samples.push(mp3Sample);\n track.length += data.length;\n }\n }\n\n _parseAACAudioData(arrayBuffer, dataOffset, dataSize) {\n if (dataSize <= 1) {\n Log.w(this.TAG, 'Flv: Invalid AAC packet, missing AACPacketType or/and Data!');\n return;\n }\n\n let result = {};\n let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);\n\n result.packetType = array[0];\n\n if (array[0] === 0) {\n result.data = this._parseAACAudioSpecificConfig(arrayBuffer, dataOffset + 1, dataSize - 1);\n } else {\n result.data = array.subarray(1);\n }\n\n return result;\n }\n\n _parseAACAudioSpecificConfig(arrayBuffer, dataOffset, dataSize) {\n let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);\n let config = null;\n\n /* Audio Object Type:\n 0: Null\n 1: AAC Main\n 2: AAC LC\n 3: AAC SSR (Scalable Sample Rate)\n 4: AAC LTP (Long Term Prediction)\n 5: HE-AAC / SBR (Spectral Band Replication)\n 6: AAC Scalable\n */\n\n let audioObjectType = 0;\n let originalAudioObjectType = 0;\n let audioExtensionObjectType = null;\n let samplingIndex = 0;\n let extensionSamplingIndex = null;\n\n // 5 bits\n audioObjectType = originalAudioObjectType = array[0] >>> 3;\n // 4 bits\n samplingIndex = ((array[0] & 0x07) << 1) | (array[1] >>> 7);\n if (samplingIndex < 0 || samplingIndex >= this._mpegSamplingRates.length) {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid sampling frequency index!');\n return;\n }\n\n let samplingFrequence = this._mpegSamplingRates[samplingIndex];\n\n // 4 bits\n let channelConfig = (array[1] & 0x78) >>> 3;\n if (channelConfig < 0 || channelConfig >= 8) {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid channel configuration');\n return;\n }\n\n if (audioObjectType === 5) { // HE-AAC?\n // 4 bits\n extensionSamplingIndex = ((array[1] & 0x07) << 1) | (array[2] >>> 7);\n // 5 bits\n audioExtensionObjectType = (array[2] & 0x7C) >>> 2;\n }\n\n // workarounds for various browsers\n let userAgent = self.navigator.userAgent.toLowerCase();\n\n if (userAgent.indexOf('firefox') !== -1) {\n // firefox: use SBR (HE-AAC) if freq less than 24kHz\n if (samplingIndex >= 6) {\n audioObjectType = 5;\n config = new Array(4);\n extensionSamplingIndex = samplingIndex - 3;\n } else { // use LC-AAC\n audioObjectType = 2;\n config = new Array(2);\n extensionSamplingIndex = samplingIndex;\n }\n } else if (userAgent.indexOf('android') !== -1) {\n // android: always use LC-AAC\n audioObjectType = 2;\n config = new Array(2);\n extensionSamplingIndex = samplingIndex;\n } else {\n // for other browsers, e.g. chrome...\n // Always use HE-AAC to make it easier to switch aac codec profile\n audioObjectType = 5;\n extensionSamplingIndex = samplingIndex;\n config = new Array(4);\n\n if (samplingIndex >= 6) {\n extensionSamplingIndex = samplingIndex - 3;\n } else if (channelConfig === 1) { // Mono channel\n audioObjectType = 2;\n config = new Array(2);\n extensionSamplingIndex = samplingIndex;\n }\n }\n\n config[0] = audioObjectType << 3;\n config[0] |= (samplingIndex & 0x0F) >>> 1;\n config[1] = (samplingIndex & 0x0F) << 7;\n config[1] |= (channelConfig & 0x0F) << 3;\n if (audioObjectType === 5) {\n config[1] |= ((extensionSamplingIndex & 0x0F) >>> 1);\n config[2] = (extensionSamplingIndex & 0x01) << 7;\n // extended audio object type: force to 2 (LC-AAC)\n config[2] |= (2 << 2);\n config[3] = 0;\n }\n\n return {\n config: config,\n samplingRate: samplingFrequence,\n channelCount: channelConfig,\n codec: 'mp4a.40.' + audioObjectType,\n originalCodec: 'mp4a.40.' + originalAudioObjectType\n };\n }\n\n _parseMP3AudioData(arrayBuffer, dataOffset, dataSize, requestHeader) {\n if (dataSize < 4) {\n Log.w(this.TAG, 'Flv: Invalid MP3 packet, header missing!');\n return;\n }\n\n let le = this._littleEndian;\n let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);\n let result = null;\n\n if (requestHeader) {\n if (array[0] !== 0xFF) {\n return;\n }\n let ver = (array[1] >>> 3) & 0x03;\n let layer = (array[1] & 0x06) >> 1;\n\n let bitrate_index = (array[2] & 0xF0) >>> 4;\n let sampling_freq_index = (array[2] & 0x0C) >>> 2;\n\n let channel_mode = (array[3] >>> 6) & 0x03;\n let channel_count = channel_mode !== 3 ? 2 : 1;\n\n let sample_rate = 0;\n let bit_rate = 0;\n let object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types\n\n let codec = 'mp3';\n\n switch (ver) {\n case 0: // MPEG 2.5\n sample_rate = this._mpegAudioV25SampleRateTable[sampling_freq_index];\n break;\n case 2: // MPEG 2\n sample_rate = this._mpegAudioV20SampleRateTable[sampling_freq_index];\n break;\n case 3: // MPEG 1\n sample_rate = this._mpegAudioV10SampleRateTable[sampling_freq_index];\n break;\n }\n\n switch (layer) {\n case 1: // Layer 3\n object_type = 34;\n if (bitrate_index < this._mpegAudioL3BitRateTable.length) {\n bit_rate = this._mpegAudioL3BitRateTable[bitrate_index];\n }\n break;\n case 2: // Layer 2\n object_type = 33;\n if (bitrate_index < this._mpegAudioL2BitRateTable.length) {\n bit_rate = this._mpegAudioL2BitRateTable[bitrate_index];\n }\n break;\n case 3: // Layer 1\n object_type = 32;\n if (bitrate_index < this._mpegAudioL1BitRateTable.length) {\n bit_rate = this._mpegAudioL1BitRateTable[bitrate_index];\n }\n break;\n }\n\n result = {\n bitRate: bit_rate,\n samplingRate: sample_rate,\n channelCount: channel_count,\n codec: codec,\n originalCodec: codec\n };\n } else {\n result = array;\n }\n\n return result;\n }\n\n _parseVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition) {\n if (dataSize <= 1) {\n Log.w(this.TAG, 'Flv: Invalid video packet, missing VideoData payload!');\n return;\n }\n\n if (this._hasVideoFlagOverrided === true && this._hasVideo === false) {\n // If hasVideo: false indicated explicitly in MediaDataSource,\n // Ignore all the video packets\n return;\n }\n\n let spec = (new Uint8Array(arrayBuffer, dataOffset, dataSize))[0];\n\n let frameType = (spec & 240) >>> 4;\n let codecId = spec & 15;\n\n if (codecId !== 7) {\n this._onError(DemuxErrors.CODEC_UNSUPPORTED, `Flv: Unsupported codec in video frame: ${codecId}`);\n return;\n }\n\n this._parseAVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType);\n }\n\n _parseAVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType) {\n if (dataSize < 4) {\n Log.w(this.TAG, 'Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime');\n return;\n }\n\n let le = this._littleEndian;\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n\n let packetType = v.getUint8(0);\n let cts_unsigned = v.getUint32(0, !le) & 0x00FFFFFF;\n let cts = (cts_unsigned << 8) >> 8; // convert to 24-bit signed int\n\n if (packetType === 0) { // AVCDecoderConfigurationRecord\n this._parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset + 4, dataSize - 4);\n } else if (packetType === 1) { // One or more Nalus\n this._parseAVCVideoData(arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts);\n } else if (packetType === 2) {\n // empty, AVC end of sequence\n } else {\n this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);\n return;\n }\n }\n\n _parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize) {\n if (dataSize < 7) {\n Log.w(this.TAG, 'Flv: Invalid AVCDecoderConfigurationRecord, lack of data!');\n return;\n }\n\n let meta = this._videoMetadata;\n let track = this._videoTrack;\n let le = this._littleEndian;\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n\n if (!meta) {\n if (this._hasVideo === false && this._hasVideoFlagOverrided === false) {\n this._hasVideo = true;\n this._mediaInfo.hasVideo = true;\n }\n\n meta = this._videoMetadata = {};\n meta.type = 'video';\n meta.id = track.id;\n meta.timescale = this._timescale;\n meta.duration = this._duration;\n } else {\n if (typeof meta.avcc !== 'undefined') {\n Log.w(this.TAG, 'Found another AVCDecoderConfigurationRecord!');\n }\n }\n\n let version = v.getUint8(0); // configurationVersion\n let avcProfile = v.getUint8(1); // avcProfileIndication\n let profileCompatibility = v.getUint8(2); // profile_compatibility\n let avcLevel = v.getUint8(3); // AVCLevelIndication\n\n if (version !== 1 || avcProfile === 0) {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord');\n return;\n }\n\n this._naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne\n if (this._naluLengthSize !== 3 && this._naluLengthSize !== 4) { // holy shit!!!\n this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${this._naluLengthSize - 1}`);\n return;\n }\n\n let spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets\n if (spsCount === 0) {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS');\n return;\n } else if (spsCount > 1) {\n Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: SPS Count = ${spsCount}`);\n }\n\n let offset = 6;\n\n for (let i = 0; i < spsCount; i++) {\n let len = v.getUint16(offset, !le); // sequenceParameterSetLength\n offset += 2;\n\n if (len === 0) {\n continue;\n }\n\n // Notice: Nalu without startcode header (00 00 00 01)\n let sps = new Uint8Array(arrayBuffer, dataOffset + offset, len);\n offset += len;\n\n let config = SPSParser.parseSPS(sps);\n if (i !== 0) {\n // ignore other sps's config\n continue;\n }\n\n meta.codecWidth = config.codec_size.width;\n meta.codecHeight = config.codec_size.height;\n meta.presentWidth = config.present_size.width;\n meta.presentHeight = config.present_size.height;\n\n meta.profile = config.profile_string;\n meta.level = config.level_string;\n meta.bitDepth = config.bit_depth;\n meta.chromaFormat = config.chroma_format;\n meta.sarRatio = config.sar_ratio;\n meta.frameRate = config.frame_rate;\n\n if (config.frame_rate.fixed === false ||\n config.frame_rate.fps_num === 0 ||\n config.frame_rate.fps_den === 0) {\n meta.frameRate = this._referenceFrameRate;\n }\n\n let fps_den = meta.frameRate.fps_den;\n let fps_num = meta.frameRate.fps_num;\n meta.refSampleDuration = meta.timescale * (fps_den / fps_num);\n\n let codecArray = sps.subarray(1, 4);\n let codecString = 'avc1.';\n for (let j = 0; j < 3; j++) {\n let h = codecArray[j].toString(16);\n if (h.length < 2) {\n h = '0' + h;\n }\n codecString += h;\n }\n meta.codec = codecString;\n\n let mi = this._mediaInfo;\n mi.width = meta.codecWidth;\n mi.height = meta.codecHeight;\n mi.fps = meta.frameRate.fps;\n mi.profile = meta.profile;\n mi.level = meta.level;\n mi.refFrames = config.ref_frames;\n mi.chromaFormat = config.chroma_format_string;\n mi.sarNum = meta.sarRatio.width;\n mi.sarDen = meta.sarRatio.height;\n mi.videoCodec = codecString;\n\n if (mi.hasAudio) {\n if (mi.audioCodec != null) {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.videoCodec + ',' + mi.audioCodec + '\"';\n }\n } else {\n mi.mimeType = 'video/x-flv; codecs=\"' + mi.videoCodec + '\"';\n }\n if (mi.isComplete()) {\n this._onMediaInfo(mi);\n }\n }\n\n let ppsCount = v.getUint8(offset); // numOfPictureParameterSets\n if (ppsCount === 0) {\n this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS');\n return;\n } else if (ppsCount > 1) {\n Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: PPS Count = ${ppsCount}`);\n }\n\n offset++;\n\n for (let i = 0; i < ppsCount; i++) {\n let len = v.getUint16(offset, !le); // pictureParameterSetLength\n offset += 2;\n\n if (len === 0) {\n continue;\n }\n\n // pps is useless for extracting video information\n offset += len;\n }\n\n meta.avcc = new Uint8Array(dataSize);\n meta.avcc.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0);\n Log.v(this.TAG, 'Parsed AVCDecoderConfigurationRecord');\n\n if (this._isInitialMetadataDispatched()) {\n // flush parsed frames\n if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {\n this._onDataAvailable(this._audioTrack, this._videoTrack);\n }\n } else {\n this._videoInitialMetadataDispatched = true;\n }\n // notify new metadata\n this._dispatch = false;\n this._onTrackMetadata('video', meta);\n }\n\n _parseAVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) {\n let le = this._littleEndian;\n let v = new DataView(arrayBuffer, dataOffset, dataSize);\n\n let units = [], length = 0;\n\n let offset = 0;\n const lengthSize = this._naluLengthSize;\n let dts = this._timestampBase + tagTimestamp;\n let keyframe = (frameType === 1); // from FLV Frame Type constants\n\n while (offset < dataSize) {\n if (offset + 4 >= dataSize) {\n Log.w(this.TAG, `Malformed Nalu near timestamp ${dts}, offset = ${offset}, dataSize = ${dataSize}`);\n break; // data not enough for next Nalu\n }\n // Nalu with length-header (AVC1)\n let naluSize = v.getUint32(offset, !le); // Big-Endian read\n if (lengthSize === 3) {\n naluSize >>>= 8;\n }\n if (naluSize > dataSize - lengthSize) {\n Log.w(this.TAG, `Malformed Nalus near timestamp ${dts}, NaluSize > DataSize!`);\n return;\n }\n\n let unitType = v.getUint8(offset + lengthSize) & 0x1F;\n\n if (unitType === 5) { // IDR\n keyframe = true;\n }\n\n let data = new Uint8Array(arrayBuffer, dataOffset + offset, lengthSize + naluSize);\n let unit = {type: unitType, data: data};\n units.push(unit);\n length += data.byteLength;\n\n offset += lengthSize + naluSize;\n }\n\n if (units.length) {\n let track = this._videoTrack;\n let avcSample = {\n units: units,\n length: length,\n isKeyframe: keyframe,\n dts: dts,\n cts: cts,\n pts: (dts + cts)\n };\n if (keyframe) {\n avcSample.fileposition = tagPosition;\n }\n track.samples.push(avcSample);\n track.length += length;\n }\n }\n\n}\n\nexport default FLVDemuxer;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * This file is derived from dailymotion's hls.js library (hls.js/src/remux/mp4-generator.js)\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// MP4 boxes generator for ISO BMFF (ISO Base Media File Format, defined in ISO/IEC 14496-12)\nclass MP4 {\n\n static init() {\n MP4.types = {\n avc1: [], avcC: [], btrt: [], dinf: [],\n dref: [], esds: [], ftyp: [], hdlr: [],\n mdat: [], mdhd: [], mdia: [], mfhd: [],\n minf: [], moof: [], moov: [], mp4a: [],\n mvex: [], mvhd: [], sdtp: [], stbl: [],\n stco: [], stsc: [], stsd: [], stsz: [],\n stts: [], tfdt: [], tfhd: [], traf: [],\n trak: [], trun: [], trex: [], tkhd: [],\n vmhd: [], smhd: [], '.mp3': []\n };\n\n for (let name in MP4.types) {\n if (MP4.types.hasOwnProperty(name)) {\n MP4.types[name] = [\n name.charCodeAt(0),\n name.charCodeAt(1),\n name.charCodeAt(2),\n name.charCodeAt(3)\n ];\n }\n }\n\n let constants = MP4.constants = {};\n\n constants.FTYP = new Uint8Array([\n 0x69, 0x73, 0x6F, 0x6D, // major_brand: isom\n 0x0, 0x0, 0x0, 0x1, // minor_version: 0x01\n 0x69, 0x73, 0x6F, 0x6D, // isom\n 0x61, 0x76, 0x63, 0x31 // avc1\n ]);\n\n constants.STSD_PREFIX = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x01 // entry_count\n ]);\n\n constants.STTS = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00 // entry_count\n ]);\n\n constants.STSC = constants.STCO = constants.STTS;\n\n constants.STSZ = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // sample_size\n 0x00, 0x00, 0x00, 0x00 // sample_count\n ]);\n\n constants.HDLR_VIDEO = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // pre_defined\n 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'\n 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x56, 0x69, 0x64, 0x65,\n 0x6F, 0x48, 0x61, 0x6E,\n 0x64, 0x6C, 0x65, 0x72, 0x00 // name: VideoHandler\n ]);\n\n constants.HDLR_AUDIO = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // pre_defined\n 0x73, 0x6F, 0x75, 0x6E, // handler_type: 'soun'\n 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x53, 0x6F, 0x75, 0x6E,\n 0x64, 0x48, 0x61, 0x6E,\n 0x64, 0x6C, 0x65, 0x72, 0x00 // name: SoundHandler\n ]);\n\n constants.DREF = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x01, // entry_count\n 0x00, 0x00, 0x00, 0x0C, // entry_size\n 0x75, 0x72, 0x6C, 0x20, // type 'url '\n 0x00, 0x00, 0x00, 0x01 // version(0) + flags\n ]);\n\n // Sound media header\n constants.SMHD = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00 // balance(2) + reserved(2)\n ]);\n\n // video media header\n constants.VMHD = new Uint8Array([\n 0x00, 0x00, 0x00, 0x01, // version(0) + flags\n 0x00, 0x00, // graphicsmode: 2 bytes\n 0x00, 0x00, 0x00, 0x00, // opcolor: 3 * 2 bytes\n 0x00, 0x00\n ]);\n }\n\n // Generate a box\n static box(type) {\n let size = 8;\n let result = null;\n let datas = Array.prototype.slice.call(arguments, 1);\n let arrayCount = datas.length;\n\n for (let i = 0; i < arrayCount; i++) {\n size += datas[i].byteLength;\n }\n\n result = new Uint8Array(size);\n result[0] = (size >>> 24) & 0xFF; // size\n result[1] = (size >>> 16) & 0xFF;\n result[2] = (size >>> 8) & 0xFF;\n result[3] = (size) & 0xFF;\n\n result.set(type, 4); // type\n\n let offset = 8;\n for (let i = 0; i < arrayCount; i++) { // data body\n result.set(datas[i], offset);\n offset += datas[i].byteLength;\n }\n\n return result;\n }\n\n // emit ftyp & moov\n static generateInitSegment(meta) {\n let ftyp = MP4.box(MP4.types.ftyp, MP4.constants.FTYP);\n let moov = MP4.moov(meta);\n\n let result = new Uint8Array(ftyp.byteLength + moov.byteLength);\n result.set(ftyp, 0);\n result.set(moov, ftyp.byteLength);\n return result;\n }\n\n // Movie metadata box\n static moov(meta) {\n let mvhd = MP4.mvhd(meta.timescale, meta.duration);\n let trak = MP4.trak(meta);\n let mvex = MP4.mvex(meta);\n return MP4.box(MP4.types.moov, mvhd, trak, mvex);\n }\n\n // Movie header box\n static mvhd(timescale, duration) {\n return MP4.box(MP4.types.mvhd, new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // creation_time\n 0x00, 0x00, 0x00, 0x00, // modification_time\n (timescale >>> 24) & 0xFF, // timescale: 4 bytes\n (timescale >>> 16) & 0xFF,\n (timescale >>> 8) & 0xFF,\n (timescale) & 0xFF,\n (duration >>> 24) & 0xFF, // duration: 4 bytes\n (duration >>> 16) & 0xFF,\n (duration >>> 8) & 0xFF,\n (duration) & 0xFF,\n 0x00, 0x01, 0x00, 0x00, // Preferred rate: 1.0\n 0x01, 0x00, 0x00, 0x00, // PreferredVolume(1.0, 2bytes) + reserved(2bytes)\n 0x00, 0x00, 0x00, 0x00, // reserved: 4 + 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix----\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x01, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x40, 0x00, 0x00, 0x00, // ----end composition matrix----\n 0x00, 0x00, 0x00, 0x00, // ----begin pre_defined 6 * 4 bytes----\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, // ----end pre_defined 6 * 4 bytes----\n 0xFF, 0xFF, 0xFF, 0xFF // next_track_ID\n ]));\n }\n\n // Track box\n static trak(meta) {\n return MP4.box(MP4.types.trak, MP4.tkhd(meta), MP4.mdia(meta));\n }\n\n // Track header box\n static tkhd(meta) {\n let trackId = meta.id, duration = meta.duration;\n let width = meta.presentWidth, height = meta.presentHeight;\n\n return MP4.box(MP4.types.tkhd, new Uint8Array([\n 0x00, 0x00, 0x00, 0x07, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // creation_time\n 0x00, 0x00, 0x00, 0x00, // modification_time\n (trackId >>> 24) & 0xFF, // track_ID: 4 bytes\n (trackId >>> 16) & 0xFF,\n (trackId >>> 8) & 0xFF,\n (trackId) & 0xFF,\n 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes\n (duration >>> 24) & 0xFF, // duration: 4 bytes\n (duration >>> 16) & 0xFF,\n (duration >>> 8) & 0xFF,\n (duration) & 0xFF,\n 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, // layer(2bytes) + alternate_group(2bytes)\n 0x00, 0x00, 0x00, 0x00, // volume(2bytes) + reserved(2bytes)\n 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix----\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x01, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x40, 0x00, 0x00, 0x00, // ----end composition matrix----\n (width >>> 8) & 0xFF, // width and height\n (width) & 0xFF,\n 0x00, 0x00,\n (height >>> 8) & 0xFF,\n (height) & 0xFF,\n 0x00, 0x00\n ]));\n }\n\n // Media Box\n static mdia(meta) {\n return MP4.box(MP4.types.mdia, MP4.mdhd(meta), MP4.hdlr(meta), MP4.minf(meta));\n }\n\n // Media header box\n static mdhd(meta) {\n let timescale = meta.timescale;\n let duration = meta.duration;\n return MP4.box(MP4.types.mdhd, new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n 0x00, 0x00, 0x00, 0x00, // creation_time\n 0x00, 0x00, 0x00, 0x00, // modification_time\n (timescale >>> 24) & 0xFF, // timescale: 4 bytes\n (timescale >>> 16) & 0xFF,\n (timescale >>> 8) & 0xFF,\n (timescale) & 0xFF,\n (duration >>> 24) & 0xFF, // duration: 4 bytes\n (duration >>> 16) & 0xFF,\n (duration >>> 8) & 0xFF,\n (duration) & 0xFF,\n 0x55, 0xC4, // language: und (undetermined)\n 0x00, 0x00 // pre_defined = 0\n ]));\n }\n\n // Media handler reference box\n static hdlr(meta) {\n let data = null;\n if (meta.type === 'audio') {\n data = MP4.constants.HDLR_AUDIO;\n } else {\n data = MP4.constants.HDLR_VIDEO;\n }\n return MP4.box(MP4.types.hdlr, data);\n }\n\n // Media infomation box\n static minf(meta) {\n let xmhd = null;\n if (meta.type === 'audio') {\n xmhd = MP4.box(MP4.types.smhd, MP4.constants.SMHD);\n } else {\n xmhd = MP4.box(MP4.types.vmhd, MP4.constants.VMHD);\n }\n return MP4.box(MP4.types.minf, xmhd, MP4.dinf(), MP4.stbl(meta));\n }\n\n // Data infomation box\n static dinf() {\n let result = MP4.box(MP4.types.dinf,\n MP4.box(MP4.types.dref, MP4.constants.DREF)\n );\n return result;\n }\n\n // Sample table box\n static stbl(meta) {\n let result = MP4.box(MP4.types.stbl, // type: stbl\n MP4.stsd(meta), // Sample Description Table\n MP4.box(MP4.types.stts, MP4.constants.STTS), // Time-To-Sample\n MP4.box(MP4.types.stsc, MP4.constants.STSC), // Sample-To-Chunk\n MP4.box(MP4.types.stsz, MP4.constants.STSZ), // Sample size\n MP4.box(MP4.types.stco, MP4.constants.STCO) // Chunk offset\n ); \n return result; \n }\n\n // Sample description box\n static stsd(meta) {\n if (meta.type === 'audio') {\n if (meta.codec === 'mp3') {\n return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.mp3(meta));\n }\n // else: aac -> mp4a\n return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.mp4a(meta));\n } else {\n return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.avc1(meta));\n }\n }\n\n static mp3(meta) {\n let channelCount = meta.channelCount;\n let sampleRate = meta.audioSampleRate;\n\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // reserved(4)\n 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2)\n 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, channelCount, // channelCount(2)\n 0x00, 0x10, // sampleSize(2)\n 0x00, 0x00, 0x00, 0x00, // reserved(4)\n (sampleRate >>> 8) & 0xFF, // Audio sample rate\n (sampleRate) & 0xFF,\n 0x00, 0x00\n ]);\n\n return MP4.box(MP4.types['.mp3'], data);\n }\n\n static mp4a(meta) {\n let channelCount = meta.channelCount;\n let sampleRate = meta.audioSampleRate;\n\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // reserved(4)\n 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2)\n 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, channelCount, // channelCount(2)\n 0x00, 0x10, // sampleSize(2)\n 0x00, 0x00, 0x00, 0x00, // reserved(4)\n (sampleRate >>> 8) & 0xFF, // Audio sample rate\n (sampleRate) & 0xFF,\n 0x00, 0x00\n ]);\n\n return MP4.box(MP4.types.mp4a, data, MP4.esds(meta));\n }\n\n static esds(meta) {\n let config = meta.config || [];\n let configSize = config.length;\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version 0 + flags\n\n 0x03, // descriptor_type\n 0x17 + configSize, // length3\n 0x00, 0x01, // es_id\n 0x00, // stream_priority\n\n 0x04, // descriptor_type\n 0x0F + configSize, // length\n 0x40, // codec: mpeg4_audio\n 0x15, // stream_type: Audio\n 0x00, 0x00, 0x00, // buffer_size\n 0x00, 0x00, 0x00, 0x00, // maxBitrate\n 0x00, 0x00, 0x00, 0x00, // avgBitrate\n\n 0x05 // descriptor_type\n ].concat([\n configSize\n ]).concat(\n config\n ).concat([\n 0x06, 0x01, 0x02 // GASpecificConfig\n ]));\n return MP4.box(MP4.types.esds, data);\n }\n\n static avc1(meta) {\n let avcc = meta.avcc;\n let width = meta.codecWidth, height = meta.codecHeight;\n\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // reserved(4)\n 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2)\n 0x00, 0x00, 0x00, 0x00, // pre_defined(2) + reserved(2)\n 0x00, 0x00, 0x00, 0x00, // pre_defined: 3 * 4 bytes\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n (width >>> 8) & 0xFF, // width: 2 bytes\n (width) & 0xFF,\n (height >>> 8) & 0xFF, // height: 2 bytes\n (height) & 0xFF,\n 0x00, 0x48, 0x00, 0x00, // horizresolution: 4 bytes\n 0x00, 0x48, 0x00, 0x00, // vertresolution: 4 bytes\n 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes\n 0x00, 0x01, // frame_count\n 0x0A, // strlen\n 0x78, 0x71, 0x71, 0x2F, // compressorname: 32 bytes\n 0x66, 0x6C, 0x76, 0x2E,\n 0x6A, 0x73, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00,\n 0x00, 0x18, // depth\n 0xFF, 0xFF // pre_defined = -1\n ]);\n return MP4.box(MP4.types.avc1, data, MP4.box(MP4.types.avcC, avcc));\n }\n\n // Movie Extends box\n static mvex(meta) {\n return MP4.box(MP4.types.mvex, MP4.trex(meta));\n }\n\n // Track Extends box\n static trex(meta) {\n let trackId = meta.id;\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) + flags\n (trackId >>> 24) & 0xFF, // track_ID\n (trackId >>> 16) & 0xFF,\n (trackId >>> 8) & 0xFF,\n (trackId) & 0xFF,\n 0x00, 0x00, 0x00, 0x01, // default_sample_description_index\n 0x00, 0x00, 0x00, 0x00, // default_sample_duration\n 0x00, 0x00, 0x00, 0x00, // default_sample_size\n 0x00, 0x01, 0x00, 0x01 // default_sample_flags\n ]);\n return MP4.box(MP4.types.trex, data);\n }\n\n // Movie fragment box\n static moof(track, baseMediaDecodeTime) {\n return MP4.box(MP4.types.moof, MP4.mfhd(track.sequenceNumber), MP4.traf(track, baseMediaDecodeTime));\n }\n\n static mfhd(sequenceNumber) {\n let data = new Uint8Array([\n 0x00, 0x00, 0x00, 0x00,\n (sequenceNumber >>> 24) & 0xFF, // sequence_number: int32\n (sequenceNumber >>> 16) & 0xFF,\n (sequenceNumber >>> 8) & 0xFF,\n (sequenceNumber) & 0xFF\n ]);\n return MP4.box(MP4.types.mfhd, data);\n }\n\n // Track fragment box\n static traf(track, baseMediaDecodeTime) {\n let trackId = track.id;\n\n // Track fragment header box\n let tfhd = MP4.box(MP4.types.tfhd, new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) & flags\n (trackId >>> 24) & 0xFF, // track_ID\n (trackId >>> 16) & 0xFF,\n (trackId >>> 8) & 0xFF,\n (trackId) & 0xFF\n ]));\n // Track Fragment Decode Time\n let tfdt = MP4.box(MP4.types.tfdt, new Uint8Array([\n 0x00, 0x00, 0x00, 0x00, // version(0) & flags\n (baseMediaDecodeTime >>> 24) & 0xFF, // baseMediaDecodeTime: int32\n (baseMediaDecodeTime >>> 16) & 0xFF,\n (baseMediaDecodeTime >>> 8) & 0xFF,\n (baseMediaDecodeTime) & 0xFF\n ]));\n let sdtp = MP4.sdtp(track);\n let trun = MP4.trun(track, sdtp.byteLength + 16 + 16 + 8 + 16 + 8 + 8);\n\n return MP4.box(MP4.types.traf, tfhd, tfdt, trun, sdtp);\n }\n\n // Sample Dependency Type box\n static sdtp(track) {\n let samples = track.samples || [];\n let sampleCount = samples.length;\n let data = new Uint8Array(4 + sampleCount);\n // 0~4 bytes: version(0) & flags\n for (let i = 0; i < sampleCount; i++) {\n let flags = samples[i].flags;\n data[i + 4] = (flags.isLeading << 6) // is_leading: 2 (bit)\n | (flags.dependsOn << 4) // sample_depends_on\n | (flags.isDependedOn << 2) // sample_is_depended_on\n | (flags.hasRedundancy); // sample_has_redundancy\n }\n return MP4.box(MP4.types.sdtp, data);\n }\n\n // Track fragment run box\n static trun(track, offset) {\n let samples = track.samples || [];\n let sampleCount = samples.length;\n let dataSize = 12 + 16 * sampleCount;\n let data = new Uint8Array(dataSize);\n offset += 8 + dataSize;\n\n data.set([\n 0x00, 0x00, 0x0F, 0x01, // version(0) & flags\n (sampleCount >>> 24) & 0xFF, // sample_count\n (sampleCount >>> 16) & 0xFF,\n (sampleCount >>> 8) & 0xFF,\n (sampleCount) & 0xFF,\n (offset >>> 24) & 0xFF, // data_offset\n (offset >>> 16) & 0xFF,\n (offset >>> 8) & 0xFF,\n (offset) & 0xFF\n ], 0);\n\n for (let i = 0; i < sampleCount; i++) {\n let duration = samples[i].duration;\n let size = samples[i].size;\n let flags = samples[i].flags;\n let cts = samples[i].cts;\n data.set([\n (duration >>> 24) & 0xFF, // sample_duration\n (duration >>> 16) & 0xFF,\n (duration >>> 8) & 0xFF,\n (duration) & 0xFF,\n (size >>> 24) & 0xFF, // sample_size\n (size >>> 16) & 0xFF,\n (size >>> 8) & 0xFF,\n (size) & 0xFF,\n (flags.isLeading << 2) | flags.dependsOn, // sample_flags\n (flags.isDependedOn << 6) | (flags.hasRedundancy << 4) | flags.isNonSync,\n 0x00, 0x00, // sample_degradation_priority\n (cts >>> 24) & 0xFF, // sample_composition_time_offset\n (cts >>> 16) & 0xFF,\n (cts >>> 8) & 0xFF,\n (cts) & 0xFF\n ], 12 + 16 * i);\n }\n return MP4.box(MP4.types.trun, data);\n }\n\n static mdat(data) {\n return MP4.box(MP4.types.mdat, data);\n }\n\n}\n\nMP4.init();\n\nexport default MP4;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * This file is modified from dailymotion's hls.js library (hls.js/src/helper/aac.js)\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass AAC {\n\n static getSilentFrame(codec, channelCount) {\n if (codec === 'mp4a.40.2') {\n // handle LC-AAC\n if (channelCount === 1) {\n return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);\n } else if (channelCount === 2) {\n return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);\n } else if (channelCount === 3) {\n return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);\n } else if (channelCount === 4) {\n return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);\n } else if (channelCount === 5) {\n return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);\n } else if (channelCount === 6) {\n return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]);\n }\n } else {\n // handle HE-AAC (mp4a.40.5 / mp4a.40.29)\n if (channelCount === 1) {\n // ffmpeg -y -f lavfi -i \"aevalsrc=0:d=0.05\" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n } else if (channelCount === 2) {\n // ffmpeg -y -f lavfi -i \"aevalsrc=0|0:d=0.05\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n } else if (channelCount === 3) {\n // ffmpeg -y -f lavfi -i \"aevalsrc=0|0|0:d=0.05\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n }\n }\n return null;\n }\n\n}\n\nexport default AAC;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport MP4 from './mp4-generator.js';\nimport AAC from './aac-silent.js';\nimport Browser from '../utils/browser.js';\nimport { SampleInfo, MediaSegmentInfo, MediaSegmentInfoList } from '../core/media-segment-info.js';\nimport { IllegalStateException } from '../utils/exception.js';\n\n\n// Fragmented mp4 remuxer\nclass MP4Remuxer {\n\n constructor(config) {\n this.TAG = 'MP4Remuxer';\n\n this._config = config;\n this._isLive = (config.isLive === true) ? true : false;\n\n this._dtsBase = -1;\n this._dtsBaseInited = false;\n this._audioDtsBase = Infinity;\n this._videoDtsBase = Infinity;\n this._audioNextDts = undefined;\n this._videoNextDts = undefined;\n this._audioStashedLastSample = null;\n this._videoStashedLastSample = null;\n\n this._audioMeta = null;\n this._videoMeta = null;\n\n this._audioSegmentInfoList = new MediaSegmentInfoList('audio');\n this._videoSegmentInfoList = new MediaSegmentInfoList('video');\n\n this._onInitSegment = null;\n this._onMediaSegment = null;\n\n // Workaround for chrome < 50: Always force first sample as a Random Access Point in media segment\n // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412\n this._forceFirstIDR = (Browser.chrome &&\n (Browser.version.major < 50 ||\n (Browser.version.major === 50 && Browser.version.build < 2661))) ? true : false;\n\n // Workaround for IE11/Edge: Fill silent aac frame after keyframe-seeking\n // Make audio beginDts equals with video beginDts, in order to fix seek freeze\n this._fillSilentAfterSeek = (Browser.msedge || Browser.msie);\n\n // While only FireFox supports 'audio/mp4, codecs=\"mp3\"', use 'audio/mpeg' for chrome, safari, ...\n this._mp3UseMpegAudio = !Browser.firefox;\n\n this._fillAudioTimestampGap = this._config.fixAudioTimestampGap;\n }\n\n destroy() {\n this._dtsBase = -1;\n this._dtsBaseInited = false;\n this._audioMeta = null;\n this._videoMeta = null;\n this._audioSegmentInfoList.clear();\n this._audioSegmentInfoList = null;\n this._videoSegmentInfoList.clear();\n this._videoSegmentInfoList = null;\n this._onInitSegment = null;\n this._onMediaSegment = null;\n }\n\n bindDataSource(producer) {\n producer.onDataAvailable = this.remux.bind(this);\n producer.onTrackMetadata = this._onTrackMetadataReceived.bind(this);\n return this;\n }\n\n /* prototype: function onInitSegment(type: string, initSegment: ArrayBuffer): void\n InitSegment: {\n type: string,\n data: ArrayBuffer,\n codec: string,\n container: string\n }\n */\n get onInitSegment() {\n return this._onInitSegment;\n }\n\n set onInitSegment(callback) {\n this._onInitSegment = callback;\n }\n\n /* prototype: function onMediaSegment(type: string, mediaSegment: MediaSegment): void\n MediaSegment: {\n type: string,\n data: ArrayBuffer,\n sampleCount: int32\n info: MediaSegmentInfo\n }\n */\n get onMediaSegment() {\n return this._onMediaSegment;\n }\n\n set onMediaSegment(callback) {\n this._onMediaSegment = callback;\n }\n\n insertDiscontinuity() {\n this._audioNextDts = this._videoNextDts = undefined;\n }\n\n seek(originalDts) {\n this._audioStashedLastSample = null;\n this._videoStashedLastSample = null;\n this._videoSegmentInfoList.clear();\n this._audioSegmentInfoList.clear();\n }\n\n remux(audioTrack, videoTrack) {\n if (!this._onMediaSegment) {\n throw new IllegalStateException('MP4Remuxer: onMediaSegment callback must be specificed!');\n }\n if (!this._dtsBaseInited) {\n this._calculateDtsBase(audioTrack, videoTrack);\n }\n this._remuxVideo(videoTrack);\n this._remuxAudio(audioTrack);\n }\n\n _onTrackMetadataReceived(type, metadata) {\n let metabox = null;\n\n let container = 'mp4';\n let codec = metadata.codec;\n\n if (type === 'audio') {\n this._audioMeta = metadata;\n if (metadata.codec === 'mp3' && this._mp3UseMpegAudio) {\n // 'audio/mpeg' for MP3 audio track\n container = 'mpeg';\n codec = '';\n metabox = new Uint8Array();\n } else {\n // 'audio/mp4, codecs=\"codec\"'\n metabox = MP4.generateInitSegment(metadata);\n }\n } else if (type === 'video') {\n this._videoMeta = metadata;\n metabox = MP4.generateInitSegment(metadata);\n } else {\n return;\n }\n\n // dispatch metabox (Initialization Segment)\n if (!this._onInitSegment) {\n throw new IllegalStateException('MP4Remuxer: onInitSegment callback must be specified!');\n }\n this._onInitSegment(type, {\n type: type,\n data: metabox.buffer,\n codec: codec,\n container: `${type}/${container}`,\n mediaDuration: metadata.duration // in timescale 1000 (milliseconds)\n });\n }\n\n _calculateDtsBase(audioTrack, videoTrack) {\n if (this._dtsBaseInited) {\n return;\n }\n\n if (audioTrack.samples && audioTrack.samples.length) {\n this._audioDtsBase = audioTrack.samples[0].dts;\n }\n if (videoTrack.samples && videoTrack.samples.length) {\n this._videoDtsBase = videoTrack.samples[0].dts;\n }\n\n this._dtsBase = Math.min(this._audioDtsBase, this._videoDtsBase);\n this._dtsBaseInited = true;\n }\n\n flushStashedSamples() {\n let videoSample = this._videoStashedLastSample;\n let audioSample = this._audioStashedLastSample;\n\n let videoTrack = {\n type: 'video',\n id: 1,\n sequenceNumber: 0,\n samples: [],\n length: 0\n };\n\n if (videoSample != null) {\n videoTrack.samples.push(videoSample);\n videoTrack.length = videoSample.length;\n }\n\n let audioTrack = {\n type: 'audio',\n id: 2,\n sequenceNumber: 0,\n samples: [],\n length: 0\n };\n\n if (audioSample != null) {\n audioTrack.samples.push(audioSample);\n audioTrack.length = audioSample.length;\n }\n\n this._videoStashedLastSample = null;\n this._audioStashedLastSample = null;\n\n this._remuxVideo(videoTrack, true);\n this._remuxAudio(audioTrack, true);\n }\n\n _remuxAudio(audioTrack, force) {\n if (this._audioMeta == null) {\n return;\n }\n\n let track = audioTrack;\n let samples = track.samples;\n let dtsCorrection = undefined;\n let firstDts = -1, lastDts = -1, lastPts = -1;\n let refSampleDuration = this._audioMeta.refSampleDuration;\n\n let mpegRawTrack = this._audioMeta.codec === 'mp3' && this._mp3UseMpegAudio;\n let firstSegmentAfterSeek = this._dtsBaseInited && this._audioNextDts === undefined;\n\n let insertPrefixSilentFrame = false;\n\n if (!samples || samples.length === 0) {\n return;\n }\n if (samples.length === 1 && !force) {\n // If [sample count in current batch] === 1 && (force != true)\n // Ignore and keep in demuxer's queue\n return;\n } // else if (force === true) do remux\n\n let offset = 0;\n let mdatbox = null;\n let mdatBytes = 0;\n\n // calculate initial mdat size\n if (mpegRawTrack) {\n // for raw mpeg buffer\n offset = 0;\n mdatBytes = track.length;\n } else {\n // for fmp4 mdat box\n offset = 8; // size + type\n mdatBytes = 8 + track.length;\n }\n\n\n let lastSample = null;\n\n // Pop the lastSample and waiting for stash\n if (samples.length > 1) {\n lastSample = samples.pop();\n mdatBytes -= lastSample.length;\n }\n\n // Insert [stashed lastSample in the previous batch] to the front\n if (this._audioStashedLastSample != null) {\n let sample = this._audioStashedLastSample;\n this._audioStashedLastSample = null;\n samples.unshift(sample);\n mdatBytes += sample.length;\n }\n\n // Stash the lastSample of current batch, waiting for next batch\n if (lastSample != null) {\n this._audioStashedLastSample = lastSample;\n }\n\n\n let firstSampleOriginalDts = samples[0].dts - this._dtsBase;\n\n // calculate dtsCorrection\n if (this._audioNextDts) {\n dtsCorrection = firstSampleOriginalDts - this._audioNextDts;\n } else { // this._audioNextDts == undefined\n if (this._audioSegmentInfoList.isEmpty()) {\n dtsCorrection = 0;\n if (this._fillSilentAfterSeek && !this._videoSegmentInfoList.isEmpty()) {\n if (this._audioMeta.originalCodec !== 'mp3') {\n insertPrefixSilentFrame = true;\n }\n }\n } else {\n let lastSample = this._audioSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts);\n if (lastSample != null) {\n let distance = (firstSampleOriginalDts - (lastSample.originalDts + lastSample.duration));\n if (distance <= 3) {\n distance = 0;\n }\n let expectedDts = lastSample.dts + lastSample.duration + distance;\n dtsCorrection = firstSampleOriginalDts - expectedDts;\n } else { // lastSample == null, cannot found\n dtsCorrection = 0;\n }\n }\n }\n\n if (insertPrefixSilentFrame) {\n // align audio segment beginDts to match with current video segment's beginDts\n let firstSampleDts = firstSampleOriginalDts - dtsCorrection;\n let videoSegment = this._videoSegmentInfoList.getLastSegmentBefore(firstSampleOriginalDts);\n if (videoSegment != null && videoSegment.beginDts < firstSampleDts) {\n let silentUnit = AAC.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount);\n if (silentUnit) {\n let dts = videoSegment.beginDts;\n let silentFrameDuration = firstSampleDts - videoSegment.beginDts;\n Log.v(this.TAG, `InsertPrefixSilentAudio: dts: ${dts}, duration: ${silentFrameDuration}`);\n samples.unshift({ unit: silentUnit, dts: dts, pts: dts });\n mdatBytes += silentUnit.byteLength;\n } // silentUnit == null: Cannot generate, skip\n } else {\n insertPrefixSilentFrame = false;\n }\n }\n\n let mp4Samples = [];\n\n // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples\n for (let i = 0; i < samples.length; i++) {\n let sample = samples[i];\n let unit = sample.unit;\n let originalDts = sample.dts - this._dtsBase;\n let dts = originalDts;\n let needFillSilentFrames = false;\n let silentFrames = null;\n let sampleDuration = 0;\n\n if (originalDts < -0.001) {\n continue; //pass the first sample with the invalid dts\n }\n\n if (this._audioMeta.codec !== 'mp3') {\n // for AAC codec, we need to keep dts increase based on refSampleDuration\n let curRefDts = originalDts;\n const maxAudioFramesDrift = 3;\n if (this._audioNextDts) {\n curRefDts = this._audioNextDts;\n }\n\n dtsCorrection = originalDts - curRefDts;\n if (dtsCorrection <= -maxAudioFramesDrift * refSampleDuration) {\n // If we're overlapping by more than maxAudioFramesDrift number of frame, drop this sample\n Log.w(this.TAG, `Dropping 1 audio frame (originalDts: ${originalDts} ms ,curRefDts: ${curRefDts} ms) due to dtsCorrection: ${dtsCorrection} ms overlap.`);\n continue;\n }\n else if (dtsCorrection >= maxAudioFramesDrift * refSampleDuration && this._fillAudioTimestampGap && !Browser.safari) {\n // Silent frame generation, if large timestamp gap detected && config.fixAudioTimestampGap\n needFillSilentFrames = true;\n // We need to insert silent frames to fill timestamp gap\n let frameCount = Math.floor(dtsCorrection / refSampleDuration);\n Log.w(this.TAG, 'Large audio timestamp gap detected, may cause AV sync to drift. ' +\n 'Silent frames will be generated to avoid unsync.\\n' +\n `originalDts: ${originalDts} ms, curRefDts: ${curRefDts} ms, ` +\n `dtsCorrection: ${Math.round(dtsCorrection)} ms, generate: ${frameCount} frames`);\n\n\n dts = Math.floor(curRefDts);\n sampleDuration = Math.floor(curRefDts + refSampleDuration) - dts;\n\n let silentUnit = AAC.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount);\n if (silentUnit == null) {\n Log.w(this.TAG, 'Unable to generate silent frame for ' +\n `${this._audioMeta.originalCodec} with ${this._audioMeta.channelCount} channels, repeat last frame`);\n // Repeat last frame\n silentUnit = unit;\n }\n silentFrames = [];\n\n for (let j = 0; j < frameCount; j++) {\n curRefDts = curRefDts + refSampleDuration;\n let intDts = Math.floor(curRefDts); // change to integer\n let intDuration = Math.floor(curRefDts + refSampleDuration) - intDts;\n let frame = {\n dts: intDts,\n pts: intDts,\n cts: 0,\n unit: silentUnit,\n size: silentUnit.byteLength,\n duration: intDuration, // wait for next sample\n originalDts: originalDts,\n flags: {\n isLeading: 0,\n dependsOn: 1,\n isDependedOn: 0,\n hasRedundancy: 0\n }\n };\n silentFrames.push(frame);\n mdatBytes += frame.size;;\n\n }\n\n this._audioNextDts = curRefDts + refSampleDuration;\n\n } else {\n\n dts = Math.floor(curRefDts);\n sampleDuration = Math.floor(curRefDts + refSampleDuration) - dts;\n this._audioNextDts = curRefDts + refSampleDuration;\n\n }\n } else {\n // keep the original dts calculate algorithm for mp3\n dts = originalDts - dtsCorrection;\n\n\n if (i !== samples.length - 1) {\n let nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection;\n sampleDuration = nextDts - dts;\n } else { // the last sample\n if (lastSample != null) { // use stashed sample's dts to calculate sample duration\n let nextDts = lastSample.dts - this._dtsBase - dtsCorrection;\n sampleDuration = nextDts - dts;\n } else if (mp4Samples.length >= 1) { // use second last sample duration\n sampleDuration = mp4Samples[mp4Samples.length - 1].duration;\n } else { // the only one sample, use reference sample duration\n sampleDuration = Math.floor(refSampleDuration);\n }\n }\n this._audioNextDts = dts + sampleDuration;\n }\n\n if (firstDts === -1) {\n firstDts = dts;\n }\n mp4Samples.push({\n dts: dts,\n pts: dts,\n cts: 0,\n unit: sample.unit,\n size: sample.unit.byteLength,\n duration: sampleDuration,\n originalDts: originalDts,\n flags: {\n isLeading: 0,\n dependsOn: 1,\n isDependedOn: 0,\n hasRedundancy: 0\n }\n });\n\n if (needFillSilentFrames) {\n // Silent frames should be inserted after wrong-duration frame\n mp4Samples.push.apply(mp4Samples, silentFrames);\n }\n }\n\n if (mp4Samples.length === 0) {\n //no samples need to remux\n track.samples = [];\n track.length = 0;\n return;\n }\n\n // allocate mdatbox\n if (mpegRawTrack) {\n // allocate for raw mpeg buffer\n mdatbox = new Uint8Array(mdatBytes);\n } else {\n // allocate for fmp4 mdat box\n mdatbox = new Uint8Array(mdatBytes);\n // size field\n mdatbox[0] = (mdatBytes >>> 24) & 0xFF;\n mdatbox[1] = (mdatBytes >>> 16) & 0xFF;\n mdatbox[2] = (mdatBytes >>> 8) & 0xFF;\n mdatbox[3] = (mdatBytes) & 0xFF;\n // type field (fourCC)\n mdatbox.set(MP4.types.mdat, 4);\n }\n\n // Write samples into mdatbox\n for (let i = 0; i < mp4Samples.length; i++) {\n let unit = mp4Samples[i].unit;\n mdatbox.set(unit, offset);\n offset += unit.byteLength;\n }\n\n let latest = mp4Samples[mp4Samples.length - 1];\n lastDts = latest.dts + latest.duration;\n //this._audioNextDts = lastDts;\n\n // fill media segment info & add to info list\n let info = new MediaSegmentInfo();\n info.beginDts = firstDts;\n info.endDts = lastDts;\n info.beginPts = firstDts;\n info.endPts = lastDts;\n info.originalBeginDts = mp4Samples[0].originalDts;\n info.originalEndDts = latest.originalDts + latest.duration;\n info.firstSample = new SampleInfo(mp4Samples[0].dts,\n mp4Samples[0].pts,\n mp4Samples[0].duration,\n mp4Samples[0].originalDts,\n false);\n info.lastSample = new SampleInfo(latest.dts,\n latest.pts,\n latest.duration,\n latest.originalDts,\n false);\n if (!this._isLive) {\n this._audioSegmentInfoList.append(info);\n }\n\n track.samples = mp4Samples;\n track.sequenceNumber++;\n\n let moofbox = null;\n\n if (mpegRawTrack) {\n // Generate empty buffer, because useless for raw mpeg\n moofbox = new Uint8Array();\n } else {\n // Generate moof for fmp4 segment\n moofbox = MP4.moof(track, firstDts);\n }\n\n track.samples = [];\n track.length = 0;\n\n let segment = {\n type: 'audio',\n data: this._mergeBoxes(moofbox, mdatbox).buffer,\n sampleCount: mp4Samples.length,\n info: info\n };\n\n if (mpegRawTrack && firstSegmentAfterSeek) {\n // For MPEG audio stream in MSE, if seeking occurred, before appending new buffer\n // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer.\n segment.timestampOffset = firstDts;\n }\n\n this._onMediaSegment('audio', segment);\n }\n\n _remuxVideo(videoTrack, force) {\n if (this._videoMeta == null) {\n return;\n }\n\n let track = videoTrack;\n let samples = track.samples;\n let dtsCorrection = undefined;\n let firstDts = -1, lastDts = -1;\n let firstPts = -1, lastPts = -1;\n\n if (!samples || samples.length === 0) {\n return;\n }\n if (samples.length === 1 && !force) {\n // If [sample count in current batch] === 1 && (force != true)\n // Ignore and keep in demuxer's queue\n return;\n } // else if (force === true) do remux\n\n let offset = 8;\n let mdatbox = null;\n let mdatBytes = 8 + videoTrack.length;\n\n\n let lastSample = null;\n\n // Pop the lastSample and waiting for stash\n if (samples.length > 1) {\n lastSample = samples.pop();\n mdatBytes -= lastSample.length;\n }\n\n // Insert [stashed lastSample in the previous batch] to the front\n if (this._videoStashedLastSample != null) {\n let sample = this._videoStashedLastSample;\n this._videoStashedLastSample = null;\n samples.unshift(sample);\n mdatBytes += sample.length;\n }\n\n // Stash the lastSample of current batch, waiting for next batch\n if (lastSample != null) {\n this._videoStashedLastSample = lastSample;\n }\n\n\n let firstSampleOriginalDts = samples[0].dts - this._dtsBase;\n\n // calculate dtsCorrection\n if (this._videoNextDts) {\n dtsCorrection = firstSampleOriginalDts - this._videoNextDts;\n } else { // this._videoNextDts == undefined\n if (this._videoSegmentInfoList.isEmpty()) {\n dtsCorrection = 0;\n } else {\n let lastSample = this._videoSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts);\n if (lastSample != null) {\n let distance = (firstSampleOriginalDts - (lastSample.originalDts + lastSample.duration));\n if (distance <= 3) {\n distance = 0;\n }\n let expectedDts = lastSample.dts + lastSample.duration + distance;\n dtsCorrection = firstSampleOriginalDts - expectedDts;\n } else { // lastSample == null, cannot found\n dtsCorrection = 0;\n }\n }\n }\n\n let info = new MediaSegmentInfo();\n let mp4Samples = [];\n\n // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples\n for (let i = 0; i < samples.length; i++) {\n let sample = samples[i];\n let originalDts = sample.dts - this._dtsBase;\n let isKeyframe = sample.isKeyframe;\n let dts = originalDts - dtsCorrection;\n let cts = sample.cts;\n let pts = dts + cts;\n\n if (firstDts === -1) {\n firstDts = dts;\n firstPts = pts;\n }\n\n let sampleDuration = 0;\n\n if (i !== samples.length - 1) {\n let nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection;\n sampleDuration = nextDts - dts;\n } else { // the last sample\n if (lastSample != null) { // use stashed sample's dts to calculate sample duration\n let nextDts = lastSample.dts - this._dtsBase - dtsCorrection;\n sampleDuration = nextDts - dts;\n } else if (mp4Samples.length >= 1) { // use second last sample duration\n sampleDuration = mp4Samples[mp4Samples.length - 1].duration;\n } else { // the only one sample, use reference sample duration\n sampleDuration = Math.floor(this._videoMeta.refSampleDuration);\n }\n }\n\n if (isKeyframe) {\n let syncPoint = new SampleInfo(dts, pts, sampleDuration, sample.dts, true);\n syncPoint.fileposition = sample.fileposition;\n info.appendSyncPoint(syncPoint);\n }\n\n mp4Samples.push({\n dts: dts,\n pts: pts,\n cts: cts,\n units: sample.units,\n size: sample.length,\n isKeyframe: isKeyframe,\n duration: sampleDuration,\n originalDts: originalDts,\n flags: {\n isLeading: 0,\n dependsOn: isKeyframe ? 2 : 1,\n isDependedOn: isKeyframe ? 1 : 0,\n hasRedundancy: 0,\n isNonSync: isKeyframe ? 0 : 1\n }\n });\n }\n\n // allocate mdatbox\n mdatbox = new Uint8Array(mdatBytes);\n mdatbox[0] = (mdatBytes >>> 24) & 0xFF;\n mdatbox[1] = (mdatBytes >>> 16) & 0xFF;\n mdatbox[2] = (mdatBytes >>> 8) & 0xFF;\n mdatbox[3] = (mdatBytes) & 0xFF;\n mdatbox.set(MP4.types.mdat, 4);\n\n // Write samples into mdatbox\n for (let i = 0; i < mp4Samples.length; i++) {\n let units = mp4Samples[i].units;\n while (units.length) {\n let unit = units.shift();\n let data = unit.data;\n mdatbox.set(data, offset);\n offset += data.byteLength;\n }\n }\n\n let latest = mp4Samples[mp4Samples.length - 1];\n lastDts = latest.dts + latest.duration;\n lastPts = latest.pts + latest.duration;\n this._videoNextDts = lastDts;\n\n // fill media segment info & add to info list\n info.beginDts = firstDts;\n info.endDts = lastDts;\n info.beginPts = firstPts;\n info.endPts = lastPts;\n info.originalBeginDts = mp4Samples[0].originalDts;\n info.originalEndDts = latest.originalDts + latest.duration;\n info.firstSample = new SampleInfo(mp4Samples[0].dts,\n mp4Samples[0].pts,\n mp4Samples[0].duration,\n mp4Samples[0].originalDts,\n mp4Samples[0].isKeyframe);\n info.lastSample = new SampleInfo(latest.dts,\n latest.pts,\n latest.duration,\n latest.originalDts,\n latest.isKeyframe);\n if (!this._isLive) {\n this._videoSegmentInfoList.append(info);\n }\n\n track.samples = mp4Samples;\n track.sequenceNumber++;\n\n // workaround for chrome < 50: force first sample as a random access point\n // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412\n if (this._forceFirstIDR) {\n let flags = mp4Samples[0].flags;\n flags.dependsOn = 2;\n flags.isNonSync = 0;\n }\n\n let moofbox = MP4.moof(track, firstDts);\n track.samples = [];\n track.length = 0;\n\n this._onMediaSegment('video', {\n type: 'video',\n data: this._mergeBoxes(moofbox, mdatbox).buffer,\n sampleCount: mp4Samples.length,\n info: info\n });\n }\n\n _mergeBoxes(moof, mdat) {\n let result = new Uint8Array(moof.byteLength + mdat.byteLength);\n result.set(moof, 0);\n result.set(mdat, moof.byteLength);\n return result;\n }\n\n}\n\nexport default MP4Remuxer;\n","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport Log from '../utils/logger.js';\nimport Browser from '../utils/browser.js';\nimport MediaInfo from './media-info.js';\nimport FLVDemuxer from '../demux/flv-demuxer.js';\nimport MP4Remuxer from '../remux/mp4-remuxer.js';\nimport DemuxErrors from '../demux/demux-errors.js';\nimport IOController from '../io/io-controller.js';\nimport TransmuxingEvents from './transmuxing-events.js';\nimport {LoaderStatus, LoaderErrors} from '../io/loader.js';\n\n// Transmuxing (IO, Demuxing, Remuxing) controller, with multipart support\nclass TransmuxingController {\n\n constructor(mediaDataSource, config) {\n this.TAG = 'TransmuxingController';\n this._emitter = new EventEmitter();\n\n this._config = config;\n\n // treat single part media as multipart media, which has only one segment\n if (!mediaDataSource.segments) {\n mediaDataSource.segments = [{\n duration: mediaDataSource.duration,\n filesize: mediaDataSource.filesize,\n url: mediaDataSource.url\n }];\n }\n\n // fill in default IO params if not exists\n if (typeof mediaDataSource.cors !== 'boolean') {\n mediaDataSource.cors = true;\n }\n if (typeof mediaDataSource.withCredentials !== 'boolean') {\n mediaDataSource.withCredentials = false;\n }\n\n this._mediaDataSource = mediaDataSource;\n this._currentSegmentIndex = 0;\n let totalDuration = 0;\n\n this._mediaDataSource.segments.forEach((segment) => {\n // timestampBase for each segment, and calculate total duration\n segment.timestampBase = totalDuration;\n totalDuration += segment.duration;\n // params needed by IOController\n segment.cors = mediaDataSource.cors;\n segment.withCredentials = mediaDataSource.withCredentials;\n // referrer policy control, if exist\n if (config.referrerPolicy) {\n segment.referrerPolicy = config.referrerPolicy;\n }\n });\n\n if (!isNaN(totalDuration) && this._mediaDataSource.duration !== totalDuration) {\n this._mediaDataSource.duration = totalDuration;\n }\n\n this._mediaInfo = null;\n this._demuxer = null;\n this._remuxer = null;\n this._ioctl = null;\n\n this._pendingSeekTime = null;\n this._pendingResolveSeekPoint = null;\n\n this._statisticsReporter = null;\n }\n\n destroy() {\n this._mediaInfo = null;\n this._mediaDataSource = null;\n\n if (this._statisticsReporter) {\n this._disableStatisticsReporter();\n }\n if (this._ioctl) {\n this._ioctl.destroy();\n this._ioctl = null;\n }\n if (this._demuxer) {\n this._demuxer.destroy();\n this._demuxer = null;\n }\n if (this._remuxer) {\n this._remuxer.destroy();\n this._remuxer = null;\n }\n\n this._emitter.removeAllListeners();\n this._emitter = null;\n }\n\n on(event, listener) {\n this._emitter.addListener(event, listener);\n }\n\n off(event, listener) {\n this._emitter.removeListener(event, listener);\n }\n\n start() {\n this._loadSegment(0);\n this._enableStatisticsReporter();\n }\n\n _loadSegment(segmentIndex, optionalFrom) {\n this._currentSegmentIndex = segmentIndex;\n let dataSource = this._mediaDataSource.segments[segmentIndex];\n\n let ioctl = this._ioctl = new IOController(dataSource, this._config, segmentIndex);\n ioctl.onError = this._onIOException.bind(this);\n ioctl.onSeeked = this._onIOSeeked.bind(this);\n ioctl.onComplete = this._onIOComplete.bind(this);\n ioctl.onRedirect = this._onIORedirect.bind(this);\n ioctl.onRecoveredEarlyEof = this._onIORecoveredEarlyEof.bind(this);\n\n if (optionalFrom) {\n this._demuxer.bindDataSource(this._ioctl);\n } else {\n ioctl.onDataArrival = this._onInitChunkArrival.bind(this);\n }\n\n ioctl.open(optionalFrom);\n }\n\n stop() {\n this._internalAbort();\n this._disableStatisticsReporter();\n }\n\n _internalAbort() {\n if (this._ioctl) {\n this._ioctl.destroy();\n this._ioctl = null;\n }\n }\n\n pause() { // take a rest\n if (this._ioctl && this._ioctl.isWorking()) {\n this._ioctl.pause();\n this._disableStatisticsReporter();\n }\n }\n\n resume() {\n if (this._ioctl && this._ioctl.isPaused()) {\n this._ioctl.resume();\n this._enableStatisticsReporter();\n }\n }\n\n seek(milliseconds) {\n if (this._mediaInfo == null || !this._mediaInfo.isSeekable()) {\n return;\n }\n\n let targetSegmentIndex = this._searchSegmentIndexContains(milliseconds);\n\n if (targetSegmentIndex === this._currentSegmentIndex) {\n // intra-segment seeking\n let segmentInfo = this._mediaInfo.segments[targetSegmentIndex];\n\n if (segmentInfo == undefined) {\n // current segment loading started, but mediainfo hasn't received yet\n // wait for the metadata loaded, then seek to expected position\n this._pendingSeekTime = milliseconds;\n } else {\n let keyframe = segmentInfo.getNearestKeyframe(milliseconds);\n this._remuxer.seek(keyframe.milliseconds);\n this._ioctl.seek(keyframe.fileposition);\n // Will be resolved in _onRemuxerMediaSegmentArrival()\n this._pendingResolveSeekPoint = keyframe.milliseconds;\n }\n } else {\n // cross-segment seeking\n let targetSegmentInfo = this._mediaInfo.segments[targetSegmentIndex];\n\n if (targetSegmentInfo == undefined) {\n // target segment hasn't been loaded. We need metadata then seek to expected time\n this._pendingSeekTime = milliseconds;\n this._internalAbort();\n this._remuxer.seek();\n this._remuxer.insertDiscontinuity();\n this._loadSegment(targetSegmentIndex);\n // Here we wait for the metadata loaded, then seek to expected position\n } else {\n // We have target segment's metadata, direct seek to target position\n let keyframe = targetSegmentInfo.getNearestKeyframe(milliseconds);\n this._internalAbort();\n this._remuxer.seek(milliseconds);\n this._remuxer.insertDiscontinuity();\n this._demuxer.resetMediaInfo();\n this._demuxer.timestampBase = this._mediaDataSource.segments[targetSegmentIndex].timestampBase;\n this._loadSegment(targetSegmentIndex, keyframe.fileposition);\n this._pendingResolveSeekPoint = keyframe.milliseconds;\n this._reportSegmentMediaInfo(targetSegmentIndex);\n }\n }\n\n this._enableStatisticsReporter();\n }\n\n _searchSegmentIndexContains(milliseconds) {\n let segments = this._mediaDataSource.segments;\n let idx = segments.length - 1;\n\n for (let i = 0; i < segments.length; i++) {\n if (milliseconds < segments[i].timestampBase) {\n idx = i - 1;\n break;\n }\n }\n return idx;\n }\n\n _onInitChunkArrival(data, byteStart) {\n let probeData = null;\n let consumed = 0;\n\n if (byteStart > 0) {\n // IOController seeked immediately after opened, byteStart > 0 callback may received\n this._demuxer.bindDataSource(this._ioctl);\n this._demuxer.timestampBase = this._mediaDataSource.segments[this._currentSegmentIndex].timestampBase;\n\n consumed = this._demuxer.parseChunks(data, byteStart);\n } else if ((probeData = FLVDemuxer.probe(data)).match) {\n // Always create new FLVDemuxer\n this._demuxer = new FLVDemuxer(probeData, this._config);\n\n if (!this._remuxer) {\n this._remuxer = new MP4Remuxer(this._config);\n }\n\n let mds = this._mediaDataSource;\n if (mds.duration != undefined && !isNaN(mds.duration)) {\n this._demuxer.overridedDuration = mds.duration;\n }\n if (typeof mds.hasAudio === 'boolean') {\n this._demuxer.overridedHasAudio = mds.hasAudio;\n }\n if (typeof mds.hasVideo === 'boolean') {\n this._demuxer.overridedHasVideo = mds.hasVideo;\n }\n\n this._demuxer.timestampBase = mds.segments[this._currentSegmentIndex].timestampBase;\n\n this._demuxer.onError = this._onDemuxException.bind(this);\n this._demuxer.onMediaInfo = this._onMediaInfo.bind(this);\n this._demuxer.onMetaDataArrived = this._onMetaDataArrived.bind(this);\n this._demuxer.onScriptDataArrived = this._onScriptDataArrived.bind(this);\n\n this._remuxer.bindDataSource(this._demuxer\n .bindDataSource(this._ioctl\n ));\n\n this._remuxer.onInitSegment = this._onRemuxerInitSegmentArrival.bind(this);\n this._remuxer.onMediaSegment = this._onRemuxerMediaSegmentArrival.bind(this);\n\n consumed = this._demuxer.parseChunks(data, byteStart);\n } else {\n probeData = null;\n Log.e(this.TAG, 'Non-FLV, Unsupported media type!');\n Promise.resolve().then(() => {\n this._internalAbort();\n });\n this._emitter.emit(TransmuxingEvents.DEMUX_ERROR, DemuxErrors.FORMAT_UNSUPPORTED, 'Non-FLV, Unsupported media type');\n\n consumed = 0;\n }\n\n return consumed;\n }\n\n _onMediaInfo(mediaInfo) {\n if (this._mediaInfo == null) {\n // Store first segment's mediainfo as global mediaInfo\n this._mediaInfo = Object.assign({}, mediaInfo);\n this._mediaInfo.keyframesIndex = null;\n this._mediaInfo.segments = [];\n this._mediaInfo.segmentCount = this._mediaDataSource.segments.length;\n Object.setPrototypeOf(this._mediaInfo, MediaInfo.prototype);\n }\n\n let segmentInfo = Object.assign({}, mediaInfo);\n Object.setPrototypeOf(segmentInfo, MediaInfo.prototype);\n this._mediaInfo.segments[this._currentSegmentIndex] = segmentInfo;\n\n // notify mediaInfo update\n this._reportSegmentMediaInfo(this._currentSegmentIndex);\n\n if (this._pendingSeekTime != null) {\n Promise.resolve().then(() => {\n let target = this._pendingSeekTime;\n this._pendingSeekTime = null;\n this.seek(target);\n });\n }\n }\n\n _onMetaDataArrived(metadata) {\n this._emitter.emit(TransmuxingEvents.METADATA_ARRIVED, metadata);\n }\n\n _onScriptDataArrived(data) {\n this._emitter.emit(TransmuxingEvents.SCRIPTDATA_ARRIVED, data);\n }\n\n _onIOSeeked() {\n this._remuxer.insertDiscontinuity();\n }\n\n _onIOComplete(extraData) {\n let segmentIndex = extraData;\n let nextSegmentIndex = segmentIndex + 1;\n\n if (nextSegmentIndex < this._mediaDataSource.segments.length) {\n this._internalAbort();\n this._remuxer.flushStashedSamples();\n this._loadSegment(nextSegmentIndex);\n } else {\n this._remuxer.flushStashedSamples();\n this._emitter.emit(TransmuxingEvents.LOADING_COMPLETE);\n this._disableStatisticsReporter();\n }\n }\n\n _onIORedirect(redirectedURL) {\n let segmentIndex = this._ioctl.extraData;\n this._mediaDataSource.segments[segmentIndex].redirectedURL = redirectedURL;\n }\n\n _onIORecoveredEarlyEof() {\n this._emitter.emit(TransmuxingEvents.RECOVERED_EARLY_EOF);\n }\n\n _onIOException(type, info) {\n Log.e(this.TAG, `IOException: type = ${type}, code = ${info.code}, msg = ${info.msg}`);\n this._emitter.emit(TransmuxingEvents.IO_ERROR, type, info);\n this._disableStatisticsReporter();\n }\n\n _onDemuxException(type, info) {\n Log.e(this.TAG, `DemuxException: type = ${type}, info = ${info}`);\n this._emitter.emit(TransmuxingEvents.DEMUX_ERROR, type, info);\n }\n\n _onRemuxerInitSegmentArrival(type, initSegment) {\n this._emitter.emit(TransmuxingEvents.INIT_SEGMENT, type, initSegment);\n }\n\n _onRemuxerMediaSegmentArrival(type, mediaSegment) {\n if (this._pendingSeekTime != null) {\n // Media segments after new-segment cross-seeking should be dropped.\n return;\n }\n this._emitter.emit(TransmuxingEvents.MEDIA_SEGMENT, type, mediaSegment);\n\n // Resolve pending seekPoint\n if (this._pendingResolveSeekPoint != null && type === 'video') {\n let syncPoints = mediaSegment.info.syncPoints;\n let seekpoint = this._pendingResolveSeekPoint;\n this._pendingResolveSeekPoint = null;\n\n // Safari: Pass PTS for recommend_seekpoint\n if (Browser.safari && syncPoints.length > 0 && syncPoints[0].originalDts === seekpoint) {\n seekpoint = syncPoints[0].pts;\n }\n // else: use original DTS (keyframe.milliseconds)\n\n this._emitter.emit(TransmuxingEvents.RECOMMEND_SEEKPOINT, seekpoint);\n }\n }\n\n _enableStatisticsReporter() {\n if (this._statisticsReporter == null) {\n this._statisticsReporter = self.setInterval(\n this._reportStatisticsInfo.bind(this),\n this._config.statisticsInfoReportInterval);\n }\n }\n\n _disableStatisticsReporter() {\n if (this._statisticsReporter) {\n self.clearInterval(this._statisticsReporter);\n this._statisticsReporter = null;\n }\n }\n\n _reportSegmentMediaInfo(segmentIndex) {\n let segmentInfo = this._mediaInfo.segments[segmentIndex];\n let exportInfo = Object.assign({}, segmentInfo);\n\n exportInfo.duration = this._mediaInfo.duration;\n exportInfo.segmentCount = this._mediaInfo.segmentCount;\n delete exportInfo.segments;\n delete exportInfo.keyframesIndex;\n\n this._emitter.emit(TransmuxingEvents.MEDIA_INFO, exportInfo);\n }\n\n _reportStatisticsInfo() {\n let info = {};\n\n info.url = this._ioctl.currentURL;\n info.hasRedirect = this._ioctl.hasRedirect;\n if (info.hasRedirect) {\n info.redirectedURL = this._ioctl.currentRedirectedURL;\n }\n\n info.speed = this._ioctl.currentSpeed;\n info.loaderType = this._ioctl.loaderType;\n info.currentSegmentIndex = this._currentSegmentIndex;\n info.totalSegmentCount = this._mediaDataSource.segments.length;\n\n this._emitter.emit(TransmuxingEvents.STATISTICS_INFO, info);\n }\n\n}\n\nexport default TransmuxingController;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst TransmuxingEvents = {\n IO_ERROR: 'io_error',\n DEMUX_ERROR: 'demux_error',\n INIT_SEGMENT: 'init_segment',\n MEDIA_SEGMENT: 'media_segment',\n LOADING_COMPLETE: 'loading_complete',\n RECOVERED_EARLY_EOF: 'recovered_early_eof',\n MEDIA_INFO: 'media_info',\n METADATA_ARRIVED: 'metadata_arrived',\n SCRIPTDATA_ARRIVED: 'scriptdata_arrived',\n STATISTICS_INFO: 'statistics_info',\n RECOMMEND_SEEKPOINT: 'recommend_seekpoint'\n};\n\nexport default TransmuxingEvents;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst DemuxErrors = {\n OK: 'OK',\n FORMAT_ERROR: 'FormatError',\n FORMAT_UNSUPPORTED: 'FormatUnsupported',\n CODEC_UNSUPPORTED: 'CodecUnsupported'\n};\n\nexport default DemuxErrors;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const defaultConfig = {\n enableWorker: false,\n enableStashBuffer: true,\n stashInitialSize: undefined,\n\n isLive: false,\n\n lazyLoad: true,\n lazyLoadMaxDuration: 3 * 60,\n lazyLoadRecoverDuration: 30,\n deferLoadAfterSourceOpen: true,\n\n // autoCleanupSourceBuffer: default as false, leave unspecified\n autoCleanupMaxBackwardDuration: 3 * 60,\n autoCleanupMinBackwardDuration: 2 * 60,\n\n statisticsInfoReportInterval: 600,\n\n fixAudioTimestampGap: true,\n\n accurateSeek: false,\n seekType: 'range', // [range, param, custom]\n seekParamStart: 'bstart',\n seekParamEnd: 'bend',\n rangeLoadZeroStart: false,\n customSeekHandler: undefined,\n reuseRedirectedURL: false,\n // referrerPolicy: leave as unspecified\n\n headers: undefined,\n customLoader: undefined\n};\n\nexport function createDefaultConfig() {\n return Object.assign({}, defaultConfig);\n}","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport IOController from '../io/io-controller.js';\nimport {createDefaultConfig} from '../config.js';\n\nclass Features {\n\n static supportMSEH264Playback() {\n return window.MediaSource &&\n window.MediaSource.isTypeSupported('video/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"');\n }\n\n static supportNetworkStreamIO() {\n let ioctl = new IOController({}, createDefaultConfig());\n let loaderType = ioctl.loaderType;\n ioctl.destroy();\n return loaderType == 'fetch-stream-loader' || loaderType == 'xhr-moz-chunked-loader';\n }\n\n static getNetworkLoaderTypeName() {\n let ioctl = new IOController({}, createDefaultConfig());\n let loaderType = ioctl.loaderType;\n ioctl.destroy();\n return loaderType;\n }\n\n static supportNativeMediaPlayback(mimeType) {\n if (Features.videoElement == undefined) {\n Features.videoElement = window.document.createElement('video');\n }\n let canPlay = Features.videoElement.canPlayType(mimeType);\n return canPlay === 'probably' || canPlay == 'maybe';\n }\n\n static getFeatureList() {\n let features = {\n mseFlvPlayback: false,\n mseLiveFlvPlayback: false,\n networkStreamIO: false,\n networkLoaderName: '',\n nativeMP4H264Playback: false,\n nativeWebmVP8Playback: false,\n nativeWebmVP9Playback: false\n };\n\n features.mseFlvPlayback = Features.supportMSEH264Playback();\n features.networkStreamIO = Features.supportNetworkStreamIO();\n features.networkLoaderName = Features.getNetworkLoaderTypeName();\n features.mseLiveFlvPlayback = features.mseFlvPlayback && features.networkStreamIO;\n features.nativeMP4H264Playback = Features.supportNativeMediaPlayback('video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"');\n features.nativeWebmVP8Playback = Features.supportNativeMediaPlayback('video/webm; codecs=\"vp8.0, vorbis\"');\n features.nativeWebmVP9Playback = Features.supportNativeMediaPlayback('video/webm; codecs=\"vp9\"');\n\n return features;\n }\n\n}\n\nexport default Features;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst PlayerEvents = {\n ERROR: 'error',\n LOADING_COMPLETE: 'loading_complete',\n RECOVERED_EARLY_EOF: 'recovered_early_eof',\n MEDIA_INFO: 'media_info',\n METADATA_ARRIVED: 'metadata_arrived',\n SCRIPTDATA_ARRIVED: 'scriptdata_arrived',\n STATISTICS_INFO: 'statistics_info'\n};\n\nexport default PlayerEvents;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport work from 'webworkify-webpack';\nimport Log from '../utils/logger.js';\nimport LoggingControl from '../utils/logging-control.js';\nimport TransmuxingController from './transmuxing-controller.js';\nimport TransmuxingEvents from './transmuxing-events.js';\nimport TransmuxingWorker from './transmuxing-worker.js';\nimport MediaInfo from './media-info.js';\n\nclass Transmuxer {\n\n constructor(mediaDataSource, config) {\n this.TAG = 'Transmuxer';\n this._emitter = new EventEmitter();\n\n if (config.enableWorker && typeof (Worker) !== 'undefined') {\n try {\n this._worker = work(require.resolve('./transmuxing-worker'));\n this._workerDestroying = false;\n this._worker.addEventListener('message', this._onWorkerMessage.bind(this));\n this._worker.postMessage({cmd: 'init', param: [mediaDataSource, config]});\n this.e = {\n onLoggingConfigChanged: this._onLoggingConfigChanged.bind(this)\n };\n LoggingControl.registerListener(this.e.onLoggingConfigChanged);\n this._worker.postMessage({cmd: 'logging_config', param: LoggingControl.getConfig()});\n } catch (error) {\n Log.e(this.TAG, 'Error while initialize transmuxing worker, fallback to inline transmuxing');\n this._worker = null;\n this._controller = new TransmuxingController(mediaDataSource, config);\n }\n } else {\n this._controller = new TransmuxingController(mediaDataSource, config);\n }\n\n if (this._controller) {\n let ctl = this._controller;\n ctl.on(TransmuxingEvents.IO_ERROR, this._onIOError.bind(this));\n ctl.on(TransmuxingEvents.DEMUX_ERROR, this._onDemuxError.bind(this));\n ctl.on(TransmuxingEvents.INIT_SEGMENT, this._onInitSegment.bind(this));\n ctl.on(TransmuxingEvents.MEDIA_SEGMENT, this._onMediaSegment.bind(this));\n ctl.on(TransmuxingEvents.LOADING_COMPLETE, this._onLoadingComplete.bind(this));\n ctl.on(TransmuxingEvents.RECOVERED_EARLY_EOF, this._onRecoveredEarlyEof.bind(this));\n ctl.on(TransmuxingEvents.MEDIA_INFO, this._onMediaInfo.bind(this));\n ctl.on(TransmuxingEvents.METADATA_ARRIVED, this._onMetaDataArrived.bind(this));\n ctl.on(TransmuxingEvents.SCRIPTDATA_ARRIVED, this._onScriptDataArrived.bind(this));\n ctl.on(TransmuxingEvents.STATISTICS_INFO, this._onStatisticsInfo.bind(this));\n ctl.on(TransmuxingEvents.RECOMMEND_SEEKPOINT, this._onRecommendSeekpoint.bind(this));\n }\n }\n\n destroy() {\n if (this._worker) {\n if (!this._workerDestroying) {\n this._workerDestroying = true;\n this._worker.postMessage({cmd: 'destroy'});\n LoggingControl.removeListener(this.e.onLoggingConfigChanged);\n this.e = null;\n }\n } else {\n this._controller.destroy();\n this._controller = null;\n }\n this._emitter.removeAllListeners();\n this._emitter = null;\n }\n\n on(event, listener) {\n this._emitter.addListener(event, listener);\n }\n\n off(event, listener) {\n this._emitter.removeListener(event, listener);\n }\n\n hasWorker() {\n return this._worker != null;\n }\n\n open() {\n if (this._worker) {\n this._worker.postMessage({cmd: 'start'});\n } else {\n this._controller.start();\n }\n }\n\n close() {\n if (this._worker) {\n this._worker.postMessage({cmd: 'stop'});\n } else {\n this._controller.stop();\n }\n }\n\n seek(milliseconds) {\n if (this._worker) {\n this._worker.postMessage({cmd: 'seek', param: milliseconds});\n } else {\n this._controller.seek(milliseconds);\n }\n }\n\n pause() {\n if (this._worker) {\n this._worker.postMessage({cmd: 'pause'});\n } else {\n this._controller.pause();\n }\n }\n\n resume() {\n if (this._worker) {\n this._worker.postMessage({cmd: 'resume'});\n } else {\n this._controller.resume();\n }\n }\n\n _onInitSegment(type, initSegment) {\n // do async invoke\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.INIT_SEGMENT, type, initSegment);\n });\n }\n\n _onMediaSegment(type, mediaSegment) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.MEDIA_SEGMENT, type, mediaSegment);\n });\n }\n\n _onLoadingComplete() {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.LOADING_COMPLETE);\n });\n }\n\n _onRecoveredEarlyEof() {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.RECOVERED_EARLY_EOF);\n });\n }\n\n _onMediaInfo(mediaInfo) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.MEDIA_INFO, mediaInfo);\n });\n }\n\n _onMetaDataArrived(metadata) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.METADATA_ARRIVED, metadata);\n });\n }\n\n _onScriptDataArrived(data) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.SCRIPTDATA_ARRIVED, data);\n });\n }\n\n _onStatisticsInfo(statisticsInfo) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.STATISTICS_INFO, statisticsInfo);\n });\n }\n\n _onIOError(type, info) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.IO_ERROR, type, info);\n });\n }\n\n _onDemuxError(type, info) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.DEMUX_ERROR, type, info);\n });\n }\n\n _onRecommendSeekpoint(milliseconds) {\n Promise.resolve().then(() => {\n this._emitter.emit(TransmuxingEvents.RECOMMEND_SEEKPOINT, milliseconds);\n });\n }\n\n _onLoggingConfigChanged(config) {\n if (this._worker) {\n this._worker.postMessage({cmd: 'logging_config', param: config});\n }\n }\n\n _onWorkerMessage(e) {\n let message = e.data;\n let data = message.data;\n\n if (message.msg === 'destroyed' || this._workerDestroying) {\n this._workerDestroying = false;\n this._worker.terminate();\n this._worker = null;\n return;\n }\n\n switch (message.msg) {\n case TransmuxingEvents.INIT_SEGMENT:\n case TransmuxingEvents.MEDIA_SEGMENT:\n this._emitter.emit(message.msg, data.type, data.data);\n break;\n case TransmuxingEvents.LOADING_COMPLETE:\n case TransmuxingEvents.RECOVERED_EARLY_EOF:\n this._emitter.emit(message.msg);\n break;\n case TransmuxingEvents.MEDIA_INFO:\n Object.setPrototypeOf(data, MediaInfo.prototype);\n this._emitter.emit(message.msg, data);\n break;\n case TransmuxingEvents.METADATA_ARRIVED:\n case TransmuxingEvents.SCRIPTDATA_ARRIVED:\n case TransmuxingEvents.STATISTICS_INFO:\n this._emitter.emit(message.msg, data);\n break;\n case TransmuxingEvents.IO_ERROR:\n case TransmuxingEvents.DEMUX_ERROR:\n this._emitter.emit(message.msg, data.type, data.info);\n break;\n case TransmuxingEvents.RECOMMEND_SEEKPOINT:\n this._emitter.emit(message.msg, data);\n break;\n case 'logcat_callback':\n Log.emitter.emit('log', data.type, data.logcat);\n break;\n default:\n break;\n }\n }\n\n}\n\nexport default Transmuxer;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst MSEEvents = {\n ERROR: 'error',\n SOURCE_OPEN: 'source_open',\n UPDATE_END: 'update_end',\n BUFFER_FULL: 'buffer_full'\n};\n\nexport default MSEEvents;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport Log from '../utils/logger.js';\nimport Browser from '../utils/browser.js';\nimport MSEEvents from './mse-events.js';\nimport {SampleInfo, IDRSampleList} from './media-segment-info.js';\nimport {IllegalStateException} from '../utils/exception.js';\n\n// Media Source Extensions controller\nclass MSEController {\n\n constructor(config) {\n this.TAG = 'MSEController';\n\n this._config = config;\n this._emitter = new EventEmitter();\n\n if (this._config.isLive && this._config.autoCleanupSourceBuffer == undefined) {\n // For live stream, do auto cleanup by default\n this._config.autoCleanupSourceBuffer = true;\n }\n\n this.e = {\n onSourceOpen: this._onSourceOpen.bind(this),\n onSourceEnded: this._onSourceEnded.bind(this),\n onSourceClose: this._onSourceClose.bind(this),\n onSourceBufferError: this._onSourceBufferError.bind(this),\n onSourceBufferUpdateEnd: this._onSourceBufferUpdateEnd.bind(this)\n };\n\n this._mediaSource = null;\n this._mediaSourceObjectURL = null;\n this._mediaElement = null;\n\n this._isBufferFull = false;\n this._hasPendingEos = false;\n\n this._requireSetMediaDuration = false;\n this._pendingMediaDuration = 0;\n\n this._pendingSourceBufferInit = [];\n this._mimeTypes = {\n video: null,\n audio: null\n };\n this._sourceBuffers = {\n video: null,\n audio: null\n };\n this._lastInitSegments = {\n video: null,\n audio: null\n };\n this._pendingSegments = {\n video: [],\n audio: []\n };\n this._pendingRemoveRanges = {\n video: [],\n audio: []\n };\n this._idrList = new IDRSampleList();\n }\n\n destroy() {\n if (this._mediaElement || this._mediaSource) {\n this.detachMediaElement();\n }\n this.e = null;\n this._emitter.removeAllListeners();\n this._emitter = null;\n }\n\n on(event, listener) {\n this._emitter.addListener(event, listener);\n }\n\n off(event, listener) {\n this._emitter.removeListener(event, listener);\n }\n\n attachMediaElement(mediaElement) {\n if (this._mediaSource) {\n throw new IllegalStateException('MediaSource has been attached to an HTMLMediaElement!');\n }\n let ms = this._mediaSource = new window.MediaSource();\n ms.addEventListener('sourceopen', this.e.onSourceOpen);\n ms.addEventListener('sourceended', this.e.onSourceEnded);\n ms.addEventListener('sourceclose', this.e.onSourceClose);\n\n this._mediaElement = mediaElement;\n this._mediaSourceObjectURL = window.URL.createObjectURL(this._mediaSource);\n mediaElement.src = this._mediaSourceObjectURL;\n }\n\n detachMediaElement() {\n if (this._mediaSource) {\n let ms = this._mediaSource;\n for (let type in this._sourceBuffers) {\n // pending segments should be discard\n let ps = this._pendingSegments[type];\n ps.splice(0, ps.length);\n this._pendingSegments[type] = null;\n this._pendingRemoveRanges[type] = null;\n this._lastInitSegments[type] = null;\n\n // remove all sourcebuffers\n let sb = this._sourceBuffers[type];\n if (sb) {\n if (ms.readyState !== 'closed') {\n // ms edge can throw an error: Unexpected call to method or property access\n try {\n ms.removeSourceBuffer(sb);\n } catch (error) {\n Log.e(this.TAG, error.message);\n }\n sb.removeEventListener('error', this.e.onSourceBufferError);\n sb.removeEventListener('updateend', this.e.onSourceBufferUpdateEnd);\n }\n this._mimeTypes[type] = null;\n this._sourceBuffers[type] = null;\n }\n }\n if (ms.readyState === 'open') {\n try {\n ms.endOfStream();\n } catch (error) {\n Log.e(this.TAG, error.message);\n }\n }\n ms.removeEventListener('sourceopen', this.e.onSourceOpen);\n ms.removeEventListener('sourceended', this.e.onSourceEnded);\n ms.removeEventListener('sourceclose', this.e.onSourceClose);\n this._pendingSourceBufferInit = [];\n this._isBufferFull = false;\n this._idrList.clear();\n this._mediaSource = null;\n }\n\n if (this._mediaElement) {\n this._mediaElement.src = '';\n this._mediaElement.removeAttribute('src');\n this._mediaElement = null;\n }\n if (this._mediaSourceObjectURL) {\n window.URL.revokeObjectURL(this._mediaSourceObjectURL);\n this._mediaSourceObjectURL = null;\n }\n }\n\n appendInitSegment(initSegment, deferred) {\n if (!this._mediaSource || this._mediaSource.readyState !== 'open') {\n // sourcebuffer creation requires mediaSource.readyState === 'open'\n // so we defer the sourcebuffer creation, until sourceopen event triggered\n this._pendingSourceBufferInit.push(initSegment);\n // make sure that this InitSegment is in the front of pending segments queue\n this._pendingSegments[initSegment.type].push(initSegment);\n return;\n }\n\n let is = initSegment;\n let mimeType = `${is.container}`;\n if (is.codec && is.codec.length > 0) {\n mimeType += `;codecs=${is.codec}`;\n }\n\n let firstInitSegment = false;\n\n Log.v(this.TAG, 'Received Initialization Segment, mimeType: ' + mimeType);\n this._lastInitSegments[is.type] = is;\n\n if (mimeType !== this._mimeTypes[is.type]) {\n if (!this._mimeTypes[is.type]) { // empty, first chance create sourcebuffer\n firstInitSegment = true;\n try {\n let sb = this._sourceBuffers[is.type] = this._mediaSource.addSourceBuffer(mimeType);\n sb.addEventListener('error', this.e.onSourceBufferError);\n sb.addEventListener('updateend', this.e.onSourceBufferUpdateEnd);\n } catch (error) {\n Log.e(this.TAG, error.message);\n this._emitter.emit(MSEEvents.ERROR, {code: error.code, msg: error.message});\n return;\n }\n } else {\n Log.v(this.TAG, `Notice: ${is.type} mimeType changed, origin: ${this._mimeTypes[is.type]}, target: ${mimeType}`);\n }\n this._mimeTypes[is.type] = mimeType;\n }\n\n if (!deferred) {\n // deferred means this InitSegment has been pushed to pendingSegments queue\n this._pendingSegments[is.type].push(is);\n }\n if (!firstInitSegment) { // append immediately only if init segment in subsequence\n if (this._sourceBuffers[is.type] && !this._sourceBuffers[is.type].updating) {\n this._doAppendSegments();\n }\n }\n if (Browser.safari && is.container === 'audio/mpeg' && is.mediaDuration > 0) {\n // 'audio/mpeg' track under Safari may cause MediaElement's duration to be NaN\n // Manually correct MediaSource.duration to make progress bar seekable, and report right duration\n this._requireSetMediaDuration = true;\n this._pendingMediaDuration = is.mediaDuration / 1000; // in seconds\n this._updateMediaSourceDuration();\n }\n }\n\n appendMediaSegment(mediaSegment) {\n let ms = mediaSegment;\n this._pendingSegments[ms.type].push(ms);\n\n if (this._config.autoCleanupSourceBuffer && this._needCleanupSourceBuffer()) {\n this._doCleanupSourceBuffer();\n }\n\n let sb = this._sourceBuffers[ms.type];\n if (sb && !sb.updating && !this._hasPendingRemoveRanges()) {\n this._doAppendSegments();\n }\n }\n\n seek(seconds) {\n // remove all appended buffers\n for (let type in this._sourceBuffers) {\n if (!this._sourceBuffers[type]) {\n continue;\n }\n\n // abort current buffer append algorithm\n let sb = this._sourceBuffers[type];\n if (this._mediaSource.readyState === 'open') {\n try {\n // If range removal algorithm is running, InvalidStateError will be throwed\n // Ignore it.\n sb.abort();\n } catch (error) {\n Log.e(this.TAG, error.message);\n }\n }\n\n // IDRList should be clear\n this._idrList.clear();\n\n // pending segments should be discard\n let ps = this._pendingSegments[type];\n ps.splice(0, ps.length);\n\n if (this._mediaSource.readyState === 'closed') {\n // Parent MediaSource object has been detached from HTMLMediaElement\n continue;\n }\n\n // record ranges to be remove from SourceBuffer\n for (let i = 0; i < sb.buffered.length; i++) {\n let start = sb.buffered.start(i);\n let end = sb.buffered.end(i);\n this._pendingRemoveRanges[type].push({start, end});\n }\n\n // if sb is not updating, let's remove ranges now!\n if (!sb.updating) {\n this._doRemoveRanges();\n }\n\n // Safari 10 may get InvalidStateError in the later appendBuffer() after SourceBuffer.remove() call\n // Internal parser's state may be invalid at this time. Re-append last InitSegment to workaround.\n // Related issue: https://bugs.webkit.org/show_bug.cgi?id=159230\n if (Browser.safari) {\n let lastInitSegment = this._lastInitSegments[type];\n if (lastInitSegment) {\n this._pendingSegments[type].push(lastInitSegment);\n if (!sb.updating) {\n this._doAppendSegments();\n }\n }\n }\n }\n }\n\n endOfStream() {\n let ms = this._mediaSource;\n let sb = this._sourceBuffers;\n if (!ms || ms.readyState !== 'open') {\n if (ms && ms.readyState === 'closed' && this._hasPendingSegments()) {\n // If MediaSource hasn't turned into open state, and there're pending segments\n // Mark pending endOfStream, defer call until all pending segments appended complete\n this._hasPendingEos = true;\n }\n return;\n }\n if (sb.video && sb.video.updating || sb.audio && sb.audio.updating) {\n // If any sourcebuffer is updating, defer endOfStream operation\n // See _onSourceBufferUpdateEnd()\n this._hasPendingEos = true;\n } else {\n this._hasPendingEos = false;\n // Notify media data loading complete\n // This is helpful for correcting total duration to match last media segment\n // Otherwise MediaElement's ended event may not be triggered\n ms.endOfStream();\n }\n }\n\n getNearestKeyframe(dts) {\n return this._idrList.getLastSyncPointBeforeDts(dts);\n }\n\n _needCleanupSourceBuffer() {\n if (!this._config.autoCleanupSourceBuffer) {\n return false;\n }\n\n let currentTime = this._mediaElement.currentTime;\n\n for (let type in this._sourceBuffers) {\n let sb = this._sourceBuffers[type];\n if (sb) {\n let buffered = sb.buffered;\n if (buffered.length >= 1) {\n if (currentTime - buffered.start(0) >= this._config.autoCleanupMaxBackwardDuration) {\n return true;\n }\n }\n }\n }\n\n return false;\n }\n\n _doCleanupSourceBuffer() {\n let currentTime = this._mediaElement.currentTime;\n\n for (let type in this._sourceBuffers) {\n let sb = this._sourceBuffers[type];\n if (sb) {\n let buffered = sb.buffered;\n let doRemove = false;\n\n for (let i = 0; i < buffered.length; i++) {\n let start = buffered.start(i);\n let end = buffered.end(i);\n\n if (start <= currentTime && currentTime < end + 3) { // padding 3 seconds\n if (currentTime - start >= this._config.autoCleanupMaxBackwardDuration) {\n doRemove = true;\n let removeEnd = currentTime - this._config.autoCleanupMinBackwardDuration;\n this._pendingRemoveRanges[type].push({start: start, end: removeEnd});\n }\n } else if (end < currentTime) {\n doRemove = true;\n this._pendingRemoveRanges[type].push({start: start, end: end});\n }\n }\n\n if (doRemove && !sb.updating) {\n this._doRemoveRanges();\n }\n }\n }\n }\n\n _updateMediaSourceDuration() {\n let sb = this._sourceBuffers;\n if (this._mediaElement.readyState === 0 || this._mediaSource.readyState !== 'open') {\n return;\n }\n if ((sb.video && sb.video.updating) || (sb.audio && sb.audio.updating)) {\n return;\n }\n\n let current = this._mediaSource.duration;\n let target = this._pendingMediaDuration;\n\n if (target > 0 && (isNaN(current) || target > current)) {\n Log.v(this.TAG, `Update MediaSource duration from ${current} to ${target}`);\n this._mediaSource.duration = target;\n }\n\n this._requireSetMediaDuration = false;\n this._pendingMediaDuration = 0;\n }\n\n _doRemoveRanges() {\n for (let type in this._pendingRemoveRanges) {\n if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating) {\n continue;\n }\n let sb = this._sourceBuffers[type];\n let ranges = this._pendingRemoveRanges[type];\n while (ranges.length && !sb.updating) {\n let range = ranges.shift();\n sb.remove(range.start, range.end);\n }\n }\n }\n\n _doAppendSegments() {\n let pendingSegments = this._pendingSegments;\n\n for (let type in pendingSegments) {\n if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating) {\n continue;\n }\n\n if (pendingSegments[type].length > 0) {\n let segment = pendingSegments[type].shift();\n\n if (segment.timestampOffset) {\n // For MPEG audio stream in MSE, if unbuffered-seeking occurred\n // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer.\n let currentOffset = this._sourceBuffers[type].timestampOffset;\n let targetOffset = segment.timestampOffset / 1000; // in seconds\n\n let delta = Math.abs(currentOffset - targetOffset);\n if (delta > 0.1) { // If time delta > 100ms\n Log.v(this.TAG, `Update MPEG audio timestampOffset from ${currentOffset} to ${targetOffset}`);\n this._sourceBuffers[type].timestampOffset = targetOffset;\n }\n delete segment.timestampOffset;\n }\n\n if (!segment.data || segment.data.byteLength === 0) {\n // Ignore empty buffer\n continue;\n }\n\n try {\n this._sourceBuffers[type].appendBuffer(segment.data);\n this._isBufferFull = false;\n if (type === 'video' && segment.hasOwnProperty('info')) {\n this._idrList.appendArray(segment.info.syncPoints);\n }\n } catch (error) {\n this._pendingSegments[type].unshift(segment);\n if (error.code === 22) { // QuotaExceededError\n /* Notice that FireFox may not throw QuotaExceededError if SourceBuffer is full\n * Currently we can only do lazy-load to avoid SourceBuffer become scattered.\n * SourceBuffer eviction policy may be changed in future version of FireFox.\n *\n * Related issues:\n * https://bugzilla.mozilla.org/show_bug.cgi?id=1279885\n * https://bugzilla.mozilla.org/show_bug.cgi?id=1280023\n */\n\n // report buffer full, abort network IO\n if (!this._isBufferFull) {\n this._emitter.emit(MSEEvents.BUFFER_FULL);\n }\n this._isBufferFull = true;\n } else {\n Log.e(this.TAG, error.message);\n this._emitter.emit(MSEEvents.ERROR, {code: error.code, msg: error.message});\n }\n }\n }\n }\n }\n\n _onSourceOpen() {\n Log.v(this.TAG, 'MediaSource onSourceOpen');\n this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen);\n // deferred sourcebuffer creation / initialization\n if (this._pendingSourceBufferInit.length > 0) {\n let pendings = this._pendingSourceBufferInit;\n while (pendings.length) {\n let segment = pendings.shift();\n this.appendInitSegment(segment, true);\n }\n }\n // there may be some pending media segments, append them\n if (this._hasPendingSegments()) {\n this._doAppendSegments();\n }\n this._emitter.emit(MSEEvents.SOURCE_OPEN);\n }\n\n _onSourceEnded() {\n // fired on endOfStream\n Log.v(this.TAG, 'MediaSource onSourceEnded');\n }\n\n _onSourceClose() {\n // fired on detaching from media element\n Log.v(this.TAG, 'MediaSource onSourceClose');\n if (this._mediaSource && this.e != null) {\n this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen);\n this._mediaSource.removeEventListener('sourceended', this.e.onSourceEnded);\n this._mediaSource.removeEventListener('sourceclose', this.e.onSourceClose);\n }\n }\n\n _hasPendingSegments() {\n let ps = this._pendingSegments;\n return ps.video.length > 0 || ps.audio.length > 0;\n }\n\n _hasPendingRemoveRanges() {\n let prr = this._pendingRemoveRanges;\n return prr.video.length > 0 || prr.audio.length > 0;\n }\n\n _onSourceBufferUpdateEnd() {\n if (this._requireSetMediaDuration) {\n this._updateMediaSourceDuration();\n } else if (this._hasPendingRemoveRanges()) {\n this._doRemoveRanges();\n } else if (this._hasPendingSegments()) {\n this._doAppendSegments();\n } else if (this._hasPendingEos) {\n this.endOfStream();\n }\n this._emitter.emit(MSEEvents.UPDATE_END);\n }\n\n _onSourceBufferError(e) {\n Log.e(this.TAG, `SourceBuffer Error: ${e}`);\n // this error might not always be fatal, just ignore it\n }\n\n}\n\nexport default MSEController;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {LoaderErrors} from '../io/loader.js';\nimport DemuxErrors from '../demux/demux-errors.js';\n\nexport const ErrorTypes = {\n NETWORK_ERROR: 'NetworkError',\n MEDIA_ERROR: 'MediaError',\n OTHER_ERROR: 'OtherError'\n};\n\nexport const ErrorDetails = {\n NETWORK_EXCEPTION: LoaderErrors.EXCEPTION,\n NETWORK_STATUS_CODE_INVALID: LoaderErrors.HTTP_STATUS_CODE_INVALID,\n NETWORK_TIMEOUT: LoaderErrors.CONNECTING_TIMEOUT,\n NETWORK_UNRECOVERABLE_EARLY_EOF: LoaderErrors.UNRECOVERABLE_EARLY_EOF,\n\n MEDIA_MSE_ERROR: 'MediaMSEError',\n\n MEDIA_FORMAT_ERROR: DemuxErrors.FORMAT_ERROR,\n MEDIA_FORMAT_UNSUPPORTED: DemuxErrors.FORMAT_UNSUPPORTED,\n MEDIA_CODEC_UNSUPPORTED: DemuxErrors.CODEC_UNSUPPORTED\n};","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport Log from '../utils/logger.js';\nimport Browser from '../utils/browser.js';\nimport PlayerEvents from './player-events.js';\nimport Transmuxer from '../core/transmuxer.js';\nimport TransmuxingEvents from '../core/transmuxing-events.js';\nimport MSEController from '../core/mse-controller.js';\nimport MSEEvents from '../core/mse-events.js';\nimport {ErrorTypes, ErrorDetails} from './player-errors.js';\nimport {createDefaultConfig} from '../config.js';\nimport {InvalidArgumentException, IllegalStateException} from '../utils/exception.js';\n\nclass FlvPlayer {\n\n constructor(mediaDataSource, config) {\n this.TAG = 'FlvPlayer';\n this._type = 'FlvPlayer';\n this._emitter = new EventEmitter();\n\n this._config = createDefaultConfig();\n if (typeof config === 'object') {\n Object.assign(this._config, config);\n }\n\n if (mediaDataSource.type.toLowerCase() !== 'flv') {\n throw new InvalidArgumentException('FlvPlayer requires an flv MediaDataSource input!');\n }\n\n if (mediaDataSource.isLive === true) {\n this._config.isLive = true;\n }\n\n this.e = {\n onvLoadedMetadata: this._onvLoadedMetadata.bind(this),\n onvSeeking: this._onvSeeking.bind(this),\n onvCanPlay: this._onvCanPlay.bind(this),\n onvStalled: this._onvStalled.bind(this),\n onvProgress: this._onvProgress.bind(this)\n };\n\n if (self.performance && self.performance.now) {\n this._now = self.performance.now.bind(self.performance);\n } else {\n this._now = Date.now;\n }\n\n this._pendingSeekTime = null; // in seconds\n this._requestSetTime = false;\n this._seekpointRecord = null;\n this._progressChecker = null;\n\n this._mediaDataSource = mediaDataSource;\n this._mediaElement = null;\n this._msectl = null;\n this._transmuxer = null;\n\n this._mseSourceOpened = false;\n this._hasPendingLoad = false;\n this._receivedCanPlay = false;\n\n this._mediaInfo = null;\n this._statisticsInfo = null;\n\n let chromeNeedIDRFix = (Browser.chrome &&\n (Browser.version.major < 50 ||\n (Browser.version.major === 50 && Browser.version.build < 2661)));\n this._alwaysSeekKeyframe = (chromeNeedIDRFix || Browser.msedge || Browser.msie) ? true : false;\n\n if (this._alwaysSeekKeyframe) {\n this._config.accurateSeek = false;\n }\n }\n\n destroy() {\n if (this._progressChecker != null) {\n window.clearInterval(this._progressChecker);\n this._progressChecker = null;\n }\n if (this._transmuxer) {\n this.unload();\n }\n if (this._mediaElement) {\n this.detachMediaElement();\n }\n this.e = null;\n this._mediaDataSource = null;\n\n this._emitter.removeAllListeners();\n this._emitter = null;\n }\n\n on(event, listener) {\n if (event === PlayerEvents.MEDIA_INFO) {\n if (this._mediaInfo != null) {\n Promise.resolve().then(() => {\n this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo);\n });\n }\n } else if (event === PlayerEvents.STATISTICS_INFO) {\n if (this._statisticsInfo != null) {\n Promise.resolve().then(() => {\n this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo);\n });\n }\n }\n this._emitter.addListener(event, listener);\n }\n\n off(event, listener) {\n this._emitter.removeListener(event, listener);\n }\n\n attachMediaElement(mediaElement) {\n this._mediaElement = mediaElement;\n mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata);\n mediaElement.addEventListener('seeking', this.e.onvSeeking);\n mediaElement.addEventListener('canplay', this.e.onvCanPlay);\n mediaElement.addEventListener('stalled', this.e.onvStalled);\n mediaElement.addEventListener('progress', this.e.onvProgress);\n\n this._msectl = new MSEController(this._config);\n\n this._msectl.on(MSEEvents.UPDATE_END, this._onmseUpdateEnd.bind(this));\n this._msectl.on(MSEEvents.BUFFER_FULL, this._onmseBufferFull.bind(this));\n this._msectl.on(MSEEvents.SOURCE_OPEN, () => {\n this._mseSourceOpened = true;\n if (this._hasPendingLoad) {\n this._hasPendingLoad = false;\n this.load();\n }\n });\n this._msectl.on(MSEEvents.ERROR, (info) => {\n this._emitter.emit(PlayerEvents.ERROR,\n ErrorTypes.MEDIA_ERROR,\n ErrorDetails.MEDIA_MSE_ERROR,\n info\n );\n });\n\n this._msectl.attachMediaElement(mediaElement);\n\n if (this._pendingSeekTime != null) {\n try {\n mediaElement.currentTime = this._pendingSeekTime;\n this._pendingSeekTime = null;\n } catch (e) {\n // IE11 may throw InvalidStateError if readyState === 0\n // We can defer set currentTime operation after loadedmetadata\n }\n }\n }\n\n detachMediaElement() {\n if (this._mediaElement) {\n this._msectl.detachMediaElement();\n this._mediaElement.removeEventListener('loadedmetadata', this.e.onvLoadedMetadata);\n this._mediaElement.removeEventListener('seeking', this.e.onvSeeking);\n this._mediaElement.removeEventListener('canplay', this.e.onvCanPlay);\n this._mediaElement.removeEventListener('stalled', this.e.onvStalled);\n this._mediaElement.removeEventListener('progress', this.e.onvProgress);\n this._mediaElement = null;\n }\n if (this._msectl) {\n this._msectl.destroy();\n this._msectl = null;\n }\n }\n\n load() {\n if (!this._mediaElement) {\n throw new IllegalStateException('HTMLMediaElement must be attached before load()!');\n }\n if (this._transmuxer) {\n throw new IllegalStateException('FlvPlayer.load() has been called, please call unload() first!');\n }\n if (this._hasPendingLoad) {\n return;\n }\n\n if (this._config.deferLoadAfterSourceOpen && this._mseSourceOpened === false) {\n this._hasPendingLoad = true;\n return;\n }\n\n if (this._mediaElement.readyState > 0) {\n this._requestSetTime = true;\n // IE11 may throw InvalidStateError if readyState === 0\n this._mediaElement.currentTime = 0;\n }\n\n this._transmuxer = new Transmuxer(this._mediaDataSource, this._config);\n\n this._transmuxer.on(TransmuxingEvents.INIT_SEGMENT, (type, is) => {\n this._msectl.appendInitSegment(is);\n });\n this._transmuxer.on(TransmuxingEvents.MEDIA_SEGMENT, (type, ms) => {\n this._msectl.appendMediaSegment(ms);\n\n // lazyLoad check\n if (this._config.lazyLoad && !this._config.isLive) {\n let currentTime = this._mediaElement.currentTime;\n if (ms.info.endDts >= (currentTime + this._config.lazyLoadMaxDuration) * 1000) {\n if (this._progressChecker == null) {\n Log.v(this.TAG, 'Maximum buffering duration exceeded, suspend transmuxing task');\n this._suspendTransmuxer();\n }\n }\n }\n });\n this._transmuxer.on(TransmuxingEvents.LOADING_COMPLETE, () => {\n this._msectl.endOfStream();\n this._emitter.emit(PlayerEvents.LOADING_COMPLETE);\n });\n this._transmuxer.on(TransmuxingEvents.RECOVERED_EARLY_EOF, () => {\n this._emitter.emit(PlayerEvents.RECOVERED_EARLY_EOF);\n });\n this._transmuxer.on(TransmuxingEvents.IO_ERROR, (detail, info) => {\n this._emitter.emit(PlayerEvents.ERROR, ErrorTypes.NETWORK_ERROR, detail, info);\n });\n this._transmuxer.on(TransmuxingEvents.DEMUX_ERROR, (detail, info) => {\n this._emitter.emit(PlayerEvents.ERROR, ErrorTypes.MEDIA_ERROR, detail, {code: -1, msg: info});\n });\n this._transmuxer.on(TransmuxingEvents.MEDIA_INFO, (mediaInfo) => {\n this._mediaInfo = mediaInfo;\n this._emitter.emit(PlayerEvents.MEDIA_INFO, Object.assign({}, mediaInfo));\n });\n this._transmuxer.on(TransmuxingEvents.METADATA_ARRIVED, (metadata) => {\n this._emitter.emit(PlayerEvents.METADATA_ARRIVED, metadata);\n });\n this._transmuxer.on(TransmuxingEvents.SCRIPTDATA_ARRIVED, (data) => {\n this._emitter.emit(PlayerEvents.SCRIPTDATA_ARRIVED, data);\n });\n this._transmuxer.on(TransmuxingEvents.STATISTICS_INFO, (statInfo) => {\n this._statisticsInfo = this._fillStatisticsInfo(statInfo);\n this._emitter.emit(PlayerEvents.STATISTICS_INFO, Object.assign({}, this._statisticsInfo));\n });\n this._transmuxer.on(TransmuxingEvents.RECOMMEND_SEEKPOINT, (milliseconds) => {\n if (this._mediaElement && !this._config.accurateSeek) {\n this._requestSetTime = true;\n this._mediaElement.currentTime = milliseconds / 1000;\n }\n });\n\n this._transmuxer.open();\n }\n\n unload() {\n if (this._mediaElement) {\n this._mediaElement.pause();\n }\n if (this._msectl) {\n this._msectl.seek(0);\n }\n if (this._transmuxer) {\n this._transmuxer.close();\n this._transmuxer.destroy();\n this._transmuxer = null;\n }\n }\n\n play() {\n return this._mediaElement.play();\n }\n\n pause() {\n this._mediaElement.pause();\n }\n\n get type() {\n return this._type;\n }\n\n get buffered() {\n return this._mediaElement.buffered;\n }\n\n get duration() {\n return this._mediaElement.duration;\n }\n\n get volume() {\n return this._mediaElement.volume;\n }\n\n set volume(value) {\n this._mediaElement.volume = value;\n }\n\n get muted() {\n return this._mediaElement.muted;\n }\n\n set muted(muted) {\n this._mediaElement.muted = muted;\n }\n\n get currentTime() {\n if (this._mediaElement) {\n return this._mediaElement.currentTime;\n }\n return 0;\n }\n\n set currentTime(seconds) {\n if (this._mediaElement) {\n this._internalSeek(seconds);\n } else {\n this._pendingSeekTime = seconds;\n }\n }\n\n get mediaInfo() {\n return Object.assign({}, this._mediaInfo);\n }\n\n get statisticsInfo() {\n if (this._statisticsInfo == null) {\n this._statisticsInfo = {};\n }\n this._statisticsInfo = this._fillStatisticsInfo(this._statisticsInfo);\n return Object.assign({}, this._statisticsInfo);\n }\n\n _fillStatisticsInfo(statInfo) {\n statInfo.playerType = this._type;\n\n if (!(this._mediaElement instanceof HTMLVideoElement)) {\n return statInfo;\n }\n\n let hasQualityInfo = true;\n let decoded = 0;\n let dropped = 0;\n\n if (this._mediaElement.getVideoPlaybackQuality) {\n let quality = this._mediaElement.getVideoPlaybackQuality();\n decoded = quality.totalVideoFrames;\n dropped = quality.droppedVideoFrames;\n } else if (this._mediaElement.webkitDecodedFrameCount != undefined) {\n decoded = this._mediaElement.webkitDecodedFrameCount;\n dropped = this._mediaElement.webkitDroppedFrameCount;\n } else {\n hasQualityInfo = false;\n }\n\n if (hasQualityInfo) {\n statInfo.decodedFrames = decoded;\n statInfo.droppedFrames = dropped;\n }\n\n return statInfo;\n }\n\n _onmseUpdateEnd() {\n if (!this._config.lazyLoad || this._config.isLive) {\n return;\n }\n\n let buffered = this._mediaElement.buffered;\n let currentTime = this._mediaElement.currentTime;\n let currentRangeStart = 0;\n let currentRangeEnd = 0;\n\n for (let i = 0; i < buffered.length; i++) {\n let start = buffered.start(i);\n let end = buffered.end(i);\n if (start <= currentTime && currentTime < end) {\n currentRangeStart = start;\n currentRangeEnd = end;\n break;\n }\n }\n\n if (currentRangeEnd >= currentTime + this._config.lazyLoadMaxDuration && this._progressChecker == null) {\n Log.v(this.TAG, 'Maximum buffering duration exceeded, suspend transmuxing task');\n this._suspendTransmuxer();\n }\n }\n\n _onmseBufferFull() {\n Log.v(this.TAG, 'MSE SourceBuffer is full, suspend transmuxing task');\n if (this._progressChecker == null) {\n this._suspendTransmuxer();\n }\n }\n\n _suspendTransmuxer() {\n if (this._transmuxer) {\n this._transmuxer.pause();\n\n if (this._progressChecker == null) {\n this._progressChecker = window.setInterval(this._checkProgressAndResume.bind(this), 1000);\n }\n }\n }\n\n _checkProgressAndResume() {\n let currentTime = this._mediaElement.currentTime;\n let buffered = this._mediaElement.buffered;\n\n let needResume = false;\n\n for (let i = 0; i < buffered.length; i++) {\n let from = buffered.start(i);\n let to = buffered.end(i);\n if (currentTime >= from && currentTime < to) {\n if (currentTime >= to - this._config.lazyLoadRecoverDuration) {\n needResume = true;\n }\n break;\n }\n }\n\n if (needResume) {\n window.clearInterval(this._progressChecker);\n this._progressChecker = null;\n if (needResume) {\n Log.v(this.TAG, 'Continue loading from paused position');\n this._transmuxer.resume();\n }\n }\n }\n\n _isTimepointBuffered(seconds) {\n let buffered = this._mediaElement.buffered;\n\n for (let i = 0; i < buffered.length; i++) {\n let from = buffered.start(i);\n let to = buffered.end(i);\n if (seconds >= from && seconds < to) {\n return true;\n }\n }\n return false;\n }\n\n _internalSeek(seconds) {\n let directSeek = this._isTimepointBuffered(seconds);\n\n let directSeekBegin = false;\n let directSeekBeginTime = 0;\n\n if (seconds < 1.0 && this._mediaElement.buffered.length > 0) {\n let videoBeginTime = this._mediaElement.buffered.start(0);\n if ((videoBeginTime < 1.0 && seconds < videoBeginTime) || Browser.safari) {\n directSeekBegin = true;\n // also workaround for Safari: Seek to 0 may cause video stuck, use 0.1 to avoid\n directSeekBeginTime = Browser.safari ? 0.1 : videoBeginTime;\n }\n }\n\n if (directSeekBegin) { // seek to video begin, set currentTime directly if beginPTS buffered\n this._requestSetTime = true;\n this._mediaElement.currentTime = directSeekBeginTime;\n } else if (directSeek) { // buffered position\n if (!this._alwaysSeekKeyframe) {\n this._requestSetTime = true;\n this._mediaElement.currentTime = seconds;\n } else {\n let idr = this._msectl.getNearestKeyframe(Math.floor(seconds * 1000));\n this._requestSetTime = true;\n if (idr != null) {\n this._mediaElement.currentTime = idr.dts / 1000;\n } else {\n this._mediaElement.currentTime = seconds;\n }\n }\n if (this._progressChecker != null) {\n this._checkProgressAndResume();\n }\n } else {\n if (this._progressChecker != null) {\n window.clearInterval(this._progressChecker);\n this._progressChecker = null;\n }\n this._msectl.seek(seconds);\n this._transmuxer.seek(Math.floor(seconds * 1000)); // in milliseconds\n // no need to set mediaElement.currentTime if non-accurateSeek,\n // just wait for the recommend_seekpoint callback\n if (this._config.accurateSeek) {\n this._requestSetTime = true;\n this._mediaElement.currentTime = seconds;\n }\n }\n }\n\n _checkAndApplyUnbufferedSeekpoint() {\n if (this._seekpointRecord) {\n if (this._seekpointRecord.recordTime <= this._now() - 100) {\n let target = this._mediaElement.currentTime;\n this._seekpointRecord = null;\n if (!this._isTimepointBuffered(target)) {\n if (this._progressChecker != null) {\n window.clearTimeout(this._progressChecker);\n this._progressChecker = null;\n }\n // .currentTime is consists with .buffered timestamp\n // Chrome/Edge use DTS, while FireFox/Safari use PTS\n this._msectl.seek(target);\n this._transmuxer.seek(Math.floor(target * 1000));\n // set currentTime if accurateSeek, or wait for recommend_seekpoint callback\n if (this._config.accurateSeek) {\n this._requestSetTime = true;\n this._mediaElement.currentTime = target;\n }\n }\n } else {\n window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50);\n }\n }\n }\n\n _checkAndResumeStuckPlayback(stalled) {\n let media = this._mediaElement;\n if (stalled || !this._receivedCanPlay || media.readyState < 2) { // HAVE_CURRENT_DATA\n let buffered = media.buffered;\n if (buffered.length > 0 && media.currentTime < buffered.start(0)) {\n Log.w(this.TAG, `Playback seems stuck at ${media.currentTime}, seek to ${buffered.start(0)}`);\n this._requestSetTime = true;\n this._mediaElement.currentTime = buffered.start(0);\n this._mediaElement.removeEventListener('progress', this.e.onvProgress);\n }\n } else {\n // Playback didn't stuck, remove progress event listener\n this._mediaElement.removeEventListener('progress', this.e.onvProgress);\n }\n }\n\n _onvLoadedMetadata(e) {\n if (this._pendingSeekTime != null) {\n this._mediaElement.currentTime = this._pendingSeekTime;\n this._pendingSeekTime = null;\n }\n }\n\n _onvSeeking(e) { // handle seeking request from browser's progress bar\n let target = this._mediaElement.currentTime;\n let buffered = this._mediaElement.buffered;\n\n if (this._requestSetTime) {\n this._requestSetTime = false;\n return;\n }\n\n if (target < 1.0 && buffered.length > 0) {\n // seek to video begin, set currentTime directly if beginPTS buffered\n let videoBeginTime = buffered.start(0);\n if ((videoBeginTime < 1.0 && target < videoBeginTime) || Browser.safari) {\n this._requestSetTime = true;\n // also workaround for Safari: Seek to 0 may cause video stuck, use 0.1 to avoid\n this._mediaElement.currentTime = Browser.safari ? 0.1 : videoBeginTime;\n return;\n }\n }\n\n if (this._isTimepointBuffered(target)) {\n if (this._alwaysSeekKeyframe) {\n let idr = this._msectl.getNearestKeyframe(Math.floor(target * 1000));\n if (idr != null) {\n this._requestSetTime = true;\n this._mediaElement.currentTime = idr.dts / 1000;\n }\n }\n if (this._progressChecker != null) {\n this._checkProgressAndResume();\n }\n return;\n }\n\n this._seekpointRecord = {\n seekPoint: target,\n recordTime: this._now()\n };\n window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50);\n }\n\n _onvCanPlay(e) {\n this._receivedCanPlay = true;\n this._mediaElement.removeEventListener('canplay', this.e.onvCanPlay);\n }\n\n _onvStalled(e) {\n this._checkAndResumeStuckPlayback(true);\n }\n\n _onvProgress(e) {\n this._checkAndResumeStuckPlayback();\n }\n\n}\n\nexport default FlvPlayer;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport PlayerEvents from './player-events.js';\nimport {createDefaultConfig} from '../config.js';\nimport {InvalidArgumentException, IllegalStateException} from '../utils/exception.js';\n\n// Player wrapper for browser's native player (HTMLVideoElement) without MediaSource src. \nclass NativePlayer {\n\n constructor(mediaDataSource, config) {\n this.TAG = 'NativePlayer';\n this._type = 'NativePlayer';\n this._emitter = new EventEmitter();\n\n this._config = createDefaultConfig();\n if (typeof config === 'object') {\n Object.assign(this._config, config);\n }\n\n if (mediaDataSource.type.toLowerCase() === 'flv') {\n throw new InvalidArgumentException('NativePlayer does\\'t support flv MediaDataSource input!');\n }\n if (mediaDataSource.hasOwnProperty('segments')) {\n throw new InvalidArgumentException(`NativePlayer(${mediaDataSource.type}) doesn't support multipart playback!`);\n }\n\n this.e = {\n onvLoadedMetadata: this._onvLoadedMetadata.bind(this)\n };\n\n this._pendingSeekTime = null;\n this._statisticsReporter = null;\n\n this._mediaDataSource = mediaDataSource;\n this._mediaElement = null;\n }\n\n destroy() {\n if (this._mediaElement) {\n this.unload();\n this.detachMediaElement();\n }\n this.e = null;\n this._mediaDataSource = null;\n this._emitter.removeAllListeners();\n this._emitter = null;\n }\n\n on(event, listener) {\n if (event === PlayerEvents.MEDIA_INFO) {\n if (this._mediaElement != null && this._mediaElement.readyState !== 0) { // HAVE_NOTHING\n Promise.resolve().then(() => {\n this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo);\n });\n }\n } else if (event === PlayerEvents.STATISTICS_INFO) {\n if (this._mediaElement != null && this._mediaElement.readyState !== 0) {\n Promise.resolve().then(() => {\n this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo);\n });\n }\n }\n this._emitter.addListener(event, listener);\n }\n\n off(event, listener) {\n this._emitter.removeListener(event, listener);\n }\n\n attachMediaElement(mediaElement) {\n this._mediaElement = mediaElement;\n mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata);\n\n if (this._pendingSeekTime != null) {\n try {\n mediaElement.currentTime = this._pendingSeekTime;\n this._pendingSeekTime = null;\n } catch (e) {\n // IE11 may throw InvalidStateError if readyState === 0\n // Defer set currentTime operation after loadedmetadata\n }\n }\n }\n\n detachMediaElement() {\n if (this._mediaElement) {\n this._mediaElement.src = '';\n this._mediaElement.removeAttribute('src');\n this._mediaElement.removeEventListener('loadedmetadata', this.e.onvLoadedMetadata);\n this._mediaElement = null;\n }\n if (this._statisticsReporter != null) {\n window.clearInterval(this._statisticsReporter);\n this._statisticsReporter = null;\n }\n }\n\n load() {\n if (!this._mediaElement) {\n throw new IllegalStateException('HTMLMediaElement must be attached before load()!');\n }\n this._mediaElement.src = this._mediaDataSource.url;\n\n if (this._mediaElement.readyState > 0) {\n this._mediaElement.currentTime = 0;\n }\n\n this._mediaElement.preload = 'auto';\n this._mediaElement.load();\n this._statisticsReporter = window.setInterval(\n this._reportStatisticsInfo.bind(this),\n this._config.statisticsInfoReportInterval);\n }\n\n unload() {\n if (this._mediaElement) {\n this._mediaElement.src = '';\n this._mediaElement.removeAttribute('src');\n }\n if (this._statisticsReporter != null) {\n window.clearInterval(this._statisticsReporter);\n this._statisticsReporter = null;\n }\n }\n\n play() {\n return this._mediaElement.play();\n }\n\n pause() {\n this._mediaElement.pause();\n }\n\n get type() {\n return this._type;\n }\n\n get buffered() {\n return this._mediaElement.buffered;\n }\n\n get duration() {\n return this._mediaElement.duration;\n }\n\n get volume() {\n return this._mediaElement.volume;\n }\n\n set volume(value) {\n this._mediaElement.volume = value;\n }\n\n get muted() {\n return this._mediaElement.muted;\n }\n\n set muted(muted) {\n this._mediaElement.muted = muted;\n }\n\n get currentTime() {\n if (this._mediaElement) {\n return this._mediaElement.currentTime;\n }\n return 0;\n }\n\n set currentTime(seconds) {\n if (this._mediaElement) {\n this._mediaElement.currentTime = seconds;\n } else {\n this._pendingSeekTime = seconds;\n }\n }\n\n get mediaInfo() {\n let mediaPrefix = (this._mediaElement instanceof HTMLAudioElement) ? 'audio/' : 'video/';\n let info = {\n mimeType: mediaPrefix + this._mediaDataSource.type\n };\n if (this._mediaElement) {\n info.duration = Math.floor(this._mediaElement.duration * 1000);\n if (this._mediaElement instanceof HTMLVideoElement) {\n info.width = this._mediaElement.videoWidth;\n info.height = this._mediaElement.videoHeight;\n }\n }\n return info;\n }\n\n get statisticsInfo() {\n let info = {\n playerType: this._type,\n url: this._mediaDataSource.url\n };\n\n if (!(this._mediaElement instanceof HTMLVideoElement)) {\n return info;\n }\n\n let hasQualityInfo = true;\n let decoded = 0;\n let dropped = 0;\n\n if (this._mediaElement.getVideoPlaybackQuality) {\n let quality = this._mediaElement.getVideoPlaybackQuality();\n decoded = quality.totalVideoFrames;\n dropped = quality.droppedVideoFrames;\n } else if (this._mediaElement.webkitDecodedFrameCount != undefined) {\n decoded = this._mediaElement.webkitDecodedFrameCount;\n dropped = this._mediaElement.webkitDroppedFrameCount;\n } else {\n hasQualityInfo = false;\n }\n\n if (hasQualityInfo) {\n info.decodedFrames = decoded;\n info.droppedFrames = dropped;\n }\n \n return info;\n }\n\n _onvLoadedMetadata(e) {\n if (this._pendingSeekTime != null) {\n this._mediaElement.currentTime = this._pendingSeekTime;\n this._pendingSeekTime = null;\n }\n this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo);\n }\n\n _reportStatisticsInfo() {\n this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo);\n }\n\n}\n\nexport default NativePlayer;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Polyfill from './utils/polyfill.js';\nimport Features from './core/features.js';\nimport {BaseLoader, LoaderStatus, LoaderErrors} from './io/loader.js';\nimport FlvPlayer from './player/flv-player.js';\nimport NativePlayer from './player/native-player.js';\nimport PlayerEvents from './player/player-events.js';\nimport {ErrorTypes, ErrorDetails} from './player/player-errors.js';\nimport LoggingControl from './utils/logging-control.js';\nimport {InvalidArgumentException} from './utils/exception.js';\n\n// here are all the interfaces\n\n// install polyfills\nPolyfill.install();\n\n\n// factory method\nfunction createPlayer(mediaDataSource, optionalConfig) {\n let mds = mediaDataSource;\n if (mds == null || typeof mds !== 'object') {\n throw new InvalidArgumentException('MediaDataSource must be an javascript object!');\n }\n\n if (!mds.hasOwnProperty('type')) {\n throw new InvalidArgumentException('MediaDataSource must has type field to indicate video file type!');\n }\n\n switch (mds.type) {\n case 'flv':\n return new FlvPlayer(mds, optionalConfig);\n default:\n return new NativePlayer(mds, optionalConfig);\n }\n}\n\n\n// feature detection\nfunction isSupported() {\n return Features.supportMSEH264Playback();\n}\n\nfunction getFeatureList() {\n return Features.getFeatureList();\n}\n\n\n// interfaces\nlet flvjs = {};\n\nflvjs.createPlayer = createPlayer;\nflvjs.isSupported = isSupported;\nflvjs.getFeatureList = getFeatureList;\n\nflvjs.BaseLoader = BaseLoader;\nflvjs.LoaderStatus = LoaderStatus;\nflvjs.LoaderErrors = LoaderErrors;\n\nflvjs.Events = PlayerEvents;\nflvjs.ErrorTypes = ErrorTypes;\nflvjs.ErrorDetails = ErrorDetails;\n\nflvjs.FlvPlayer = FlvPlayer;\nflvjs.NativePlayer = NativePlayer;\nflvjs.LoggingControl = LoggingControl;\n\nObject.defineProperty(flvjs, 'version', {\n enumerable: true,\n get: function () {\n // replace by webpack.DefinePlugin\n return __VERSION__;\n }\n});\n\nexport default flvjs;","// entry/index file\n\n// make it compatible with browserify's umd wrapper\nmodule.exports = require('./flv.js').default;\n","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Utility class to calculate realtime network I/O speed\nclass SpeedSampler {\n\n constructor() {\n // milliseconds\n this._firstCheckpoint = 0;\n this._lastCheckpoint = 0;\n this._intervalBytes = 0;\n this._totalBytes = 0;\n this._lastSecondBytes = 0;\n\n // compatibility detection\n if (self.performance && self.performance.now) {\n this._now = self.performance.now.bind(self.performance);\n } else {\n this._now = Date.now;\n }\n }\n\n reset() {\n this._firstCheckpoint = this._lastCheckpoint = 0;\n this._totalBytes = this._intervalBytes = 0;\n this._lastSecondBytes = 0;\n }\n\n addBytes(bytes) {\n if (this._firstCheckpoint === 0) {\n this._firstCheckpoint = this._now();\n this._lastCheckpoint = this._firstCheckpoint;\n this._intervalBytes += bytes;\n this._totalBytes += bytes;\n } else if (this._now() - this._lastCheckpoint < 1000) {\n this._intervalBytes += bytes;\n this._totalBytes += bytes;\n } else { // duration >= 1000\n this._lastSecondBytes = this._intervalBytes;\n this._intervalBytes = bytes;\n this._totalBytes += bytes;\n this._lastCheckpoint = this._now();\n }\n }\n\n get currentKBps() {\n this.addBytes(0);\n\n let durationSeconds = (this._now() - this._lastCheckpoint) / 1000;\n if (durationSeconds == 0) durationSeconds = 1;\n return (this._intervalBytes / durationSeconds) / 1024;\n }\n\n get lastSecondKBps() {\n this.addBytes(0);\n\n if (this._lastSecondBytes !== 0) {\n return this._lastSecondBytes / 1024;\n } else { // lastSecondBytes === 0\n if (this._now() - this._lastCheckpoint >= 500) {\n // if time interval since last checkpoint has exceeded 500ms\n // the speed is nearly accurate\n return this.currentKBps;\n } else {\n // We don't know\n return 0;\n }\n }\n }\n\n get averageKBps() {\n let durationSeconds = (this._now() - this._firstCheckpoint) / 1000;\n return (this._totalBytes / durationSeconds) / 1024;\n }\n\n}\n\nexport default SpeedSampler;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport Browser from '../utils/browser.js';\nimport {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';\nimport {RuntimeException} from '../utils/exception.js';\n\n/* fetch + stream IO loader. Currently working on chrome 43+.\n * fetch provides a better alternative http API to XMLHttpRequest\n *\n * fetch spec https://fetch.spec.whatwg.org/\n * stream spec https://streams.spec.whatwg.org/\n */\nclass FetchStreamLoader extends BaseLoader {\n\n static isSupported() {\n try {\n // fetch + stream is broken on Microsoft Edge. Disable before build 15048.\n // see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8196907/\n // Fixed in Jan 10, 2017. Build 15048+ removed from blacklist.\n let isWorkWellEdge = Browser.msedge && Browser.version.minor >= 15048;\n let browserNotBlacklisted = Browser.msedge ? isWorkWellEdge : true;\n return (self.fetch && self.ReadableStream && browserNotBlacklisted);\n } catch (e) {\n return false;\n }\n }\n\n constructor(seekHandler, config) {\n super('fetch-stream-loader');\n this.TAG = 'FetchStreamLoader';\n\n this._seekHandler = seekHandler;\n this._config = config;\n this._needStash = true;\n\n this._requestAbort = false;\n this._contentLength = null;\n this._receivedLength = 0;\n }\n\n destroy() {\n if (this.isWorking()) {\n this.abort();\n }\n super.destroy();\n }\n\n open(dataSource, range) {\n this._dataSource = dataSource;\n this._range = range;\n\n let sourceURL = dataSource.url;\n if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) {\n sourceURL = dataSource.redirectedURL;\n }\n\n let seekConfig = this._seekHandler.getConfig(sourceURL, range);\n\n let headers = new self.Headers();\n\n if (typeof seekConfig.headers === 'object') {\n let configHeaders = seekConfig.headers;\n for (let key in configHeaders) {\n if (configHeaders.hasOwnProperty(key)) {\n headers.append(key, configHeaders[key]);\n }\n }\n }\n\n let params = {\n method: 'GET',\n headers: headers,\n mode: 'cors',\n cache: 'default',\n // The default policy of Fetch API in the whatwg standard\n // Safari incorrectly indicates 'no-referrer' as default policy, fuck it\n referrerPolicy: 'no-referrer-when-downgrade'\n };\n\n // add additional headers\n if (typeof this._config.headers === 'object') {\n for (let key in this._config.headers) {\n headers.append(key, this._config.headers[key]);\n }\n }\n\n // cors is enabled by default\n if (dataSource.cors === false) {\n // no-cors means 'disregard cors policy', which can only be used in ServiceWorker\n params.mode = 'same-origin';\n }\n\n // withCredentials is disabled by default\n if (dataSource.withCredentials) {\n params.credentials = 'include';\n }\n\n // referrerPolicy from config\n if (dataSource.referrerPolicy) {\n params.referrerPolicy = dataSource.referrerPolicy;\n }\n\n // add abort controller, by wmlgl 2019-5-10 12:21:27\n if (self.AbortController) {\n this._abortController = new self.AbortController();\n params.signal = this._abortController.signal; \n }\n\n this._status = LoaderStatus.kConnecting;\n self.fetch(seekConfig.url, params).then((res) => {\n if (this._requestAbort) {\n this._status = LoaderStatus.kIdle;\n res.body.cancel();\n return;\n }\n if (res.ok && (res.status >= 200 && res.status <= 299)) {\n if (res.url !== seekConfig.url) {\n if (this._onURLRedirect) {\n let redirectedURL = this._seekHandler.removeURLParameters(res.url);\n this._onURLRedirect(redirectedURL);\n }\n }\n\n let lengthHeader = res.headers.get('Content-Length');\n if (lengthHeader != null) {\n this._contentLength = parseInt(lengthHeader);\n if (this._contentLength !== 0) {\n if (this._onContentLengthKnown) {\n this._onContentLengthKnown(this._contentLength);\n }\n }\n }\n\n return this._pump.call(this, res.body.getReader());\n } else {\n this._status = LoaderStatus.kError;\n if (this._onError) {\n this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: res.status, msg: res.statusText});\n } else {\n throw new RuntimeException('FetchStreamLoader: Http code invalid, ' + res.status + ' ' + res.statusText);\n }\n }\n }).catch((e) => {\n if (this._abortController && this._abortController.signal.aborted) {\n return;\n }\n\n this._status = LoaderStatus.kError;\n if (this._onError) {\n this._onError(LoaderErrors.EXCEPTION, {code: -1, msg: e.message});\n } else {\n throw e;\n }\n });\n }\n\n abort() {\n this._requestAbort = true;\n\n if (this._status !== LoaderStatus.kBuffering || !Browser.chrome) {\n // Chrome may throw Exception-like things here, avoid using if is buffering\n if (this._abortController) {\n try {\n this._abortController.abort();\n } catch (e) {}\n }\n }\n }\n\n _pump(reader) { // ReadableStreamReader\n return reader.read().then((result) => {\n if (result.done) {\n // First check received length\n if (this._contentLength !== null && this._receivedLength < this._contentLength) {\n // Report Early-EOF\n this._status = LoaderStatus.kError;\n let type = LoaderErrors.EARLY_EOF;\n let info = {code: -1, msg: 'Fetch stream meet Early-EOF'};\n if (this._onError) {\n this._onError(type, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n } else {\n // OK. Download complete\n this._status = LoaderStatus.kComplete;\n if (this._onComplete) {\n this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);\n }\n }\n } else {\n if (this._abortController && this._abortController.signal.aborted) {\n this._status = LoaderStatus.kComplete;\n return;\n } else if (this._requestAbort === true) {\n this._status = LoaderStatus.kComplete;\n return reader.cancel();\n }\n\n this._status = LoaderStatus.kBuffering;\n\n let chunk = result.value.buffer;\n let byteStart = this._range.from + this._receivedLength;\n this._receivedLength += chunk.byteLength;\n\n if (this._onDataArrival) {\n this._onDataArrival(chunk, byteStart, this._receivedLength);\n }\n\n this._pump(reader);\n }\n }).catch((e) => {\n if (this._abortController && this._abortController.signal.aborted) {\n this._status = LoaderStatus.kComplete;\n return;\n }\n\n if (e.code === 11 && Browser.msedge) { // InvalidStateError on Microsoft Edge\n // Workaround: Edge may throw InvalidStateError after ReadableStreamReader.cancel() call\n // Ignore the unknown exception.\n // Related issue: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11265202/\n return;\n }\n\n this._status = LoaderStatus.kError;\n let type = 0;\n let info = null;\n\n if ((e.code === 19 || e.message === 'network error') && // NETWORK_ERR\n (this._contentLength === null ||\n (this._contentLength !== null && this._receivedLength < this._contentLength))) {\n type = LoaderErrors.EARLY_EOF;\n info = {code: e.code, msg: 'Fetch stream meet Early-EOF'};\n } else {\n type = LoaderErrors.EXCEPTION;\n info = {code: e.code, msg: e.message};\n }\n\n if (this._onError) {\n this._onError(type, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n });\n }\n\n}\n\nexport default FetchStreamLoader;\n","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';\nimport {RuntimeException} from '../utils/exception.js';\n\n// For FireFox browser which supports `xhr.responseType = 'moz-chunked-arraybuffer'`\nclass MozChunkedLoader extends BaseLoader {\n\n static isSupported() {\n try {\n let xhr = new XMLHttpRequest();\n // Firefox 37- requires .open() to be called before setting responseType\n xhr.open('GET', 'https://example.com', true);\n xhr.responseType = 'moz-chunked-arraybuffer';\n return (xhr.responseType === 'moz-chunked-arraybuffer');\n } catch (e) {\n Log.w('MozChunkedLoader', e.message);\n return false;\n }\n }\n\n constructor(seekHandler, config) {\n super('xhr-moz-chunked-loader');\n this.TAG = 'MozChunkedLoader';\n\n this._seekHandler = seekHandler;\n this._config = config;\n this._needStash = true;\n\n this._xhr = null;\n this._requestAbort = false;\n this._contentLength = null;\n this._receivedLength = 0;\n }\n\n destroy() {\n if (this.isWorking()) {\n this.abort();\n }\n if (this._xhr) {\n this._xhr.onreadystatechange = null;\n this._xhr.onprogress = null;\n this._xhr.onloadend = null;\n this._xhr.onerror = null;\n this._xhr = null;\n }\n super.destroy();\n }\n\n open(dataSource, range) {\n this._dataSource = dataSource;\n this._range = range;\n\n let sourceURL = dataSource.url;\n if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) {\n sourceURL = dataSource.redirectedURL;\n }\n\n let seekConfig = this._seekHandler.getConfig(sourceURL, range);\n this._requestURL = seekConfig.url;\n\n let xhr = this._xhr = new XMLHttpRequest();\n xhr.open('GET', seekConfig.url, true);\n xhr.responseType = 'moz-chunked-arraybuffer';\n xhr.onreadystatechange = this._onReadyStateChange.bind(this);\n xhr.onprogress = this._onProgress.bind(this);\n xhr.onloadend = this._onLoadEnd.bind(this);\n xhr.onerror = this._onXhrError.bind(this);\n\n // cors is auto detected and enabled by xhr\n\n // withCredentials is disabled by default\n if (dataSource.withCredentials) {\n xhr.withCredentials = true;\n }\n\n if (typeof seekConfig.headers === 'object') {\n let headers = seekConfig.headers;\n\n for (let key in headers) {\n if (headers.hasOwnProperty(key)) {\n xhr.setRequestHeader(key, headers[key]);\n }\n }\n }\n\n // add additional headers\n if (typeof this._config.headers === 'object') {\n let headers = this._config.headers;\n\n for (let key in headers) {\n if (headers.hasOwnProperty(key)) {\n xhr.setRequestHeader(key, headers[key]);\n }\n }\n }\n\n this._status = LoaderStatus.kConnecting;\n xhr.send();\n }\n\n abort() {\n this._requestAbort = true;\n if (this._xhr) {\n this._xhr.abort();\n }\n this._status = LoaderStatus.kComplete;\n }\n\n _onReadyStateChange(e) {\n let xhr = e.target;\n\n if (xhr.readyState === 2) { // HEADERS_RECEIVED\n if (xhr.responseURL != undefined && xhr.responseURL !== this._requestURL) {\n if (this._onURLRedirect) {\n let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);\n this._onURLRedirect(redirectedURL);\n }\n }\n\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status > 299)) {\n this._status = LoaderStatus.kError;\n if (this._onError) {\n this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: xhr.status, msg: xhr.statusText});\n } else {\n throw new RuntimeException('MozChunkedLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);\n }\n } else {\n this._status = LoaderStatus.kBuffering;\n }\n }\n }\n\n _onProgress(e) {\n if (this._status === LoaderStatus.kError) {\n // Ignore error response\n return;\n }\n\n if (this._contentLength === null) {\n if (e.total !== null && e.total !== 0) {\n this._contentLength = e.total;\n if (this._onContentLengthKnown) {\n this._onContentLengthKnown(this._contentLength);\n }\n }\n }\n\n let chunk = e.target.response;\n let byteStart = this._range.from + this._receivedLength;\n this._receivedLength += chunk.byteLength;\n\n if (this._onDataArrival) {\n this._onDataArrival(chunk, byteStart, this._receivedLength);\n }\n }\n\n _onLoadEnd(e) {\n if (this._requestAbort === true) {\n this._requestAbort = false;\n return;\n } else if (this._status === LoaderStatus.kError) {\n return;\n }\n\n this._status = LoaderStatus.kComplete;\n if (this._onComplete) {\n this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);\n }\n }\n\n _onXhrError(e) {\n this._status = LoaderStatus.kError;\n let type = 0;\n let info = null;\n\n if (this._contentLength && e.loaded < this._contentLength) {\n type = LoaderErrors.EARLY_EOF;\n info = {code: -1, msg: 'Moz-Chunked stream meet Early-Eof'};\n } else {\n type = LoaderErrors.EXCEPTION;\n info = {code: -1, msg: e.constructor.name + ' ' + e.type};\n }\n\n if (this._onError) {\n this._onError(type, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n }\n\n}\n\nexport default MozChunkedLoader;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport SpeedSampler from './speed-sampler.js';\nimport {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';\nimport {RuntimeException} from '../utils/exception.js';\n\n// Universal IO Loader, implemented by adding Range header in xhr's request header\nclass RangeLoader extends BaseLoader {\n\n static isSupported() {\n try {\n let xhr = new XMLHttpRequest();\n xhr.open('GET', 'https://example.com', true);\n xhr.responseType = 'arraybuffer';\n return (xhr.responseType === 'arraybuffer');\n } catch (e) {\n Log.w('RangeLoader', e.message);\n return false;\n }\n }\n\n constructor(seekHandler, config) {\n super('xhr-range-loader');\n this.TAG = 'RangeLoader';\n\n this._seekHandler = seekHandler;\n this._config = config;\n this._needStash = false;\n\n this._chunkSizeKBList = [\n 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192\n ];\n this._currentChunkSizeKB = 384;\n this._currentSpeedNormalized = 0;\n this._zeroSpeedChunkCount = 0;\n\n this._xhr = null;\n this._speedSampler = new SpeedSampler();\n\n this._requestAbort = false;\n this._waitForTotalLength = false;\n this._totalLengthReceived = false;\n\n this._currentRequestURL = null;\n this._currentRedirectedURL = null;\n this._currentRequestRange = null;\n this._totalLength = null; // size of the entire file\n this._contentLength = null; // Content-Length of entire request range\n this._receivedLength = 0; // total received bytes\n this._lastTimeLoaded = 0; // received bytes of current request sub-range\n }\n\n destroy() {\n if (this.isWorking()) {\n this.abort();\n }\n if (this._xhr) {\n this._xhr.onreadystatechange = null;\n this._xhr.onprogress = null;\n this._xhr.onload = null;\n this._xhr.onerror = null;\n this._xhr = null;\n }\n super.destroy();\n }\n\n get currentSpeed() {\n return this._speedSampler.lastSecondKBps;\n }\n\n open(dataSource, range) {\n this._dataSource = dataSource;\n this._range = range;\n this._status = LoaderStatus.kConnecting;\n\n let useRefTotalLength = false;\n if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) {\n useRefTotalLength = true;\n this._totalLength = this._dataSource.filesize;\n }\n\n if (!this._totalLengthReceived && !useRefTotalLength) {\n // We need total filesize\n this._waitForTotalLength = true;\n this._internalOpen(this._dataSource, {from: 0, to: -1});\n } else {\n // We have filesize, start loading\n this._openSubRange();\n }\n }\n\n _openSubRange() {\n let chunkSize = this._currentChunkSizeKB * 1024;\n\n let from = this._range.from + this._receivedLength;\n let to = from + chunkSize;\n\n if (this._contentLength != null) {\n if (to - this._range.from >= this._contentLength) {\n to = this._range.from + this._contentLength - 1;\n }\n }\n\n this._currentRequestRange = {from, to};\n this._internalOpen(this._dataSource, this._currentRequestRange);\n }\n\n _internalOpen(dataSource, range) {\n this._lastTimeLoaded = 0;\n\n let sourceURL = dataSource.url;\n if (this._config.reuseRedirectedURL) {\n if (this._currentRedirectedURL != undefined) {\n sourceURL = this._currentRedirectedURL;\n } else if (dataSource.redirectedURL != undefined) {\n sourceURL = dataSource.redirectedURL;\n }\n }\n\n let seekConfig = this._seekHandler.getConfig(sourceURL, range);\n this._currentRequestURL = seekConfig.url;\n\n let xhr = this._xhr = new XMLHttpRequest();\n xhr.open('GET', seekConfig.url, true);\n xhr.responseType = 'arraybuffer';\n xhr.onreadystatechange = this._onReadyStateChange.bind(this);\n xhr.onprogress = this._onProgress.bind(this);\n xhr.onload = this._onLoad.bind(this);\n xhr.onerror = this._onXhrError.bind(this);\n\n if (dataSource.withCredentials) {\n xhr.withCredentials = true;\n }\n\n if (typeof seekConfig.headers === 'object') {\n let headers = seekConfig.headers;\n\n for (let key in headers) {\n if (headers.hasOwnProperty(key)) {\n xhr.setRequestHeader(key, headers[key]);\n }\n }\n }\n\n // add additional headers\n if (typeof this._config.headers === 'object') {\n let headers = this._config.headers;\n\n for (let key in headers) {\n if (headers.hasOwnProperty(key)) {\n xhr.setRequestHeader(key, headers[key]);\n }\n }\n }\n\n xhr.send();\n }\n\n abort() {\n this._requestAbort = true;\n this._internalAbort();\n this._status = LoaderStatus.kComplete;\n }\n\n _internalAbort() {\n if (this._xhr) {\n this._xhr.onreadystatechange = null;\n this._xhr.onprogress = null;\n this._xhr.onload = null;\n this._xhr.onerror = null;\n this._xhr.abort();\n this._xhr = null;\n }\n }\n\n _onReadyStateChange(e) {\n let xhr = e.target;\n\n if (xhr.readyState === 2) { // HEADERS_RECEIVED\n if (xhr.responseURL != undefined) { // if the browser support this property\n let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);\n if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) {\n this._currentRedirectedURL = redirectedURL;\n if (this._onURLRedirect) {\n this._onURLRedirect(redirectedURL);\n }\n }\n }\n\n if ((xhr.status >= 200 && xhr.status <= 299)) {\n if (this._waitForTotalLength) {\n return;\n }\n this._status = LoaderStatus.kBuffering;\n } else {\n this._status = LoaderStatus.kError;\n if (this._onError) {\n this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: xhr.status, msg: xhr.statusText});\n } else {\n throw new RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);\n }\n }\n }\n }\n\n _onProgress(e) {\n if (this._status === LoaderStatus.kError) {\n // Ignore error response\n return;\n }\n\n if (this._contentLength === null) {\n let openNextRange = false;\n\n if (this._waitForTotalLength) {\n this._waitForTotalLength = false;\n this._totalLengthReceived = true;\n openNextRange = true;\n\n let total = e.total;\n this._internalAbort();\n if (total != null & total !== 0) {\n this._totalLength = total;\n }\n }\n\n // calculate currrent request range's contentLength\n if (this._range.to === -1) {\n this._contentLength = this._totalLength - this._range.from;\n } else { // to !== -1\n this._contentLength = this._range.to - this._range.from + 1;\n }\n\n if (openNextRange) {\n this._openSubRange();\n return;\n }\n if (this._onContentLengthKnown) {\n this._onContentLengthKnown(this._contentLength);\n }\n }\n\n let delta = e.loaded - this._lastTimeLoaded;\n this._lastTimeLoaded = e.loaded;\n this._speedSampler.addBytes(delta);\n }\n\n _normalizeSpeed(input) {\n let list = this._chunkSizeKBList;\n let last = list.length - 1;\n let mid = 0;\n let lbound = 0;\n let ubound = last;\n\n if (input < list[0]) {\n return list[0];\n }\n\n while (lbound <= ubound) {\n mid = lbound + Math.floor((ubound - lbound) / 2);\n if (mid === last || (input >= list[mid] && input < list[mid + 1])) {\n return list[mid];\n } else if (list[mid] < input) {\n lbound = mid + 1;\n } else {\n ubound = mid - 1;\n }\n }\n }\n\n _onLoad(e) {\n if (this._status === LoaderStatus.kError) {\n // Ignore error response\n return;\n }\n\n if (this._waitForTotalLength) {\n this._waitForTotalLength = false;\n return;\n }\n\n this._lastTimeLoaded = 0;\n let KBps = this._speedSampler.lastSecondKBps;\n if (KBps === 0) {\n this._zeroSpeedChunkCount++;\n if (this._zeroSpeedChunkCount >= 3) {\n // Try get currentKBps after 3 chunks\n KBps = this._speedSampler.currentKBps;\n }\n }\n\n if (KBps !== 0) {\n let normalized = this._normalizeSpeed(KBps);\n if (this._currentSpeedNormalized !== normalized) {\n this._currentSpeedNormalized = normalized;\n this._currentChunkSizeKB = normalized;\n }\n }\n\n let chunk = e.target.response;\n let byteStart = this._range.from + this._receivedLength;\n this._receivedLength += chunk.byteLength;\n\n let reportComplete = false;\n\n if (this._contentLength != null && this._receivedLength < this._contentLength) {\n // continue load next chunk\n this._openSubRange();\n } else {\n reportComplete = true;\n }\n\n // dispatch received chunk\n if (this._onDataArrival) {\n this._onDataArrival(chunk, byteStart, this._receivedLength);\n }\n\n if (reportComplete) {\n this._status = LoaderStatus.kComplete;\n if (this._onComplete) {\n this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);\n }\n }\n }\n\n _onXhrError(e) {\n this._status = LoaderStatus.kError;\n let type = 0;\n let info = null;\n\n if (this._contentLength && this._receivedLength > 0\n && this._receivedLength < this._contentLength) {\n type = LoaderErrors.EARLY_EOF;\n info = {code: -1, msg: 'RangeLoader meet Early-Eof'};\n } else {\n type = LoaderErrors.EXCEPTION;\n info = {code: -1, msg: e.constructor.name + ' ' + e.type};\n }\n\n if (this._onError) {\n this._onError(type, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n }\n\n}\n\nexport default RangeLoader;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';\nimport {RuntimeException} from '../utils/exception.js';\n\n// For FLV over WebSocket live stream\nclass WebSocketLoader extends BaseLoader {\n\n static isSupported() {\n try {\n return (typeof self.WebSocket !== 'undefined');\n } catch (e) {\n return false;\n }\n }\n\n constructor() {\n super('websocket-loader');\n this.TAG = 'WebSocketLoader';\n\n this._needStash = true;\n\n this._ws = null;\n this._requestAbort = false;\n this._receivedLength = 0;\n }\n\n destroy() {\n if (this._ws) {\n this.abort();\n }\n super.destroy();\n }\n\n open(dataSource) {\n try {\n let ws = this._ws = new self.WebSocket(dataSource.url);\n ws.binaryType = 'arraybuffer';\n ws.onopen = this._onWebSocketOpen.bind(this);\n ws.onclose = this._onWebSocketClose.bind(this);\n ws.onmessage = this._onWebSocketMessage.bind(this);\n ws.onerror = this._onWebSocketError.bind(this);\n\n this._status = LoaderStatus.kConnecting;\n } catch (e) {\n this._status = LoaderStatus.kError;\n\n let info = {code: e.code, msg: e.message};\n\n if (this._onError) {\n this._onError(LoaderErrors.EXCEPTION, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n }\n }\n\n abort() {\n let ws = this._ws;\n if (ws && (ws.readyState === 0 || ws.readyState === 1)) { // CONNECTING || OPEN\n this._requestAbort = true;\n ws.close();\n }\n\n this._ws = null;\n this._status = LoaderStatus.kComplete;\n }\n\n _onWebSocketOpen(e) {\n this._status = LoaderStatus.kBuffering;\n }\n\n _onWebSocketClose(e) {\n if (this._requestAbort === true) {\n this._requestAbort = false;\n return;\n }\n\n this._status = LoaderStatus.kComplete;\n\n if (this._onComplete) {\n this._onComplete(0, this._receivedLength - 1);\n }\n }\n\n _onWebSocketMessage(e) {\n if (e.data instanceof ArrayBuffer) {\n this._dispatchArrayBuffer(e.data);\n } else if (e.data instanceof Blob) {\n let reader = new FileReader();\n reader.onload = () => {\n this._dispatchArrayBuffer(reader.result);\n };\n reader.readAsArrayBuffer(e.data);\n } else {\n this._status = LoaderStatus.kError;\n let info = {code: -1, msg: 'Unsupported WebSocket message type: ' + e.data.constructor.name};\n\n if (this._onError) {\n this._onError(LoaderErrors.EXCEPTION, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n }\n }\n\n _dispatchArrayBuffer(arraybuffer) {\n let chunk = arraybuffer;\n let byteStart = this._receivedLength;\n this._receivedLength += chunk.byteLength;\n\n if (this._onDataArrival) {\n this._onDataArrival(chunk, byteStart, this._receivedLength);\n }\n }\n\n _onWebSocketError(e) {\n this._status = LoaderStatus.kError;\n\n let info = {\n code: e.code,\n msg: e.message\n };\n\n if (this._onError) {\n this._onError(LoaderErrors.EXCEPTION, info);\n } else {\n throw new RuntimeException(info.msg);\n }\n }\n\n}\n\nexport default WebSocketLoader;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass RangeSeekHandler {\n\n constructor(zeroStart) {\n this._zeroStart = zeroStart || false;\n }\n\n getConfig(url, range) {\n let headers = {};\n\n if (range.from !== 0 || range.to !== -1) {\n let param;\n if (range.to !== -1) {\n param = `bytes=${range.from.toString()}-${range.to.toString()}`;\n } else {\n param = `bytes=${range.from.toString()}-`;\n }\n headers['Range'] = param;\n } else if (this._zeroStart) {\n headers['Range'] = 'bytes=0-';\n }\n\n return {\n url: url,\n headers: headers\n };\n }\n\n removeURLParameters(seekedURL) {\n return seekedURL;\n }\n\n}\n\nexport default RangeSeekHandler;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass ParamSeekHandler {\n\n constructor(paramStart, paramEnd) {\n this._startName = paramStart;\n this._endName = paramEnd;\n }\n\n getConfig(baseUrl, range) {\n let url = baseUrl;\n\n if (range.from !== 0 || range.to !== -1) {\n let needAnd = true;\n if (url.indexOf('?') === -1) {\n url += '?';\n needAnd = false;\n }\n\n if (needAnd) {\n url += '&';\n }\n\n url += `${this._startName}=${range.from.toString()}`;\n\n if (range.to !== -1) {\n url += `&${this._endName}=${range.to.toString()}`;\n }\n }\n\n return {\n url: url,\n headers: {}\n };\n }\n\n removeURLParameters(seekedURL) {\n let baseURL = seekedURL.split('?')[0];\n let params = undefined;\n\n let queryIndex = seekedURL.indexOf('?');\n if (queryIndex !== -1) {\n params = seekedURL.substring(queryIndex + 1);\n }\n\n let resultParams = '';\n\n if (params != undefined && params.length > 0) {\n let pairs = params.split('&');\n\n for (let i = 0; i < pairs.length; i++) {\n let pair = pairs[i].split('=');\n let requireAnd = (i > 0);\n\n if (pair[0] !== this._startName && pair[0] !== this._endName) {\n if (requireAnd) {\n resultParams += '&';\n }\n resultParams += pairs[i];\n }\n }\n }\n\n return (resultParams.length === 0) ? baseURL : baseURL + '?' + resultParams;\n }\n\n}\n\nexport default ParamSeekHandler;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Log from '../utils/logger.js';\nimport SpeedSampler from './speed-sampler.js';\nimport {LoaderStatus, LoaderErrors} from './loader.js';\nimport FetchStreamLoader from './fetch-stream-loader.js';\nimport MozChunkedLoader from './xhr-moz-chunked-loader.js';\nimport MSStreamLoader from './xhr-msstream-loader.js';\nimport RangeLoader from './xhr-range-loader.js';\nimport WebSocketLoader from './websocket-loader.js';\nimport RangeSeekHandler from './range-seek-handler.js';\nimport ParamSeekHandler from './param-seek-handler.js';\nimport {RuntimeException, IllegalStateException, InvalidArgumentException} from '../utils/exception.js';\n\n/**\n * DataSource: {\n * url: string,\n * filesize: number,\n * cors: boolean,\n * withCredentials: boolean\n * }\n * \n */\n\n// Manage IO Loaders\nclass IOController {\n\n constructor(dataSource, config, extraData) {\n this.TAG = 'IOController';\n\n this._config = config;\n this._extraData = extraData;\n\n this._stashInitialSize = 1024 * 384; // default initial size: 384KB\n if (config.stashInitialSize != undefined && config.stashInitialSize > 0) {\n // apply from config\n this._stashInitialSize = config.stashInitialSize;\n }\n\n this._stashUsed = 0;\n this._stashSize = this._stashInitialSize;\n this._bufferSize = 1024 * 1024 * 3; // initial size: 3MB\n this._stashBuffer = new ArrayBuffer(this._bufferSize);\n this._stashByteStart = 0;\n this._enableStash = true;\n if (config.enableStashBuffer === false) {\n this._enableStash = false;\n }\n\n this._loader = null;\n this._loaderClass = null;\n this._seekHandler = null;\n\n this._dataSource = dataSource;\n this._isWebSocketURL = /wss?:\\/\\/(.+?)/.test(dataSource.url);\n this._refTotalLength = dataSource.filesize ? dataSource.filesize : null;\n this._totalLength = this._refTotalLength;\n this._fullRequestFlag = false;\n this._currentRange = null;\n this._redirectedURL = null;\n\n this._speedNormalized = 0;\n this._speedSampler = new SpeedSampler();\n this._speedNormalizeList = [64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096];\n\n this._isEarlyEofReconnecting = false;\n\n this._paused = false;\n this._resumeFrom = 0;\n\n this._onDataArrival = null;\n this._onSeeked = null;\n this._onError = null;\n this._onComplete = null;\n this._onRedirect = null;\n this._onRecoveredEarlyEof = null;\n\n this._selectSeekHandler();\n this._selectLoader();\n this._createLoader();\n }\n\n destroy() {\n if (this._loader.isWorking()) {\n this._loader.abort();\n }\n this._loader.destroy();\n this._loader = null;\n this._loaderClass = null;\n this._dataSource = null;\n this._stashBuffer = null;\n this._stashUsed = this._stashSize = this._bufferSize = this._stashByteStart = 0;\n this._currentRange = null;\n this._speedSampler = null;\n\n this._isEarlyEofReconnecting = false;\n\n this._onDataArrival = null;\n this._onSeeked = null;\n this._onError = null;\n this._onComplete = null;\n this._onRedirect = null;\n this._onRecoveredEarlyEof = null;\n\n this._extraData = null;\n }\n\n isWorking() {\n return this._loader && this._loader.isWorking() && !this._paused;\n }\n\n isPaused() {\n return this._paused;\n }\n\n get status() {\n return this._loader.status;\n }\n\n get extraData() {\n return this._extraData;\n }\n\n set extraData(data) {\n this._extraData = data;\n }\n\n // prototype: function onDataArrival(chunks: ArrayBuffer, byteStart: number): number\n get onDataArrival() {\n return this._onDataArrival;\n }\n\n set onDataArrival(callback) {\n this._onDataArrival = callback;\n }\n\n get onSeeked() {\n return this._onSeeked;\n }\n\n set onSeeked(callback) {\n this._onSeeked = callback;\n }\n\n // prototype: function onError(type: number, info: {code: number, msg: string}): void\n get onError() {\n return this._onError;\n }\n\n set onError(callback) {\n this._onError = callback;\n }\n\n get onComplete() {\n return this._onComplete;\n }\n\n set onComplete(callback) {\n this._onComplete = callback;\n }\n\n get onRedirect() {\n return this._onRedirect;\n }\n\n set onRedirect(callback) {\n this._onRedirect = callback;\n }\n\n get onRecoveredEarlyEof() {\n return this._onRecoveredEarlyEof;\n }\n\n set onRecoveredEarlyEof(callback) {\n this._onRecoveredEarlyEof = callback;\n }\n\n get currentURL() {\n return this._dataSource.url;\n }\n\n get hasRedirect() {\n return (this._redirectedURL != null || this._dataSource.redirectedURL != undefined);\n }\n\n get currentRedirectedURL() {\n return this._redirectedURL || this._dataSource.redirectedURL;\n }\n\n // in KB/s\n get currentSpeed() {\n if (this._loaderClass === RangeLoader) {\n // SpeedSampler is inaccuracy if loader is RangeLoader\n return this._loader.currentSpeed;\n }\n return this._speedSampler.lastSecondKBps;\n }\n\n get loaderType() {\n return this._loader.type;\n }\n\n _selectSeekHandler() {\n let config = this._config;\n\n if (config.seekType === 'range') {\n this._seekHandler = new RangeSeekHandler(this._config.rangeLoadZeroStart);\n } else if (config.seekType === 'param') {\n let paramStart = config.seekParamStart || 'bstart';\n let paramEnd = config.seekParamEnd || 'bend';\n\n this._seekHandler = new ParamSeekHandler(paramStart, paramEnd);\n } else if (config.seekType === 'custom') {\n if (typeof config.customSeekHandler !== 'function') {\n throw new InvalidArgumentException('Custom seekType specified in config but invalid customSeekHandler!');\n }\n this._seekHandler = new config.customSeekHandler();\n } else {\n throw new InvalidArgumentException(`Invalid seekType in config: ${config.seekType}`);\n }\n }\n\n _selectLoader() {\n if (this._config.customLoader != null) {\n this._loaderClass = this._config.customLoader;\n } else if (this._isWebSocketURL) {\n this._loaderClass = WebSocketLoader;\n } else if (FetchStreamLoader.isSupported()) {\n this._loaderClass = FetchStreamLoader;\n } else if (MozChunkedLoader.isSupported()) {\n this._loaderClass = MozChunkedLoader;\n } else if (RangeLoader.isSupported()) {\n this._loaderClass = RangeLoader;\n } else {\n throw new RuntimeException('Your browser doesn\\'t support xhr with arraybuffer responseType!');\n }\n }\n\n _createLoader() {\n this._loader = new this._loaderClass(this._seekHandler, this._config);\n if (this._loader.needStashBuffer === false) {\n this._enableStash = false;\n }\n this._loader.onContentLengthKnown = this._onContentLengthKnown.bind(this);\n this._loader.onURLRedirect = this._onURLRedirect.bind(this);\n this._loader.onDataArrival = this._onLoaderChunkArrival.bind(this);\n this._loader.onComplete = this._onLoaderComplete.bind(this);\n this._loader.onError = this._onLoaderError.bind(this);\n }\n\n open(optionalFrom) {\n this._currentRange = {from: 0, to: -1};\n if (optionalFrom) {\n this._currentRange.from = optionalFrom;\n }\n\n this._speedSampler.reset();\n if (!optionalFrom) {\n this._fullRequestFlag = true;\n }\n\n this._loader.open(this._dataSource, Object.assign({}, this._currentRange));\n }\n\n abort() {\n this._loader.abort();\n\n if (this._paused) {\n this._paused = false;\n this._resumeFrom = 0;\n }\n }\n\n pause() {\n if (this.isWorking()) {\n this._loader.abort();\n\n if (this._stashUsed !== 0) {\n this._resumeFrom = this._stashByteStart;\n this._currentRange.to = this._stashByteStart - 1;\n } else {\n this._resumeFrom = this._currentRange.to + 1;\n }\n this._stashUsed = 0;\n this._stashByteStart = 0;\n this._paused = true;\n }\n }\n\n resume() {\n if (this._paused) {\n this._paused = false;\n let bytes = this._resumeFrom;\n this._resumeFrom = 0;\n this._internalSeek(bytes, true);\n }\n }\n\n seek(bytes) {\n this._paused = false;\n this._stashUsed = 0;\n this._stashByteStart = 0;\n this._internalSeek(bytes, true);\n }\n\n /**\n * When seeking request is from media seeking, unconsumed stash data should be dropped\n * However, stash data shouldn't be dropped if seeking requested from http reconnection\n *\n * @dropUnconsumed: Ignore and discard all unconsumed data in stash buffer\n */\n _internalSeek(bytes, dropUnconsumed) {\n if (this._loader.isWorking()) {\n this._loader.abort();\n }\n\n // dispatch & flush stash buffer before seek\n this._flushStashBuffer(dropUnconsumed);\n\n this._loader.destroy();\n this._loader = null;\n\n let requestRange = {from: bytes, to: -1};\n this._currentRange = {from: requestRange.from, to: -1};\n\n this._speedSampler.reset();\n this._stashSize = this._stashInitialSize;\n this._createLoader();\n this._loader.open(this._dataSource, requestRange);\n\n if (this._onSeeked) {\n this._onSeeked();\n }\n }\n\n updateUrl(url) {\n if (!url || typeof url !== 'string' || url.length === 0) {\n throw new InvalidArgumentException('Url must be a non-empty string!');\n }\n\n this._dataSource.url = url;\n\n // TODO: replace with new url\n }\n\n _expandBuffer(expectedBytes) {\n let bufferNewSize = this._stashSize;\n while (bufferNewSize + 1024 * 1024 * 1 < expectedBytes) {\n bufferNewSize *= 2;\n }\n\n bufferNewSize += 1024 * 1024 * 1; // bufferSize = stashSize + 1MB\n if (bufferNewSize === this._bufferSize) {\n return;\n }\n\n let newBuffer = new ArrayBuffer(bufferNewSize);\n\n if (this._stashUsed > 0) { // copy existing data into new buffer\n let stashOldArray = new Uint8Array(this._stashBuffer, 0, this._stashUsed);\n let stashNewArray = new Uint8Array(newBuffer, 0, bufferNewSize);\n stashNewArray.set(stashOldArray, 0);\n }\n\n this._stashBuffer = newBuffer;\n this._bufferSize = bufferNewSize;\n }\n\n _normalizeSpeed(input) {\n let list = this._speedNormalizeList;\n let last = list.length - 1;\n let mid = 0;\n let lbound = 0;\n let ubound = last;\n\n if (input < list[0]) {\n return list[0];\n }\n\n // binary search\n while (lbound <= ubound) {\n mid = lbound + Math.floor((ubound - lbound) / 2);\n if (mid === last || (input >= list[mid] && input < list[mid + 1])) {\n return list[mid];\n } else if (list[mid] < input) {\n lbound = mid + 1;\n } else {\n ubound = mid - 1;\n }\n }\n }\n\n _adjustStashSize(normalized) {\n let stashSizeKB = 0;\n\n if (this._config.isLive) {\n // live stream: always use single normalized speed for size of stashSizeKB\n stashSizeKB = normalized;\n } else {\n if (normalized < 512) {\n stashSizeKB = normalized;\n } else if (normalized >= 512 && normalized <= 1024) {\n stashSizeKB = Math.floor(normalized * 1.5);\n } else {\n stashSizeKB = normalized * 2;\n }\n }\n\n if (stashSizeKB > 8192) {\n stashSizeKB = 8192;\n }\n\n let bufferSize = stashSizeKB * 1024 + 1024 * 1024 * 1; // stashSize + 1MB\n if (this._bufferSize < bufferSize) {\n this._expandBuffer(bufferSize);\n }\n this._stashSize = stashSizeKB * 1024;\n }\n\n _dispatchChunks(chunks, byteStart) {\n this._currentRange.to = byteStart + chunks.byteLength - 1;\n return this._onDataArrival(chunks, byteStart);\n }\n\n _onURLRedirect(redirectedURL) {\n this._redirectedURL = redirectedURL;\n if (this._onRedirect) {\n this._onRedirect(redirectedURL);\n }\n }\n\n _onContentLengthKnown(contentLength) {\n if (contentLength && this._fullRequestFlag) {\n this._totalLength = contentLength;\n this._fullRequestFlag = false;\n }\n }\n\n _onLoaderChunkArrival(chunk, byteStart, receivedLength) {\n if (!this._onDataArrival) {\n throw new IllegalStateException('IOController: No existing consumer (onDataArrival) callback!');\n }\n if (this._paused) {\n return;\n }\n if (this._isEarlyEofReconnecting) {\n // Auto-reconnect for EarlyEof succeed, notify to upper-layer by callback\n this._isEarlyEofReconnecting = false;\n if (this._onRecoveredEarlyEof) {\n this._onRecoveredEarlyEof();\n }\n }\n\n this._speedSampler.addBytes(chunk.byteLength);\n\n // adjust stash buffer size according to network speed dynamically\n let KBps = this._speedSampler.lastSecondKBps;\n if (KBps !== 0) {\n let normalized = this._normalizeSpeed(KBps);\n if (this._speedNormalized !== normalized) {\n this._speedNormalized = normalized;\n this._adjustStashSize(normalized);\n }\n }\n\n if (!this._enableStash) { // disable stash\n if (this._stashUsed === 0) {\n // dispatch chunk directly to consumer;\n // check ret value (consumed bytes) and stash unconsumed to stashBuffer\n let consumed = this._dispatchChunks(chunk, byteStart);\n if (consumed < chunk.byteLength) { // unconsumed data remain.\n let remain = chunk.byteLength - consumed;\n if (remain > this._bufferSize) {\n this._expandBuffer(remain);\n }\n let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n stashArray.set(new Uint8Array(chunk, consumed), 0);\n this._stashUsed += remain;\n this._stashByteStart = byteStart + consumed;\n }\n } else {\n // else: Merge chunk into stashBuffer, and dispatch stashBuffer to consumer.\n if (this._stashUsed + chunk.byteLength > this._bufferSize) {\n this._expandBuffer(this._stashUsed + chunk.byteLength);\n }\n let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n stashArray.set(new Uint8Array(chunk), this._stashUsed);\n this._stashUsed += chunk.byteLength;\n let consumed = this._dispatchChunks(this._stashBuffer.slice(0, this._stashUsed), this._stashByteStart);\n if (consumed < this._stashUsed && consumed > 0) { // unconsumed data remain\n let remainArray = new Uint8Array(this._stashBuffer, consumed);\n stashArray.set(remainArray, 0);\n }\n this._stashUsed -= consumed;\n this._stashByteStart += consumed;\n }\n } else { // enable stash\n if (this._stashUsed === 0 && this._stashByteStart === 0) { // seeked? or init chunk?\n // This is the first chunk after seek action\n this._stashByteStart = byteStart;\n }\n if (this._stashUsed + chunk.byteLength <= this._stashSize) {\n // just stash\n let stashArray = new Uint8Array(this._stashBuffer, 0, this._stashSize);\n stashArray.set(new Uint8Array(chunk), this._stashUsed);\n this._stashUsed += chunk.byteLength;\n } else { // stashUsed + chunkSize > stashSize, size limit exceeded\n let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n if (this._stashUsed > 0) { // There're stash datas in buffer\n // dispatch the whole stashBuffer, and stash remain data\n // then append chunk to stashBuffer (stash)\n let buffer = this._stashBuffer.slice(0, this._stashUsed);\n let consumed = this._dispatchChunks(buffer, this._stashByteStart);\n if (consumed < buffer.byteLength) {\n if (consumed > 0) {\n let remainArray = new Uint8Array(buffer, consumed);\n stashArray.set(remainArray, 0);\n this._stashUsed = remainArray.byteLength;\n this._stashByteStart += consumed;\n }\n } else {\n this._stashUsed = 0;\n this._stashByteStart += consumed;\n }\n if (this._stashUsed + chunk.byteLength > this._bufferSize) {\n this._expandBuffer(this._stashUsed + chunk.byteLength);\n stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n }\n stashArray.set(new Uint8Array(chunk), this._stashUsed);\n this._stashUsed += chunk.byteLength;\n } else { // stash buffer empty, but chunkSize > stashSize (oh, holy shit)\n // dispatch chunk directly and stash remain data\n let consumed = this._dispatchChunks(chunk, byteStart);\n if (consumed < chunk.byteLength) {\n let remain = chunk.byteLength - consumed;\n if (remain > this._bufferSize) {\n this._expandBuffer(remain);\n stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n }\n stashArray.set(new Uint8Array(chunk, consumed), 0);\n this._stashUsed += remain;\n this._stashByteStart = byteStart + consumed;\n }\n }\n }\n }\n }\n\n _flushStashBuffer(dropUnconsumed) {\n if (this._stashUsed > 0) {\n let buffer = this._stashBuffer.slice(0, this._stashUsed);\n let consumed = this._dispatchChunks(buffer, this._stashByteStart);\n let remain = buffer.byteLength - consumed;\n\n if (consumed < buffer.byteLength) {\n if (dropUnconsumed) {\n Log.w(this.TAG, `${remain} bytes unconsumed data remain when flush buffer, dropped`);\n } else {\n if (consumed > 0) {\n let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);\n let remainArray = new Uint8Array(buffer, consumed);\n stashArray.set(remainArray, 0);\n this._stashUsed = remainArray.byteLength;\n this._stashByteStart += consumed;\n }\n return 0;\n }\n }\n this._stashUsed = 0;\n this._stashByteStart = 0;\n return remain;\n }\n return 0;\n }\n\n _onLoaderComplete(from, to) {\n // Force-flush stash buffer, and drop unconsumed data\n this._flushStashBuffer(true);\n\n if (this._onComplete) {\n this._onComplete(this._extraData);\n }\n }\n\n _onLoaderError(type, data) {\n Log.e(this.TAG, `Loader error, code = ${data.code}, msg = ${data.msg}`);\n\n this._flushStashBuffer(false);\n\n if (this._isEarlyEofReconnecting) {\n // Auto-reconnect for EarlyEof failed, throw UnrecoverableEarlyEof error to upper-layer\n this._isEarlyEofReconnecting = false;\n type = LoaderErrors.UNRECOVERABLE_EARLY_EOF;\n }\n\n switch (type) {\n case LoaderErrors.EARLY_EOF: {\n if (!this._config.isLive) {\n // Do internal http reconnect if not live stream\n if (this._totalLength) {\n let nextFrom = this._currentRange.to + 1;\n if (nextFrom < this._totalLength) {\n Log.w(this.TAG, 'Connection lost, trying reconnect...');\n this._isEarlyEofReconnecting = true;\n this._internalSeek(nextFrom, false);\n }\n return;\n }\n // else: We don't know totalLength, throw UnrecoverableEarlyEof\n }\n // live stream: throw UnrecoverableEarlyEof error to upper-layer\n type = LoaderErrors.UNRECOVERABLE_EARLY_EOF;\n break;\n }\n case LoaderErrors.UNRECOVERABLE_EARLY_EOF:\n case LoaderErrors.CONNECTING_TIMEOUT:\n case LoaderErrors.HTTP_STATUS_CODE_INVALID:\n case LoaderErrors.EXCEPTION:\n break;\n }\n\n if (this._onError) {\n this._onError(type, data);\n } else {\n throw new RuntimeException('IOException: ' + data.msg);\n }\n }\n\n}\n\nexport default IOController;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {NotImplementedException} from '../utils/exception.js';\n\nexport const LoaderStatus = {\n kIdle: 0,\n kConnecting: 1,\n kBuffering: 2,\n kError: 3,\n kComplete: 4\n};\n\nexport const LoaderErrors = {\n OK: 'OK',\n EXCEPTION: 'Exception',\n HTTP_STATUS_CODE_INVALID: 'HttpStatusCodeInvalid',\n CONNECTING_TIMEOUT: 'ConnectingTimeout',\n EARLY_EOF: 'EarlyEof',\n UNRECOVERABLE_EARLY_EOF: 'UnrecoverableEarlyEof'\n};\n\n/* Loader has callbacks which have following prototypes:\n * function onContentLengthKnown(contentLength: number): void\n * function onURLRedirect(url: string): void\n * function onDataArrival(chunk: ArrayBuffer, byteStart: number, receivedLength: number): void\n * function onError(errorType: number, errorInfo: {code: number, msg: string}): void\n * function onComplete(rangeFrom: number, rangeTo: number): void\n */\nexport class BaseLoader {\n\n constructor(typeName) {\n this._type = typeName || 'undefined';\n this._status = LoaderStatus.kIdle;\n this._needStash = false;\n // callbacks\n this._onContentLengthKnown = null;\n this._onURLRedirect = null;\n this._onDataArrival = null;\n this._onError = null;\n this._onComplete = null;\n }\n\n destroy() {\n this._status = LoaderStatus.kIdle;\n this._onContentLengthKnown = null;\n this._onURLRedirect = null;\n this._onDataArrival = null;\n this._onError = null;\n this._onComplete = null;\n }\n\n isWorking() {\n return this._status === LoaderStatus.kConnecting || this._status === LoaderStatus.kBuffering;\n }\n\n get type() {\n return this._type;\n }\n\n get status() {\n return this._status;\n }\n\n get needStashBuffer() {\n return this._needStash;\n }\n\n get onContentLengthKnown() {\n return this._onContentLengthKnown;\n }\n\n set onContentLengthKnown(callback) {\n this._onContentLengthKnown = callback;\n }\n\n get onURLRedirect() {\n return this._onURLRedirect;\n }\n\n set onURLRedirect(callback) {\n this._onURLRedirect = callback;\n }\n\n get onDataArrival() {\n return this._onDataArrival;\n }\n\n set onDataArrival(callback) {\n this._onDataArrival = callback;\n }\n\n get onError() {\n return this._onError;\n }\n\n set onError(callback) {\n this._onError = callback;\n }\n\n get onComplete() {\n return this._onComplete;\n }\n\n set onComplete(callback) {\n this._onComplete = callback;\n }\n\n // pure virtual\n open(dataSource, range) {\n throw new NotImplementedException('Unimplemented abstract function!');\n }\n\n abort() {\n throw new NotImplementedException('Unimplemented abstract function!');\n }\n\n\n}","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nlet Browser = {};\n\nfunction detect() {\n // modified from jquery-browser-plugin\n\n let ua = self.navigator.userAgent.toLowerCase();\n\n let match = /(edge)\\/([\\w.]+)/.exec(ua) ||\n /(opr)[\\/]([\\w.]+)/.exec(ua) ||\n /(chrome)[ \\/]([\\w.]+)/.exec(ua) ||\n /(iemobile)[\\/]([\\w.]+)/.exec(ua) ||\n /(version)(applewebkit)[ \\/]([\\w.]+).*(safari)[ \\/]([\\w.]+)/.exec(ua) ||\n /(webkit)[ \\/]([\\w.]+).*(version)[ \\/]([\\w.]+).*(safari)[ \\/]([\\w.]+)/.exec(ua) ||\n /(webkit)[ \\/]([\\w.]+)/.exec(ua) ||\n /(opera)(?:.*version|)[ \\/]([\\w.]+)/.exec(ua) ||\n /(msie) ([\\w.]+)/.exec(ua) ||\n ua.indexOf('trident') >= 0 && /(rv)(?::| )([\\w.]+)/.exec(ua) ||\n ua.indexOf('compatible') < 0 && /(firefox)[ \\/]([\\w.]+)/.exec(ua) ||\n [];\n\n let platform_match = /(ipad)/.exec(ua) ||\n /(ipod)/.exec(ua) ||\n /(windows phone)/.exec(ua) ||\n /(iphone)/.exec(ua) ||\n /(kindle)/.exec(ua) ||\n /(android)/.exec(ua) ||\n /(windows)/.exec(ua) ||\n /(mac)/.exec(ua) ||\n /(linux)/.exec(ua) ||\n /(cros)/.exec(ua) ||\n [];\n\n let matched = {\n browser: match[5] || match[3] || match[1] || '',\n version: match[2] || match[4] || '0',\n majorVersion: match[4] || match[2] || '0',\n platform: platform_match[0] || ''\n };\n\n let browser = {};\n if (matched.browser) {\n browser[matched.browser] = true;\n\n let versionArray = matched.majorVersion.split('.');\n browser.version = {\n major: parseInt(matched.majorVersion, 10),\n string: matched.version\n };\n if (versionArray.length > 1) {\n browser.version.minor = parseInt(versionArray[1], 10);\n }\n if (versionArray.length > 2) {\n browser.version.build = parseInt(versionArray[2], 10);\n }\n }\n\n if (matched.platform) {\n browser[matched.platform] = true;\n }\n\n if (browser.chrome || browser.opr || browser.safari) {\n browser.webkit = true;\n }\n\n // MSIE. IE11 has 'rv' identifer\n if (browser.rv || browser.iemobile) {\n if (browser.rv) {\n delete browser.rv;\n }\n let msie = 'msie';\n matched.browser = msie;\n browser[msie] = true;\n }\n\n // Microsoft Edge\n if (browser.edge) {\n delete browser.edge;\n let msedge = 'msedge';\n matched.browser = msedge;\n browser[msedge] = true;\n }\n\n // Opera 15+\n if (browser.opr) {\n let opera = 'opera';\n matched.browser = opera;\n browser[opera] = true;\n }\n\n // Stock android browsers are marked as Safari\n if (browser.safari && browser.android) {\n let android = 'android';\n matched.browser = android;\n browser[android] = true;\n }\n\n browser.name = matched.browser;\n browser.platform = matched.platform;\n\n for (let key in Browser) {\n if (Browser.hasOwnProperty(key)) {\n delete Browser[key];\n }\n }\n Object.assign(Browser, browser);\n}\n\ndetect();\n\nexport default Browser;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport class RuntimeException {\n\n constructor(message) {\n this._message = message;\n }\n\n get name() {\n return 'RuntimeException';\n }\n\n get message() {\n return this._message;\n }\n\n toString() {\n return this.name + ': ' + this.message;\n }\n\n}\n\nexport class IllegalStateException extends RuntimeException {\n\n constructor(message) {\n super(message);\n }\n\n get name() {\n return 'IllegalStateException';\n }\n\n}\n\nexport class InvalidArgumentException extends RuntimeException {\n\n constructor(message) {\n super(message);\n }\n\n get name() {\n return 'InvalidArgumentException';\n }\n\n}\n\nexport class NotImplementedException extends RuntimeException {\n\n constructor(message) {\n super(message);\n }\n\n get name() {\n return 'NotImplementedException';\n }\n\n}\n","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\n\nclass Log {\n\n static e(tag, msg) {\n if (!tag || Log.FORCE_GLOBAL_TAG)\n tag = Log.GLOBAL_TAG;\n\n let str = `[${tag}] > ${msg}`;\n\n if (Log.ENABLE_CALLBACK) {\n Log.emitter.emit('log', 'error', str);\n }\n\n if (!Log.ENABLE_ERROR) {\n return;\n }\n\n if (console.error) {\n console.error(str);\n } else if (console.warn) {\n console.warn(str);\n } else {\n console.log(str);\n }\n }\n\n static i(tag, msg) {\n if (!tag || Log.FORCE_GLOBAL_TAG)\n tag = Log.GLOBAL_TAG;\n\n let str = `[${tag}] > ${msg}`;\n\n if (Log.ENABLE_CALLBACK) {\n Log.emitter.emit('log', 'info', str);\n }\n\n if (!Log.ENABLE_INFO) {\n return;\n }\n\n if (console.info) {\n console.info(str);\n } else {\n console.log(str);\n }\n }\n\n static w(tag, msg) {\n if (!tag || Log.FORCE_GLOBAL_TAG)\n tag = Log.GLOBAL_TAG;\n\n let str = `[${tag}] > ${msg}`;\n\n if (Log.ENABLE_CALLBACK) {\n Log.emitter.emit('log', 'warn', str);\n }\n\n if (!Log.ENABLE_WARN) {\n return;\n }\n\n if (console.warn) {\n console.warn(str);\n } else {\n console.log(str);\n }\n }\n\n static d(tag, msg) {\n if (!tag || Log.FORCE_GLOBAL_TAG)\n tag = Log.GLOBAL_TAG;\n\n let str = `[${tag}] > ${msg}`;\n\n if (Log.ENABLE_CALLBACK) {\n Log.emitter.emit('log', 'debug', str);\n }\n\n if (!Log.ENABLE_DEBUG) {\n return;\n }\n\n if (console.debug) {\n console.debug(str);\n } else {\n console.log(str);\n }\n }\n\n static v(tag, msg) {\n if (!tag || Log.FORCE_GLOBAL_TAG)\n tag = Log.GLOBAL_TAG;\n\n let str = `[${tag}] > ${msg}`;\n\n if (Log.ENABLE_CALLBACK) {\n Log.emitter.emit('log', 'verbose', str);\n }\n\n if (!Log.ENABLE_VERBOSE) {\n return;\n }\n\n console.log(str);\n }\n\n}\n\nLog.GLOBAL_TAG = 'flv.js';\nLog.FORCE_GLOBAL_TAG = false;\nLog.ENABLE_ERROR = true;\nLog.ENABLE_INFO = true;\nLog.ENABLE_WARN = true;\nLog.ENABLE_DEBUG = true;\nLog.ENABLE_VERBOSE = true;\n\nLog.ENABLE_CALLBACK = false;\n\nLog.emitter = new EventEmitter();\n\nexport default Log;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport EventEmitter from 'events';\nimport Log from './logger.js';\n\nclass LoggingControl {\n\n static get forceGlobalTag() {\n return Log.FORCE_GLOBAL_TAG;\n }\n\n static set forceGlobalTag(enable) {\n Log.FORCE_GLOBAL_TAG = enable;\n LoggingControl._notifyChange();\n }\n\n static get globalTag() {\n return Log.GLOBAL_TAG;\n }\n\n static set globalTag(tag) {\n Log.GLOBAL_TAG = tag;\n LoggingControl._notifyChange();\n }\n\n static get enableAll() {\n return Log.ENABLE_VERBOSE\n && Log.ENABLE_DEBUG\n && Log.ENABLE_INFO\n && Log.ENABLE_WARN\n && Log.ENABLE_ERROR;\n }\n\n static set enableAll(enable) {\n Log.ENABLE_VERBOSE = enable;\n Log.ENABLE_DEBUG = enable;\n Log.ENABLE_INFO = enable;\n Log.ENABLE_WARN = enable;\n Log.ENABLE_ERROR = enable;\n LoggingControl._notifyChange();\n }\n\n static get enableDebug() {\n return Log.ENABLE_DEBUG;\n }\n\n static set enableDebug(enable) {\n Log.ENABLE_DEBUG = enable;\n LoggingControl._notifyChange();\n }\n\n static get enableVerbose() {\n return Log.ENABLE_VERBOSE;\n }\n\n static set enableVerbose(enable) {\n Log.ENABLE_VERBOSE = enable;\n LoggingControl._notifyChange();\n }\n\n static get enableInfo() {\n return Log.ENABLE_INFO;\n }\n\n static set enableInfo(enable) {\n Log.ENABLE_INFO = enable;\n LoggingControl._notifyChange();\n }\n\n static get enableWarn() {\n return Log.ENABLE_WARN;\n }\n\n static set enableWarn(enable) {\n Log.ENABLE_WARN = enable;\n LoggingControl._notifyChange();\n }\n\n static get enableError() {\n return Log.ENABLE_ERROR;\n }\n\n static set enableError(enable) {\n Log.ENABLE_ERROR = enable;\n LoggingControl._notifyChange();\n }\n\n static getConfig() {\n return {\n globalTag: Log.GLOBAL_TAG,\n forceGlobalTag: Log.FORCE_GLOBAL_TAG,\n enableVerbose: Log.ENABLE_VERBOSE,\n enableDebug: Log.ENABLE_DEBUG,\n enableInfo: Log.ENABLE_INFO,\n enableWarn: Log.ENABLE_WARN,\n enableError: Log.ENABLE_ERROR,\n enableCallback: Log.ENABLE_CALLBACK\n };\n }\n\n static applyConfig(config) {\n Log.GLOBAL_TAG = config.globalTag;\n Log.FORCE_GLOBAL_TAG = config.forceGlobalTag;\n Log.ENABLE_VERBOSE = config.enableVerbose;\n Log.ENABLE_DEBUG = config.enableDebug;\n Log.ENABLE_INFO = config.enableInfo;\n Log.ENABLE_WARN = config.enableWarn;\n Log.ENABLE_ERROR = config.enableError;\n Log.ENABLE_CALLBACK = config.enableCallback;\n }\n\n static _notifyChange() {\n let emitter = LoggingControl.emitter;\n\n if (emitter.listenerCount('change') > 0) {\n let config = LoggingControl.getConfig();\n emitter.emit('change', config);\n }\n }\n\n static registerListener(listener) {\n LoggingControl.emitter.addListener('change', listener);\n }\n\n static removeListener(listener) {\n LoggingControl.emitter.removeListener('change', listener);\n }\n\n static addLogListener(listener) {\n Log.emitter.addListener('log', listener);\n if (Log.emitter.listenerCount('log') > 0) {\n Log.ENABLE_CALLBACK = true;\n LoggingControl._notifyChange();\n }\n }\n\n static removeLogListener(listener) {\n Log.emitter.removeListener('log', listener);\n if (Log.emitter.listenerCount('log') === 0) {\n Log.ENABLE_CALLBACK = false;\n LoggingControl._notifyChange();\n }\n }\n\n}\n\nLoggingControl.emitter = new EventEmitter();\n\nexport default LoggingControl;","/*\n * Copyright (C) 2016 Bilibili. All Rights Reserved.\n *\n * @author zheng qian \n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nclass Polyfill {\n\n static install() {\n // ES6 Object.setPrototypeOf\n Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {\n obj.__proto__ = proto;\n return obj;\n };\n\n // ES6 Object.assign\n Object.assign = Object.assign || function (target) {\n if (target === undefined || target === null) {\n throw new TypeError('Cannot convert undefined or null to object');\n }\n\n let output = Object(target);\n for (let i = 1; i < arguments.length; i++) {\n let source = arguments[i];\n if (source !== undefined && source !== null) {\n for (let key in source) {\n if (source.hasOwnProperty(key)) {\n output[key] = source[key];\n }\n }\n }\n }\n return output;\n };\n\n // ES6 Promise (missing support in IE11)\n if (typeof self.Promise !== 'function') {\n require('es6-promise').polyfill();\n }\n }\n\n}\n\nPolyfill.install();\n\nexport default Polyfill;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// module factories are used so entry inlining is disabled\n// startup\n// Load entry module and return exports\nvar __webpack_exports__ = __webpack_require__(324);\n"],"sourceRoot":""} \ No newline at end of file diff --git a/public/js/index.js b/public/js/index.js new file mode 100644 index 0000000..ec84b2e --- /dev/null +++ b/public/js/index.js @@ -0,0 +1,226 @@ +class StreamDashboard { + constructor() { + this.streams = []; + this.filteredStreams = []; + this.searchTimeout = null; + this.refreshInterval = null; + + this.initializeElements(); + this.bindEvents(); + this.loadStreams(); + this.startAutoRefresh(); + } + + initializeElements() { + this.elements = { + searchInput: document.getElementById("searchInput"), + totalStreams: document.getElementById("totalStreams"), + onlineStreams: document.getElementById("onlineStreams"), + loadingState: document.getElementById("loadingState"), + errorState: document.getElementById("errorState"), + streamsContainer: document.getElementById("streamsContainer"), + noResults: document.getElementById("noResults"), + retryBtn: document.getElementById("retryBtn"), + }; + } + + bindEvents() { + this.elements.searchInput.addEventListener("input", (e) => { + clearTimeout(this.searchTimeout); + this.searchTimeout = setTimeout(() => { + this.filterStreams(e.target.value); + }, 300); + }); + + this.elements.retryBtn.addEventListener("click", () => { + this.loadStreams(); + }); + + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + this.stopAutoRefresh(); + } else { + this.startAutoRefresh(); + } + }); + } + + startAutoRefresh() { + this.refreshInterval = setInterval(() => { + this.loadStreams(true); + }, 10000); + } + + stopAutoRefresh() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + } + + async loadStreams(silentRefresh = false) { + if (!silentRefresh) { + this.showLoading(); + } + + try { + const response = await fetch("/api/streams"); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error(result.error || "Failed to load streams"); + } + + const allStreams = result.data.streams || []; + this.streams = allStreams.filter( + (stream) => stream.publish?.active === true, + ); + this.filteredStreams = [...this.streams]; + + this.updateStats(); + this.renderStreams(); + + if (!silentRefresh) { + this.showStreams(); + } + } catch { + if (!silentRefresh) { + this.showError(); + } + } + } + + filterStreams(searchTerm) { + const term = searchTerm.toLowerCase().trim(); + + if (!term) { + this.filteredStreams = [...this.streams]; + } else { + this.filteredStreams = this.streams.filter( + (stream) => + stream.name?.toLowerCase().includes(term) || + stream.url?.toLowerCase().includes(term) || + stream.app?.toLowerCase().includes(term), + ); + } + + this.renderStreams(); + + if (this.filteredStreams.length === 0 && term) { + this.showNoResults(); + } else { + this.showStreams(); + } + } + + updateStats() { + const total = this.streams.length; + + this.elements.totalStreams.textContent = total; + this.elements.onlineStreams.textContent = total; + } + + renderStreams() { + this.elements.streamsContainer.innerHTML = ""; + + for (const stream of this.filteredStreams) { + const streamCard = this.createStreamCard(stream); + this.elements.streamsContainer.appendChild(streamCard); + } + } + + createStreamCard(stream) { + const card = document.createElement("div"); + card.className = "stream-card"; + + const formatBytes = (bytes) => { + if (!bytes) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`; + }; + + const watchName = stream.name?.replace(/\/live\//, "/"); + const watchUrl = watchName + ? `${window.location.origin}/watch/${encodeURIComponent(watchName)}` + : "Unknown Stream"; + + card.innerHTML = ` +
+

${this.escapeHtml(stream.name || "Unknown Stream")}

+
+ +
+
+ Clients: + ${stream.clients || 0} +
+ +
+ Bitrate: + ${formatBytes((stream.kbps?.recv_30s || 0) * 1024)}/s +
+ +
+ Video: + ${stream.video?.codec || "N/A"} ${stream.video?.profile || ""} +
+ +
+ Audio: + ${stream.audio?.codec || "N/A"} +
+
+ + +
+ ${this.escapeHtml(watchUrl)} +
+
+ `; + + return card; + } + + escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + + showLoading() { + this.elements.loadingState.style.display = "block"; + this.elements.errorState.style.display = "none"; + this.elements.streamsContainer.style.display = "none"; + this.elements.noResults.style.display = "none"; + } + + showError() { + this.elements.loadingState.style.display = "none"; + this.elements.errorState.style.display = "block"; + this.elements.streamsContainer.style.display = "none"; + this.elements.noResults.style.display = "none"; + } + + showStreams() { + this.elements.loadingState.style.display = "none"; + this.elements.errorState.style.display = "none"; + this.elements.streamsContainer.style.display = "grid"; + this.elements.noResults.style.display = "none"; + } + + showNoResults() { + this.elements.loadingState.style.display = "none"; + this.elements.errorState.style.display = "none"; + this.elements.streamsContainer.style.display = "none"; + this.elements.noResults.style.display = "block"; + } +} + +new StreamDashboard(); diff --git a/public/js/watch.js b/public/js/watch.js new file mode 100644 index 0000000..9db6087 --- /dev/null +++ b/public/js/watch.js @@ -0,0 +1,631 @@ +class StreamWatcher { + constructor() { + this.streamId = this.getStreamId(); + this.srsUrl = this.getSrsUrl(); + this.streamData = null; + this.refreshInterval = null; + this.isFullscreen = false; + this.player = null; + this.isDestroyed = false; + this.retryCount = 0; + this.maxRetries = 3; + this.userInteracted = false; + this.pendingPlay = false; + + this.initializeElements(); + this.bindEvents(); + this.setupUserInteractionDetection(); + this.loadStreamData(); + this.startAutoRefresh(); + } + + getStreamId() { + const metaTag = document.querySelector('meta[name="stream-id"]'); + if (!metaTag) { + throw new Error("Stream ID not found in meta tags"); + } + return metaTag.content; + } + + getSrsUrl() { + const metaTag = document.querySelector('meta[name="srs-url"]'); + if (!metaTag) { + throw new Error("SRS URL not found in meta tags"); + } + return metaTag.content; + } + + initializeElements() { + this.elements = { + streamTitle: document.getElementById("streamTitle"), + loadingState: document.getElementById("loadingState"), + errorState: document.getElementById("errorState"), + errorMessage: document.getElementById("errorMessage"), + retryBtn: document.getElementById("retryBtn"), + videoPlayer: document.getElementById("videoPlayer"), + streamStatus: document.getElementById("streamStatus"), + streamStats: document.getElementById("streamStats"), + viewerCount: document.getElementById("viewerCount"), + bitrate: document.getElementById("bitrate"), + quality: document.getElementById("quality"), + streamId: document.getElementById("streamId"), + streamApp: document.getElementById("streamApp"), + streamDuration: document.getElementById("streamDuration"), + videoCodec: document.getElementById("videoCodec"), + audioCodec: document.getElementById("audioCodec"), + resolution: document.getElementById("resolution"), + rtmpUrl: document.getElementById("rtmpUrl"), + hlsUrl: document.getElementById("hlsUrl"), + flvUrl: document.getElementById("flvUrl"), + webrtcUrl: document.getElementById("webrtcUrl"), + videoContainer: document.querySelector(".video-container"), + }; + } + + setupUserInteractionDetection() { + const events = ["click", "touchstart", "keydown", "scroll"]; + const handleInteraction = () => { + this.userInteracted = true; + if (this.pendingPlay && this.elements.videoPlayer) { + this.attemptAutoplay(); + } + + for (const event of events) { + document.removeEventListener(event, handleInteraction, { + once: true, + passive: true, + capture: true, + }); + } + }; + + for (const event of events) { + document.addEventListener(event, handleInteraction, { + once: true, + passive: true, + capture: true, + }); + } + } + + bindEvents() { + this.elements.retryBtn.addEventListener("click", () => { + this.retryCount = 0; + this.userInteracted = true; + this.loadStreamData(); + }); + + this.elements.videoContainer.addEventListener("click", () => { + this.userInteracted = true; + if (this.elements.videoPlayer?.paused) { + this.attemptAutoplay(); + } + }); + + const urlElements = [ + this.elements.rtmpUrl, + this.elements.hlsUrl, + this.elements.flvUrl, + this.elements.webrtcUrl, + ]; + + for (const element of urlElements) { + element.addEventListener("click", (e) => { + e.stopPropagation(); + const url = element.textContent.trim(); + if (url !== "-") { + navigator.clipboard + .writeText(url) + .then(() => { + element.style.background = "rgba(34, 197, 94, 0.2)"; + setTimeout(() => { + element.style.background = ""; + }, 1000); + }) + .catch(() => {}); + } + }); + } + + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + this.stopAutoRefresh(); + } else { + this.startAutoRefresh(); + setTimeout(() => this.loadStreamData(), 100); + } + }); + + document.addEventListener("fullscreenchange", () => { + this.isFullscreen = !!document.fullscreenElement; + }); + + window.addEventListener("beforeunload", () => { + this.cleanup(); + }); + } + + async loadStreamData() { + const wasPlaying = + this.elements.videoPlayer && !this.elements.videoPlayer.paused; + + if (!wasPlaying) { + this.showLoading(); + } + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 8000); + + const response = await fetch("/api/streams", { + method: "GET", + headers: { + Accept: "application/json", + "Cache-Control": "no-cache", + }, + credentials: "same-origin", + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error(result.error || "Failed to load streams"); + } + + const streams = result.data.streams || []; + const newStreamData = streams.find( + (stream) => + stream.name === this.streamId || stream.id === this.streamId, + ); + + if (!newStreamData) { + this.elements.streamTitle.textContent = `No stream found for: ${this.streamId}`; + throw new Error("Stream not found"); + } + + const wasOnline = this.streamData?.publish?.active === true; + const isOnline = newStreamData.publish?.active === true; + + this.streamData = newStreamData; + this.updateStreamInfo(); + + if (wasOnline !== isOnline || !this.player) { + await this.setupVideoPlayer(); + } + + this.retryCount = 0; + } catch (error) { + if (error.name === "AbortError") { + this.showError("Request timeout - please check your connection"); + } else { + this.showError(error.message); + } + } + } + + updateStreamInfo() { + const stream = this.streamData; + const isOnline = stream.publish?.active === true; + + this.elements.streamTitle.textContent = + stream.name || `Stream ${this.streamId}`; + + this.elements.streamStats.style.display = isOnline ? "flex" : "none"; + + if (isOnline) { + this.elements.viewerCount.textContent = stream.clients || 0; + this.elements.bitrate.textContent = this.formatBitrate( + stream.kbps?.recv_30s || 0, + ); + this.elements.quality.textContent = this.getQualityString(stream); + } + + this.elements.streamId.textContent = stream.name || this.streamId; + this.elements.streamApp.textContent = stream.app || "-"; + this.elements.streamDuration.textContent = isOnline + ? this.formatDuration(stream.publish?.duration) + : "-"; + this.elements.videoCodec.textContent = stream.video + ? `${stream.video.codec} ${stream.video.profile || ""}`.trim() + : "-"; + this.elements.audioCodec.textContent = stream.audio?.codec || "-"; + this.elements.resolution.textContent = stream.video + ? `${stream.video.width || "?"}x${stream.video.height || "?"}` + : "-"; + + this.updateStreamUrls(stream); + } + + updateStreamUrls(stream) { + const srsHost = new URL(this.srsUrl).host; + const streamPath = `${stream.app}/${stream.name}`; + + this.elements.rtmpUrl.textContent = `rtmp://${srsHost}/${streamPath}`; + this.elements.hlsUrl.textContent = `${this.srsUrl}/${streamPath}.m3u8`; + this.elements.flvUrl.textContent = `${this.srsUrl}/${streamPath}.flv`; + this.elements.webrtcUrl.textContent = `webrtc://${srsHost}/${streamPath}`; + } + + async setupVideoPlayer() { + const stream = this.streamData; + + if (!stream.publish?.active) { + this.showError("Stream is currently offline"); + return; + } + + if ( + this.player && + this.elements.videoPlayer && + !this.elements.videoPlayer.paused + ) { + this.showVideo(); + return; + } + + this.cleanupPlayer(); + + const videoElement = this.elements.videoPlayer; + const hlsUrl = `${this.srsUrl}/${stream.app}/${stream.name}.m3u8`; + const flvUrl = `${this.srsUrl}/${stream.app}/${stream.name}.flv`; + + const hasFlvjs = typeof flvjs !== "undefined"; + if (hasFlvjs && flvjs.isSupported()) { + await this.setupFlvjsPlayer(videoElement, flvUrl, hlsUrl); + } else { + await this.setupNativePlayer(videoElement, hlsUrl); + } + } + + async setupFlvjsPlayer(videoElement, flvUrl, hlsUrl) { + try { + this.player = flvjs.createPlayer( + { + type: "flv", + url: flvUrl, + isLive: true, + cors: true, + }, + { + enableWorker: false, + enableStashBuffer: false, + stashInitialSize: 128, + isLive: true, + lazyLoad: false, + lazyLoadMaxDuration: 0, + lazyLoadRecoverDuration: 0, + deferLoadAfterSourceOpen: false, + autoCleanupMaxBackwardDuration: 2, + autoCleanupMinBackwardDuration: 1, + statisticsInfoReportInterval: 1000, + fixAudioTimestampGap: true, + accurateSeek: false, + seekType: "range", + liveBufferLatencyChasing: true, + liveBufferLatencyMaxLatency: 1.5, + liveBufferLatencyMinRemain: 0.3, + }, + ); + + const newVideoElement = this.setupVideoEvents(videoElement); + + this.player.on(flvjs.Events.ERROR, (errorType, errorDetail) => { + console.warn( + "FLV.js error, falling back to HLS:", + errorType, + errorDetail, + ); + this.cleanupPlayer(); + setTimeout(() => this.setupNativePlayer(newVideoElement, hlsUrl), 100); + }); + + this.player.attachMediaElement(newVideoElement); + + const loadPromise = new Promise((resolve, reject) => { + const loadTimeout = setTimeout(() => { + reject(new Error("FLV load timeout")); + }, 3000); + + this.player.on(flvjs.Events.MEDIA_INFO, () => { + clearTimeout(loadTimeout); + resolve(); + }); + }); + + this.player.load(); + + try { + await loadPromise; + } catch { + console.warn("FLV.js load timeout, falling back to HLS"); + this.cleanupPlayer(); + await this.setupNativePlayer(newVideoElement, hlsUrl); + } + } catch (error) { + console.warn("FLV.js setup failed, using native player:", error); + await this.setupNativePlayer(videoElement, hlsUrl); + } + } + + async setupNativePlayer(videoElement, url) { + try { + const newVideoElement = this.setupVideoEvents(videoElement); + + newVideoElement.crossOrigin = "anonymous"; + newVideoElement.preload = "metadata"; + newVideoElement.autoplay = true; + newVideoElement.muted = true; + newVideoElement.playsInline = true; + newVideoElement.controls = false; + + if (newVideoElement.canPlayType("application/vnd.apple.mpegurl")) { + newVideoElement.setAttribute("playsinline", ""); + newVideoElement.setAttribute("webkit-playsinline", ""); + } + + newVideoElement.src = url; + newVideoElement.load(); + } catch (error) { + console.error("Native player setup failed:", error); + this.showError("Failed to load video stream"); + } + } + + setupVideoEvents(videoElement) { + const newVideoElement = videoElement.cloneNode(true); + videoElement.parentNode.replaceChild(newVideoElement, videoElement); + this.elements.videoPlayer = newVideoElement; + + newVideoElement.removeAttribute("src"); + newVideoElement.load(); + + newVideoElement.addEventListener("loadstart", () => { + this.showLoading(); + }); + + newVideoElement.addEventListener("loadedmetadata", () => { + this.attemptAutoplay(); + }); + + newVideoElement.addEventListener("canplay", () => { + this.showVideo(); + this.attemptAutoplay(); + }); + + newVideoElement.addEventListener("playing", () => { + this.showVideo(); + this.pendingPlay = false; + }); + + newVideoElement.addEventListener("waiting", () => { + setTimeout(() => { + if (newVideoElement.readyState < 3) { + this.showLoading(); + } + }, 500); + }); + + newVideoElement.addEventListener("error", () => { + const error = newVideoElement.error; + console.error("Video error:", error); + + if (this.retryCount < this.maxRetries) { + this.retryCount++; + setTimeout( + () => { + if (!this.isDestroyed) { + console.log(`Retrying video load (attempt ${this.retryCount})`); + const stream = this.streamData; + const hlsUrl = `${this.srsUrl}/${stream.app}/${stream.name}.m3u8`; + this.cleanupPlayer(); + this.setupNativePlayer(newVideoElement, hlsUrl); + } + }, + Math.min(1000 * this.retryCount, 5000), + ); + } else { + this.showError( + `Video failed to load: ${error?.message || "Unknown error"}`, + ); + } + }); + + newVideoElement.addEventListener("pause", () => { + if (!this.isDestroyed && this.streamData?.publish?.active) { + setTimeout(() => { + if (newVideoElement.paused && !this.isDestroyed) { + this.attemptAutoplay(); + } + }, 1000); + } + }); + + return newVideoElement; + } + + async attemptAutoplay() { + if (!this.elements.videoPlayer || this.isDestroyed) return; + + try { + this.elements.videoPlayer.muted = true; + + const playPromise = this.elements.videoPlayer.play(); + + if (playPromise !== undefined) { + await playPromise; + console.log("Autoplay successful"); + } + } catch (error) { + console.warn("Autoplay failed:", error); + + if (!this.userInteracted) { + this.pendingPlay = true; + this.showPlayButton(); + } else { + setTimeout(() => { + if (!this.elements.videoPlayer.paused) return; + this.elements.videoPlayer.play().catch(() => {}); + }, 500); + } + } + } + + showPlayButton() { + if (!document.getElementById("playButtonOverlay")) { + const overlay = document.createElement("div"); + overlay.id = "playButtonOverlay"; + overlay.style.cssText = ` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0,0,0,0.7); + color: white; + padding: 20px; + border-radius: 50%; + cursor: pointer; + font-size: 24px; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + height: 80px; + `; + overlay.innerHTML = "▶"; + + overlay.addEventListener("click", () => { + this.userInteracted = true; + this.attemptAutoplay(); + overlay.remove(); + }); + + this.elements.videoContainer.style.position = "relative"; + this.elements.videoContainer.appendChild(overlay); + } + } + + cleanupPlayer() { + if (this.player) { + try { + this.player.pause(); + this.player.unload(); + this.player.detachMediaElement(); + this.player.destroy(); + } catch (e) { + console.warn("Error cleaning up player:", e); + } + this.player = null; + } + + if (this.elements.videoPlayer) { + this.elements.videoPlayer.pause(); + this.elements.videoPlayer.src = ""; + this.elements.videoPlayer.removeAttribute("src"); + this.elements.videoPlayer.load(); + } + + const overlay = document.getElementById("playButtonOverlay"); + if (overlay) { + overlay.remove(); + } + } + + cleanup() { + this.isDestroyed = true; + this.stopAutoRefresh(); + this.cleanupPlayer(); + } + + formatBitrate(kbps) { + if (kbps >= 1000) { + return `${(kbps / 1000).toFixed(1)} Mbps`; + } + return `${kbps} kbps`; + } + + formatDuration(ms) { + if (!ms) return "0s"; + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } + if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } + return `${seconds}s`; + } + + getQualityString(stream) { + if (!stream.video) return "Unknown"; + const width = stream.video.width; + + if (width >= 1920) return "1080p"; + if (width >= 1280) return "720p"; + if (width >= 854) return "480p"; + if (width >= 640) return "360p"; + return `${width}x${stream.video.height}`; + } + + toggleFullscreen() { + if (!this.isFullscreen) { + this.elements.videoContainer.requestFullscreen().catch(() => {}); + } else { + document.exitFullscreen().catch(() => {}); + } + } + + startAutoRefresh() { + if (this.refreshInterval) { + this.stopAutoRefresh(); + } + + this.refreshInterval = setInterval(() => { + if (!this.isDestroyed) { + this.loadStreamData(); + } + }, 15000); + } + + stopAutoRefresh() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + } + + showLoading() { + this.elements.loadingState.style.display = "flex"; + this.elements.errorState.style.display = "none"; + this.elements.videoPlayer.style.display = "none"; + } + + showError(message) { + this.elements.loadingState.style.display = "none"; + this.elements.errorState.style.display = "flex"; + this.elements.videoPlayer.style.display = "none"; + this.elements.errorMessage.textContent = message; + } + + showVideo() { + this.elements.loadingState.style.display = "none"; + this.elements.errorState.style.display = "none"; + this.elements.videoPlayer.style.display = "block"; + } +} + +try { + new StreamWatcher(); +} catch (error) { + console.error("Failed to initialize StreamWatcher:", error); +} diff --git a/public/watch.html b/public/watch.html new file mode 100644 index 0000000..e4e7276 --- /dev/null +++ b/public/watch.html @@ -0,0 +1,129 @@ + + + + + + + Watch Stream + + + + + + + + + + + +
+
+
+ + + + + Back to Dashboard + +

Loading Stream...

+
+
+ +
+
+
+

Loading stream...

+
+ + + + +
+ +
+ +
+ +
+
+

Stream Information

+
+
+ Stream ID: + - +
+
+ Application: + - +
+
+ Duration: + - +
+
+ Video Codec: + - +
+
+ Audio Codec: + - +
+
+ Resolution: + - +
+
+
+ +
+

Stream URLs

+
+
+ RTMP: +
-
+
+
+ HLS: +
-
+
+
+ HTTP-FLV: +
-
+
+
+ WebRTC: +
-
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f095bcd --- /dev/null +++ b/src/index.ts @@ -0,0 +1,15 @@ +import { echo } from "@atums/echo"; + +import { verifyRequiredVariables } from "@config"; +import { serverHandler } from "@server"; + +async function main(): Promise { + verifyRequiredVariables(); + + serverHandler.initialize(); +} + +main().catch((error: Error) => { + echo.error({ message: "Error initializing the server:", error }); + process.exit(1); +}); diff --git a/src/routes/api/streams.ts b/src/routes/api/streams.ts new file mode 100644 index 0000000..b412be9 --- /dev/null +++ b/src/routes/api/streams.ts @@ -0,0 +1,42 @@ +import { srsUrl } from "@config"; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "application/json", +}; + +async function handler(): Promise { + try { + const response = await fetch(`${srsUrl}/api/v1/streams`); + + if (!response.ok) { + return Response.json( + { + success: false, + code: response.status, + error: `SRS API error: ${response.statusText}`, + }, + { status: response.status }, + ); + } + + const data = await response.json(); + + return Response.json({ + success: true, + data, + }); + } catch { + return Response.json( + { + success: false, + code: 500, + error: "Failed to fetch streams from SRS server", + }, + { status: 500 }, + ); + } +} + +export { handler, routeDef }; diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..fd06550 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,20 @@ +import { resolve } from "node:path"; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "text/html", +}; + +async function handler(): Promise { + const filePath = resolve("public", "index.html"); + const file = Bun.file(filePath); + + return new Response(file, { + headers: { + "Content-Type": "text/html", + }, + }); +} + +export { handler, routeDef }; diff --git a/src/routes/watch/[id].ts b/src/routes/watch/[id].ts new file mode 100644 index 0000000..6602473 --- /dev/null +++ b/src/routes/watch/[id].ts @@ -0,0 +1,40 @@ +import { resolve } from "node:path"; +import { srsUrl } from "@config"; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "text/html", +}; + +async function handler(request: ExtendedRequest): Promise { + const { id } = request.params; + + if (!id) { + return new Response("Stream ID is required", { status: 400 }); + } + + const filePath = resolve("public", "watch.html"); + const bunFile = Bun.file(filePath); + + const html = new HTMLRewriter() + .on("head", { + element(head) { + head.append(``, { + html: true, + }); + head.append(``, { + html: true, + }); + }, + }) + .transform(await bunFile.text()); + + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); +} + +export { handler, routeDef }; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..d24b025 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,305 @@ +import { resolve } from "node:path"; +import { Echo, echo } from "@atums/echo"; +import { environment } from "@config"; +import { + type BunFile, + FileSystemRouter, + type MatchedRoute, + type Server, +} from "bun"; + +import { webSocketHandler } from "@websocket"; + +class ServerHandler { + private router: FileSystemRouter; + + constructor( + private port: number, + private host: string, + ) { + this.router = new FileSystemRouter({ + style: "nextjs", + dir: resolve("src", "routes"), + fileExtensions: [".ts"], + origin: `http://${this.host}:${this.port}`, + }); + } + + public initialize(): void { + const server: Server = Bun.serve({ + port: this.port, + hostname: this.host, + fetch: this.handleRequest.bind(this), + websocket: { + open: webSocketHandler.handleOpen.bind(webSocketHandler), + message: webSocketHandler.handleMessage.bind(webSocketHandler), + close: webSocketHandler.handleClose.bind(webSocketHandler), + }, + }); + + const echoChild = new Echo({ disableFile: true }); + + echoChild.info( + `Server running at http://${server.hostname}:${server.port}`, + ); + this.logRoutes(echoChild); + } + + private logRoutes(echo: Echo): void { + echo.info("Available routes:"); + + const sortedRoutes: [string, string][] = Object.entries( + this.router.routes, + ).sort(([pathA]: [string, string], [pathB]: [string, string]) => + pathA.localeCompare(pathB), + ); + + for (const [path, filePath] of sortedRoutes) { + echo.info(`Route: ${path}, File: ${filePath}`); + } + } + + private async serveStaticFile( + request: ExtendedRequest, + pathname: string, + ip: string, + ): Promise { + let filePath: string; + let response: Response; + + try { + if (pathname === "/favicon.ico") { + filePath = resolve("public", "assets", "favicon.ico"); + } else { + filePath = resolve(`.${pathname}`); + } + + const file: BunFile = Bun.file(filePath); + + if (await file.exists()) { + const fileContent: ArrayBuffer = await file.arrayBuffer(); + const contentType: string = file.type ?? "application/octet-stream"; + + response = new Response(fileContent, { + headers: { "Content-Type": contentType }, + }); + } else { + echo.warn(`File not found: ${filePath}`); + response = new Response("Not Found", { status: 404 }); + } + } catch (error) { + echo.error({ + message: `Error serving static file: ${pathname}`, + error: error as Error, + }); + response = new Response("Internal Server Error", { status: 500 }); + } + + this.logRequest(request, response, ip); + return response; + } + + private logRequest( + request: ExtendedRequest, + response: Response, + ip: string | undefined, + ): void { + const pathname = new URL(request.url).pathname; + + const ignoredStartsWith: string[] = ["/public"]; + const ignoredPaths: string[] = ["/favicon.ico"]; + + if ( + ignoredStartsWith.some((prefix) => pathname.startsWith(prefix)) || + ignoredPaths.includes(pathname) + ) { + return; + } + + echo.custom(`${request.method}`, `${response.status}`, [ + request.url, + `${(performance.now() - request.startPerf).toFixed(2)}ms`, + ip || "unknown", + ]); + } + + private async handleRequest( + request: Request, + server: Server, + ): Promise { + const extendedRequest: ExtendedRequest = request as ExtendedRequest; + extendedRequest.startPerf = performance.now(); + + const headers = request.headers; + let ip = server.requestIP(request)?.address; + let response: Response; + + if (!ip || ip.startsWith("172.") || ip === "127.0.0.1") { + ip = + headers.get("CF-Connecting-IP")?.trim() || + headers.get("X-Real-IP")?.trim() || + headers.get("X-Forwarded-For")?.split(",")[0]?.trim() || + "unknown"; + } + + const pathname: string = new URL(request.url).pathname; + + const baseDir = resolve("public", "custom"); + const customPath = resolve(baseDir, pathname.slice(1)); + + if (!customPath.startsWith(baseDir)) { + response = new Response("Forbidden", { status: 403 }); + this.logRequest(extendedRequest, response, ip); + return response; + } + + const customFile = Bun.file(customPath); + if (await customFile.exists()) { + const content = await customFile.arrayBuffer(); + const type: string = customFile.type ?? "application/octet-stream"; + response = new Response(content, { + headers: { "Content-Type": type }, + }); + this.logRequest(extendedRequest, response, ip); + return response; + } + + if (pathname.startsWith("/public") || pathname === "/favicon.ico") { + return await this.serveStaticFile(extendedRequest, pathname, ip); + } + + const match: MatchedRoute | null = this.router.match(request); + let requestBody: unknown = {}; + + if (match) { + const { filePath, params, query } = match; + + try { + const routeModule: RouteModule = await import(filePath); + const contentType: string | null = request.headers.get("Content-Type"); + const actualContentType: string | null = contentType + ? (contentType.split(";")[0]?.trim() ?? null) + : null; + + if ( + routeModule.routeDef.needsBody === "json" && + actualContentType === "application/json" + ) { + try { + requestBody = await request.json(); + } catch { + requestBody = {}; + } + } else if ( + routeModule.routeDef.needsBody === "multipart" && + actualContentType === "multipart/form-data" + ) { + try { + requestBody = await request.formData(); + } catch { + requestBody = {}; + } + } + + if ( + (Array.isArray(routeModule.routeDef.method) && + !routeModule.routeDef.method.includes(request.method)) || + (!Array.isArray(routeModule.routeDef.method) && + routeModule.routeDef.method !== request.method) + ) { + response = Response.json( + { + success: false, + code: 405, + error: `Method ${request.method} Not Allowed, expected ${ + Array.isArray(routeModule.routeDef.method) + ? routeModule.routeDef.method.join(", ") + : routeModule.routeDef.method + }`, + }, + { status: 405 }, + ); + } else { + const expectedContentType: string | string[] | null = + routeModule.routeDef.accepts; + + let matchesAccepts: boolean; + + if (Array.isArray(expectedContentType)) { + matchesAccepts = + expectedContentType.includes("*/*") || + expectedContentType.includes(actualContentType || ""); + } else { + matchesAccepts = + expectedContentType === "*/*" || + actualContentType === expectedContentType; + } + + if (!matchesAccepts) { + response = Response.json( + { + success: false, + code: 406, + error: `Content-Type ${actualContentType} Not Acceptable, expected ${ + Array.isArray(expectedContentType) + ? expectedContentType.join(", ") + : expectedContentType + }`, + }, + { status: 406 }, + ); + } else { + extendedRequest.params = params; + extendedRequest.query = query; + + response = await routeModule.handler( + extendedRequest, + requestBody, + server, + ); + + if (routeModule.routeDef.returns !== "*/*") { + response.headers.set( + "Content-Type", + routeModule.routeDef.returns, + ); + } + } + } + } catch (error: unknown) { + echo.error({ + message: `Error handling route ${request.url}`, + error: error, + }); + + response = Response.json( + { + success: false, + code: 500, + error: "Internal Server Error", + }, + { status: 500 }, + ); + } + } else { + response = Response.json( + { + success: false, + code: 404, + error: "Not Found", + }, + { status: 404 }, + ); + } + + this.logRequest(extendedRequest, response, ip); + return response; + } +} + +const serverHandler: ServerHandler = new ServerHandler( + environment.port, + environment.host, +); + +export { serverHandler }; diff --git a/src/websocket.ts b/src/websocket.ts new file mode 100644 index 0000000..87ef56e --- /dev/null +++ b/src/websocket.ts @@ -0,0 +1,29 @@ +import { echo } from "@atums/echo"; +import type { ServerWebSocket } from "bun"; + +class WebSocketHandler { + public handleMessage(ws: ServerWebSocket, message: string): void { + echo.info(`WebSocket received: ${message}`); + try { + ws.send(`You said: ${message}`); + } catch (error) { + echo.error({ message: "WebSocket send error", error }); + } + } + + public handleOpen(ws: ServerWebSocket): void { + echo.info("WebSocket connection opened."); + try { + ws.send("Welcome to the WebSocket server!"); + } catch (error) { + echo.error({ message: "WebSocket send error", error }); + } + } + + public handleClose(_ws: ServerWebSocket, code: number, reason: string): void { + echo.warn(`WebSocket closed with code ${code}, reason: ${reason}`); + } +} + +const webSocketHandler: WebSocketHandler = new WebSocketHandler(); +export { webSocketHandler, WebSocketHandler }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c28c71d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@*": ["src/*"], + "@config": ["config/index.ts"], + "@config/*": ["config/*"], + "@types/*": ["types/*"] + }, + "typeRoots": ["./types", "./node_modules/@types"], + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": false, + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src", "types"] +} diff --git a/types/config.d.ts b/types/config.d.ts new file mode 100644 index 0000000..57584ed --- /dev/null +++ b/types/config.d.ts @@ -0,0 +1,5 @@ +type Environment = { + port: number; + host: string; + development: boolean; +}; diff --git a/types/logger.d.ts b/types/logger.d.ts new file mode 100644 index 0000000..ff6a601 --- /dev/null +++ b/types/logger.d.ts @@ -0,0 +1,9 @@ +type ILogMessagePart = { value: string; color: string }; + +type ILogMessageParts = { + level: ILogMessagePart; + filename: ILogMessagePart; + readableTimestamp: ILogMessagePart; + message: ILogMessagePart; + [key: string]: ILogMessagePart; +}; diff --git a/types/routes.d.ts b/types/routes.d.ts new file mode 100644 index 0000000..9814e87 --- /dev/null +++ b/types/routes.d.ts @@ -0,0 +1,15 @@ +type RouteDef = { + method: string | string[]; + accepts: string | null | string[]; + returns: string; + needsBody?: "multipart" | "json"; +}; + +type RouteModule = { + handler: ( + request: Request | ExtendedRequest, + requestBody: unknown, + server: Server, + ) => Promise | Response; + routeDef: RouteDef; +}; diff --git a/types/server.d.ts b/types/server.d.ts new file mode 100644 index 0000000..9afe286 --- /dev/null +++ b/types/server.d.ts @@ -0,0 +1,8 @@ +type Query = Record; +type Params = Record; + +interface ExtendedRequest extends Request { + startPerf: number; + query: Query; + params: Params; +}